@crowdin/app-project-module 1.10.0 → 1.11.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.
@@ -34,13 +34,16 @@ function handle(integration) {
34
34
  res.send(progress);
35
35
  }
36
36
  catch (e) {
37
+ const errorWithData = new logger_1.AppUserModuleError(e, {
38
+ fileId,
39
+ });
37
40
  yield (0, logger_1.handleUserError)({
38
41
  action: 'Get Crowdin files progress',
39
- error: e,
42
+ error: errorWithData,
40
43
  crowdinId: req.crowdinContext.crowdinId,
41
44
  clientId: req.crowdinContext.clientId,
42
45
  });
43
- throw e;
46
+ throw errorWithData;
44
47
  }
45
48
  }
46
49
  else {
@@ -103,10 +103,22 @@ function handle(integration) {
103
103
  crowdinId,
104
104
  clientId,
105
105
  });
106
- res.send({ data: [], message: (e === null || e === void 0 ? void 0 : e.message) || e });
106
+ // Different response structure for API (Crowdin wraps) vs UI
107
+ if (req.isApiCall) {
108
+ res.send({ files: [], message: (e === null || e === void 0 ? void 0 : e.message) || e });
109
+ }
110
+ else {
111
+ res.send({ data: [], message: (e === null || e === void 0 ? void 0 : e.message) || e });
112
+ }
107
113
  throw e;
108
114
  }
109
115
  req.logInfo(`Integration data response ${JSON.stringify(files, null, 2)}`);
110
- res.send({ data: files, message, stopPagination });
116
+ // Different response structure for API (Crowdin wraps) vs UI
117
+ if (req.isApiCall) {
118
+ res.send({ files, message, stopPagination });
119
+ }
120
+ else {
121
+ res.send({ data: files, message, stopPagination });
122
+ }
111
123
  }));
112
124
  }
@@ -20,7 +20,6 @@ const job_1 = require("../util/job");
20
20
  const types_2 = require("../util/types");
