@crowdin/app-project-module 0.5.0 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/out/handlers/install.js +2 -0
- package/out/index.js +6 -0
- package/out/middlewares/crowdin-client.js +1 -25
- package/out/middlewares/integration-credentials.js +6 -39
- package/out/models/index.d.ts +13 -0
- package/out/models/index.js +6 -0
- package/out/static/js/main.js +4 -0
- package/out/storage/index.d.ts +2 -0
- package/out/storage/index.js +33 -9
- package/out/util/index.d.ts +8 -1
- package/out/util/index.js +127 -3
- package/out/views/main.handlebars +11 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@ In both options you will need to provide Crowdin App configuration file. Please
|
|
|
27
27
|
- [OAuth2 login](#oauth2-support)
|
|
28
28
|
- [Settings window](#settings-window)
|
|
29
29
|
- [Info window](#info-window)
|
|
30
|
+
- [Background tasks](#background-tasks)
|
|
31
|
+
- [Error propagation](#error-propagation)
|
|
30
32
|
- [Contributing](#contributing)
|
|
31
33
|
- [Seeking Assistance](#seeking-assistance)
|
|
32
34
|
- [License](#license)
|
|
@@ -320,6 +322,58 @@ configuration.integration.infoModal = {
|
|
|
320
322
|
}
|
|
321
323
|
```
|
|
322
324
|
|
|
325
|
+
## Background tasks
|
|
326
|
+
|
|
327
|
+
In order to register background tasks that app will invoke periodically invoke you can use `cronJobs` field.
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
configuration.integration.cronJobs = [
|
|
331
|
+
{
|
|
332
|
+
//every 10 seconds
|
|
333
|
+
expression: '*/10 * * * * *',
|
|
334
|
+
task: (projectId, client, apiCredentials, appRootFolder, config) => {
|
|
335
|
+
console.log(`Running background task for project : ${projectId}`);
|
|
336
|
+
console.log(`Api credentials : ${JSON.stringify(apiCredentials)}`);
|
|
337
|
+
console.log(`App config : ${JSON.stringify(config)}`);
|
|
338
|
+
console.log(appRootFolder ? JSON.stringify(appRootFolder) : 'No root folder');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
]
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
For cron syntax guide please refer to this [documentation](https://github.com/node-cron/node-cron#cron-syntax).
|
|
345
|
+
|
|
346
|
+
## Error propagation
|
|
347
|
+
|
|
348
|
+
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.
|
|
349
|
+
e.g. check if entered credentials are valid:
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
configuration.integration.checkConnection = (credentials) => {
|
|
353
|
+
if (!credentials.password || credentials.password.length < 6) {
|
|
354
|
+
throw 'Password is too weak';
|
|
355
|
+
}
|
|
356
|
+
//or call an service API with those credentials and check if request will be successful
|
|
357
|
+
};
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Or if you need to manually control users liveness session you can throw an error with `401` code then your app will automatically do a log out action.
|
|
361
|
+
e.g. when your service has some specific session duration timeout or extra conditions which are not covered by this framework
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
configuration.integrartion.getIntegrationFiles = async (credentials, appSettings) => {
|
|
365
|
+
//do a request/custom logic here
|
|
366
|
+
const sessionStillValid = false;
|
|
367
|
+
if (!sessionStillValid) {
|
|
368
|
+
throw {
|
|
369
|
+
message: 'session expired',
|
|
370
|
+
code: 401
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
//business logic
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
323
377
|
## Contributing
|
|
324
378
|
|
|
325
379
|
If you want to contribute please read the [Contributing](/CONTRIBUTING.md) guidelines.
|
package/out/handlers/install.js
CHANGED
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const crowdin_apps_functions_1 = require("@crowdin/crowdin-apps-functions");
|
|
13
|
+
const models_1 = require("../models");
|
|
13
14
|
const storage_1 = require("../storage");
|
|
14
15
|
const util_1 = require("../util");
|
|
15
16
|
function handle(config) {
|
|
@@ -21,6 +22,7 @@ function handle(config) {
|
|
|
21
22
|
accessToken: (0, util_1.encryptData)(config.clientSecret, token.accessToken),
|
|
22
23
|
refreshToken: (0, util_1.encryptData)(config.clientSecret, token.refreshToken),
|
|
23
24
|
expire: (new Date().getTime() / 1000 + token.expiresIn).toString(),
|
|
25
|
+
type: event.domain ? models_1.AccountType.ENTERPRISE : models_1.AccountType.NORMAL,
|
|
24
26
|
};
|
|
25
27
|
yield (0, storage_1.saveCrowdinCredentials)(credentials);
|
|
26
28
|
res.status(204).end();
|
package/out/index.js
CHANGED
|
@@ -25,6 +25,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
25
25
|
exports.createApp = exports.addCrowdinEndpoints = void 0;
|
|
26
26
|
const express_1 = __importStar(require("express"));
|
|
27
27
|
const express_handlebars_1 = __importDefault(require("express-handlebars"));
|
|
28
|
+
const cron = __importStar(require("node-cron"));
|
|
28
29
|
const path_1 = require("path");
|
|
29
30
|
const crowdin_file_progress_1 = __importDefault(require("./handlers/crowdin-file-progress"));
|
|
30
31
|
const crowdin_files_1 = __importDefault(require("./handlers/crowdin-files"));
|
|
@@ -81,6 +82,11 @@ function addCrowdinEndpoints(app, config) {
|
|
|
81
82
|
app.get('/api/integration/data', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config), (0, integration_data_1.default)(config));
|
|
82
83
|
app.post('/api/crowdin/update', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config), (0, crowdin_update_1.default)(config));
|
|
83
84
|
app.post('/api/integration/update', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config), (0, integration_update_1.default)(config));
|
|
85
|
+
if (config.integration.cronJobs) {
|
|
86
|
+
config.integration.cronJobs.forEach(job => {
|
|
87
|
+
cron.schedule(job.expression, () => (0, util_1.runJob)(config, job).catch(console.error));
|
|
88
|
+
});
|
|
89
|
+
}
|
|
84
90
|
}
|
|
85
91
|
exports.addCrowdinEndpoints = addCrowdinEndpoints;
|
|
86
92
|
function createApp(config) {
|
|
@@ -8,11 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
const crowdin_api_client_1 = __importDefault(require("@crowdin/crowdin-api-client"));
|
|
16
12
|
const crowdin_apps_functions_1 = require("@crowdin/crowdin-apps-functions");
|
|
17
13
|
const storage_1 = require("../storage");
|
|
18
14
|
const util_1 = require("../util");
|
|
@@ -43,27 +39,7 @@ function handle(config, optional = false) {
|
|
|
43
39
|
}
|
|
44
40
|
return res.status(403).send({ error: "Can't find organization by id" });
|
|
45
41
|
}
|
|
46
|
-
|
|
47
|
-
if (!isExpired) {
|
|
48
|
-
const crowdinToken = (0, util_1.decryptData)(config.clientSecret, credentials.accessToken);
|
|
49
|
-
req.crowdinApiClient = new crowdin_api_client_1.default({
|
|
50
|
-
token: crowdinToken,
|
|
51
|
-
organization: req.crowdinContext.jwtPayload.domain ? credentials.id : undefined,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
const newCredentials = yield (0, crowdin_apps_functions_1.refreshOAuthToken)(config.clientId, config.clientSecret, (0, util_1.decryptData)(config.clientSecret, credentials.refreshToken));
|
|
56
|
-
(0, storage_1.updateCrowdinCredentials)({
|
|
57
|
-
id: req.crowdinContext.crowdinId,
|
|
58
|
-
refreshToken: (0, util_1.encryptData)(config.clientSecret, newCredentials.refreshToken),
|
|
59
|
-
accessToken: (0, util_1.encryptData)(config.clientSecret, newCredentials.accessToken),
|
|
60
|
-
expire: (new Date().getTime() / 1000 + newCredentials.expiresIn).toString(),
|
|
61
|
-
});
|
|
62
|
-
req.crowdinApiClient = new crowdin_api_client_1.default({
|
|
63
|
-
token: newCredentials.accessToken,
|
|
64
|
-
organization: req.crowdinContext.jwtPayload.domain ? credentials.id : undefined,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
42
|
+
req.crowdinApiClient = yield (0, util_1.prepareCrowdinClient)(config, credentials);
|
|
67
43
|
next();
|
|
68
44
|
}
|
|
69
45
|
catch (e) {
|
|
@@ -8,16 +8,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
const axios_1 = __importDefault(require("axios"));
|
|
16
12
|
const storage_1 = require("../storage");
|
|
17
13
|
const util_1 = require("../util");
|
|
18
14
|
function handle(config, optional = false) {
|
|
19
15
|
return (0, util_1.runAsyncWrapper)((req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
20
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
21
16
|
const clientId = req.crowdinContext.clientId;
|
|
22
17
|
const integrationCredentials = yield (0, storage_1.getIntegrationCredentials)(clientId);
|
|
23
18
|
if (!integrationCredentials) {
|
|
@@ -29,41 +24,13 @@ function handle(config, optional = false) {
|
|
|
29
24
|
if (integrationCredentials.config) {
|
|
30
25
|
req.integrationSettings = JSON.parse(integrationCredentials.config);
|
|
31
26
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (isExpired) {
|
|
39
|
-
let newCredentials;
|
|
40
|
-
if (oauthLogin.performRefreshTokenRequest) {
|
|
41
|
-
newCredentials = yield oauthLogin.performRefreshTokenRequest(credentials);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
const url = oauthLogin.refreshTokenUrl || oauthLogin.accessTokenUrl;
|
|
45
|
-
const request = {};
|
|
46
|
-
request[((_b = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _b === void 0 ? void 0 : _b.clientId) || 'client_id'] = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.clientId;
|
|
47
|
-
request[((_c = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _c === void 0 ? void 0 : _c.clientSecret) || 'client_secret'] = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.clientSecret;
|
|
48
|
-
request[((_d = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _d === void 0 ? void 0 : _d.refreshToken) || 'refresh_token'] = credentials.refreshToken;
|
|
49
|
-
if (oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.extraRefreshTokenParameters) {
|
|
50
|
-
Object.entries(oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.extraRefreshTokenParameters).forEach(([key, value]) => (request[key] = value));
|
|
51
|
-
}
|
|
52
|
-
newCredentials = (yield axios_1.default.post(url || '', request, {
|
|
53
|
-
headers: { Accept: 'application/json' },
|
|
54
|
-
})).data;
|
|
55
|
-
}
|
|
56
|
-
credentials.accessToken = newCredentials[((_e = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _e === void 0 ? void 0 : _e.accessToken) || 'access_token'];
|
|
57
|
-
credentials.expireIn =
|
|
58
|
-
Number(newCredentials[((_f = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _f === void 0 ? void 0 : _f.expiresIn) || 'expires_in']) + Date.now() / 1000;
|
|
59
|
-
if (newCredentials[((_g = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _g === void 0 ? void 0 : _g.refreshToken) || 'refresh_token']) {
|
|
60
|
-
credentials.refreshToken =
|
|
61
|
-
newCredentials[((_h = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _h === void 0 ? void 0 : _h.refreshToken) || 'refresh_token'];
|
|
62
|
-
}
|
|
63
|
-
yield (0, storage_1.updateIntegrationCredentials)(req.crowdinContext.clientId, (0, util_1.encryptData)(config.clientSecret, JSON.stringify(credentials)));
|
|
64
|
-
}
|
|
27
|
+
try {
|
|
28
|
+
req.integrationCredentials = yield (0, util_1.prepareIntegrationCredentials)(config, integrationCredentials);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
console.error(e);
|
|
32
|
+
throw new util_1.CodeError('Credentials to integration either exprired or invalid', 401);
|
|
65
33
|
}
|
|
66
|
-
req.integrationCredentials = credentials;
|
|
67
34
|
next();
|
|
68
35
|
}));
|
|
69
36
|
}
|
package/out/models/index.d.ts
CHANGED
|
@@ -91,6 +91,10 @@ export interface IntegrationLogic {
|
|
|
91
91
|
title: string;
|
|
92
92
|
content: string;
|
|
93
93
|
};
|
|
94
|
+
/**
|
|
95
|
+
* background jobs that will be executed for each crowdin project and user
|
|
96
|
+
*/
|
|
97
|
+
cronJobs?: CronJob[];
|
|
94
98
|
}
|
|
95
99
|
export interface ConfigurationField {
|
|
96
100
|
key: string;
|
|
@@ -241,6 +245,11 @@ export interface CrowdinCredentials {
|
|
|
241
245
|
accessToken: string;
|
|
242
246
|
refreshToken: string;
|
|
243
247
|
expire: string;
|
|
248
|
+
type: AccountType;
|
|
249
|
+
}
|
|
250
|
+
export declare enum AccountType {
|
|
251
|
+
NORMAL = "normal",
|
|
252
|
+
ENTERPRISE = "enterprise"
|
|
244
253
|
}
|
|
245
254
|
export interface CrowdinContextInfo {
|
|
246
255
|
jwtPayload: JwtPayload;
|
|
@@ -262,3 +271,7 @@ export interface IntegrationFile {
|
|
|
262
271
|
export interface UpdateIntegrationRequest {
|
|
263
272
|
[fileId: string]: string[];
|
|
264
273
|
}
|
|
274
|
+
export interface CronJob {
|
|
275
|
+
task: (projectId: number, client: Crowdin, apiCredentials: any, appRootFolder?: SourceFilesModel.Directory, config?: any) => Promise<void>;
|
|
276
|
+
expression: string;
|
|
277
|
+
}
|
package/out/models/index.js
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AccountType = void 0;
|
|
4
|
+
var AccountType;
|
|
5
|
+
(function (AccountType) {
|
|
6
|
+
AccountType["NORMAL"] = "normal";
|
|
7
|
+
AccountType["ENTERPRISE"] = "enterprise";
|
|
8
|
+
})(AccountType = exports.AccountType || (exports.AccountType = {}));
|
package/out/static/js/main.js
CHANGED
package/out/storage/index.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ export declare function connect(folder: string): Promise<void>;
|
|
|
3
3
|
export declare function saveCrowdinCredentials(credentials: CrowdinCredentials): Promise<void>;
|
|
4
4
|
export declare function updateCrowdinCredentials(credentials: CrowdinCredentials): Promise<void>;
|
|
5
5
|
export declare function getCrowdinCredentials(id: string): Promise<CrowdinCredentials | undefined>;
|
|
6
|
+
export declare function getAllCrowdinCredentials(): Promise<CrowdinCredentials[]>;
|
|
6
7
|
export declare function deleteCrowdinCredentials(id: string): Promise<void>;
|
|
7
8
|
export declare function saveIntegrationCredentials(id: string, credentials: any, crowdinId: string): Promise<void>;
|
|
8
9
|
export declare function updateIntegrationCredentials(id: string, credentials: any): Promise<void>;
|
|
9
10
|
export declare function updateIntegrationConfig(id: string, config: any): Promise<void>;
|
|
10
11
|
export declare function getIntegrationCredentials(id: string): Promise<IntegrationCredentials | undefined>;
|
|
12
|
+
export declare function getAllIntegrationCredentials(crowdinId: string): Promise<IntegrationCredentials[]>;
|
|
11
13
|
export declare function deleteIntegrationCredentials(id: string): Promise<void>;
|
package/out/storage/index.js
CHANGED
|
@@ -13,7 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
14
|
};
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.deleteIntegrationCredentials = exports.getIntegrationCredentials = exports.updateIntegrationConfig = exports.updateIntegrationCredentials = exports.saveIntegrationCredentials = exports.deleteCrowdinCredentials = exports.getCrowdinCredentials = exports.updateCrowdinCredentials = exports.saveCrowdinCredentials = exports.connect = void 0;
|
|
16
|
+
exports.deleteIntegrationCredentials = exports.getAllIntegrationCredentials = exports.getIntegrationCredentials = exports.updateIntegrationConfig = exports.updateIntegrationCredentials = exports.saveIntegrationCredentials = exports.deleteCrowdinCredentials = exports.getAllCrowdinCredentials = exports.getCrowdinCredentials = exports.updateCrowdinCredentials = exports.saveCrowdinCredentials = exports.connect = void 0;
|
|
17
17
|
const path_1 = require("path");
|
|
18
18
|
const sqlite3_1 = __importDefault(require("sqlite3"));
|
|
19
19
|
let _res;
|
|
@@ -61,7 +61,8 @@ function connect(folder) {
|
|
|
61
61
|
id varchar not null primary key,
|
|
62
62
|
access_token varchar not null,
|
|
63
63
|
refresh_token varchar not null,
|
|
64
|
-
expire varchar not null
|
|
64
|
+
expire varchar not null,
|
|
65
|
+
type varchar not null
|
|
65
66
|
);
|
|
66
67
|
`, []);
|
|
67
68
|
yield _run(`
|
|
@@ -111,13 +112,24 @@ function get(query, params) {
|
|
|
111
112
|
});
|
|
112
113
|
});
|
|
113
114
|
}
|
|
115
|
+
function each(query, params) {
|
|
116
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
117
|
+
yield dbPromise;
|
|
118
|
+
return new Promise((res, rej) => {
|
|
119
|
+
const result = [];
|
|
120
|
+
db.each(query, params, (err, row) => {
|
|
121
|
+
if (err) {
|
|
122
|
+
rej(err);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
result.push(row);
|
|
126
|
+
}
|
|
127
|
+
}, () => res(result));
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
114
131
|
function saveCrowdinCredentials(credentials) {
|
|
115
|
-
return run('INSERT INTO crowdin_credentials(id, access_token, refresh_token, expire) VALUES (?, ?, ?, ?)', [
|
|
116
|
-
credentials.id,
|
|
117
|
-
credentials.accessToken,
|
|
118
|
-
credentials.refreshToken,
|
|
119
|
-
credentials.expire,
|
|
120
|
-
]);
|
|
132
|
+
return run('INSERT INTO crowdin_credentials(id, access_token, refresh_token, expire, type) VALUES (?, ?, ?, ?, ?)', [credentials.id, credentials.accessToken, credentials.refreshToken, credentials.expire, credentials.type]);
|
|
121
133
|
}
|
|
122
134
|
exports.saveCrowdinCredentials = saveCrowdinCredentials;
|
|
123
135
|
function updateCrowdinCredentials(credentials) {
|
|
@@ -131,13 +143,19 @@ function updateCrowdinCredentials(credentials) {
|
|
|
131
143
|
exports.updateCrowdinCredentials = updateCrowdinCredentials;
|
|
132
144
|
function getCrowdinCredentials(id) {
|
|
133
145
|
return __awaiter(this, void 0, void 0, function* () {
|
|
134
|
-
const row = yield get('SELECT id, access_token as accessToken, refresh_token as refreshToken, expire FROM crowdin_credentials WHERE id = ?', [id]);
|
|
146
|
+
const row = yield get('SELECT id, access_token as accessToken, refresh_token as refreshToken, expire, type FROM crowdin_credentials WHERE id = ?', [id]);
|
|
135
147
|
if (row) {
|
|
136
148
|
return row;
|
|
137
149
|
}
|
|
138
150
|
});
|
|
139
151
|
}
|
|
140
152
|
exports.getCrowdinCredentials = getCrowdinCredentials;
|
|
153
|
+
function getAllCrowdinCredentials() {
|
|
154
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
155
|
+
return each('SELECT id, access_token as accessToken, refresh_token as refreshToken, expire, type FROM crowdin_credentials', []);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
exports.getAllCrowdinCredentials = getAllCrowdinCredentials;
|
|
141
159
|
function deleteCrowdinCredentials(id) {
|
|
142
160
|
return __awaiter(this, void 0, void 0, function* () {
|
|
143
161
|
yield run('DELETE FROM crowdin_credentials where id = ?', [id]);
|
|
@@ -170,6 +188,12 @@ function getIntegrationCredentials(id) {
|
|
|
170
188
|
});
|
|
171
189
|
}
|
|
172
190
|
exports.getIntegrationCredentials = getIntegrationCredentials;
|
|
191
|
+
function getAllIntegrationCredentials(crowdinId) {
|
|
192
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
193
|
+
return each('SELECT id, credentials, config, crowdin_id as crowdinId FROM integration_credentials WHERE crowdin_id = ?', [crowdinId]);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
exports.getAllIntegrationCredentials = getAllIntegrationCredentials;
|
|
173
197
|
function deleteIntegrationCredentials(id) {
|
|
174
198
|
return run('DELETE FROM integration_credentials where id = ?', [id]);
|
|
175
199
|
}
|
package/out/util/index.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import Crowdin, { SourceFilesModel } from '@crowdin/crowdin-api-client';
|
|
2
2
|
import { Request, Response } from 'express';
|
|
3
|
-
import { Config } from '../models';
|
|
3
|
+
import { Config, CronJob, CrowdinCredentials, IntegrationCredentials } from '../models';
|
|
4
|
+
export declare class CodeError extends Error {
|
|
5
|
+
code: number | undefined;
|
|
6
|
+
constructor(message: string, code?: number);
|
|
7
|
+
}
|
|
4
8
|
export declare function runAsyncWrapper(callback: Function): (req: Request, res: Response, next: Function) => void;
|
|
5
9
|
export declare function encryptData(secret: string, data: string): string;
|
|
6
10
|
export declare function decryptData(secret: string, data: string): string;
|
|
7
11
|
export declare function getOauthRoute(config: Config): string;
|
|
8
12
|
export declare function getRootFolder(config: Config, client: Crowdin, projectId: number): Promise<SourceFilesModel.Directory | undefined>;
|
|
9
13
|
export declare function applyDefaults(config: Config): void;
|
|
14
|
+
export declare function prepareCrowdinClient(config: Config, credentials: CrowdinCredentials): Promise<Crowdin>;
|
|
15
|
+
export declare function prepareIntegrationCredentials(config: Config, integrationCredentials: IntegrationCredentials): Promise<any>;
|
|
16
|
+
export declare function runJob(config: Config, job: CronJob): Promise<void>;
|
package/out/util/index.js
CHANGED
|
@@ -27,13 +27,47 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
27
27
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
28
28
|
});
|
|
29
29
|
};
|
|
30
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
|
+
};
|
|
30
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
-
exports.applyDefaults = exports.getRootFolder = exports.getOauthRoute = exports.decryptData = exports.encryptData = exports.runAsyncWrapper = void 0;
|
|
34
|
+
exports.runJob = exports.prepareIntegrationCredentials = exports.prepareCrowdinClient = exports.applyDefaults = exports.getRootFolder = exports.getOauthRoute = exports.decryptData = exports.encryptData = exports.runAsyncWrapper = exports.CodeError = void 0;
|
|
35
|
+
const crowdin_api_client_1 = __importDefault(require("@crowdin/crowdin-api-client"));
|
|
32
36
|
const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions"));
|
|
37
|
+
const axios_1 = __importDefault(require("axios"));
|
|
33
38
|
const crypto = __importStar(require("crypto-js"));
|
|
39
|
+
const models_1 = require("../models");
|
|
40
|
+
const storage_1 = require("../storage");
|
|
41
|
+
class CodeError extends Error {
|
|
42
|
+
constructor(message, code) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.code = code;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.CodeError = CodeError;
|
|
48
|
+
function isCrowdinClientRequest(req) {
|
|
49
|
+
return req.crowdinContext;
|
|
50
|
+
}
|
|
51
|
+
function handleError(err, req, res) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
console.error(err);
|
|
54
|
+
const code = err.code ? err.code : 500;
|
|
55
|
+
if (code === 401 && isCrowdinClientRequest(req)) {
|
|
56
|
+
yield (0, storage_1.deleteIntegrationCredentials)(req.crowdinContext.clientId);
|
|
57
|
+
}
|
|
58
|
+
if (code === 401 && req.path === '/') {
|
|
59
|
+
res.redirect('/');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
res.status(code).send({
|
|
63
|
+
message: err.message ? err.message : JSON.stringify(err),
|
|
64
|
+
code,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
34
68
|
function runAsyncWrapper(callback) {
|
|
35
69
|
return (req, res, next) => {
|
|
36
|
-
callback(req, res, next).catch((e) =>
|
|
70
|
+
callback(req, res, next).catch((e) => handleError(e, req, res));
|
|
37
71
|
};
|
|
38
72
|
}
|
|
39
73
|
exports.runAsyncWrapper = runAsyncWrapper;
|
|
@@ -69,7 +103,11 @@ function applyDefaults(config) {
|
|
|
69
103
|
if (rootFolder) {
|
|
70
104
|
allDirectories = (yield client.sourceFilesApi
|
|
71
105
|
.withFetchAll()
|
|
72
|
-
|
|
106
|
+
// @ts-expect-error: Method overloading
|
|
107
|
+
.listProjectDirectories(projectId, {
|
|
108
|
+
directoryId: rootFolder.id,
|
|
109
|
+
recursion: 'true',
|
|
110
|
+
})).data.map(d => d.data);
|
|
73
111
|
}
|
|
74
112
|
else {
|
|
75
113
|
allDirectories = (yield client.sourceFilesApi.withFetchAll().listProjectDirectories(projectId)).data.map(d => d.data);
|
|
@@ -113,3 +151,89 @@ function applyDefaults(config) {
|
|
|
113
151
|
}
|
|
114
152
|
}
|
|
115
153
|
exports.applyDefaults = applyDefaults;
|
|
154
|
+
function prepareCrowdinClient(config, credentials) {
|
|
155
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
156
|
+
const isExpired = +credentials.expire < +new Date().getTime() / 1000;
|
|
157
|
+
if (!isExpired) {
|
|
158
|
+
const crowdinToken = decryptData(config.clientSecret, credentials.accessToken);
|
|
159
|
+
return new crowdin_api_client_1.default({
|
|
160
|
+
token: crowdinToken,
|
|
161
|
+
organization: credentials.type === models_1.AccountType.ENTERPRISE ? credentials.id : undefined,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const newCredentials = yield crowdinAppFunctions.refreshOAuthToken(config.clientId, config.clientSecret, decryptData(config.clientSecret, credentials.refreshToken));
|
|
166
|
+
yield (0, storage_1.updateCrowdinCredentials)({
|
|
167
|
+
id: credentials.id,
|
|
168
|
+
refreshToken: encryptData(config.clientSecret, newCredentials.refreshToken),
|
|
169
|
+
accessToken: encryptData(config.clientSecret, newCredentials.accessToken),
|
|
170
|
+
expire: (new Date().getTime() / 1000 + newCredentials.expiresIn).toString(),
|
|
171
|
+
type: credentials.type,
|
|
172
|
+
});
|
|
173
|
+
return new crowdin_api_client_1.default({
|
|
174
|
+
token: newCredentials.accessToken,
|
|
175
|
+
organization: credentials.type === models_1.AccountType.ENTERPRISE ? credentials.id : undefined,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
exports.prepareCrowdinClient = prepareCrowdinClient;
|
|
181
|
+
function prepareIntegrationCredentials(config, integrationCredentials) {
|
|
182
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
183
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
184
|
+
const credentials = JSON.parse(decryptData(config.clientSecret, integrationCredentials.credentials));
|
|
185
|
+
if ((_a = config.oauthLogin) === null || _a === void 0 ? void 0 : _a.refresh) {
|
|
186
|
+
const oauthLogin = config.oauthLogin;
|
|
187
|
+
const { expireIn } = credentials;
|
|
188
|
+
//2 min as an extra buffer
|
|
189
|
+
const isExpired = expireIn + 120 < Date.now() / 1000;
|
|
190
|
+
if (isExpired) {
|
|
191
|
+
let newCredentials;
|
|
192
|
+
if (oauthLogin.performRefreshTokenRequest) {
|
|
193
|
+
newCredentials = yield oauthLogin.performRefreshTokenRequest(credentials);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const url = oauthLogin.refreshTokenUrl || oauthLogin.accessTokenUrl;
|
|
197
|
+
const request = {};
|
|
198
|
+
request[((_b = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _b === void 0 ? void 0 : _b.clientId) || 'client_id'] = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.clientId;
|
|
199
|
+
request[((_c = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _c === void 0 ? void 0 : _c.clientSecret) || 'client_secret'] = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.clientSecret;
|
|
200
|
+
request[((_d = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _d === void 0 ? void 0 : _d.refreshToken) || 'refresh_token'] = credentials.refreshToken;
|
|
201
|
+
if (oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.extraRefreshTokenParameters) {
|
|
202
|
+
Object.entries(oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.extraRefreshTokenParameters).forEach(([key, value]) => (request[key] = value));
|
|
203
|
+
}
|
|
204
|
+
newCredentials = (yield axios_1.default.post(url || '', request, {
|
|
205
|
+
headers: { Accept: 'application/json' },
|
|
206
|
+
})).data;
|
|
207
|
+
}
|
|
208
|
+
credentials.accessToken = newCredentials[((_e = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _e === void 0 ? void 0 : _e.accessToken) || 'access_token'];
|
|
209
|
+
credentials.expireIn =
|
|
210
|
+
Number(newCredentials[((_f = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _f === void 0 ? void 0 : _f.expiresIn) || 'expires_in']) + Date.now() / 1000;
|
|
211
|
+
if (newCredentials[((_g = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _g === void 0 ? void 0 : _g.refreshToken) || 'refresh_token']) {
|
|
212
|
+
credentials.refreshToken = newCredentials[((_h = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _h === void 0 ? void 0 : _h.refreshToken) || 'refresh_token'];
|
|
213
|
+
}
|
|
214
|
+
yield (0, storage_1.updateIntegrationCredentials)(integrationCredentials.id, encryptData(config.clientSecret, JSON.stringify(credentials)));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return credentials;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
exports.prepareIntegrationCredentials = prepareIntegrationCredentials;
|
|
221
|
+
function runJob(config, job) {
|
|
222
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
223
|
+
const crowdinCredentialsList = yield (0, storage_1.getAllCrowdinCredentials)();
|
|
224
|
+
yield Promise.all(crowdinCredentialsList.map((crowdinCredentials) => __awaiter(this, void 0, void 0, function* () {
|
|
225
|
+
const crowdinClient = yield prepareCrowdinClient(config, crowdinCredentials);
|
|
226
|
+
const integrationCredentialsList = yield (0, storage_1.getAllIntegrationCredentials)(crowdinCredentials.id);
|
|
227
|
+
yield Promise.all(integrationCredentialsList.map((integrationCredentials) => __awaiter(this, void 0, void 0, function* () {
|
|
228
|
+
const projectId = crowdinAppFunctions.getProjectId(integrationCredentials.id);
|
|
229
|
+
const apiCredentials = yield prepareIntegrationCredentials(config, integrationCredentials);
|
|
230
|
+
const rootFolder = yield getRootFolder(config, crowdinClient, projectId);
|
|
231
|
+
const intConfig = integrationCredentials.config
|
|
232
|
+
? JSON.parse(integrationCredentials.config)
|
|
233
|
+
: undefined;
|
|
234
|
+
yield job.task(projectId, crowdinClient, apiCredentials, rootFolder, intConfig);
|
|
235
|
+
})));
|
|
236
|
+
})));
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
exports.runJob = runJob;
|
|
@@ -300,6 +300,17 @@
|
|
|
300
300
|
{{#if infoModal}}
|
|
301
301
|
const infoModal = document.getElementById('info-modal');
|
|
302
302
|
{{/if}}
|
|
303
|
+
|
|
304
|
+
document.addEventListener('keydown', (event) => {
|
|
305
|
+
if (event.keyCode == 27) {
|
|
306
|
+
if (infoModal) {
|
|
307
|
+
infoModal.close();
|
|
308
|
+
}
|
|
309
|
+
if (settingsModal) {
|
|
310
|
+
settingsModal.close();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
303
314
|
</script>
|
|
304
315
|
|
|
305
316
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crowdin/app-project-module",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Module that generates for you all common endpoints for serving standalone Crowdin App",
|
|
5
5
|
"main": "out/index.js",
|
|
6
6
|
"types": "out/index.d.ts",
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
"test": "echo \"test not implemented\""
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"
|
|
16
|
-
"@crowdin/crowdin-apps-functions": "0.0.2",
|
|
15
|
+
"@crowdin/crowdin-apps-functions": "0.0.3",
|
|
17
16
|
"crypto-js": "^4.0.0",
|
|
17
|
+
"express": "4.17.1",
|
|
18
18
|
"express-handlebars": "^5.3.4",
|
|
19
|
+
"node-cron": "^3.0.0",
|
|
19
20
|
"sqlite3": "^5.0.2"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
"@types/express-handlebars": "^5.3.1",
|
|
25
26
|
"@types/node": "^12.0.10",
|
|
26
27
|
"@types/sqlite3": "^3.1.7",
|
|
28
|
+
"@types/node-cron": "^3.0.0",
|
|
27
29
|
"@typescript-eslint/eslint-plugin": "^2.3.1",
|
|
28
30
|
"@typescript-eslint/parser": "^2.3.1",
|
|
29
31
|
"eslint": "^6.4.0",
|