@crowdin/app-project-module 0.108.0 → 1.0.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.
Files changed (202) hide show
  1. package/out/app-test/integration/get-integration-files.js +16 -4
  2. package/out/app-test/integration/types.d.ts +1 -1
  3. package/out/app-test/integration/update-crowdin.js +2 -2
  4. package/out/app-test/integration/update-integration.js +2 -2
  5. package/out/index.d.ts +2 -1
  6. package/out/index.js +73 -30
  7. package/out/middlewares/auto-credentials-masker.d.ts +4 -0
  8. package/out/middlewares/auto-credentials-masker.js +45 -0
  9. package/out/middlewares/crowdin-client.d.ts +1 -1
  10. package/out/middlewares/integration-credentials.d.ts +2 -2
  11. package/out/middlewares/integration-credentials.js +7 -4
  12. package/out/middlewares/render-ui-module.d.ts +3 -3
  13. package/out/middlewares/render-ui-module.js +10 -13
  14. package/out/middlewares/ui-module.d.ts +1 -1
  15. package/out/middlewares/ui-module.js +11 -1
  16. package/out/modules/about.d.ts +2 -1
  17. package/out/modules/about.js +10 -3
  18. package/out/modules/ai-prompt-provider/handlers/compile.d.ts +1 -1
  19. package/out/modules/ai-prompt-provider/handlers/compile.js +7 -1
  20. package/out/modules/ai-prompt-provider/index.js +2 -2
  21. package/out/modules/ai-prompt-provider/types.d.ts +7 -1
  22. package/out/modules/ai-provider/handlers/chat-completions.d.ts +1 -1
  23. package/out/modules/ai-provider/handlers/chat-completions.js +1 -1
  24. package/out/modules/ai-provider/handlers/get-model-list.d.ts +1 -1
  25. package/out/modules/ai-provider/index.js +2 -2
  26. package/out/modules/ai-provider/types.d.ts +4 -4
  27. package/out/modules/ai-request-processors/handler.d.ts +1 -1
  28. package/out/modules/ai-request-processors/handler.js +12 -2
  29. package/out/modules/ai-request-processors/types.d.ts +12 -2
  30. package/out/modules/ai-tools/handlers/tool-calls.d.ts +1 -1
  31. package/out/modules/ai-tools/handlers/tool-calls.js +1 -1
  32. package/out/modules/ai-tools/index.js +1 -1
  33. package/out/modules/ai-tools/types.d.ts +1 -1
  34. package/out/modules/auth-guard/handlers/verify.d.ts +1 -1
  35. package/out/modules/auth-guard/index.js +1 -1
  36. package/out/modules/automation-action/handlers/execute.d.ts +1 -1
  37. package/out/modules/automation-action/handlers/execute.js +1 -1
  38. package/out/modules/automation-action/handlers/input-schema.d.ts +1 -1
  39. package/out/modules/automation-action/handlers/output-schema.d.ts +1 -1
  40. package/out/modules/automation-action/handlers/validate-settings.d.ts +1 -1
  41. package/out/modules/automation-action/handlers/validate-settings.js +1 -1
  42. package/out/modules/automation-action/index.js +1 -1
  43. package/out/modules/automation-action/types.d.ts +4 -4
  44. package/out/modules/automation-action/util/index.js +2 -5
  45. package/out/modules/context-menu/index.js +2 -2
  46. package/out/modules/custom-mt/handlers/translate.d.ts +1 -1
  47. package/out/modules/custom-mt/handlers/translate.js +25 -9
  48. package/out/modules/custom-mt/index.js +3 -3
  49. package/out/modules/custom-mt/types.d.ts +10 -2
  50. package/out/modules/custom-spell-check/handlers/get-languages-list.d.ts +1 -1
  51. package/out/modules/custom-spell-check/handlers/spell-check.d.ts +1 -1
  52. package/out/modules/custom-spell-check/index.js +4 -4
  53. package/out/modules/editor-right-panel/index.js +1 -1
  54. package/out/modules/external-qa-check/handlers/validate.d.ts +1 -1
  55. package/out/modules/external-qa-check/index.js +2 -2
  56. package/out/modules/file-processing/handlers/custom-file-format.d.ts +7 -2
  57. package/out/modules/file-processing/handlers/custom-file-format.js +62 -22
  58. package/out/modules/file-processing/handlers/file-download.d.ts +1 -1
  59. package/out/modules/file-processing/handlers/file-download.js +5 -0
  60. package/out/modules/file-processing/handlers/pre-post-process.d.ts +1 -1
  61. package/out/modules/file-processing/handlers/pre-post-process.js +41 -15
  62. package/out/modules/file-processing/handlers/translations-alignment.d.ts +1 -1
  63. package/out/modules/file-processing/handlers/translations-alignment.js +10 -4
  64. package/out/modules/file-processing/index.js +12 -2
  65. package/out/modules/file-processing/types.d.ts +36 -5
  66. package/out/modules/file-processing/util/defaults.js +50 -6
  67. package/out/modules/file-processing/util/files.js +2 -1
  68. package/out/modules/form-data-display.d.ts +1 -1
  69. package/out/modules/form-data-save.d.ts +1 -1
  70. package/out/modules/install.d.ts +1 -1
  71. package/out/modules/integration/handlers/crowdin-file-progress.d.ts +1 -1
  72. package/out/modules/integration/handlers/crowdin-file-progress.js +5 -1
  73. package/out/modules/integration/handlers/crowdin-files-target-languages.d.ts +5 -0
  74. package/out/modules/integration/handlers/crowdin-files-target-languages.js +103 -0
  75. package/out/modules/integration/handlers/crowdin-files.d.ts +1 -1
  76. package/out/modules/integration/handlers/crowdin-files.js +7 -1
  77. package/out/modules/integration/handlers/crowdin-project.d.ts +1 -1
  78. package/out/modules/integration/handlers/crowdin-update.d.ts +1 -1
  79. package/out/modules/integration/handlers/crowdin-update.js +59 -18
  80. package/out/modules/integration/handlers/crowdin-webhook.d.ts +1 -1
  81. package/out/modules/integration/handlers/crowdin-webhook.js +15 -8
  82. package/out/modules/integration/handlers/integration-data.d.ts +1 -1
  83. package/out/modules/integration/handlers/integration-data.js +13 -6
  84. package/out/modules/integration/handlers/integration-login.d.ts +1 -1
  85. package/out/modules/integration/handlers/integration-logout.d.ts +1 -1
  86. package/out/modules/integration/handlers/integration-logout.js +8 -3
  87. package/out/modules/integration/handlers/integration-update.d.ts +1 -1
  88. package/out/modules/integration/handlers/integration-update.js +44 -6
  89. package/out/modules/integration/handlers/integration-webhook.d.ts +1 -1
  90. package/out/modules/integration/handlers/invite-users.d.ts +1 -1
  91. package/out/modules/integration/handlers/job-cancel.d.ts +1 -1
  92. package/out/modules/integration/handlers/job-info-deprecated.d.ts +1 -1
  93. package/out/modules/integration/handlers/job-info-deprecated.js +3 -2
  94. package/out/modules/integration/handlers/job-info.d.ts +1 -1
  95. package/out/modules/integration/handlers/job-list.d.ts +1 -1
  96. package/out/modules/integration/handlers/main.d.ts +1 -1
  97. package/out/modules/integration/handlers/main.js +32 -2
  98. package/out/modules/integration/handlers/oauth-login.d.ts +1 -1
  99. package/out/modules/integration/handlers/oauth-login.js +28 -6
  100. package/out/modules/integration/handlers/oauth-polling.d.ts +1 -1
  101. package/out/modules/integration/handlers/oauth-url.d.ts +1 -1
  102. package/out/modules/integration/handlers/settings-save.d.ts +1 -1
  103. package/out/modules/integration/handlers/settings-save.js +9 -9
  104. package/out/modules/integration/handlers/settings.d.ts +1 -1
  105. package/out/modules/integration/handlers/sync-settings-save.d.ts +1 -1
  106. package/out/modules/integration/handlers/sync-settings-save.js +5 -9
  107. package/out/modules/integration/handlers/sync-settings.d.ts +1 -1
  108. package/out/modules/integration/handlers/user-errors.d.ts +1 -1
  109. package/out/modules/integration/handlers/users.d.ts +1 -1
  110. package/out/modules/integration/index.js +19 -33
  111. package/out/modules/integration/types.d.ts +133 -29
  112. package/out/modules/integration/types.js +1 -0
  113. package/out/modules/integration/util/cron.d.ts +2 -2
  114. package/out/modules/integration/util/cron.js +40 -24
  115. package/out/modules/integration/util/defaults.d.ts +4 -0
  116. package/out/modules/integration/util/defaults.js +175 -15
  117. package/out/modules/integration/util/files.d.ts +2 -1
  118. package/out/modules/integration/util/files.js +19 -8
  119. package/out/modules/integration/util/job.js +4 -3
  120. package/out/modules/integration/util/snapshot.js +19 -3
  121. package/out/modules/integration/util/types.d.ts +1 -0
  122. package/out/modules/integration/util/types.js +1 -0
  123. package/out/modules/integration/util/webhooks.d.ts +8 -8
  124. package/out/modules/integration/util/webhooks.js +34 -17
  125. package/out/modules/manifest.js +12 -12
  126. package/out/modules/modal/index.js +2 -2
  127. package/out/modules/organization-menu/index.js +5 -4
  128. package/out/modules/organization-settings-menu/index.js +5 -4
  129. package/out/modules/profile-resources-menu/index.js +5 -4
  130. package/out/modules/profile-settings-menu/index.js +5 -4
  131. package/out/modules/project-menu/index.js +1 -1
  132. package/out/modules/project-menu-crowdsource/index.js +1 -1
  133. package/out/modules/project-reports/index.js +3 -2
  134. package/out/modules/project-tools/index.js +3 -2
  135. package/out/modules/status.d.ts +1 -1
  136. package/out/modules/status.js +12 -3
  137. package/out/modules/subscription-paid.d.ts +1 -1
  138. package/out/modules/uninstall.d.ts +1 -1
  139. package/out/modules/uninstall.js +1 -1
  140. package/out/modules/webhooks/handlers/webhook-handler.d.ts +2 -2
  141. package/out/modules/webhooks/handlers/webhook-handler.js +33 -22
  142. package/out/modules/webhooks/types.d.ts +7 -0
  143. package/out/modules/workflow-step-type/handlers/delete-step.d.ts +1 -1
  144. package/out/modules/workflow-step-type/handlers/step-settings-save.d.ts +1 -1
  145. package/out/modules/workflow-step-type/index.js +2 -2
  146. package/out/modules/workflow-step-type/util/index.js +3 -6
  147. package/out/static/js/dependent.js +16 -7
  148. package/out/static/ui/error.bundle.js +474 -0
  149. package/out/static/ui/error.bundle.js.map +1 -0
  150. package/out/static/ui/install.bundle.js +459 -0
  151. package/out/static/ui/install.bundle.js.map +1 -0
  152. package/out/static/ui/login.bundle.js +630 -0
  153. package/out/static/ui/login.bundle.js.map +1 -0
  154. package/out/static/ui/main.bundle.js +2109 -0
  155. package/out/static/ui/main.bundle.js.map +1 -0
  156. package/out/static/ui/oauth.bundle.js +467 -0
  157. package/out/static/ui/oauth.bundle.js.map +1 -0
  158. package/out/storage/d1.d.ts +107 -0
  159. package/out/storage/d1.js +831 -0
  160. package/out/storage/index.js +8 -1
  161. package/out/storage/mysql.js +22 -19
  162. package/out/storage/postgre.d.ts +0 -1
  163. package/out/storage/postgre.js +20 -30
  164. package/out/storage/sqlite.d.ts +2 -5
  165. package/out/storage/sqlite.js +29 -69
  166. package/out/types.d.ts +78 -18
  167. package/out/util/connection.js +1 -1
  168. package/out/util/credentials-masker.d.ts +3 -3
  169. package/out/util/credentials-masker.js +14 -20
  170. package/out/util/cron.d.ts +29 -0
  171. package/out/util/cron.js +87 -0
  172. package/out/util/index.d.ts +14 -1
  173. package/out/util/index.js +121 -10
  174. package/out/util/jsx-renderer.d.ts +14 -0
  175. package/out/util/jsx-renderer.js +35 -0
  176. package/out/util/logger.d.ts +1 -0
  177. package/out/util/logger.js +2 -2
  178. package/out/util/static-files.d.ts +19 -0
  179. package/out/util/static-files.js +87 -0
  180. package/out/util/subscription.js +1 -1
  181. package/out/views/AboutPage.d.ts +10 -0
  182. package/out/views/AboutPage.js +76 -0
  183. package/out/views/FormPage.d.ts +14 -0
  184. package/out/views/FormPage.js +28 -0
  185. package/out/views/SubscriptionPage.d.ts +7 -0
  186. package/out/views/SubscriptionPage.js +26 -0
  187. package/out/views/index.d.ts +8 -0
  188. package/out/views/index.js +15 -0
  189. package/out/views/layout/Head.d.ts +9 -0
  190. package/out/views/layout/Head.js +54 -0
  191. package/package.json +43 -41
  192. package/out/util/handlebars.d.ts +0 -1
  193. package/out/util/handlebars.js +0 -46
  194. package/out/views/about.handlebars +0 -102
  195. package/out/views/error.handlebars +0 -54
  196. package/out/views/form.handlebars +0 -31
  197. package/out/views/install.handlebars +0 -16
  198. package/out/views/login.handlebars +0 -332
  199. package/out/views/main.handlebars +0 -2042
  200. package/out/views/oauth.handlebars +0 -11
  201. package/out/views/partials/head.handlebars +0 -53
  202. package/out/views/subscription.handlebars +0 -26
