@crowdin/app-project-module 0.69.0 → 0.70.1

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 (39) hide show
  1. package/out/index.js +16 -3
  2. package/out/modules/ai-provider/handlers/chat-completions.js +20 -21
  3. package/out/modules/ai-provider/handlers/get-model-list.js +3 -2
  4. package/out/modules/ai-provider/types.d.ts +112 -7
  5. package/out/modules/ai-provider/util/index.d.ts +5 -0
  6. package/out/modules/ai-provider/util/index.js +72 -1
  7. package/out/modules/file-processing/handlers/pre-post-process.js +6 -0
  8. package/out/modules/file-processing/types.d.ts +16 -8
  9. package/out/modules/manifest.js +34 -0
  10. package/out/modules/organization-settings-menu/index.d.ts +6 -0
  11. package/out/modules/organization-settings-menu/index.js +18 -0
  12. package/out/modules/profile-settings-menu/index.d.ts +6 -0
  13. package/out/modules/profile-settings-menu/index.js +18 -0
  14. package/out/modules/webhooks/handlers/webhook-handler.d.ts +2 -2
  15. package/out/modules/webhooks/handlers/webhook-handler.js +9 -6
  16. package/out/modules/webhooks/index.js +1 -1
  17. package/out/modules/webhooks/types.d.ts +2 -0
  18. package/out/modules/workflow-step-type/handlers/delete-step.d.ts +5 -0
  19. package/out/modules/workflow-step-type/handlers/delete-step.js +70 -0
  20. package/out/modules/workflow-step-type/handlers/step-settings-save.d.ts +5 -0
  21. package/out/modules/workflow-step-type/handlers/step-settings-save.js +80 -0
  22. package/out/modules/workflow-step-type/index.d.ts +6 -0
  23. package/out/modules/workflow-step-type/index.js +36 -0
  24. package/out/modules/workflow-step-type/types.d.ts +57 -0
  25. package/out/modules/workflow-step-type/types.js +2 -0
  26. package/out/modules/workflow-step-type/util/index.d.ts +3 -0
  27. package/out/modules/workflow-step-type/util/index.js +16 -0
  28. package/out/static/js/form.js +32 -32
  29. package/out/storage/index.d.ts +1 -0
  30. package/out/storage/mysql.d.ts +1 -0
  31. package/out/storage/mysql.js +9 -0
  32. package/out/storage/postgre.d.ts +1 -0
  33. package/out/storage/postgre.js +9 -0
  34. package/out/storage/sqlite.d.ts +1 -0
  35. package/out/storage/sqlite.js +3 -0
  36. package/out/types.d.ts +14 -1
  37. package/out/util/logger.d.ts +5 -2
  38. package/out/util/logger.js +26 -10
  39. package/package.json +14 -8
package/out/index.js CHANGED
@@ -74,12 +74,15 @@ const fileProcessingApps = __importStar(require("./modules/file-processing"));
74
74
  const integrationApp = __importStar(require("./modules/integration"));
75
75
  const modalApp = __importStar(require("./modules/modal"));
76
76
  const organizationMenuApp = __importStar(require("./modules/organization-menu"));
77
+ const organizationSettingsMenuApp = __importStar(require("./modules/organization-settings-menu"));
77
78
  const profileResourcesMenuApp = __importStar(require("./modules/profile-resources-menu"));
79
+ const profileSettingsMenuApp = __importStar(require("./modules/profile-settings-menu"));
78
80
  const projectMenuApp = __importStar(require("./modules/project-menu"));
79
81
  const projectMenuCrowdsourceApp = __importStar(require("./modules/project-menu-crowdsource"));
80
82
  const projectReportsApp = __importStar(require("./modules/project-reports"));
81
83
  const projectToolsApp = __importStar(require("./modules/project-tools"));
82
84
  const webhooks = __importStar(require("./modules/webhooks"));
85
+ const workflowStepType = __importStar(require("./modules/workflow-step-type"));
83
86
  const subscription_1 = require("./util/subscription");
84
87
  var types_2 = require("./types");
85
88
  Object.defineProperty(exports, "ProjectPermissions", { enumerable: true, get: function () { return types_2.ProjectPermissions; } });
@@ -123,7 +126,14 @@ function addCrowdinEndpoints(app, clientConfig) {
123
126
  }
124
127
  storage.initialize(config);
125
128
  logger.initialize(config);
