@crowdin/app-project-module 0.81.0 → 0.82.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/README.md CHANGED
@@ -1,13 +1,11 @@
1
- # Crowdin App Project Module
1
+ # Crowdin Apps SDK
2
2
 
3
- Module that will automatically add all necessary endpoints for Crowdin App.
3
+ The Crowdin Apps SDK is a framework that allows you to build custom apps for Crowdin. It provides a set of tools and components that you can use to build your app and integrate it with Crowdin.
4
4
 
5
- :bookmark: See the [docs](https://crowdin.github.io/app-project-module/) for more information.
6
-
7
- ## Contributing
5
+ It is designed to help you create apps that can be easily installed and used by Crowdin users. It handles the communication between your app and Crowdin, so you can focus on building the functionality of your app.
8
6
 
9
- If you would like to contribute please read the [Contributing](/CONTRIBUTING.md) guidelines.
7
+ :bookmark: See the [docs](https://crowdin.github.io/app-project-module/) for more information.
10
8
 
11
9
  ## Seeking Assistance
12
10
 
13
- If you find any problems or would like to suggest a feature, please feel free to file an issue on GitHub at [Issues Page](https://github.com/crowdin/app-project-module/issues) or [Contact Customer Success Service](https://crowdin.com/contacts).
11
+ If you are experiencing problems or would like to suggest a feature, [Contact Customer Success Service](https://crowdin.com/contacts).
package/out/index.js CHANGED
@@ -83,6 +83,7 @@ const projectReportsApp = __importStar(require("./modules/project-reports"));
83
83
  const projectToolsApp = __importStar(require("./modules/project-tools"));
84
84
  const webhooks = __importStar(require("./modules/webhooks"));
85
85
  const workflowStepType = __importStar(require("./modules/workflow-step-type"));
86
+ const aiRequestProcessors = __importStar(require("./modules/ai-request-processors"));
86
87
  const subscription_1 = require("./util/subscription");
87
88
  var types_2 = require("./types");
88
89
  Object.defineProperty(exports, "ProjectPermissions", { enumerable: true, get: function () { return types_2.ProjectPermissions; } });
@@ -190,6 +191,7 @@ function addCrowdinEndpoints(app, clientConfig) {
190
191
  externalQaCheck.register({ config, app });
191
192
  webhooks.register({ config, app });
192
193
  workflowStepType.register({ config, app });
194
+ aiRequestProcessors.register({ config, app });
193
195
  addFormSchema({ config, app });
194
196
  return Object.assign(Object.assign({}, exports.metadataStore), { storage: storage.getStorage(), establishCrowdinConnection: (authRequest, moduleKey) => {
195
197
  let jwtToken = '';
@@ -0,0 +1,5 @@
1
+ /// <reference types="qs" />
2
+ import { Response } from 'express';
3
+ import { CrowdinClientRequest } from '../../types';
4
+ import { AiRequestProcessorModule, AiStreamProcessorModule } from './types';
5
+ export default function handle(module: AiRequestProcessorModule | AiStreamProcessorModule, canHandleStream?: boolean): (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;
@@ -0,0 +1,63 @@
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
+ const util_1 = require("../../util");
13
+ const logger_1 = require("../../util/logger");
14
+ const BUFFERING_FLAG = '~buffering~';
15
+ function handle(module, canHandleStream = false) {
16
+ return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
17
+ try {
18
+ if (canHandleStream && req.headers['transfer-encoding'] === 'chunked') {
19
+ if (!('processStream' in module)) {
20
+ res.writeHead(202, {}).end();
21
+ return;
22
+ }
23
+ res.writeHead(200, {
24
+ Connection: 'keep-alive',
25
+ 'Cache-Control': 'no-cache',
26
+ 'Content-Type': 'text/event-stream',
27
+ 'X-Accel-Buffering': 'no',
28
+ });
29
+ req.on('data', (chunk) => __awaiter(this, void 0, void 0, function* () {
30
+ let chunkStr = chunk.toString();
31
+ // actually we checked if processStream exists already, but need do to this again to calm down TS compiler
32
+ chunkStr = module.processStream
33
+ ? yield module.processStream(chunkStr, req.query, req.crowdinApiClient, req.crowdinContext)
34
+ : chunkStr;
35
+ if (!chunkStr && typeof chunkStr !== 'string') {
36
+ res.write(BUFFERING_FLAG);
37
+ return;
38
+ }
39
+ res.write(chunkStr);
40
+ }));
41
+ req.on('end', () => {
42
+ res.end();
43
+ });
44
+ req.on('error', (e) => {
45
+ if (req.logError) {
46
+ req.logError(e);
47
+ }
48
+ res.end();
49
+ });
50
+ return;
51
+ }
52
+ const result = yield module.processRequest(req.body, req.query, req.crowdinApiClient, req.crowdinContext);
53
+ res.send(result);
54
+ }
55
+ catch (e) {
56
+ if (req.logError) {
57
+ req.logError(e);
58
+ }
59
+ res.send({ error: { message: (0, logger_1.getErrorMessage)(e) } });
60
+ }
61
+ }));
62
+ }
63
+ exports.default = handle;
@@ -0,0 +1,16 @@
1
+ import { Express } from 'express';
2
+ import { Config } from '../../types';
3
+ export declare enum AiRequestProcessorModuleWithStream {
4
+ aiRequestPreParse = "aiRequestPreParse"
5
+ }
6
+ export declare enum AiRequestProcessorModuleWithoutStream {
7
+ aiRequestPreCompile = "aiRequestPreCompile",
8
+ aiRequestPostCompile = "aiRequestPostCompile",
9
+ aiRequestPostParse = "aiRequestPostParse"
10
+ }
11
+ export type AiRequestProcessorModuleType = AiRequestProcessorModuleWithoutStream | AiRequestProcessorModuleWithStream;
12
+ export declare function generateModuleSlugFromType(moduleType: string): string;
13
+ export declare function register({ config, app }: {
14
+ config: Config;
15
+ app: Express;
16
+ }): void;
@@ -0,0 +1,45 @@
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 = exports.generateModuleSlugFromType = exports.AiRequestProcessorModuleWithoutStream = exports.AiRequestProcessorModuleWithStream = void 0;
7
+ const json_response_1 = __importDefault(require("../../middlewares/json-response"));
8
+ const crowdin_client_1 = __importDefault(require("../../middlewares/crowdin-client"));
9
+ const handler_1 = __importDefault(require("./handler"));
10
+ var AiRequestProcessorModuleWithStream;
11
+ (function (AiRequestProcessorModuleWithStream) {
12
+ AiRequestProcessorModuleWithStream["aiRequestPreParse"] = "aiRequestPreParse";
13
+ })(AiRequestProcessorModuleWithStream = exports.AiRequestProcessorModuleWithStream || (exports.AiRequestProcessorModuleWithStream = {}));
14
+ var AiRequestProcessorModuleWithoutStream;
15
+ (function (AiRequestProcessorModuleWithoutStream) {
16
+ AiRequestProcessorModuleWithoutStream["aiRequestPreCompile"] = "aiRequestPreCompile";
17
+ AiRequestProcessorModuleWithoutStream["aiRequestPostCompile"] = "aiRequestPostCompile";
18
+ AiRequestProcessorModuleWithoutStream["aiRequestPostParse"] = "aiRequestPostParse";
19
+ })(AiRequestProcessorModuleWithoutStream = exports.AiRequestProcessorModuleWithoutStream || (exports.AiRequestProcessorModuleWithoutStream = {}));
20
+ function generateModuleSlugFromType(moduleType) {
21
+ return moduleType
22
+ .split(/\.?(?=[A-Z])/)
23
+ .join('-')
24
+ .toLowerCase();
25
+ }
26
+ exports.generateModuleSlugFromType = generateModuleSlugFromType;
27
+ function registerProcessorModuleByTypeKey(moduleKey, { config, app }) {
28
+ const module = config[moduleKey];
29
+ if (!module) {
30
+ return;
31
+ }
32
+ app.post('/ai-request-processor/' + generateModuleSlugFromType(moduleKey), json_response_1.default, (0, crowdin_client_1.default)({
33
+ config,
34
+ optional: false,
35
+ checkSubscriptionExpiration: true,
36
+ moduleKey: module.key,
37
+ }), (0, handler_1.default)(module, moduleKey in AiRequestProcessorModuleWithStream));
38
+ }
39
+ function register({ config, app }) {
40
+ [
41
+ ...Object.values(AiRequestProcessorModuleWithoutStream),
42
+ ...Object.values(AiRequestProcessorModuleWithStream),
43
+ ].forEach((moduleTypeKey) => registerProcessorModuleByTypeKey(moduleTypeKey, { config, app }));
44
+ }
45
+ exports.register = register;
@@ -0,0 +1,14 @@
1
+ import { CrowdinContextInfo, ModuleKey } from '../../types';
2
+ import Crowdin from '@crowdin/crowdin-api-client';
3
+ export interface AiRequestProcessorModule extends ModuleKey {
4
+ /**
5
+ * updates request data
6
+ */
7
+ processRequest: (requestData: any, requestContext: any, client: Crowdin, context: CrowdinContextInfo) => Promise<never>;
8
+ }
9
+ export interface AiStreamProcessorModule extends AiRequestProcessorModule {
10
+ /**
11
+ * updates request data
12
+ */
13
+ processStream?: (chunk: any, requestContext: any, client: Crowdin, context: CrowdinContextInfo) => Promise<string | null>;
14
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -92,11 +92,31 @@ function handle(baseConfig, config, folderName) {
92
92
  break;
93
93
  case types_1.ProcessFileJobType.POST_IMPORT:
94
94
  case types_1.ProcessFileJobType.PRE_EXPORT:
95
- const { strings, error: stringsFileError } = fileProcessResult;
95
+ const { strings, error: stringsFileError, previewFile } = fileProcessResult;
96
+ let maxSize = files_1.MAX_BODY_SIZE;
97
+ if (strings && previewFile && body.jobType === types_1.ProcessFileJobType.POST_IMPORT) {
98
+ maxSize = maxSize / 2;
99
+ }
100
+ if (previewFile && body.jobType === types_1.ProcessFileJobType.POST_IMPORT) {
101
+ if (Buffer.byteLength(previewFile) < maxSize) {
102
+ response.preview = previewFile.toString('base64');
103
+ }
104
+ else {
105
+ let url;
106
+ if (config.storeFile) {
107
+ url = yield config.storeFile(previewFile);
108
+ }
109
+ else {
110
+ const storedFile = yield (0, files_1.storeFile)(previewFile, path_1.default.join(folderPath, folderName));
111
+ url = `${baseFilesUrl}?file=${storedFile}`;
112
+ }
113
+ response.previewUrl = url;
114
+ }
115
+ }
96
116
  if (strings) {
97
117
  const stringsNDJson = strings.map((s) => JSON.stringify(s)).join('\n\r');
98
118
  const bufferData = Buffer.from(stringsNDJson, 'utf-8');
99
- if (Buffer.byteLength(bufferData) < files_1.MAX_BODY_SIZE) {
119
+ if (Buffer.byteLength(bufferData) < maxSize) {
100
120
  response.strings = strings;
101
121
  }
102
122
  else {
@@ -115,7 +115,7 @@ export interface BuildFileResponse {
115
115
  fileName?: string;
116
116
  fileType?: string;
117
117
  }
118
- export interface StringsFileResponse extends Omit<ParseFileResponse, 'previewFile'> {
118
+ export interface StringsFileResponse extends ParseFileResponse {
119
119
  notModified?: boolean;
120
120
  }
121
121
  export interface ContentFileResponse extends BuildFileResponse {
@@ -6,6 +6,7 @@ const subscription_1 = require("../util/subscription");
6
6
  const api_1 = require("./api/api");
7
7
  const util_2 = require("./ai-tools/util");
8
8
  const util_3 = require("./workflow-step-type/util");
9
+ const ai_request_processors_1 = require("./ai-request-processors");
9
10
  function normalizeEnvironments(environments) {
10
11
  if (Array.isArray(environments)) {
11
12
  return environments;
@@ -236,6 +237,22 @@ function handle(config) {
236
237
  })), (uiModule ? { url: '/settings/' + (uiModule.fileName || 'index.html') } : {})),
237
238
  ];
238
239
  }
240
+ [
241
+ ...Object.values(ai_request_processors_1.AiRequestProcessorModuleWithoutStream),
242
+ ...Object.values(ai_request_processors_1.AiRequestProcessorModuleWithStream),
243
+ ].forEach((moduleType) => {
244
+ const module = config[moduleType];
245
+ if (!module) {
246
+ return;
247
+ }
248
+ const moduleSlug = (0, ai_request_processors_1.generateModuleSlugFromType)(moduleType);
249
+ modules[moduleSlug] = [
250
+ {
251
+ key: config.identifier + '-' + moduleSlug,
252
+ processorUrl: '/ai-request-processor/' + moduleSlug,
253
+ },
254
+ ];
255
+ });
239
256
  if (config.aiPromptProvider) {
240
257
  // prevent possible overrides of the other modules
241
258
  config.aiPromptProvider = Object.assign(Object.assign({}, config.aiPromptProvider), { key: config.identifier + '-ai-prompt-provider' });