@@ -8,16 +8,12 @@ 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
- exports.maskKey = exports.postRequestCredentialsMasker = exports.getRequestCredentialsMasker = void 0;
16
- const lodash_get_1 = __importDefault(require("lodash.get"));
17
- const index_1 = require("./index");
18
- const index_2 = require("../index");
19
- const crowdin_client_1 = require("../middlewares/crowdin-client");
12
+ exports.postRequestCredentialsMasker = exports.maskKey = exports.getRequestCredentialsMasker = void 0;
20
13
  const crowdin_apps_functions_1 = require("@crowdin/crowdin-apps-functions");
14
+ const index_1 = require("../index");
15
+ const crowdin_client_1 = require("../middlewares/crowdin-client");
16
+ const index_2 = require("./index");
21
17
  function maskKey(key) {
22
18
  const maskWith = '*';
23
19
  const unmaskedCharsAtEnd = 3;
@@ -29,18 +25,16 @@ function getMaskableFieldsKeys(moduleConfig) {
29
25
  if (!moduleConfig.formUiSchema) {
30
26
  return [];
31
27
  }
32
- return Object.keys(moduleConfig.formUiSchema).filter((fieldKey) => {
33
- return (0, lodash_get_1.default)(moduleConfig, `formUiSchema[${fieldKey}]['ui:widget']`) === 'password';
34
- });
28
+ return Object.keys(moduleConfig.formUiSchema).filter((fieldKey) => { var _a, _b; return ((_b = (_a = moduleConfig === null || moduleConfig === void 0 ? void 0 : moduleConfig.formUiSchema) === null || _a === void 0 ? void 0 : _a[fieldKey]) === null || _b === void 0 ? void 0 : _b['ui:widget']) === 'password'; });
35
29
  }
36
30
  function getRequestCredentialsMasker({ moduleConfig, dataPath = 'formData', }) {
37
31
  return function (req, res, next) {
38
- // we can't find "password" fields without ui schema
39
- if (!moduleConfig || !moduleConfig.formSchema || !moduleConfig.formUiSchema) {
32
+ if (req.__credentialsMaskerApplied) {
40
33
  return next();
41
34
  }
42
- // temporary
43
- if (!moduleConfig.maskPasswords) {
35
+ req.__credentialsMaskerApplied = true;
36
+ // we can't find "password" fields without ui schema
37
+ if (!moduleConfig || !moduleConfig.formSchema || !moduleConfig.formUiSchema) {
44
38
  return next();
45
39
  }
46
40
  const maskableFieldsKeys = getMaskableFieldsKeys(moduleConfig);
@@ -69,13 +63,13 @@ function getRequestCredentialsMasker({ moduleConfig, dataPath = 'formData', }) {
69
63
  }
70
64
  exports.getRequestCredentialsMasker = getRequestCredentialsMasker;
71
65
  function postRequestCredentialsMasker(moduleConfig, credentialsExtractor) {
72
- return (0, index_1.runAsyncWrapper)((req, res, next) => __awaiter(this, void 0, void 0, function* () {
66
+ return (0, index_2.runAsyncWrapper)((req, res, next) => __awaiter(this, void 0, void 0, function* () {
73
67
  var _a;
74
- if (!moduleConfig || !moduleConfig.formSchema || !moduleConfig.formUiSchema) {
68
+ if (req.__credentialsMaskerApplied) {
75
69
  return next();
76
70
  }
77
- // temporary
78
- if (!moduleConfig.maskPasswords) {
71
+ req.__credentialsMaskerApplied = true;
72
+ if (!moduleConfig || !moduleConfig.formSchema || !moduleConfig.formUiSchema) {
79
73
  return next();
80
74
  }
81
75
  const fieldsKeysInRequest = Object.keys(((_a = req.body) === null || _a === void 0 ? void 0 : _a.data) || []);
@@ -88,7 +82,7 @@ function postRequestCredentialsMasker(moduleConfig, credentialsExtractor) {
88
82
  const jwtToken = (0, crowdin_client_1.getToken)(req) || '';
89
83
  const jwtPayload = yield (0, crowdin_apps_functions_1.validateJwtToken)(jwtToken, process.env.CROWDIN_CLIENT_SECRET || '');
90
84
  const crowdinId = `${jwtPayload.context.organization_id}`;
91
- unmaskedFields = yield index_2.metadataStore.getMetadata(`form-data-${crowdinId}`);
85
+ unmaskedFields = yield index_1.metadataStore.getMetadata(`form-data-${crowdinId}`);
92
86
  }
93
87
  unmaskedFields = unmaskedFields || {};
94
88
  const maskableFieldsKeys = getMaskableFieldsKeys(moduleConfig);
@@ -0,0 +1,29 @@
1
+ import { Config, UnauthorizedConfig } from '../types';
2
+ export interface Cron {
3
+ /**
4
+ * Schedule a task to run on a cron expression
5
+ * @param expression - Cron expression (e.g., '0 * * * *' for hourly)
6
+ * @param task - Async task to execute
7
+ */
8
+ schedule(expression: string, task: () => Promise<void>): void;
9
+ }
10
+ /**
11
+ * Default Node.js cron handler that uses node-cron
12
+ */
13
+ export declare class NodeCron implements Cron {
14
+ private cron;
15
+ private pendingSchedules;
16
+ private initialized;
17
+ constructor();
18
+ schedule(expression: string, task: () => Promise<void>): void;
19
+ }
20
+ /**
21
+ * Initialize the cron instance
22
+ * @param config - Client configuration
23
+ */
24
+ export declare function initialize(config: Config | UnauthorizedConfig): void;
25
+ /**
26
+ * Get the initialized cron instance
27
+ * @returns The cron instance
28
+ */
29
+ export declare function getCron(): Cron;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.getCron = exports.initialize = exports.NodeCron = void 0;
27
+ const logger_1 = require("./logger");
28
+ /**
29
+ * Default Node.js cron handler that uses node-cron
30
+ */
31
+ class NodeCron {
32
+ constructor() {
33
+ this.pendingSchedules = [];
34
+ this.initialized = false;
35
+ // Dynamically import node-cron
36
+ Promise.resolve().then(() => __importStar(require('node-cron'))).then((nodeCron) => {
37
+ this.cron = nodeCron.default || nodeCron;
38
+ this.initialized = true;
39
+ // Schedule all pending tasks
40
+ this.pendingSchedules.forEach(({ expression, task }) => {
41
+ this.cron.schedule(expression, task);
42
+ });
43
+ this.pendingSchedules = [];
44
+ })
45
+ .catch((error) => {
46
+ console.error('Failed to load node-cron:', error);
47
+ throw new Error('node-cron is required for the default cron. Please install it or provide a custom cron in config.');
48
+ });
49
+ }
50
+ schedule(expression, task) {
51
+ if (this.initialized && this.cron) {
52
+ this.cron.schedule(expression, task);
53
+ }
54
+ else {
55
+ // Queue the task until node-cron is loaded
56
+ this.pendingSchedules.push({ expression, task });
57
+ }
58
+ }
59
+ }
60
+ exports.NodeCron = NodeCron;
61
+ let cron;
62
+ /**
63
+ * Initialize the cron instance
64
+ * @param config - Client configuration
65
+ */
66
+ function initialize(config) {
67
+ if (config.cron) {
68
+ (0, logger_1.log)('Using custom cron implementation');
69
+ cron = config.cron;
70
+ }
71
+ else {
72
+ (0, logger_1.log)('Using default NodeCron implementation');
73
+ cron = new NodeCron();
74
+ }
75
+ }
76
+ exports.initialize = initialize;
77
+ /**
78
+ * Get the initialized cron instance
79
+ * @returns The cron instance
80
+ */
81
+ function getCron() {
82
+ if (!cron) {
83
+ throw new Error('Cron not initialized');
84
+ }
85
+ return cron;
86
+ }
87
+ exports.getCron = getCron;
@@ -4,14 +4,24 @@ export declare class CodeError extends Error {
4
4
  code: number | undefined;
5
5
  constructor(message: string, code?: number);
6
6
  }
7
+ export declare function extractBaseUrlFromRequest(req: Request): string;
7
8
  export declare function runAsyncWrapper(callback: Function): (req: Request | CrowdinClientRequest, res: Response, next: Function) => void;
8
9
  export declare function encryptData(config: Config, data: string): string;
9
10
  export declare function decryptData(config: Config, data: string): string;
10
11
  export declare function executeWithRetry<T>(func: () => Promise<T>, numOfRetries?: number): Promise<T>;
11
- export declare function getLogoUrl(moduleConfig?: ImagePath, modulePath?: string): string;
12
+ export declare function getLogoUrl(config: Config | UnauthorizedConfig, moduleConfig?: ImagePath, modulePath?: string): string;
13
+ /**
14
+ * Logo middleware with backwards compatibility
15
+ * Serves both /logo.png (backwards-compatible) and actual file name
16
+ * @param config - App configuration (required for Cloudflare Workers Assets support)
17
+ * @param moduleConfig - Module configuration with imagePath (optional, falls back to config.imagePath)
18
+ * @returns Express middleware
19
+ */
20
+ export declare function serveLogo(config: Config | UnauthorizedConfig, moduleConfig?: ImagePath): (req: Request, res: Response, next: Function) => Promise<void>;
12
21
  export declare function isAuthorizedConfig(config: Config | UnauthorizedConfig): config is Config;
13
22
  export declare function isJson(string: string): boolean;
14
23
  export declare function isDefined(value: any): boolean;
24
+ export declare function isString(value: any): value is string;
15
25
  export declare function getPreviousDate(days: number): Date;
16
26
  export declare function prepareFormDataMetadataId(req: CrowdinClientRequest, config: Config): Promise<string>;
17
27
  export declare function validateEmail(email: string | number): boolean;
@@ -19,3 +29,6 @@ export declare function getFormattedDate({ date, userTimezone }: {
19
29
  date: Date | number;
20
30
  userTimezone: string | undefined;
21
31
  }): string;
32
+ export declare function kebabCase(str?: string): string;
33
+ export declare function snakeCase(str?: string): string;
34
+ export declare function uniqBy<T>(array: any, key: keyof T): T[];
package/out/util/index.js CHANGED
@@ -32,12 +32,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
32
32
  });
33
33
  };
34
34
  Object.defineProperty(exports, "__esModule", { value: true });
35
- exports.getFormattedDate = exports.validateEmail = exports.prepareFormDataMetadataId = exports.getPreviousDate = exports.isDefined = exports.isJson = exports.isAuthorizedConfig = exports.getLogoUrl = exports.executeWithRetry = exports.decryptData = exports.encryptData = exports.runAsyncWrapper = exports.CodeError = void 0;
36
- const crypto = __importStar(require("crypto-js"));
35
+ exports.uniqBy = exports.snakeCase = exports.kebabCase = exports.getFormattedDate = exports.validateEmail = exports.prepareFormDataMetadataId = exports.getPreviousDate = exports.isString = exports.isDefined = exports.isJson = exports.isAuthorizedConfig = exports.serveLogo = exports.getLogoUrl = exports.executeWithRetry = exports.decryptData = exports.encryptData = exports.runAsyncWrapper = exports.extractBaseUrlFromRequest = exports.CodeError = void 0;
36
+ const crowdin_apps_functions_1 = require("@crowdin/crowdin-apps-functions");
37
+ const crypto = __importStar(require("crypto"));
37
38
  const storage_1 = require("../storage");
38
39
  const types_1 = require("../types");
40
+ const jsx_renderer_1 = require("./jsx-renderer");
39
41
  const logger_1 = require("./logger");
40
- const crowdin_apps_functions_1 = require("@crowdin/crowdin-apps-functions");
42
+ const static_files_1 = require("./static-files");
43
+ /**
44
+ * Extract file name from path (works in both Node.js and Cloudflare Workers)
45
+ * Supports both Unix (/) and Windows (\) path separators
46
+ */
47
+ function basename(filePath) {
48
+ // Replace all backslashes with forward slashes, then get the last segment
49
+ return filePath.replace(/\\/g, '/').split('/').pop();
50
+ }
41
51
  class CodeError extends Error {
42
52
  constructor(message, code) {
43
53
  super(message);
@@ -45,6 +55,12 @@ class CodeError extends Error {
45
55
  }
46
56
  }
47
57
  exports.CodeError = CodeError;
58
+ function extractBaseUrlFromRequest(req) {
59
+ const protocol = req.protocol;
60
+ const host = req.get('host');
61
+ return `${protocol}://${host}`;
62
+ }
63
+ exports.extractBaseUrlFromRequest = extractBaseUrlFromRequest;
48
64
  function isCrowdinClientRequest(req) {
49
65
  return req.crowdinContext;
50
66
  }
@@ -61,7 +77,9 @@ function handleError(err, req, res) {
61
77
  if (!res.headersSent) {
62
78
  const errorMessage = { message: (0, logger_1.getErrorMessage)(err), code };
63
79
  if ('integrationCredentials' in req) {
64
- res.render('error', errorMessage);
80
+ const html = (0, jsx_renderer_1.renderJSXOnClient)({ name: 'error', props: errorMessage });
81
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
82
+ res.send(html);
65
83
  }
66
84
  else {
67
85
  res.status(code).send({ error: errorMessage });
@@ -84,11 +102,38 @@ function runAsyncWrapper(callback) {
84
102
  }
85
103
  exports.runAsyncWrapper = runAsyncWrapper;
86
104
  function encryptData(config, data) {
87
- return crypto.AES.encrypt(data, config.cryptoSecret || config.clientSecret).toString();
105
+ const secret = config.cryptoSecret || config.clientSecret;
106
+ const salt = crypto.randomBytes(8);
107
+ const password = Buffer.concat([Buffer.from(secret, 'binary'), salt]);
108
+ const hash = [];
109
+ let digest = password;
110
+ for (let i = 0; i < 3; i++) {
111
+ hash[i] = crypto.createHash('md5').update(digest).digest();
112
+ digest = Buffer.concat([hash[i], password]);
113
+ }
114
+ const keyDerivation = Buffer.concat(hash);
115
+ const key = keyDerivation.subarray(0, 32);
116
+ const iv = keyDerivation.subarray(32);
117
+ const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
118
+ return Buffer.concat([Buffer.from('Salted__', 'utf8'), salt, cipher.update(data), cipher.final()]).toString('base64');
88
119
  }
89
120
  exports.encryptData = encryptData;
90
121
  function decryptData(config, data) {
91
- return crypto.AES.decrypt(data, config.cryptoSecret || config.clientSecret).toString(crypto.enc.Utf8);
122
+ const secret = config.cryptoSecret || config.clientSecret;
123
+ const cypher = Buffer.from(data, 'base64');
124
+ const salt = cypher.subarray(8, 16);
125
+ const password = Buffer.concat([Buffer.from(secret, 'binary'), salt]);
126
+ const md5Hashes = [];
127
+ let digest = password;
128
+ for (let i = 0; i < 3; i++) {
129
+ md5Hashes[i] = crypto.createHash('md5').update(digest).digest();
130
+ digest = Buffer.concat([md5Hashes[i], password]);
131
+ }
132
+ const key = Buffer.concat([md5Hashes[0], md5Hashes[1]]);
133
+ const iv = md5Hashes[2];
134
+ const contents = cypher.subarray(16);
135
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
136
+ return decipher.update(contents, undefined, 'utf-8') + decipher.final('utf-8');
92
137
  }
93
138
  exports.decryptData = decryptData;
94
139
  function executeWithRetry(func, numOfRetries = 2) {
@@ -108,13 +153,36 @@ function executeWithRetry(func, numOfRetries = 2) {
108
153
  });
109
154
  }
110
155
  exports.executeWithRetry = executeWithRetry;
111
- function getLogoUrl(moduleConfig, modulePath) {
112
- if (!moduleConfig && !modulePath) {
113
- return '/logo.png';
156
+ function getLogoUrl(config, moduleConfig, modulePath) {
157
+ // Extract file name from imagePath with fallback to config.imagePath
158
+ const imagePath = (moduleConfig === null || moduleConfig === void 0 ? void 0 : moduleConfig.imagePath) || config.imagePath;
159
+ const fileName = basename(imagePath);
160
+ if (!modulePath) {
161
+ return `/${fileName}`;
114
162
  }
115
- return `/logo${modulePath}/logo.png`;
163
+ return `/logo${modulePath}/${fileName}`;
116
164
  }
117
165
  exports.getLogoUrl = getLogoUrl;
166
+ /**
167
+ * Logo middleware with backwards compatibility
168
+ * Serves both /logo.png (backwards-compatible) and actual file name
169
+ * @param config - App configuration (required for Cloudflare Workers Assets support)
170
+ * @param moduleConfig - Module configuration with imagePath (optional, falls back to config.imagePath)
171
+ * @returns Express middleware
172
+ */
173
+ function serveLogo(config, moduleConfig) {
174
+ const imagePath = (moduleConfig === null || moduleConfig === void 0 ? void 0 : moduleConfig.imagePath) || config.imagePath;
175
+ const fileName = basename(imagePath);
176
+ const fileHandler = (0, static_files_1.serveFile)(config, imagePath);
177
+ return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
178
+ // Match exact paths: /logo.png (backwards-compatible) or /{actual-file-name}
179
+ if (req.path === '/logo.png' || req.path === `/${fileName}`) {
180
+ return fileHandler(req, res, next);
181
+ }
182
+ next();
183
+ });
184
+ }
185
+ exports.serveLogo = serveLogo;
118
186
  function isAuthorizedConfig(config) {
119
187
  return !!config.clientId && !!config.clientSecret && config.authenticationType !== types_1.AuthenticationType.NONE;
120
188
  }
@@ -133,6 +201,10 @@ function isDefined(value) {
133
201
  return value !== undefined && value !== null;
134
202
  }
135
203
  exports.isDefined = isDefined;
204
+ function isString(value) {
205
+ return typeof value === 'string' || value instanceof String;
206
+ }
207
+ exports.isString = isString;
136
208
  function getPreviousDate(days) {
137
209
  const date = new Date();
138
210
  date.setDate(date.getDate() - days);
@@ -182,3 +254,42 @@ function getFormattedDate({ date, userTimezone }) {
182
254
  }).format(date);
183
255
  }
184
256
  exports.getFormattedDate = getFormattedDate;
257
+ function kebabCase(str) {
258
+ if (!str) {
259
+ return '';
260
+ }
261
+ return (str
262
+ // Insert dash between lowercase & uppercase
263
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
264
+ // Insert dash between letters & numbers
265
+ .replace(/([a-zA-Z])([0-9])/g, '$1-$2')
266
+ .replace(/([0-9])([a-zA-Z])/g, '$1-$2')
267
+ // Replace spaces & underscores with dash
268
+ .replace(/[\s_]+/g, '-')
269
+ // Normalize multiple dashes
270
+ .replace(/-+/g, '-')
271
+ // Lowercase everything
272
+ .toLowerCase());
273
+ }
274
+ exports.kebabCase = kebabCase;
275
+ function snakeCase(str) {
276
+ return kebabCase(str).replace(/-/g, '_');
277
+ }
278
+ exports.snakeCase = snakeCase;
279
+ function uniqBy(array, key) {
280
+ if (!Array.isArray(array)) {
281
+ return [];
282
+ }
283
+ const seen = new Set();
284
+ return array.filter((item) => {
285
+ const k = item[key];
286
+ if (seen.has(k)) {
287
+ return false;
288
+ }
289
+ else {
290
+ seen.add(k);
291
+ return true;
292
+ }
293
+ });
294
+ }
295
+ exports.uniqBy = uniqBy;
@@ -0,0 +1,14 @@
1
+ import type { FC } from 'react';
2
+ /**
3
+ * Renders a React component to HTML string
4
+ * Uses React Server-Side Rendering (renderToStaticMarkup)
5
+ */
6
+ export declare function renderJSXOnServer<P = any>(Component: FC<P>, props: P): string;
7
+ /**
8
+ * Renders a React component to HTML string to be hydrated on client side
9
+ */
10
+ export declare function renderJSXOnClient({ name, props, withHead, }: {
11
+ name: string;
12
+ props: any;
13
+ withHead?: boolean;
14
+ }): string;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderJSXOnClient = exports.renderJSXOnServer = void 0;
4
+ const server_1 = require("react-dom/server");
5
+ const views_1 = require("../views");
6
+ /**
7
+ * Renders a React component to HTML string
8
+ * Uses React Server-Side Rendering (renderToStaticMarkup)
9
+ */
10
+ function renderJSXOnServer(Component, props) {
11
+ const element = Component(props);
12
+ return '<!DOCTYPE html>' + (0, server_1.renderToStaticMarkup)(element);
13
+ }
14
+ exports.renderJSXOnServer = renderJSXOnServer;
15
+ /**
16
+ * Renders a React component to HTML string to be hydrated on client side
17
+ */
18
+ function renderJSXOnClient({ name, props, withHead = true, }) {
19
+ const head = withHead ? (0, server_1.renderToStaticMarkup)((0, views_1.Head)(props)) : '';
20
+ return `
21
+ <!DOCTYPE html>
22
+ <html>
23
+ ${head}
24
+ <body>
25
+ <div id="root"></div>
26
+ <script>
27
+ // Make props available to client JS
28
+ window.__PAGE_PROPS__ = ${JSON.stringify(props).replace(/</g, '\\u003c')};
29
+ </script>
30
+ <script type="module" src="/assets/ui/${name}.bundle.js"></script>
31
+ </body>
32
+ </html>
33
+ `;
34
+ }
35
+ exports.renderJSXOnClient = renderJSXOnClient;
@@ -32,6 +32,7 @@ export declare class AppUserModuleError extends AppModuleError {
32
32
  }
33
33
  export declare class AppModuleAggregateError extends Error {
34
34
  errors: Error[] | AppModuleError[] | AppUserModuleError[];
35
+ isPartialSuccess?: boolean;
35
36
  constructor(errors: Error[] | AppModuleError[] | AppUserModuleError[], message: string);
36
37
  }
37
38
  export declare function handleUserError({ action, error, crowdinId, clientId, }: {
@@ -62,7 +62,7 @@ function log(message, context) {
62
62
  var _a, _b, _c;
63
63
  if (logConfig === null || logConfig === void 0 ? void 0 : logConfig.enabled) {
64
64
  if (logConfig.log) {
65
- logConfig.log(message, context);
65
+ logConfig.log({ message, context });
66
66
  }
67
67
  else {
68
68
  let prefix = `[${new Date().toISOString()}]`;
@@ -96,7 +96,7 @@ exports.log = log;
96
96
  function logError(e, context) {
97
97
  var _a, _b, _c;
98
98
  if (onError) {
99
- onError(e, context);
99
+ onError({ error: e, context });
100
100
  }
101
101
  else {
102
102
  if (context) {
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Static Files Abstraction Layer
3
+ * Dual compatibility: Node.js (Express) + Cloudflare Workers (Assets Fetcher)
4
+ */
5
+ /// <reference types="serve-static" />
6
+ import express, { Request, Response } from 'express';
7
+ import { Config, UnauthorizedConfig } from '../types';
8
+ /**
9
+ * Serve static directory with dual compatibility
10
+ * @param config - App configuration
11
+ * @param staticPath - Path to static directory
12
+ */
13
+ export declare function serveStatic(config: Config | UnauthorizedConfig, staticPath: string): ((req: Request, res: Response, next: Function) => Promise<void>) | import("serve-static").RequestHandler<express.Response<any, Record<string, any>>>;
14
+ /**
15
+ * Serve single file (e.g., logo.png, icons/star.svg) with dual compatibility
16
+ * @param config - App configuration
17
+ * @param filePath - Path to file
18
+ */
19
+ export declare function serveFile(config: Config | UnauthorizedConfig, filePath: string): (req: Request, res: Response, next: Function) => Promise<void>;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ /**
3
+ * Static Files Abstraction Layer
4
+ * Dual compatibility: Node.js (Express) + Cloudflare Workers (Assets Fetcher)
5
+ */
6
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
7
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
8
+ return new (P || (P = Promise))(function (resolve, reject) {
9
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
11
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
12
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
13
+ });
14
+ };
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.serveFile = exports.serveStatic = void 0;
20
+ const express_1 = __importDefault(require("express"));
21
+ const path_1 = __importDefault(require("path"));
22
+ /**
23
+ * Proxy Workers Assets response to Express response
24
+ */
25
+ function proxyAssetsResponse(fetcher, assetPath, baseUrl, req, res, next) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ try {
28
+ if (assetPath === '/') {
29
+ return next();
30
+ }
31
+ const assetUrl = new URL(assetPath, baseUrl);
32
+ const response = yield fetcher.fetch(assetUrl.toString());
33
+ if (!response.ok) {
34
+ return next();
35
+ }
36
+ const body = yield response.arrayBuffer();
37
+ const contentType = response.headers.get('content-type');
38
+ if (contentType) {
39
+ res.setHeader('Content-Type', contentType);
40
+ }
41
+ res.send(Buffer.from(body));
42
+ }
43
+ catch (err) {
44
+ next(err);
45
+ }
46
+ });
47
+ }
48
+ /**
49
+ * Serve static directory with dual compatibility
50
+ * @param config - App configuration
51
+ * @param staticPath - Path to static directory
52
+ */
53
+ function serveStatic(config, staticPath) {
54
+ var _a;
55
+ if ((_a = config.assetsConfig) === null || _a === void 0 ? void 0 : _a.fetcher) {
56
+ const assetsFetcher = config.assetsConfig.fetcher;
57
+ return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
58
+ const assetPath = `/${staticPath}/${req.path}`.replace(/\/+/g, '/');
59
+ yield proxyAssetsResponse(assetsFetcher, assetPath, config.baseUrl, req, res, next);
60
+ });
61
+ }
62
+ const rootDir = path_1.default.dirname(__dirname);
63
+ const absolutePath = path_1.default.isAbsolute(staticPath) ? staticPath : path_1.default.join(rootDir, staticPath);
64
+ return express_1.default.static(absolutePath);
65
+ }
66
+ exports.serveStatic = serveStatic;
67
+ /**
68
+ * Serve single file (e.g., logo.png, icons/star.svg) with dual compatibility
69
+ * @param config - App configuration
70
+ * @param filePath - Path to file
71
+ */
72
+ function serveFile(config, filePath) {
73
+ return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
74
+ var _a;
75
+ if ((_a = config.assetsConfig) === null || _a === void 0 ? void 0 : _a.fetcher) {
76
+ const assetsFetcher = config.assetsConfig.fetcher;
77
+ const assetPath = filePath.startsWith('/') ? filePath : `/${filePath}`;
78
+ yield proxyAssetsResponse(assetsFetcher, assetPath, config.baseUrl, req, res, next);
79
+ }
80
+ else {
81
+ const rootDir = path_1.default.dirname(__dirname);
82
+ const absolutePath = path_1.default.isAbsolute(filePath) ? filePath : path_1.default.join(rootDir, filePath);
83
+ res.sendFile(absolutePath);
84
+ }
85
+ });
86
+ }
87
+ exports.serveFile = serveFile;
@@ -71,7 +71,7 @@ function checkSubscription({ config, token, organization, accountType, }) {
71
71
  return { expired, subscribeLink, daysLeft, type: types_1.SubscriptionInfoType.TRIAL };
72
72
  }
73
73
  if (config.onError) {
74
- config.onError(e);
74
+ config.onError({ error: e });
75
75
  }
76
76
  else {
77
77
  console.error(e);
@@ -0,0 +1,10 @@
1
+ import { FC } from 'react';
2
+ interface AboutPageProps {
3
+ name: string;
4
+ logo: string;
5
+ manifest: string;
6
+ storeLink?: string;
7
+ showDebugLink?: boolean;
8
+ }
9
+ export declare const AboutPage: FC<AboutPageProps>;
10
+ export {};