@crowdin/app-project-module 0.62.0 → 0.63.0

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/out/index.js CHANGED
@@ -49,12 +49,14 @@ const uninstall_1 = __importDefault(require("./modules/uninstall"));
49
49
  const storage = __importStar(require("./storage"));
50
50
  const types_1 = require("./types");
51
51
  const util_1 = require("./util");
52
+ const form_schema_1 = require("./util/form-schema");
52
53
  const connection_1 = require("./util/connection");
53
54
  const handlebars_1 = require("./util/handlebars");
54
55
  const logger = __importStar(require("./util/logger"));
55
56
  const logger_1 = require("./util/logger");
56
57
  const terminus_express_1 = __importDefault(require("./util/terminus-express"));
57
58
  exports.express = terminus_express_1.default;
59
+ const credentials_masker_1 = require("./util/credentials-masker");
58
60
  //apps
59
61
  const apiApp = __importStar(require("./modules/api"));
60
62
  const contextMenuApp = __importStar(require("./modules/context-menu"));
@@ -192,13 +194,18 @@ function addCrowdinEndpoints(app, clientConfig) {
192
194
  }
193
195
  exports.addCrowdinEndpoints = addCrowdinEndpoints;
194
196
  function addFormSchema({ app, config }) {
197
+ let moduleConfigWithForm;
195
198
  const shouldAddRoutes = Object.keys(config).some((moduleKey) => {
196
199
  const moduleConfig = config[moduleKey];
197
- return (0, util_1.hasFormSchema)(moduleConfig);
200
+ if ((0, form_schema_1.hasFormSchema)(moduleConfig)) {
201
+ moduleConfigWithForm = (0, form_schema_1.getLowCodeUiConfigFromModuleConfig)(moduleConfig);
202
+ return true;
203
+ }
204
+ return false;
198
205
  });
199
206
  if (shouldAddRoutes) {
200
- app.get('/api/form-data', json_response_1.default, (0, crowdin_client_1.default)(config), (0, form_data_display_1.default)());
201
- app.post('/api/form-data', (0, crowdin_client_1.default)(config), (0, form_data_save_1.default)());
207
+ app.get('/api/form-data', json_response_1.default, (0, crowdin_client_1.default)(config), (0, credentials_masker_1.getRequestCredentialsMasker)(moduleConfigWithForm), (0, form_data_display_1.default)());
208
+ app.post('/api/form-data', (0, crowdin_client_1.default)(config), (0, credentials_masker_1.postRequestCredentialsMasker)(moduleConfigWithForm), (0, form_data_save_1.default)());
202
209
  }
203
210
  }
