@crowdin/app-project-module v0.6.1
Crowdin App Project Module
Module that will automatically add all necessary endpoints for Crowdin App.
It either can extends your Express application or even create it for you.
Module expose two main methods:
createApp
to fully create for you Express applicationaddCrowdinEndpoints
to extend your Express application
In both options you will need to provide Crowdin App configuration file. Please refer to jsdoc for more details.
Status
Table of Contents
- Installation
- Sample App
- Custom login form
- OAuth2 login
- Settings window
- Info window
- Background tasks
- Error propagation
- Contributing
- Seeking Assistance
- License
Installation
npm
npm i @crowdin/app-project-module
yarn
yarn add @crowdin/app-project-module
Sample app
const crowdinModule = require('@crowdin/app-project-module');
const crowdinAppFunctions = require('@crowdin/crowdin-apps-functions');
const axios = require('axios').default;
const configuration = {
baseUrl: 'https://123.ngrok.io',
clientId: 'clientId',
clientSecret: 'clientSecret',
name: 'Sample App',
identifier: 'sample-app',
description: 'Sample App description',
dbFolder: __dirname,
imagePath: __dirname + '/' + 'logo.png',
integration: {
withRootFolder: true,
getIntegrationFiles: async (credentials, appSettings) => {
//here you need to fetch files/objects from integration
return [
{
id: '12',
name: 'File from integration',
type: 'json',
parentId: '10'
},
{
id: '14',
name: 'File from integration2',
type: 'xml',
parentId: '11'
},
{
id: '10',
name: 'Folder from integration'
},
{
id: '11',
name: 'Folder from integratio2'
},
];
},
updateCrowdin: async (projectId, client, credentials, request, rootFolder, appSettings) => {
//here you need to get data from integration and upload it to Crowdin
console.log(`Request for updating data in Crowdin ${JSON.stringify(request)}`);
const directories = await client.sourceFilesApi
.withFetchAll()
.listProjectDirectories(projectId);
const { folder, files } = await crowdinAppFunctions.getOrCreateFolder(
directories.data.map((d) => d.data),
client,
projectId,
'Folder from integration',
rootFolder
);
const fileContent = {
title: 'Hello World',
};
await crowdinAppFunctions.updateOrCreateFile(
client,
projectId,
'integration.json',
'Sample file from integration',
'json',
folder.id,
fileContent,
files.find((f) => f.name === 'integration.json'),
);
},
updateIntegration: async (projectId, client, credentials, request, rootFolder, appSettings) => {
////here should be logic to get translations from Crowdin and upload them to integration
console.log(`Request for updating data in Integration ${JSON.stringify(request)}`);
const directories = await client.sourceFilesApi
.withFetchAll()
.listProjectDirectories(projectId);
const { files } = await crowdinAppFunctions.getFolder(
directories.data.map((d) => d.data),
client,
projectId,
'Folder from integration',
rootFolder
);
const file = files.find((f) => f.name === 'integration.json');
if (file) {
const translationsLink =
await client.translationsApi.buildProjectFileTranslation(
projectId,
file.id,
{ targetLanguageId: 'uk' },
);
if (!translationsLink) {
return;
}
const response = await axios.get(translationsLink.data.url);
console.log(response.data);
}
},
}
};
crowdinModule.createApp(configuration);
Customize your app login form
By default login page for your app will require only to enter apiToken
to communicate with third party service.
But there is also a possibility to customize it.
configuration.loginForm = {
fields: [
{
key: 'username',
label: 'Username',
},
{
key: 'password',
label: 'Password',
type: 'password'
},
{
helpText: 'Api Key for http requests',
key: 'apiKey',
label: 'Api Key'
}
]
};
OAuth2 support
In case if third party service uses OAuth2 for authorization use oauthLogin
field to configure it.loginForm
in this case should remain undefined.
Github example:
configuration.oauthLogin = {
authorizationUrl: 'https://github.com/login/oauth/authorize',
clientId: 'github_app_client_id',
clientSecret: 'github_app_client_secret',
accessTokenUrl: 'https://github.com/login/oauth/access_token'
}
Google example:
configuration.oauthLogin = {
scope: 'https%3A//www.googleapis.com/auth/userinfo.email',
authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
clientId: 'google_web_app_client_id',
clientSecret: 'google_web_app_client_secret',
accessTokenUrl: 'https://oauth2.googleapis.com/token',
extraAutorizationUrlParameters: {
response_type: 'code',
access_type: 'offline',
prompt: 'consent'
},
extraAccessTokenParameters: {
grant_type: 'authorization_code'
},
extraRefreshTokenParameters: {
grant_type: 'refresh_token'
},
refresh: true
}
The oauthLogin
property allows you to customize many different properties, mappings, etc. So that you can integrate with any OAuth2 implementation.
Main default values:
- redirect uri prefix will be
/oauth/code
- client id field name in url parameters and in request payload will be
client_id
- client secret field name in request payload will be
client_secret
- access token field name should be
access_token
- be default assumption is that token do not have any expiration date, to change this behavior use
refresh
flag so then refresh token and expires in will be taken into consideration - access token field name should be
refresh_token
- expires in field name should be
expires_in
(value should be in seconds)
This module rely that OAuth2 protocol is implemented by third party service in this way:
- request for access token should be done via POST request to
accessTokenUrl
with JSON body that will contain at leastclientId
,clientSecret
,code
andredirectUri
(also possible to add extra fields viaextraAccessTokenParameters
property) - request to refresh token should be done via POST request to
accessTokenUrl
(orrefreshTokenUrl
if definied) with JSON body that will contain at leastclientId
,clientSecret
andrefreshToken
(also possible to add extra fields viaextraRefreshTokenParameters
property) - both requests will return JSON response with body that contains
accessToken
and, if enabled,refreshToken
(optional) andexpireIn
To override those requests please use performGetTokenRequest
and performRefreshTokenRequest
(e.g. when requests should be done with different HTTP methods or data should be tranfered as query string or form data).
Mailup example:
const clientId = 'client_id';
const clientSecret = 'client_secret';
const tokenUrl = 'https://services.mailup.com/Authorization/OAuth/Token';
configuration.oauthLogin = {
authorizationUrl: 'https://services.mailup.com/Authorization/OAuth/LogOn',
clientId,
clientSecret,
extraAutorizationUrlParameters: {
response_type: 'code'
},
refresh: true,
performGetTokenRequest: async (code) => {
const url = `${tokenUrl}?code=${code}&grant_type=authorization_code`;
const headers = {
'Authorization': `Bearer ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`
};
return (await axios.get(url, { headers })).data;
},
performRefreshTokenRequest: async (credentials) => {
const params = {
refresh_token: credentials.refreshToken,
grant_type: 'refresh_token',
client_id: clientId,
client_secret: clientSecret
};
const data = Object.keys(params)
.map((key) => `${key}=${encodeURIComponent(params[key])}`)
.join('&');
const headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
return (await axios.post(tokenUrl, data, { headers })).data;
}
}
Please refer to jsdoc for more details.
Settings window
It is also possible to define settings window for your app where users can customize integration flow.
configuration.integration.getConfiguration = (projectId, crowdinClient, integrationCredentials) => {
return [
{
key: 'flag',
label: 'Checkbox',
type: 'checkbox'
},
{
key: 'text',
label: 'Text input',
type: 'text',
helpText: 'Help text'
},
{
key: 'option',
label: 'Select',
type: 'select',
options: [
{
value: '12',
label: 'Option'
}
]
}
]
}
Info window
You also can define section with some information notes or help section for your app.
configuration.integration.infoModal = {
title: 'Info',
content: `
<h1>This is your app help section</h1>
</br>
<h2>This is just an example</h2>
`
}
Background tasks
In order to register background tasks that app will invoke periodically invoke you can use cronJobs
field.
configuration.integration.cronJobs = [
{
//every 10 seconds
expression: '*/10 * * * * *',
task: (projectId, client, apiCredentials, appRootFolder, config) => {
console.log(`Running background task for project : ${projectId}`);
console.log(`Api credentials : ${JSON.stringify(apiCredentials)}`);
console.log(`App config : ${JSON.stringify(config)}`);
console.log(appRootFolder ? JSON.stringify(appRootFolder) : 'No root folder');
}
}
]
For cron syntax guide please refer to this documentation.
Error propagation
In case if something is wrong with app settings or credentials are invalid you can throw an explanation message that will be then visible on the UI side.
e.g. check if entered credentials are valid:
configuration.integration.checkConnection = (credentials) => {
if (!credentials.password || credentials.password.length < 6) {
throw 'Password is too weak';
}
//or call an service API with those credentials and check if request will be successful
};
Contributing
If you want to contribute please read the Contributing guidelines.
Seeking Assistance
If you find any problems or would like to suggest a feature, please feel free to file an issue on Github at Issues Page.
If you've found an error in these samples, please Contact Customer Success Service.