@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 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.
@@ -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
- const isExpired = +credentials.expire < +new Date().getTime() / 1000;
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
- const credentials = JSON.parse((0, util_1.decryptData)(config.clientSecret, integrationCredentials.credentials));
33
- if ((_a = config.oauthLogin) === null || _a === void 0 ? void 0 : _a.refresh) {
34
- const oauthLogin = config.oauthLogin;
35
- const { expireIn } = credentials;
36
- //2 min as an extra buffer
37
- const isExpired = expireIn + 120 < Date.now() / 1000;
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
  }
@@ -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
+ }
@@ -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 = {}));
@@ -44,6 +44,10 @@ function showToast(message) {
44
44
  }
45
45
 
46
46
  function catchRejection(e, message) {
47
+ //session expired
48
+ if (e.code && e.code === 401) {
49
+ reloadLocation();
50
+ }
47
51
  showToast(e.message || message);
48
52
  }
49
53
 
@@ -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>;
@@ -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
  }
@@ -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) => res.status(500).send({ error: JSON.stringify(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
- .listProjectDirectories(projectId, undefined, rootFolder.id, undefined, undefined, undefined, 'true')).data.map(d => d.data);
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.5.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
- "express": "4.17.1",
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",