204
211
  function convertClientConfig(clientConfig) {
@@ -81,248 +81,256 @@ function runJob({ config, integration, job, }) {
81
81
  }
82
82
  exports.runJob = runJob;
83
83
  function filesCron({ config, integration, period, }) {
84
- var _a;
85
84
  return __awaiter(this, void 0, void 0, function* () {
86
85
  (0, logger_1.log)(`Starting files cron job with period [${period}]`);
87
86
  const syncSettingsList = yield (0, storage_1.getStorage)().getAllSyncSettingsByType('schedule');
88
- for (const syncSettings of syncSettingsList) {
89
- let projectData;
90
- let crowdinClient;
91
- let token;
92
- let files = JSON.parse(syncSettings.files);
93
- let newFiles = [];
94
- const crowdinCredentials = yield (0, storage_1.getStorage)().getCrowdinCredentials(syncSettings.crowdinId);
95
- const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(syncSettings.integrationId);
96
- const integrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(syncSettings.integrationId);
97
- if (!crowdinCredentials || !integrationCredentials) {
98
- continue;
99
- }
100
- const intConfig = (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.config)
101
- ? JSON.parse(integrationConfig.config)
102
- : { schedule: '0', condition: '0' };
103
- if (period !== intConfig.schedule) {
104
- continue;
105
- }
106
- const projectId = crowdinAppFunctions.getProjectId(integrationCredentials.id);
107
- const context = {
108
- jwtPayload: {
109
- context: {
110
- // eslint-disable-next-line @typescript-eslint/camelcase
111
- project_id: projectId,
112
- // eslint-disable-next-line @typescript-eslint/camelcase
113
- organization_id: crowdinCredentials.organizationId,
114
- // eslint-disable-next-line @typescript-eslint/camelcase
115
- organization_domain: crowdinCredentials.domain,
116
- // eslint-disable-next-line @typescript-eslint/camelcase
117
- user_id: crowdinCredentials.userId,
118
- },
87
+ const crowdinSyncSettings = syncSettingsList.filter((syncSettings) => syncSettings.provider === types_1.Provider.CROWDIN);
88
+ const integrationSyncSettings = syncSettingsList.filter((syncSettings) => syncSettings.provider === types_1.Provider.INTEGRATION);
89
+ yield Promise.all(crowdinSyncSettings.map((syncSettings) => processSyncSettings({ config, integration, period, syncSettings })));
90
+ yield Promise.all([
91
+ integrationSyncSettings.map((syncSettings) => processSyncSettings({ config, integration, period, syncSettings })),
92
+ ]);
93
+ (0, logger_1.log)(`Files cron job with period [${period}] completed`);
94
+ });
95
+ }
96
+ exports.filesCron = filesCron;
97
+ function processSyncSettings({ config, integration, period, syncSettings, }) {
98
+ var _a;
99
+ return __awaiter(this, void 0, void 0, function* () {
100
+ let projectData;
101
+ let crowdinClient;
102
+ let token;
103
+ let files = JSON.parse(syncSettings.files);
104
+ let newFiles = [];
105
+ const crowdinCredentials = yield (0, storage_1.getStorage)().getCrowdinCredentials(syncSettings.crowdinId);
106
+ const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(syncSettings.integrationId);
107
+ const integrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(syncSettings.integrationId);
108
+ if (!crowdinCredentials || !integrationCredentials) {
109
+ return;
110
+ }
111
+ const intConfig = (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.config)
112
+ ? JSON.parse(integrationConfig.config)
113
+ : { schedule: '0', condition: '0' };
114
+ if (period !== intConfig.schedule) {
115
+ return;
116
+ }
117
+ const projectId = crowdinAppFunctions.getProjectId(integrationCredentials.id);
118
+ const context = {
119
+ jwtPayload: {
120
+ context: {
121
+ // eslint-disable-next-line @typescript-eslint/camelcase
122
+ project_id: projectId,
123
+ // eslint-disable-next-line @typescript-eslint/camelcase
124
+ organization_id: crowdinCredentials.organizationId,
125
+ // eslint-disable-next-line @typescript-eslint/camelcase
126
+ organization_domain: crowdinCredentials.domain,
127
+ // eslint-disable-next-line @typescript-eslint/camelcase
128
+ user_id: crowdinCredentials.userId,
119
129
  },
120
- };
130
+ },
131
+ };
132
+ try {
133
+ const preparedCrowdinClient = yield (0, connection_1.prepareCrowdinClient)({
134
+ config,
135
+ credentials: crowdinCredentials,
136
+ autoRenew: true,
137
+ context,
138
+ });
139
+ token = preparedCrowdinClient.token;
140
+ crowdinClient = preparedCrowdinClient.client;
141
+ }
142
+ catch (e) {
143
+ (0, logger_1.logError)(e);
144
+ return;
145
+ }
146
+ const { expired } = yield (0, subscription_1.checkSubscription)({
147
+ config,
148
+ token,
149
+ organization: crowdinCredentials.id,
150
+ accountType: crowdinCredentials.type,
151
+ });
152
+ if (expired) {
153
+ (0, logger_1.log)(`Subscription expired. Skipping job [${period}] for organization ${crowdinCredentials.id}`);
154
+ return;
155
+ }
156
+ try {
157
+ projectData = (yield crowdinClient.projectsGroupsApi.getProject(projectId))
158
+ .data;
159
+ }
160
+ catch (e) {
161
+ (0, logger_1.logError)(e);
162
+ return;
163
+ }
164
+ // eslint-disable-next-line @typescript-eslint/camelcase
165
+ context.jwtPayload.context.project_identifier = projectData.identifier;
166
+ const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, crowdinClient, projectId);
167
+ const apiCredentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
168
+ if (!integration.webhooks &&
169
+ ((_a = integration.syncNewElements) === null || _a === void 0 ? void 0 : _a[syncSettings.provider]) &&
170
+ intConfig[`new-${syncSettings.provider}-files`]) {
121
171
  try {
122
- const preparedCrowdinClient = yield (0, connection_1.prepareCrowdinClient)({
172
+ newFiles = yield getAllNewFiles({
123
173
  config,
124
- credentials: crowdinCredentials,
125
- autoRenew: true,
126
- context,
174
+ integration,
175
+ projectData,
176
+ syncSettings,
177
+ crowdinApiClient: crowdinClient,
178
+ crowdinId: crowdinCredentials.id,
179
+ integrationCredentials: apiCredentials,
180
+ integrationId: integrationCredentials.id,
181
+ integrationSettings: intConfig,
127
182
  });
128
- token = preparedCrowdinClient.token;
129
- crowdinClient = preparedCrowdinClient.client;
130
183
  }
131
184
  catch (e) {
132
185
  (0, logger_1.logError)(e);
133
- continue;
186
+ return;
134
187
  }
135
- const { expired } = yield (0, subscription_1.checkSubscription)({
188
+ }
189
+ if (integration.webhooks) {
190
+ const webhooks = yield (0, storage_1.getStorage)().getAllWebhooks(syncSettings.integrationId, syncSettings.crowdinId, syncSettings.provider);
191
+ const webhooksFileIds = (webhooks || []).map((webhook) => webhook.fileId);
192
+ if (syncSettings.provider === types_1.Provider.CROWDIN) {
193
+ files = webhooksFileIds.reduce((acc, fileId) => {
194
+ if (files[fileId]) {
195
+ acc[fileId] = files[fileId];
196
+ }
197
+ return acc;
198
+ }, {});
199
+ }
200
+ else {
201
+ files = files.filter((file) => webhooksFileIds.includes(file.id));
202
+ }
203
+ yield (0, storage_1.getStorage)().deleteWebhooks(webhooksFileIds, syncSettings.integrationId, syncSettings.crowdinId, syncSettings.provider);
204
+ }
205
+ if (syncSettings.provider === types_1.Provider.CROWDIN) {
206
+ const crowdinFiles = yield skipFoldersFromIntegrationRequest({
136
207
  config,
137
- token,
138
- organization: crowdinCredentials.id,
139
- accountType: crowdinCredentials.type,
208
+ integration,
209
+ projectId,
210
+ crowdinFiles: Object.assign(Object.assign({}, files), newFiles),
211
+ crowdinClient,
140
212
  });
141
- if (expired) {
142
- (0, logger_1.log)(`Subscription expired. Skipping job [${period}] for organization ${crowdinCredentials.id}`);
143
- continue;
213
+ const onlyTranslated = +intConfig.condition === types_1.SyncCondition.TRANSLATED;
214
+ const onlyApproved = +intConfig.condition === types_1.SyncCondition.APPROVED;
215
+ const all = +intConfig.condition === types_1.SyncCondition.ALL || intConfig.condition === undefined;
216
+ const filesToProcess = all
217
+ ? crowdinFiles
218
+ : yield getOnlyTranslatedOrApprovedFiles({
219
+ projectId,
220
+ crowdinFiles,
221
+ crowdinClient,
222
+ onlyApproved,
223
+ onlyTranslated,
224
+ });
225
+ if (Object.keys(filesToProcess).length <= 0) {
226
+ return;
227
+ }
228
+ (0, logger_1.log)(`Executing updateIntegration task for files cron job with period [${period}] for project ${projectId}.Files ${Object.keys(filesToProcess).length}`);
229
+ if (!all) {
230
+ if (Object.keys(filesToProcess).length === 0) {
231
+ (0, logger_1.log)(`There is no ${onlyApproved ? 'approved' : 'translated'} file`);
232
+ return;
233
+ }
234
+ }
235
+ const apiCredentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
236
+ if (!(intConfig === null || intConfig === void 0 ? void 0 : intConfig.inContext)) {
237
+ removeInContextLanguage(filesToProcess, projectData);
144
238
  }
145
239
  try {
146
- projectData = (yield crowdinClient.projectsGroupsApi.getProject(projectId))
147
- .data;
240
+ yield (0, job_1.runAsJob)({
241
+ integrationId: syncSettings.integrationId,
242
+ crowdinId: syncSettings.crowdinId,
243
+ type: types_2.JobType.UPDATE_TO_INTEGRATION,
244
+ title: `Sync files to ${config.name} [scheduled]`,
245
+ payload: filesToProcess,
246
+ jobType: types_2.JobClientType.CRON,
247
+ projectId: projectId,
248
+ client: crowdinClient,
249
+ jobCallback: (job) => __awaiter(this, void 0, void 0, function* () {
250
+ yield integration.updateIntegration({
251
+ projectId,
252
+ client: crowdinClient,
253
+ credentials: apiCredentials,
254
+ request: filesToProcess,
255
+ rootFolder,
256
+ appSettings: intConfig,
257
+ job,
258
+ });
259
+ }),
260
+ });
148
261
  }
149
262
  catch (e) {
150
- (0, logger_1.logError)(e);
151
- continue;
263
+ yield (0, logger_1.handleUserError)({
264
+ action: 'Auto sync files to External Service',
265
+ error: e,
266
+ crowdinId: syncSettings.crowdinId,
267
+ clientId: syncSettings.integrationId,
268
+ });
269
+ (0, logger_1.logError)(e, context);
270
+ return;
152
271
  }
153
- // eslint-disable-next-line @typescript-eslint/camelcase
154
- context.jwtPayload.context.project_identifier = projectData.identifier;
155
- const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, crowdinClient, projectId);
156
- const apiCredentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
157
- if (!integration.webhooks &&
158
- ((_a = integration.syncNewElements) === null || _a === void 0 ? void 0 : _a[syncSettings.provider]) &&
159
- intConfig[`new-${syncSettings.provider}-files`]) {
160
- try {
161
- newFiles = yield getAllNewFiles({
162
- config,
163
- integration,
164
- projectData,
165
- syncSettings,
166
- crowdinApiClient: crowdinClient,
167
- crowdinId: crowdinCredentials.id,
168
- integrationCredentials: apiCredentials,
169
- integrationId: integrationCredentials.id,
170
- integrationSettings: intConfig,
171
- });
172
- }
173
- catch (e) {
174
- (0, logger_1.logError)(e);
175
- continue;
176
- }
272
+ if (Object.keys(newFiles).length) {
273
+ yield (0, storage_1.getStorage)().updateSyncSettings(JSON.stringify(Object.assign(Object.assign({}, files), newFiles)), syncSettings.integrationId, syncSettings.crowdinId, 'schedule', syncSettings.provider);
274
+ const currentFileSnapshot = yield (0, snapshot_1.getCrowdinSnapshot)(config, integration, crowdinClient, projectId, intConfig);
275
+ yield (0, storage_1.getStorage)().updateFilesSnapshot(JSON.stringify(currentFileSnapshot), syncSettings.integrationId, syncSettings.crowdinId, syncSettings.provider);
177
276
  }
178
- if (integration.webhooks) {
179
- const webhooks = yield (0, storage_1.getStorage)().getAllWebhooks(syncSettings.integrationId, syncSettings.crowdinId, syncSettings.provider);
180
- const webhooksFileIds = (webhooks || []).map((webhook) => webhook.fileId);
181
- if (syncSettings.provider === types_1.Provider.CROWDIN) {
182
- files = webhooksFileIds.reduce((acc, fileId) => {
183
- if (files[fileId]) {
184
- acc[fileId] = files[fileId];
185
- }
186
- return acc;
187
- }, {});
188
- }
189
- else {
190
- files = files.filter((file) => webhooksFileIds.includes(file.id));
191
- }
192
- yield (0, storage_1.getStorage)().deleteWebhooks(webhooksFileIds, syncSettings.integrationId, syncSettings.crowdinId, syncSettings.provider);
277
+ (0, logger_1.log)(`updateIntegration task for files cron job with period [${period}] for project ${projectId} completed`);
278
+ }
279
+ else {
280
+ const allIntFiles = [...files, ...newFiles].map((file) => (Object.assign({ id: file.id, name: file.name, parentId: file.parent_id || file.parentId,
281
+ // eslint-disable-next-line @typescript-eslint/camelcase
282
+ parent_id: file.parent_id || file.parentId,
283
+ // eslint-disable-next-line @typescript-eslint/camelcase
284
+ node_type: file.nodeType || file.node_type }, (file.type ? { type: file.type } : {}))));
285
+ const intFiles = allIntFiles.filter((file) => 'type' in file);
286
+ if (intFiles.length <= 0) {
287
+ return;
193
288
  }
194
- if (syncSettings.provider === types_1.Provider.CROWDIN) {
195
- const crowdinFiles = yield skipFoldersFromIntegrationRequest({
196
- config,
197
- integration,
198
- projectId,
199
- crowdinFiles: Object.assign(Object.assign({}, files), newFiles),
200
- crowdinClient,
289
+ (0, logger_1.log)(`Executing updateCrowdin task for files cron job with period [${period}] for project ${projectId}. Files ${intFiles.length}`);
290
+ const apiCredentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
291
+ try {
292
+ yield (0, job_1.runAsJob)({
293
+ integrationId: syncSettings.integrationId,
294
+ crowdinId: syncSettings.crowdinId,
295
+ type: types_2.JobType.UPDATE_TO_CROWDIN,
296
+ title: 'Sync files to Crowdin [scheduled]',
297
+ payload: intFiles,
298
+ jobType: types_2.JobClientType.CRON,
299
+ projectId: projectId,
300
+ client: crowdinClient,
301
+ jobCallback: (job) => __awaiter(this, void 0, void 0, function* () {
302
+ yield integration.updateCrowdin({
303
+ projectId,
304
+ client: crowdinClient,
305
+ credentials: apiCredentials,
306
+ request: intFiles,
307
+ rootFolder,
308
+ appSettings: intConfig,
309
+ job,
310
+ });
311
+ }),
201
312
  });
202
- const onlyTranslated = +intConfig.condition === types_1.SyncCondition.TRANSLATED;
203
- const onlyApproved = +intConfig.condition === types_1.SyncCondition.APPROVED;
204
- const all = +intConfig.condition === types_1.SyncCondition.ALL || intConfig.condition === undefined;
205
- const filesToProcess = all
206
- ? crowdinFiles
207
- : yield getOnlyTranslatedOrApprovedFiles({
208
- projectId,
209
- crowdinFiles,
210
- crowdinClient,
211
- onlyApproved,
212
- onlyTranslated,
213
- });
214
- if (Object.keys(filesToProcess).length <= 0) {
215
- continue;
216
- }
217
- (0, logger_1.log)(`Executing updateIntegration task for files cron job with period [${period}] for project ${projectId}.Files ${Object.keys(filesToProcess).length}`);
218
- if (!all) {
219
- if (Object.keys(filesToProcess).length === 0) {
220
- (0, logger_1.log)(`There is no ${onlyApproved ? 'approved' : 'translated'} file`);
221
- continue;
222
- }
223
- }
224
- const apiCredentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
225
- if (!(intConfig === null || intConfig === void 0 ? void 0 : intConfig.inContext)) {
226
- removeInContextLanguage(filesToProcess, projectData);
227
- }
228
- try {
229
- yield (0, job_1.runAsJob)({
230
- integrationId: syncSettings.integrationId,
231
- crowdinId: syncSettings.crowdinId,
232
- type: types_2.JobType.UPDATE_TO_INTEGRATION,
233
- title: `Sync files to ${config.name} [scheduled]`,
234
- payload: filesToProcess,
235
- jobType: types_2.JobClientType.CRON,
236
- projectId: projectId,
237
- client: crowdinClient,
238
- jobCallback: (job) => __awaiter(this, void 0, void 0, function* () {
239
- yield integration.updateIntegration({
240
- projectId,
241
- client: crowdinClient,
242
- credentials: apiCredentials,
243
- request: filesToProcess,
244
- rootFolder,
245
- appSettings: intConfig,
246
- job,
247
- });
248
- }),
249
- });
250
- }
251
- catch (e) {
252
- yield (0, logger_1.handleUserError)({
253
- action: 'Auto sync files to External Service',
254
- error: e,
255
- crowdinId: syncSettings.crowdinId,
256
- clientId: syncSettings.integrationId,
257
- });
258
- (0, logger_1.logError)(e, context);
259
- continue;
260
- }
261
- if (Object.keys(newFiles).length) {
262
- yield (0, storage_1.getStorage)().updateSyncSettings(JSON.stringify(Object.assign(Object.assign({}, files), newFiles)), syncSettings.integrationId, syncSettings.crowdinId, 'schedule', syncSettings.provider);
263
- const currentFileSnapshot = yield (0, snapshot_1.getCrowdinSnapshot)(config, integration, crowdinClient, projectId, intConfig);
264
- yield (0, storage_1.getStorage)().updateFilesSnapshot(JSON.stringify(currentFileSnapshot), syncSettings.integrationId, syncSettings.crowdinId, syncSettings.provider);
265
- }
266
- (0, logger_1.log)(`updateIntegration task for files cron job with period [${period}] for project ${projectId} completed`);
267
313
  }
268
- else {
269
- const allIntFiles = [...files, ...newFiles].map((file) => (Object.assign({ id: file.id, name: file.name, parentId: file.parent_id || file.parentId,
270
- // eslint-disable-next-line @typescript-eslint/camelcase
271
- parent_id: file.parent_id || file.parentId,
272
- // eslint-disable-next-line @typescript-eslint/camelcase
273
- node_type: file.nodeType || file.node_type }, (file.type ? { type: file.type } : {}))));
274
- const intFiles = allIntFiles.filter((file) => 'type' in file);
275
- if (intFiles.length <= 0) {
276
- continue;
277
- }
278
- (0, logger_1.log)(`Executing updateCrowdin task for files cron job with period [${period}] for project ${projectId}. Files ${intFiles.length}`);
279
- const apiCredentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
280
- try {
281
- yield (0, job_1.runAsJob)({
282
- integrationId: syncSettings.integrationId,
283
- crowdinId: syncSettings.crowdinId,
284
- type: types_2.JobType.UPDATE_TO_CROWDIN,
285
- title: 'Sync files to Crowdin [scheduled]',
286
- payload: intFiles,
287
- jobType: types_2.JobClientType.CRON,
288
- projectId: projectId,
289
- client: crowdinClient,
290
- jobCallback: (job) => __awaiter(this, void 0, void 0, function* () {
291
- yield integration.updateCrowdin({
292
- projectId,
293
- client: crowdinClient,
294
- credentials: apiCredentials,
295
- request: intFiles,
296
- rootFolder,
297
- appSettings: intConfig,
298
- job,
299
- });
300
- }),
301
- });
302
- }
303
- catch (e) {
304
- yield (0, logger_1.handleUserError)({
305
- action: 'Auto sync files to Crowdin',
306
- error: e,
307
- crowdinId: syncSettings.crowdinId,
308
- clientId: syncSettings.integrationId,
309
- });
310
- (0, logger_1.logError)(e, context);
311
- continue;
312
- }
313
- if (Object.keys(newFiles).length) {
314
- const newSyncSettingsFields = allIntFiles.map((file) => (Object.assign(Object.assign({}, file), { schedule: true, sync: false })));
315
- yield (0, storage_1.getStorage)().updateSyncSettings(JSON.stringify(newSyncSettingsFields), syncSettings.integrationId, syncSettings.crowdinId, 'schedule', syncSettings.provider);
316
- const currentFileSnapshot = yield (0, snapshot_1.getIntegrationSnapshot)(integration, apiCredentials, intConfig);
317
- yield (0, storage_1.getStorage)().updateFilesSnapshot(JSON.stringify(currentFileSnapshot), syncSettings.integrationId, syncSettings.crowdinId, syncSettings.provider);
318
- }
319
- (0, logger_1.log)(`updateCrowdin task for files cron job with period [${period}] for project ${projectId} completed`);
314
+ catch (e) {
315
+ yield (0, logger_1.handleUserError)({
316
+ action: 'Auto sync files to Crowdin',
317
+ error: e,
318
+ crowdinId: syncSettings.crowdinId,
319
+ clientId: syncSettings.integrationId,
320
+ });
321
+ (0, logger_1.logError)(e, context);
322
+ return;
323
+ }
324
+ if (Object.keys(newFiles).length) {
325
+ const newSyncSettingsFields = allIntFiles.map((file) => (Object.assign(Object.assign({}, file), { schedule: true, sync: false })));
326
+ yield (0, storage_1.getStorage)().updateSyncSettings(JSON.stringify(newSyncSettingsFields), syncSettings.integrationId, syncSettings.crowdinId, 'schedule', syncSettings.provider);
327
+ const currentFileSnapshot = yield (0, snapshot_1.getIntegrationSnapshot)(integration, apiCredentials, intConfig);
328
+ yield (0, storage_1.getStorage)().updateFilesSnapshot(JSON.stringify(currentFileSnapshot), syncSettings.integrationId, syncSettings.crowdinId, syncSettings.provider);
320
329
  }
330
+ (0, logger_1.log)(`updateCrowdin task for files cron job with period [${period}] for project ${projectId} completed`);
321
331
  }
322
- (0, logger_1.log)(`Files cron job with period [${period}] completed`);
323
332
  });
324
333
  }
325
- exports.filesCron = filesCron;
326
334
  function getFileDiff(currentFiles, savedFiles) {
327
335
  return currentFiles.filter((x) => !savedFiles.some((x2) => x2.id === x.id));
328
336
  }
package/out/types.d.ts CHANGED
@@ -364,6 +364,10 @@ export interface UiModule {
364
364
  * Module name
365
365
  */
366
366
  name?: string;
367
+ /**
368
+ * Temporary property. Indicates if passwords should be masked. Will be dropped when all existing apps will migrate.
369
+ */
370
+ maskPasswords?: boolean;
367
371
  }
368
372
  export interface ImagePath {
369
373
  /**
@@ -0,0 +1,6 @@
1
+ /// <reference types="qs" />
2
+ import { CrowdinClientRequest, UiModule } from '../types';
3
+ import { Request, Response } from 'express';
4
+ declare function postRequestCredentialsMasker(moduleConfig?: UiModule, credentialsExtractor?: Function): (req: CrowdinClientRequest | Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
5
+ declare function getRequestCredentialsMasker(moduleConfig?: UiModule): (req: Request | CrowdinClientRequest, res: Response, next: Function) => any;
6
+ export { getRequestCredentialsMasker, postRequestCredentialsMasker };
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.postRequestCredentialsMasker = exports.getRequestCredentialsMasker = void 0;
16
+ const lodash_1 = __importDefault(require("lodash"));
17
+ const index_1 = require("./index");
18
+ const index_2 = require("../index");
19
+ const crowdin_client_1 = require("../middlewares/crowdin-client");
20
+ const crowdin_apps_functions_1 = require("@crowdin/crowdin-apps-functions");
21
+ function maskKey(key) {
22
+ const maskWith = '*';
23
+ const unmaskedCharsAtEnd = 3;
24
+ const repeatCount = key.length > unmaskedCharsAtEnd ? key.length - unmaskedCharsAtEnd : 0;
25
+ return maskWith.repeat(repeatCount) + key.substring(key.length - 3);
26
+ }
27
+ function getMaskableFieldsKeys(moduleConfig) {
28
+ if (!moduleConfig.formUiSchema) {
29
+ return [];
30
+ }
31
+ return Object.keys(moduleConfig.formUiSchema).filter((fieldKey) => {
32
+ return lodash_1.default.get(moduleConfig, `formUiSchema[${fieldKey}]['ui:widget']`) === 'password';
33
+ });
34
+ }
35
+ function postRequestCredentialsMasker(moduleConfig, credentialsExtractor) {
36
+ return (0, index_1.runAsyncWrapper)((req, res, next) => __awaiter(this, void 0, void 0, function* () {
37
+ if (!moduleConfig || !moduleConfig.formSchema || !moduleConfig.formUiSchema) {
38
+ return next();
39
+ }
40
+ // temporary
41
+ if (!moduleConfig.maskPasswords) {
42
+ return next();
43
+ }
44
+ const fieldsKeysInRequest = Object.keys(req.body.data);
45
+ let unmaskedFields = {};
46
+ if (credentialsExtractor) {
47
+ unmaskedFields = yield credentialsExtractor(req, res);
48
+ }
49
+ else {
50
+ // most common way of storing data
51
+ const jwtToken = (0, crowdin_client_1.getToken)(req) || '';
52
+ const jwtPayload = yield (0, crowdin_apps_functions_1.validateJwtToken)(jwtToken, process.env.CROWDIN_CLIENT_SECRET || '');
53
+ const crowdinId = `${jwtPayload.context.organization_id}`;
54
+ unmaskedFields = yield index_2.metadataStore.getMetadata(`form-data-${crowdinId}`);
55
+ }
56
+ unmaskedFields = unmaskedFields || {};
57
+ const maskableFieldsKeys = getMaskableFieldsKeys(moduleConfig);
58
+ Object.keys(unmaskedFields).forEach((fieldKey) => {
59
+ if (!maskableFieldsKeys.includes(fieldKey) || !fieldsKeysInRequest.includes(fieldKey)) {
60
+ delete unmaskedFields[fieldKey];
61
+ }
62
+ });
63
+ req.body.data = Object.assign(Object.assign({}, req.body.data), Object.keys(unmaskedFields).reduce((acc, key) => {
64
+ if (maskKey(unmaskedFields[key]) === req.body.data[key]) {
65
+ acc[key] = unmaskedFields[key];
66
+ }
67
+ return acc;
68
+ }, {}));
69
+ next();
70
+ }));
71
+ }
72
+ exports.postRequestCredentialsMasker = postRequestCredentialsMasker;
73
+ function getRequestCredentialsMasker(moduleConfig) {
74
+ return function (req, res, next) {
75
+ // we can't find "password" fields without ui schema
76
+ if (!moduleConfig || !moduleConfig.formSchema || !moduleConfig.formUiSchema) {
77
+ return next();
78
+ }
79
+ // temporary
80
+ if (!moduleConfig.maskPasswords) {
81
+ return next();
82
+ }
83
+ const maskableFieldsKeys = getMaskableFieldsKeys(moduleConfig);
84
+ if (!maskableFieldsKeys.length) {
85
+ return next();
86
+ }
87
+ const originalSend = res.send;
88
+ res.send = function (body) {
89
+ if (body.formData) {
90
+ maskableFieldsKeys.forEach((fieldKey) => {
91
+ if (!body.formData[fieldKey]) {
92
+ return;
93
+ }
94
+ body.formData[fieldKey] = maskKey(body.formData[fieldKey]);
95
+ });
96
+ }
97
+ return originalSend.apply(res, [body]);
98
+ };
99
+ return next();
100
+ };
101
+ }
102
+ exports.getRequestCredentialsMasker = getRequestCredentialsMasker;
@@ -0,0 +1,3 @@
1
+ import { UiModule } from '../types';
2
+ export declare function getLowCodeUiConfigFromModuleConfig(moduleConfig: any): UiModule | null;
3
+ export declare function hasFormSchema(moduleConfig: any): boolean;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasFormSchema = exports.getLowCodeUiConfigFromModuleConfig = void 0;
4
+ function getLowCodeUiConfigFromModuleConfig(moduleConfig) {
5
+ var _a;
6
+ if (typeof moduleConfig !== 'object' || moduleConfig === null) {
7
+ return null;
8
+ }
9
+ if ((_a = moduleConfig.settingsUiModule) === null || _a === void 0 ? void 0 : _a.formSchema) {
10
+ return moduleConfig.settingsUiModule;
11
+ }
12
+ if (moduleConfig.formSchema) {
13
+ return moduleConfig;
14
+ }
15
+ return null;
16
+ }
17
+ exports.getLowCodeUiConfigFromModuleConfig = getLowCodeUiConfigFromModuleConfig;
18
+ function hasFormSchema(moduleConfig) {
19
+ return !!getLowCodeUiConfigFromModuleConfig(moduleConfig);
20
+ }
21
+ exports.hasFormSchema = hasFormSchema;
@@ -10,4 +10,3 @@ export declare function decryptData(config: Config, data: string): string;
10
10
  export declare function executeWithRetry<T>(func: () => Promise<T>, numOfRetries?: number): Promise<T>;
11
11
  export declare function getLogoUrl(moduleConfig?: ImagePath, modulePath?: string): string;
12
12
  export declare function isAuthorizedConfig(config: Config | UnauthorizedConfig): config is Config;
13
- export declare function hasFormSchema(moduleConfig: any): boolean;
package/out/util/index.js CHANGED
@@ -32,7 +32,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
32
32
  });
33
33
  };
34
34
  Object.defineProperty(exports, "__esModule", { value: true });
35
- exports.hasFormSchema = exports.isAuthorizedConfig = exports.getLogoUrl = exports.executeWithRetry = exports.decryptData = exports.encryptData = exports.runAsyncWrapper = exports.CodeError = void 0;
35
+ exports.isAuthorizedConfig = exports.getLogoUrl = exports.executeWithRetry = exports.decryptData = exports.encryptData = exports.runAsyncWrapper = exports.CodeError = void 0;
36
36
  const crypto = __importStar(require("crypto-js"));
37
37
  const storage_1 = require("../storage");
38
38
  const types_1 = require("../types");
@@ -118,11 +118,3 @@ function isAuthorizedConfig(config) {
118
118
  return !!config.clientId && !!config.clientSecret && config.authenticationType !== types_1.AuthenticationType.NONE;
119
119
  }
120
120
  exports.isAuthorizedConfig = isAuthorizedConfig;
121
- function hasFormSchema(moduleConfig) {
122
- var _a;
123
- if (typeof moduleConfig === 'object' && moduleConfig !== null) {
124
- return moduleConfig.formSchema || ((_a = moduleConfig.settingsUiModule) === null || _a === void 0 ? void 0 : _a.formSchema);
125
- }
126
- return false;
127
- }
128
- exports.hasFormSchema = hasFormSchema;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.62.0",
3
+ "version": "0.63.0",
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",