126
- app.use(terminus_express_1.default.json({ limit: '50mb' }));
129
+ app.use((req, res, next) => {
130
+ if (config.webhooks && req.path === '/webhooks') {
131
+ return terminus_express_1.default.raw({ type: '*/*', limit: '50mb' })(req, res, next);
132
+ }
133
+ else {
134
+ return terminus_express_1.default.json({ limit: '50mb' })(req, res, next);
135
+ }
136
+ });
127
137
  if (!config.disableLogsFormatter) {
128
138
  logsFormatter.setup();
129
139
  app.use(logsFormatter.contextResolverMiddleware());
@@ -142,7 +152,9 @@ function addCrowdinEndpoints(app, clientConfig) {
142
152
  editorRightPanelApp.register({ config, app });
143
153
  projectMenuApp.register({ config, app });
144
154
  profileResourcesMenuApp.register({ config, app });
155
+ profileSettingsMenuApp.register({ config, app });
145
156
  organizationMenuApp.register({ config, app });
157
+ organizationSettingsMenuApp.register({ config, app });
146
158
  projectMenuCrowdsourceApp.register({ config, app });
147
159
  projectToolsApp.register({ config, app });
148
160
  projectReportsApp.register({ config, app });
@@ -177,6 +189,7 @@ function addCrowdinEndpoints(app, clientConfig) {
177
189
  aiTools.registerAiToolWidgets({ config, app });
178
190
  externalQaCheck.register({ config, app });
179
191
  webhooks.register({ config, app });
192
+ workflowStepType.register({ config, app });
180
193
  addFormSchema({ config, app });
181
194
  return Object.assign(Object.assign({}, exports.metadataStore), { establishCrowdinConnection: (authRequest, moduleKey) => {
182
195
  let jwtToken = '';
@@ -193,13 +206,13 @@ function addCrowdinEndpoints(app, clientConfig) {
193
206
  checkSubscriptionExpiration: false,
194
207
  moduleKey,
195
208
  });
196
- }, encryptCrowdinConnection: (data) => (0, util_1.encryptData)(config, JSON.stringify(data)), dencryptCrowdinConnection: (hash) => __awaiter(this, void 0, void 0, function* () {
209
+ }, encryptCrowdinConnection: (data) => (0, util_1.encryptData)(config, JSON.stringify(data)), dencryptCrowdinConnection: (hash, autoRenew) => __awaiter(this, void 0, void 0, function* () {
197
210
  const { crowdinId, extra } = JSON.parse((0, util_1.decryptData)(config, hash));
198
211
  const credentials = yield storage.getStorage().getCrowdinCredentials(crowdinId);
199
212
  if (!credentials) {
200
213
  throw new Error('Failed to find Crowdin credentials');
201
214
  }
202
- const { client } = yield (0, connection_1.prepareCrowdinClient)({ config, credentials });
215
+ const { client } = yield (0, connection_1.prepareCrowdinClient)({ config, credentials, autoRenew });
203
216
  return { client, extra };
204
217
  }) });
205
218
  }
@@ -13,12 +13,7 @@ const util_1 = require("../../../util");
13
13
  const logger_1 = require("../../../util/logger");
14
14
  const files_1 = require("../../integration/util/files");
15
15
  const util_2 = require("../util");
16
- // more about roles in AI messages https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages
17
- // Crowdin documentation https://support.crowdin.com/developer/crowdin-apps-module-custom-ai/#expected-response-from-the-app
18
- const ROLES = {
19
- assistant: 'assistant',
20
- };
21
- const DEFAULT_ROLE = ROLES.assistant;
16
+ const DEFAULT_ROLE = 'assistant';
22
17
  const getErrorStatus = (e) => {
23
18
  if (typeof e === 'object' && 'status' in e && typeof e.status === 'number') {
24
19
  return e.status;
@@ -39,7 +34,7 @@ function handle(aiProvider) {
39
34
  let message;
40
35
  let data;
41
36
  const chunks = [];
42
- const { crowdinApiClient: client, crowdinContext: context, body: { messages, model, action, stream, responseFormat: { type: responseFormatType } = { type: undefined }, }, } = req;
37
+ const { crowdinApiClient: client, crowdinContext: context, body: { messages, model, action, stream, tools, tool_choice: toolChoice, responseFormat: { type: responseFormatType } = { type: undefined }, }, } = req;
43
38
  isStream = !!stream;
44
39
  const startStream = () => {
45
40
  if (!res.headersSent) {
@@ -47,11 +42,12 @@ function handle(aiProvider) {
47
42
  Connection: 'keep-alive',
48
43
  'Cache-Control': 'no-cache',
49
44
  'Content-Type': 'text/event-stream',
45
+ 'X-Accel-Buffering': 'no',
50
46
  });
51
47
  }
52
48
  };
53
49
  const result = yield aiProvider.chatCompletions({
54
- messages,
50
+ messages: messages.map(util_2.inputMessageToChatCompletionMessage),
55
51
  model,
56
52
  action,
57
53
  responseFormat: responseFormatType,
@@ -59,19 +55,26 @@ function handle(aiProvider) {
59
55
  context,
60
56
  req,
61
57
  isStream,
62
- sendEvent: ({ content, role }) => __awaiter(this, void 0, void 0, function* () {
58
+ tools,
59
+ toolChoice,
60
+ /* eslint-disable @typescript-eslint/camelcase */
61
+ sendEvent: ({ content, role, tool_calls }) => __awaiter(this, void 0, void 0, function* () {
63
62
  if (!isStream) {
64
- chunks.push({ content, role });
63
+ chunks.push({ content, role, tool_calls });
65
64
  return;
66
65
  }
67
66
  startStream();
67
+ const delta = {
68
+ role: role || DEFAULT_ROLE,
69
+ content: content || null,
70
+ };
71
+ if (!!tool_calls) {
72
+ delta.tool_calls = tool_calls;
73
+ }
68
74
  const message = {
69
75
  choices: [
70
76
  {
71
- delta: {
72
- role: role || DEFAULT_ROLE,
73
- content,
74
- },
77
+ delta,
75
78
  },
76
79
  ],
77
80
  };
@@ -92,18 +95,14 @@ function handle(aiProvider) {
92
95
  data = result;
93
96
  }
94
97
  if (chunks.length) {
95
- data = [
96
- {
97
- role: chunks[0].role || DEFAULT_ROLE,
98
- content: chunks.map((chunk) => chunk.content).join(''),
99
- },
100
- ];
98
+ data = (0, util_2.mergeChatCompletionChunks)(chunks);
101
99
  }
102
100
  if (data && data.length > 0) {
103
101
  choices = data.map((message) => ({
104
102
  message: {
105
103
  role: message.role || DEFAULT_ROLE,
106
- content: message.content,
104
+ content: message.content || null,
105
+ tool_calls: message.tool_calls,
107
106
  },
108
107
  }));
109
108
  }
@@ -22,13 +22,14 @@ function handle(aiProvider) {
22
22
  context: req.crowdinContext,
23
23
  });
24
24
  const data = modelList.map((model) => {
25
- var _a, _b, _c, _d;
25
+ var _a, _b, _c, _d, _e;
26
26
  return ({
27
27
  id: model.id,
28
28
  supportsJsonMode: (_a = model.supportsJsonMode) !== null && _a !== void 0 ? _a : false,
29
29
  supportsFunctionCalling: (_b = model.supportsFunctionCalling) !== null && _b !== void 0 ? _b : false,
30
30
  supportsStreaming: (_c = model.supportsStreaming) !== null && _c !== void 0 ? _c : false,
31
- contextWindowLimit: (_d = model.contextWindowLimit) !== null && _d !== void 0 ? _d : exports.CONTEXT_WINDOW_LIMIT,
31
+ supportsVision: (_d = model.supportsVision) !== null && _d !== void 0 ? _d : false,
32
+ contextWindowLimit: (_e = model.contextWindowLimit) !== null && _e !== void 0 ? _e : exports.CONTEXT_WINDOW_LIMIT,
32
33
  });
33
34
  });
34
35
  res.send({ data });
@@ -17,8 +17,8 @@ export interface AiProviderModule extends Environments, ModuleKey {
17
17
  /**
18
18
  * processes a sequence of conversation messages and generates responses from the assistant
19
19
  */
20
- chatCompletions: ({ messages, model, action, responseFormat, client, context, req, }: {
21
- messages: Message[];
20
+ chatCompletions: ({ messages, model, action, responseFormat, client, context, req, isStream, sendEvent, tools, toolChoice, }: {
21
+ messages: ChatCompletionMessage[];
22
22
  model: string;
23
23
  action: string;
24
24
  responseFormat: string;
@@ -26,8 +26,10 @@ export interface AiProviderModule extends Environments, ModuleKey {
26
26
  context: CrowdinContextInfo;
27
27
  req: CrowdinClientRequest;
28
28
  isStream: boolean;
29
- sendEvent: (chunk: Message) => Promise<void>;
30
- }) => Promise<Message[] | ExtendedResult<Message[]>>;
29
+ sendEvent: (chunk: ChatCompletionChunkMessage) => Promise<void>;
30
+ tools?: ChatCompletionTool[];
31
+ toolChoice?: string | AiToolChoice;
32
+ }) => Promise<ChatCompletionResponseMessage[] | ExtendedResult<ChatCompletionResponseMessage[]> | void>;
31
33
  /**
32
34
  * provides a list of available model
33
35
  */
@@ -41,9 +43,112 @@ export interface SupportedModels {
41
43
  supportsJsonMode?: boolean;
42
44
  supportsFunctionCalling?: boolean;
43
45
  supportsStreaming?: boolean;
46
+ supportsVision?: boolean;
44
47
  contextWindowLimit?: number;
45
48
  }
46
- export interface Message {
47
- role?: string;
48
- content: string;
49
+ export interface ChatCompletionTool {
50
+ type: 'function';
51
+ function: ChatCompletionToolFunctionDeclaration;
49
52
  }
53
+ export interface ChatCompletionToolFunctionDeclaration {
54
+ /**
55
+ * The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
56
+ */
57
+ name: string;
58
+ /**
59
+ * A description of what the function does, used by the model to choose when and how to call the function.
60
+ */
61
+ description?: string;
62
+ /**
63
+ * The parameters the functions accepts, described as a JSON Schema object.
64
+ * https://json-schema.org/understanding-json-schema/
65
+ */
66
+ parameters?: object;
67
+ }
68
+ export interface AiToolChoice {
69
+ type: 'function';
70
+ function: {
71
+ /**
72
+ * The name of the function to call.
73
+ */
74
+ name: string;
75
+ };
76
+ }
77
+ export type ChatCompletionMessage = ChatCompletionSystemMessage | ChatCompletionUserMessage | ChatCompletionAssistantMessage | ChatCompletionToolMessage;
78
+ export interface ChatCompletionSystemMessage {
79
+ role: 'system';
80
+ content: string | ChatCompletionContentPartText[];
81
+ }
82
+ export interface ChatCompletionUserMessage {
83
+ role?: 'user';
84
+ content: string | ChatCompletionContentPart[];
85
+ }
86
+ export type ChatCompletionResponseMessage = ChatCompletionAssistantMessage;
87
+ export interface ChatCompletionAssistantMessage {
88
+ role?: 'assistant';
89
+ content?: string | ChatCompletionContentPartText[] | null;
90
+ tool_calls?: ChatCompletionMessageToolCall[] | null;
91
+ }
92
+ export interface ChatCompletionChunkMessage {
93
+ role?: ROLES;
94
+ content?: string | null;
95
+ tool_calls?: ChatCompletionDeltaMessageToolCall[];
96
+ }
97
+ export interface ChatCompletionToolMessage {
98
+ content: string | ChatCompletionContentPartText[];
99
+ role: 'tool';
100
+ /**
101
+ * Tool call that this message is responding to.
102
+ */
103
+ tool_call_id: string;
104
+ }
105
+ export type ROLES = 'user' | 'assistant' | 'system' | 'tool';
106
+ export type ChatCompletionContentPart = ChatCompletionContentPartText | ChatCompletionContentPartImage;
107
+ export type ChatCompletionContentPartText = {
108
+ type: 'text';
109
+ text: string;
110
+ };
111
+ export type ChatCompletionContentPartImage = {
112
+ type: 'image_url';
113
+ image_url: {
114
+ /**
115
+ * Either a URL of the image or the base64 encoded image data.
116
+ */
117
+ url: string;
118
+ };
119
+ };
120
+ export interface ChatCompletionMessageToolCall {
121
+ id: string;
122
+ type: 'function';
123
+ function: {
124
+ /**
125
+ * The arguments to call the function with, as generated by the model in JSON format.
126
+ * Note that the model does not always generate valid JSON.
127
+ */
128
+ arguments?: string;
129
+ name: string;
130
+ };
131
+ }
132
+ export interface ChatCompletionDeltaMessageToolCall {
133
+ index: number;
134
+ id?: string;
135
+ type?: 'function';
136
+ function?: {
137
+ /**
138
+ * The arguments to call the function with, as generated by the model in JSON format.
139
+ * Note that the model does not always generate valid JSON.
140
+ */
141
+ arguments?: string;
142
+ name?: string;
143
+ };
144
+ }
145
+ export interface InputMessage {
146
+ role?: ROLES;
147
+ content: string | InputContentPart[];
148
+ }
149
+ export type InputContentPart = ChatCompletionContentPartText | InputChatCompletionContentPartImage;
150
+ export type InputChatCompletionContentPartImage = {
151
+ type: 'image';
152
+ mimeType: string;
153
+ url: string;
154
+ };
@@ -1,4 +1,5 @@
1
1
  import { AppModuleAggregateError } from '../../../util/logger';
2
+ import { ChatCompletionChunkMessage, ChatCompletionContentPart, ChatCompletionMessage, ChatCompletionMessageToolCall, ChatCompletionResponseMessage, InputContentPart, InputMessage } from '../types';
2
3
  interface RateLimitErrorOptions {
3
4
  error?: Error;
4
5
  message?: string;
@@ -8,4 +9,8 @@ export declare class RateLimitError extends AppModuleAggregateError {
8
9
  readonly status = 429;
9
10
  constructor({ error, message }?: RateLimitErrorOptions);
10
11
  }
12
+ export declare function normalizeContentParts(content: string | InputContentPart[]): string | ChatCompletionContentPart[];
13
+ export declare function inputMessageToChatCompletionMessage(message: InputMessage): ChatCompletionMessage;
14
+ export declare function mergeToolCalls(chunks: ChatCompletionChunkMessage[]): ChatCompletionMessageToolCall[] | null;
15
+ export declare function mergeChatCompletionChunks(chunks: ChatCompletionChunkMessage[]): ChatCompletionResponseMessage[];
11
16
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RateLimitError = exports.isRateLimitError = void 0;
3
+ exports.mergeChatCompletionChunks = exports.mergeToolCalls = exports.inputMessageToChatCompletionMessage = exports.normalizeContentParts = exports.RateLimitError = exports.isRateLimitError = void 0;
4
4
  const logger_1 = require("../../../util/logger");
5
5
  const HTTP_RATE_LIMIT = 429;
6
6
  function isRateLimitError(e) {
@@ -19,3 +19,74 @@ class RateLimitError extends logger_1.AppModuleAggregateError {
19
19
  }
20
20
  }
21
21
  exports.RateLimitError = RateLimitError;
22
+ function normalizeContentParts(content) {
23
+ return Array.isArray(content)
24
+ ? content.map((part) => part.type === 'image'
25
+ ? {
26
+ type: 'image_url',
27
+ // eslint-disable-next-line @typescript-eslint/camelcase
28
+ image_url: {
29
+ url: part.url,
30
+ },
31
+ }
32
+ : part)
33
+ : content;
34
+ }
35
+ exports.normalizeContentParts = normalizeContentParts;
36
+ function inputMessageToChatCompletionMessage(message) {
37
+ switch (message.role) {
38
+ case undefined:
39
+ case 'user':
40
+ return {
41
+ role: 'user',
42
+ content: normalizeContentParts(message.content),
43
+ };
44
+ default:
45
+ return message;
46
+ }
47
+ }
48
+ exports.inputMessageToChatCompletionMessage = inputMessageToChatCompletionMessage;
49
+ function mergeToolCalls(chunks) {
50
+ const toolCalls = [];
51
+ chunks.forEach((chunk) => {
52
+ if (!chunk.tool_calls) {
53
+ return;
54
+ }
55
+ chunk.tool_calls.forEach((toolCallChunk) => {
56
+ const delta = toolCalls.find((toolCall) => toolCall.index === toolCallChunk.index);
57
+ if (!delta) {
58
+ toolCalls.push(toolCallChunk);
59
+ }
60
+ else if (!!toolCallChunk.function && 'arguments' in toolCallChunk.function) {
61
+ if (!delta.function) {
62
+ delta.function = toolCallChunk.function;
63
+ }
64
+ else if (!delta.function.arguments) {
65
+ delta.function.arguments = toolCallChunk.function.arguments;
66
+ }
67
+ else {
68
+ delta.function.arguments += toolCallChunk.function.arguments;
69
+ }
70
+ }
71
+ });
72
+ });
73
+ toolCalls.map((tool) => {
74
+ if ('index' in tool) {
75
+ delete tool.index;
76
+ }
77
+ });
78
+ return !toolCalls.length ? null : toolCalls;
79
+ }
80
+ exports.mergeToolCalls = mergeToolCalls;
81
+ function mergeChatCompletionChunks(chunks) {
82
+ /* eslint-disable @typescript-eslint/camelcase */
83
+ const tool_calls = mergeToolCalls(chunks);
84
+ return [
85
+ {
86
+ role: chunks[0].role || 'assistant',
87
+ content: tool_calls ? null : chunks.map((chunk) => chunk.content).join(''),
88
+ tool_calls,
89
+ },
90
+ ];
91
+ }
92
+ exports.mergeChatCompletionChunks = mergeChatCompletionChunks;
@@ -23,9 +23,15 @@ function handle(baseConfig, config, folderName) {
23
23
  fs_1.default.mkdirSync(path_1.default.join(folderPath, folderName), { recursive: true });
24
24
  }
25
25
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
26
+ var _a;
26
27
  const response = {};
27
28
  const baseFilesUrl = `${baseConfig.baseUrl}/file/download/${folderName}`;
28
29
  const body = req.body;
30
+ // Skip assets (e.g., images, videos, or other media files) as they typically don't need to be processed
31
+ if (((_a = body === null || body === void 0 ? void 0 : body.file) === null || _a === void 0 ? void 0 : _a.type) === 'assets' && !('processAssets' in config && config.processAssets)) {
32
+ res.sendStatus(304);
33
+ return;
34
+ }
29
35
  let processingError;
30
36
  let fileContent;
31
37
  let rawContent;
@@ -60,17 +60,24 @@ export interface CustomFileFormatLogic extends FileProcessLogic {
60
60
  }
61
61
  export type FileImportExportLogic = FilePreImportLogic | FilePostImportLogic | FilePreExportLogic | FilePostExportLogic;
62
62
  export type FileImportExportContent = ProcessFileString[] | Buffer | undefined;
63
- export interface FilePreImportLogic extends FileProcessLogic {
64
- fileProcess: (req: ProcessFileRequest, content: FileImportExportContent, client: Crowdin, context: CrowdinContextInfo, projectId: number) => Promise<ContentFileResponse>;
63
+ export interface BaseFileProcessLogic<T> {
64
+ fileProcess: (req: ProcessFileRequest, content: FileImportExportContent, client: Crowdin, context: CrowdinContextInfo, projectId: number) => Promise<T>;
65
65
  }
66
- export interface FilePostImportLogic extends FileProcessLogic {
67
- fileProcess: (req: ProcessFileRequest, content: FileImportExportContent, client: Crowdin, context: CrowdinContextInfo, projectId: number) => Promise<StringsFileResponse>;
66
+ export interface FilePreImportLogic extends FileProcessLogic, BaseFileProcessLogic<ContentFileResponse> {
67
+ /**
68
+ * Set to `true` to enable asset processing in the application.
69
+ */
70
+ processAssets?: boolean;
71
+ }
72
+ export interface FilePostImportLogic extends FileProcessLogic, BaseFileProcessLogic<StringsFileResponse> {
68
73
  }
69
- export interface FilePreExportLogic extends FileProcessLogic {
70
- fileProcess: (req: ProcessFileRequest, content: FileImportExportContent, client: Crowdin, context: CrowdinContextInfo, projectId: number) => Promise<StringsFileResponse>;
74
+ export interface FilePreExportLogic extends FileProcessLogic, BaseFileProcessLogic<StringsFileResponse> {
71
75
  }
72
- export interface FilePostExportLogic extends FileProcessLogic {
73
- fileProcess: (req: ProcessFileRequest, content: FileImportExportContent, client: Crowdin, context: CrowdinContextInfo, projectId: number) => Promise<ContentFileResponse>;
76
+ export interface FilePostExportLogic extends FileProcessLogic, BaseFileProcessLogic<ContentFileResponse> {
77
+ /**
78
+ * Set to `true` to enable asset processing in the application.
79
+ */
80
+ processAssets?: boolean;
74
81
  }
75
82
  export interface ProcessFileRequest {
76
83
  jobType: ProcessFileJobType;
@@ -87,6 +94,7 @@ export interface ProcessFileRecord {
87
94
  path?: string;
88
95
  id?: number;
89
96
  name?: string;
97
+ type?: string;
90
98
  }
91
99
  export declare enum ProcessFileJobType {
92
100
  PARSE_FILE = "parse-file",
@@ -5,6 +5,7 @@ const util_1 = require("../util");
5
5
  const subscription_1 = require("../util/subscription");
6
6
  const api_1 = require("./api/api");
7
7
  const util_2 = require("./ai-tools/util");
8
+ const util_3 = require("./workflow-step-type/util");
8
9
  function normalizeEnvironments(environments) {
9
10
  if (Array.isArray(environments)) {
10
11
  return environments;
@@ -12,6 +13,7 @@ function normalizeEnvironments(environments) {
12
13
  return [environments];
13
14
  }
14
15
  function handle(config) {
16
+ var _a, _b;
15
17
  const modules = {};
16
18
  if (config.projectIntegration) {
17
19
  config.projectIntegration.key = config.identifier + '-int';
@@ -100,6 +102,17 @@ function handle(config) {
100
102
  },
101
103
  ];
102
104
  }
105
+ if (config.organizationSettingsMenu) {
106
+ config.organizationSettingsMenu.key = config.identifier + '-organization-settings-menu';
107
+ modules['organization-settings-menu'] = [
108
+ {
109
+ key: config.organizationSettingsMenu.key,
110
+ name: config.organizationSettingsMenu.name || config.name,
111
+ url: '/settings/' + (config.organizationSettingsMenu.fileName || 'index.html'),
112
+ icon: (0, util_1.getLogoUrl)(config.organizationSettingsMenu, '/settings'),
113
+ },
114
+ ];
115
+ }
103
116
  if (config.profileResourcesMenu) {
104
117
  config.profileResourcesMenu.key = config.identifier + '-profile-resources-menu';
105
118
  modules['profile-resources-menu'] = [
@@ -108,6 +121,14 @@ function handle(config) {
108
121
  })),
109
122
  ];
110
123
  }
124
+ if (config.profileSettingsMenu) {
125
+ config.profileSettingsMenu.key = config.identifier + '-profile-settings-menu';
126
+ modules['profile-settings-menu'] = [
127
+ Object.assign({ key: config.profileSettingsMenu.key, name: config.profileSettingsMenu.name || config.name, url: '/settings/' + (config.profileSettingsMenu.fileName || 'index.html'), icon: (0, util_1.getLogoUrl)(config.profileSettingsMenu, '/settings') }, (!!config.profileSettingsMenu.environments && {
128
+ environments: normalizeEnvironments(config.profileSettingsMenu.environments),
129
+ })),
130
+ ];
131
+ }
111
132
  if (config.editorRightPanel) {
112
133
  config.editorRightPanel.key = config.identifier + '-editor-panels';
113
134
  modules['editor-right-panel'] = [
@@ -244,6 +265,19 @@ function handle(config) {
244
265
  Object.assign(Object.assign({ key: config.externalQaCheck.key, name: config.externalQaCheck.name || config.name, description: config.externalQaCheck.description || config.description, runQaCheckUrl: '/validate' }, (config.externalQaCheck.batchSize ? { getBatchSizeUrl: '/batch-size' } : {})), (uiModule ? { url: '/settings/' + (uiModule.fileName || 'index.html') } : {})),
245
266
  ];
246
267
  }
268
+ if (config.workflowStepType) {
269
+ const workflowSteps = Array.isArray(config.workflowStepType)
270
+ ? config.workflowStepType
271
+ : [config.workflowStepType];
272
+ modules['workflow-step-type'] = [];
273
+ for (const workflowStep of workflowSteps) {
274
+ if (!workflowStep.key) {
275
+ workflowStep.key = config.identifier + '-' + (0, util_3.getWorkflowStepKey)(workflowStep);
276
+ }
277
+ const uiModule = ((_a = workflowStep === null || workflowStep === void 0 ? void 0 : workflowStep.settingsUiModule) === null || _a === void 0 ? void 0 : _a.formSchema) || ((_b = workflowStep === null || workflowStep === void 0 ? void 0 : workflowStep.settingsUiModule) === null || _b === void 0 ? void 0 : _b.fileName);
278
+ modules['workflow-step-type'].push(Object.assign({ key: workflowStep.key, name: workflowStep.name || config.name, description: workflowStep.description || config.description, boundaries: workflowStep.boundaries, updateSettingsUrl: (0, util_3.getWorkflowStepUrl)('/settings', workflowStep), deleteSettingsUrl: (0, util_3.getWorkflowStepUrl)('/delete', workflowStep) }, (uiModule ? { url: (0, util_3.getWorkflowStepUrl)('/workflow-step', workflowStep) } : {})));
279
+ }
280
+ }
247
281
  const events = {
248
282
  installed: '/installed',
249
283
  uninstall: '/uninstall',
@@ -0,0 +1,6 @@
1
+ import { Express } from 'express';
2
+ import { Config, UnauthorizedConfig } from '../../types';
3
+ export declare function register({ config, app }: {
4
+ config: Config | UnauthorizedConfig;
5
+ app: Express;
6
+ }): void;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.register = void 0;
7
+ const render_ui_module_1 = __importDefault(require("../../middlewares/render-ui-module"));
8
+ const ui_module_1 = __importDefault(require("../../middlewares/ui-module"));
9
+ const util_1 = require("../../util");
10
+ function register({ config, app }) {
11
+ if (!config.organizationSettingsMenu) {
12
+ return;
13
+ }
14
+ const allowUnauthorized = !(0, util_1.isAuthorizedConfig)(config);
15
+ app.get((0, util_1.getLogoUrl)(config.organizationSettingsMenu, '/settings'), (req, res) => { var _a; return res.sendFile(((_a = config.organizationSettingsMenu) === null || _a === void 0 ? void 0 : _a.imagePath) || config.imagePath); });
16
+ app.use('/settings', (0, ui_module_1.default)({ config, allowUnauthorized, moduleType: config.organizationSettingsMenu.key }), (0, render_ui_module_1.default)(config.organizationSettingsMenu));
17
+ }
18
+ exports.register = register;
@@ -0,0 +1,6 @@
1
+ import { Express } from 'express';
2
+ import { Config, UnauthorizedConfig } from '../../types';
3
+ export declare function register({ config, app }: {
4
+ config: Config | UnauthorizedConfig;
5
+ app: Express;
6
+ }): void;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.register = void 0;
7
+ const render_ui_module_1 = __importDefault(require("../../middlewares/render-ui-module"));
8
+ const ui_module_1 = __importDefault(require("../../middlewares/ui-module"));
9
+ const util_1 = require("../../util");
10
+ function register({ config, app }) {
11
+ if (!config.profileSettingsMenu) {
12
+ return;
13
+ }
14
+ app.get((0, util_1.getLogoUrl)(config.profileSettingsMenu, '/settings'), (req, res) => { var _a; return res.sendFile(((_a = config.profileSettingsMenu) === null || _a === void 0 ? void 0 : _a.imagePath) || config.imagePath); });
15
+ const allowUnauthorized = !(0, util_1.isAuthorizedConfig)(config);
16
+ app.use('/settings', (0, ui_module_1.default)({ config, allowUnauthorized, moduleType: config.profileSettingsMenu.key }), (0, render_ui_module_1.default)(config.profileSettingsMenu));
17
+ }
18
+ exports.register = register;
@@ -1,5 +1,5 @@
1
1
  /// <reference types="qs" />
2
- import { CrowdinClientRequest } from '../../../types';
2
+ import { Config, CrowdinClientRequest } from '../../../types';
3
3
  import { Response } from 'express';
4
4
  import { Webhook } from '../types';
5
- export declare function webhookHandler(webhooks: Webhook[]): (req: CrowdinClientRequest | import("express").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
+ export declare function webhookHandler(config: Config, webhooks: Webhook[]): (req: CrowdinClientRequest | import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -40,12 +40,13 @@ const util_1 = require("../../../util");
40
40
  const lodash_isstring_1 = __importDefault(require("lodash.isstring"));
41
41
  const crypto = __importStar(require("node:crypto"));
42
42
  const storage = __importStar(require("../../../storage"));
43
- function webhookHandler(webhooks) {
43
+ const connection_1 = require("../../../util/connection");
44
+ function webhookHandler(config, webhooks) {
44
45
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
45
46
  const domain = req.headers['x-crowdin-domain'];
46
- const organizationId = req.headers['x-crowdin-organization-id'];
47
+ const organizationId = req.headers['x-crowdin-id'];
47
48
  const signature = req.headers['x-crowdin-signature'];
48
- const moduleKey = req.headers['x-application-webhook-key'];
49
+ const moduleKey = req.headers['x-module-key'];
49
50
  if (!(0, lodash_isstring_1.default)(domain) || !(0, lodash_isstring_1.default)(organizationId) || !(0, lodash_isstring_1.default)(signature) || !(0, lodash_isstring_1.default)(moduleKey)) {
50
51
  res.status(400).send({ error: 'Invalid request' });
51
52
  return;
@@ -56,17 +57,19 @@ function webhookHandler(webhooks) {
56
57
  throw new Error('Failed to find Crowdin credentials');
57
58
  }
58
59
  const hmac = crypto.createHmac('sha256', credentials.appSecret);
59
- hmac.update(JSON.stringify(req.body).replace(/\//g, '\\/'));
60
+ hmac.update(req.body.toString());
60
61
  const generatedSignature = hmac.digest('hex');
61
62
  if (generatedSignature !== signature.replace('sha256=', '')) {
62
63
  res.status(403).send({ error: 'Invalid signature' });
63
64
  return;
64
65
  }
65
66
  res.status(200).send();
67
+ const { client } = yield (0, connection_1.prepareCrowdinClient)({ config, credentials, autoRenew: true });
68
+ const json = JSON.parse(req.body.toString());
66
69
  for (const webhook of webhooks) {
67
70
  if (webhook.key === moduleKey) {
68
- for (const event of req.body.payload.events) {
69
- yield webhook.callback({ credentials, event });
71
+ for (const event of json.events) {
72
+ yield webhook.callback({ credentials, event, client });
70
73
  }
71
74
  }
72
75
  }
@@ -12,7 +12,7 @@ function register({ config, app }) {
12
12
  }
13
13
  const webhooks = Array.isArray(config.webhooks) ? config.webhooks : [config.webhooks];
14
14
  if (webhooks.length) {
15
- app.post('/webhooks', json_response_1.default, (0, webhook_handler_1.webhookHandler)(webhooks));
15
+ app.post('/webhooks', json_response_1.default, (0, webhook_handler_1.webhookHandler)(config, webhooks));
16
16
  }
17
17
  }
18
18
  exports.register = register;