21
21
  function handle(config, integration) {
22
22
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
23
- const settings = req.body.config;
24
23
  if (req.isApiCall && !req.body.config) {
25
24
  return res.status(400).json({
26
25
  error: {
@@ -28,6 +27,34 @@ function handle(config, integration) {
28
27
  },
29
28
  });
30
29
  }
30
+ if (req.isApiCall && typeof req.body.config !== 'object') {
31
+ return res.status(400).json({
32
+ error: {
33
+ message: 'Parameter "config" must be an object',
34
+ },
35
+ });
36
+ }
37
+ const settings = req.body.config;
38
+ // Validate that all config keys exist in schema
39
+ if (req.isApiCall && integration.getConfiguration) {
40
+ const projectId = req.crowdinContext.jwtPayload.context.project_id;
41
+ const schema = yield integration.getConfiguration({
42
+ projectId,
43
+ client: req.crowdinApiClient,
44
+ credentials: req.integrationCredentials,
45
+ settings: req.integrationSettings,
46
+ clientId: req.crowdinContext.clientId,
47
+ });
48
+ const validKeys = new Set(schema.filter((field) => 'key' in field).map((field) => field.key));
49
+ const invalidKeys = Object.keys(settings).filter((key) => !validKeys.has(key));
50
+ if (invalidKeys.length > 0) {
51
+ return res.status(400).json({
52
+ error: {
53
+ message: `Invalid configuration keys: ${invalidKeys.join(', ')}. Use GET /settings/schema to see available fields.`,
54
+ },
55
+ });
56
+ }
57
+ }
31
58
  if (integration.validateSettings) {
32
59
  const validationResult = yield integration.validateSettings({
33
60
  client: req.crowdinApiClient,
@@ -55,6 +82,7 @@ function handle(config, integration) {
55
82
  client: req.crowdinApiClient,
56
83
  jobType: types_2.JobClientType.HIDDEN,
57
84
  jobStoreType: integration.jobStoreType,
85
+ initiatedBy: String(req.crowdinContext.jwtPayload.context.user_id),
58
86
  jobCallback: () => __awaiter(this, void 0, void 0, function* () {
59
87
  req.logInfo(`Saving settings ${JSON.stringify(settings, null, 2)}`);
60
88
  const integrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(clientId);
@@ -0,0 +1,3 @@
1
+ import { Response } from 'express';
2
+ import { IntegrationLogic } from '../types';
3
+ export default function handle(integration: IntegrationLogic): (req: import("express").Request | import("../../../types").CrowdinClientRequest, res: Response, next: Function) => void;
@@ -0,0 +1,29 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.default = handle;
13
+ const util_1 = require("../../../util");
14
+ function handle(integration) {
15
+ return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
16
+ if (!integration.getConfiguration) {
17
+ return res.send([]);
18
+ }
19
+ const projectId = req.crowdinContext.jwtPayload.context.project_id;
20
+ const configurationFields = yield integration.getConfiguration({
21
+ projectId,
22
+ client: req.crowdinApiClient,
23
+ credentials: req.integrationCredentials,
24
+ settings: req.integrationSettings,
25
+ clientId: req.crowdinContext.clientId,
26
+ });
27
+ res.send(configurationFields);
28
+ }));
29
+ }
@@ -12,10 +12,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.default = handle;
13
13
  const storage_1 = require("../../../storage");
14
14
  const util_1 = require("../../../util");
15
+ const types_1 = require("../types");
15
16
  function handle() {
16
17
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
17
18
  let files = {};
18
- const provider = req.params.provider || req.body.provider;
19
+ const provider = req.params.provider || req.query.provider || req.body.provider;
19
20
  if (req.isApiCall && !provider) {
20
21
  return res.status(400).json({
21
22
  error: {
@@ -23,12 +24,19 @@ function handle() {
23
24
  },
24
25
  });
25
26
  }
26
- req.logInfo(`Loading sync settings for provider ${provider}`);
27
+ if (!Object.values(types_1.Provider).includes(provider)) {
28
+ return res.status(400).json({
29
+ error: {
30
+ message: `Invalid provider. Must be one of: ${Object.values(types_1.Provider).join(', ')}`,
31
+ },
32
+ });
33
+ }
34
+ req.logInfo(`Loading auto-sync files for provider ${provider}`);
27
35
  const syncSettings = yield (0, storage_1.getStorage)().getSyncSettingsByProvider(req.crowdinContext.clientId, provider);
28
36
  if (syncSettings) {
29
37
  files = JSON.parse(syncSettings.files) || [];
30
38
  }
31
- req.logInfo(`Returning sync settings ${JSON.stringify(files, null, 2)}`);
39
+ req.logInfo(`Returning auto-sync files ${JSON.stringify(files, null, 2)}`);
32
40
  res.send(files);
33
41
  }));
34
42
  }
package/out/util/index.js CHANGED
@@ -103,7 +103,9 @@ function handleError(err, req, res) {
103
103
  }
104
104
  if (!res.headersSent) {
105
105
  const errorMessage = { message: (0, logger_1.getErrorMessage)(err), code };
106
- if ('integrationCredentials' in req) {
106
+ const isApiCall = isCrowdinClientRequest(req) && req.isApiCall;
107
+ const isUiIntegration = 'integrationCredentials' in req && !isApiCall;
108
+ if (isUiIntegration) {
107
109
  const html = (0, jsx_renderer_1.renderJSXOnClient)({ name: 'error', props: errorMessage });
108
110
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
109
111
  res.send(html);
@@ -25,6 +25,7 @@ export declare class AppModuleError extends Error {
25
25
  data: any;
26
26
  appData: any;
27
27
  isAxiosError: boolean;
28
+ code?: number;
28
29
  constructor(error: CustomAxiosError | Error | string, data?: any);
29
30
  }
30
31
  export declare class AppUserModuleError extends AppModuleError {
@@ -197,6 +197,7 @@ function getErrorMessage(err) {
197
197
  }
198
198
  class AppModuleError extends Error {
199
199
  constructor(error, data) {
200
+ var _a;
200
201
  super(typeof error === 'string' ? error : error.message);
201
202
  this.isAxiosError = false;
202
203
  this.name = 'AppModuleError';
@@ -209,6 +210,12 @@ class AppModuleError extends Error {
209
210
  }
210
211
  if (isAxiosError(error)) {
211
212
  this.isAxiosError = true;
213
+ // Extract HTTP status code from axios error
214
+ this.code = ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) || error.status;
215
+ }
216
+ else if (error.code && typeof error.code === 'number') {
217
+ // If error already has a numeric code property (e.g., CodeError)
218
+ this.code = error.code;
212
219
  }
213
220
  }
214
221
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "1.10.0",
3
+ "version": "1.11.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",
@@ -10,20 +10,20 @@
10
10
  "import": "./out/index.js",
11
11
  "types": "./out/index.d.ts"
12
12
  },
13
- "./functions/crowdin": {
14
- "require": "./out/util/app-functions/crowdin.js",
15
- "import": "./out/util/app-functions/crowdin.js",
16
- "types": "./out/util/app-functions/crowdin.d.ts"
17
- },
18
- "./functions/token": {
19
- "require": "./out/util/app-functions/token.js",
20
- "import": "./out/util/app-functions/token.js",
21
- "types": "./out/util/app-functions/token.d.ts"
13
+ "./functions/*": {
14
+ "require": "./out/util/app-functions/*.js",
15
+ "import": "./out/util/app-functions/*.js",
16
+ "types": "./out/util/app-functions/*.d.ts"
22
17
  },
23
18
  "./out/*": {
24
- "require": "./out/*",
25
- "import": "./out/*",
26
- "types": "./out/*"
19
+ "require": "./out/*.js",
20
+ "import": "./out/*.js",
21
+ "types": "./out/*.d.ts"
22
+ },
23
+ "./modules/*": {
24
+ "require": "./out/modules/*/types.js",
25
+ "import": "./out/modules/*/types.js",
26
+ "types": "./out/modules/*/types.d.ts"
27
27
  }
28
28
  },
29
29
  "scripts": {