UNPKG

@crowdin/app-project-module

Version:

Module that generates for you all common endpoints for serving standalone Crowdin App

378 lines (377 loc) 19.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.metadataStore = exports.express = exports.maskKey = exports.postRequestCredentialsMasker = exports.getRequestCredentialsMasker = void 0; exports.createApp = createApp; exports.addCrowdinEndpoints = addCrowdinEndpoints; const logsFormatter = __importStar(require("@crowdin/logs-formatter")); const express_rate_limit_1 = require("express-rate-limit"); const path_1 = require("path"); const crowdin_client_1 = __importStar(require("./middlewares/crowdin-client")); const json_response_1 = __importDefault(require("./middlewares/json-response")); const form_data_display_1 = __importDefault(require("./modules/form-data-display")); const form_data_save_1 = __importDefault(require("./modules/form-data-save")); const install_1 = __importDefault(require("./modules/install")); const manifest_1 = __importDefault(require("./modules/manifest")); const about_1 = __importDefault(require("./modules/about")); const subscription_paid_1 = __importDefault(require("./modules/subscription-paid")); const uninstall_1 = __importDefault(require("./modules/uninstall")); const status_1 = __importDefault(require("./modules/status")); const storage = __importStar(require("./storage")); const d1_1 = require("./storage/d1"); const cron = __importStar(require("./util/cron")); const types_1 = require("./types"); const util_1 = require("./util"); const form_schema_1 = require("./util/form-schema"); const connection_1 = require("./util/connection"); const logger = __importStar(require("./util/logger")); const logger_1 = require("./util/logger"); const terminus_express_1 = __importDefault(require("./util/terminus-express")); exports.express = terminus_express_1.default; const credentials_masker_1 = require("./util/credentials-masker"); Object.defineProperty(exports, "getRequestCredentialsMasker", { enumerable: true, get: function () { return credentials_masker_1.getRequestCredentialsMasker; } }); Object.defineProperty(exports, "postRequestCredentialsMasker", { enumerable: true, get: function () { return credentials_masker_1.postRequestCredentialsMasker; } }); Object.defineProperty(exports, "maskKey", { enumerable: true, get: function () { return credentials_masker_1.maskKey; } }); const auto_credentials_masker_1 = require("./middlewares/auto-credentials-masker"); const static_files_1 = require("./util/static-files"); const util_2 = require("./util"); //apps const apiApp = __importStar(require("./modules/api")); const contextMenuApp = __importStar(require("./modules/context-menu")); const customMTApp = __importStar(require("./modules/custom-mt")); const spellCheckApp = __importStar(require("./modules/custom-spell-check")); const aiProvider = __importStar(require("./modules/ai-provider")); const aiPromptProvider = __importStar(require("./modules/ai-prompt-provider")); const aiTools = __importStar(require("./modules/ai-tools")); const editorRightPanelApp = __importStar(require("./modules/editor-right-panel")); const externalQaCheck = __importStar(require("./modules/external-qa-check")); const fileProcessingApps = __importStar(require("./modules/file-processing")); const integrationApp = __importStar(require("./modules/integration")); const chatApp = __importStar(require("./modules/chat")); const modalApp = __importStar(require("./modules/modal")); const organizationMenuApp = __importStar(require("./modules/organization-menu")); const organizationSettingsMenuApp = __importStar(require("./modules/organization-settings-menu")); const profileResourcesMenuApp = __importStar(require("./modules/profile-resources-menu")); const profileSettingsMenuApp = __importStar(require("./modules/profile-settings-menu")); const projectMenuApp = __importStar(require("./modules/project-menu")); const projectMenuCrowdsourceApp = __importStar(require("./modules/project-menu-crowdsource")); const projectReportsApp = __importStar(require("./modules/project-reports")); const projectToolsApp = __importStar(require("./modules/project-tools")); const webhooks = __importStar(require("./modules/webhooks")); const workflowStepType = __importStar(require("./modules/workflow-step-type")); const aiRequestProcessors = __importStar(require("./modules/ai-request-processors")); const automationAction = __importStar(require("./modules/automation-action")); const authGuard = __importStar(require("./modules/auth-guard")); const subscription_1 = require("./util/subscription"); __exportStar(require("./types"), exports); exports.metadataStore = { getMetadata: (id) => { return storage.getStorage().getMetadata(id); }, saveMetadata: (_a) => __awaiter(void 0, [_a], void 0, function* ({ id, metadata, crowdinId }) { if (!crowdinId) { throw new Error('The crowdinId parameter is required.'); } const crowdinCredentials = yield storage.getStorage().getCrowdinCredentials(crowdinId); if (!crowdinCredentials) { throw new Error('Invalid crowdinId parameter: You can get your crowdinId from the JWT payload.'); } const existing = yield storage.getStorage().getMetadata(id); if (existing) { yield storage.getStorage().updateMetadata(id, metadata, crowdinId); } else { yield storage.getStorage().saveMetadata(id, metadata, crowdinId); } }), deleteMetadata: (id) => { return storage.getStorage().deleteMetadata(id); }, getUserSettings: (clientId) => __awaiter(void 0, void 0, void 0, function* () { const integrationConfig = yield storage.getStorage().getIntegrationConfig(clientId); if (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.config) { return JSON.parse(integrationConfig.config); } }), }; function createApp(clientConfig) { const app = (0, terminus_express_1.default)(); const config = convertClientConfig(clientConfig); addCrowdinEndpoints(app, config); /* eslint no-console: "off" */ app.listen(config, () => console.log(`App started on port ${config.port}`)); } function addCrowdinEndpoints(app, clientConfig) { var _a, _b, _c; const config = convertClientConfig(clientConfig); if (!config.disableGlobalErrorHandling) { handleUncaughtErrors(); } logger.initialize(config); storage.initialize(config); cron.initialize(config); // Middleware to ensure D1 migration before handling requests app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () { try { const storageInstance = storage.getStorage(); if (storageInstance instanceof d1_1.D1Storage) { yield storageInstance.ensureMigrated(); } next(); } catch (error) { next(error); } })); // Middleware to detect and cache baseUrl from request if not provided app.use((req, res, next) => { if (!config.baseUrl) { config.baseUrl = (0, util_1.extractBaseUrlFromRequest)(req); } next(); }); app.use((req, res, next) => { if (config.webhooks && req.path === '/webhooks') { return terminus_express_1.default.raw({ type: '*/*', limit: '50mb' })(req, res, next); } else { return terminus_express_1.default.json({ limit: '50mb' })(req, res, next); } }); if (!config.disableLogsFormatter) { logsFormatter.setup(); app.use(logsFormatter.contextResolverMiddleware()); app.use(logsFormatter.expressMiddleware()); } app.use('/assets', (0, static_files_1.serveStatic)(config, config.assetsPath || 'static')); app.use('/', (0, util_2.serveLogo)(config)); app.get('/manifest.json', json_response_1.default, (0, manifest_1.default)(config)); app.get('/', (0, about_1.default)(config)); if (((_a = config === null || config === void 0 ? void 0 : config.enableStatusPage) === null || _a === void 0 ? void 0 : _a.database) || ((_b = config === null || config === void 0 ? void 0 : config.enableStatusPage) === null || _b === void 0 ? void 0 : _b.filesystem)) { app.set('trust proxy', 1); const limiter = (0, express_rate_limit_1.rateLimit)({ windowMs: 60 * 1000, // 1 minute limit: ((_c = config.enableStatusPage) === null || _c === void 0 ? void 0 : _c.rateLimit) || 10, standardHeaders: true, legacyHeaders: false, message: { status: 429, error: 'Too Many Requests', message: 'Rate limit exceeded. Please try again later.', }, }); app.get('/status', limiter, (0, status_1.default)(config)); } if (!(0, subscription_1.isAppFree)(config)) { app.post('/subscription-paid', (0, subscription_paid_1.default)()); } spellCheckApp.register({ config, app }); editorRightPanelApp.register({ config, app }); projectMenuApp.register({ config, app }); profileResourcesMenuApp.register({ config, app }); profileSettingsMenuApp.register({ config, app }); organizationMenuApp.register({ config, app }); organizationSettingsMenuApp.register({ config, app }); projectMenuCrowdsourceApp.register({ config, app }); projectToolsApp.register({ config, app }); projectReportsApp.register({ config, app }); modalApp.register({ config, app }); chatApp.register({ config, app }); contextMenuApp.register({ config, app }); //other apps only work in authorized context if (!(0, util_1.isAuthorizedConfig)(config)) { return Object.assign(Object.assign({}, exports.metadataStore), { cron: cron.getCron(), storage: storage.getStorage(), establishCrowdinConnection: () => { throw new Error('Establishing Crowdin connection is not available in unauthorized config'); }, encryptCrowdinConnection: () => { throw new Error('Encrypting Crowdin connection is not available in unauthorized config'); }, decryptCrowdinConnection: () => { throw new Error('Decrypting Crowdin connection is not available in unauthorized config'); }, jwtMiddleware: () => { throw new Error('JWT Middleware is not available in unauthorized config'); } }); } app.post('/installed', (0, install_1.default)(config)); app.post('/uninstall', (0, uninstall_1.default)(config)); /** * Module structure * /modules * /<module-name> * index.ts -> registration * types.ts -> types, interfaces, etc * /util -> folder for utilities * /handlers -> for http handlers */ integrationApp.register({ config, app }); customMTApp.register({ config, app }); fileProcessingApps.registerCustomFileFormat({ config, app }); fileProcessingApps.registerFilePreImport({ config, app }); fileProcessingApps.registerFilePostImport({ config, app }); fileProcessingApps.registerFilePreExport({ config, app }); fileProcessingApps.registerFilePostExport({ config, app }); fileProcessingApps.registerFileTranslationsAlignmentExport({ config, app }); apiApp.register({ config, app }); aiProvider.register({ config, app }); aiPromptProvider.register({ config, app }); aiTools.registerAiTools({ config, app }); aiTools.registerAiToolWidgets({ config, app }); externalQaCheck.register({ config, app }); webhooks.register({ config, app }); workflowStepType.register({ config, app }); aiRequestProcessors.register({ config, app }); automationAction.register({ config, app }); authGuard.register({ config, app }); (0, auto_credentials_masker_1.registerAutoCredentialsMasker)(app, config); addFormSchema({ config, app }); return Object.assign(Object.assign({}, exports.metadataStore), { storage: storage.getStorage(), cron: cron.getCron(), establishCrowdinConnection: ({ authRequest, jwtToken: token, moduleKey }) => { let jwtToken = ''; if (token) { jwtToken = token; } else if (authRequest) { jwtToken = (0, crowdin_client_1.getToken)(authRequest); } else { throw new Error('Either authRequest or jwtToken must be provided'); } return (0, crowdin_client_1.prepareCrowdinRequest)({ jwtToken, config, optional: false, checkSubscriptionExpiration: false, moduleKey, }); }, encryptCrowdinConnection: (data) => (0, util_1.encryptData)(config, JSON.stringify(data)), decryptCrowdinConnection: (_a) => __awaiter(this, [_a], void 0, function* ({ hash, autoRenew }) { const { crowdinId, extra } = JSON.parse((0, util_1.decryptData)(config, hash)); const credentials = yield storage.getStorage().getCrowdinCredentials(crowdinId); if (!credentials) { throw new Error('Failed to find Crowdin credentials'); } const { client } = yield (0, connection_1.prepareCrowdinClient)({ config, credentials, autoRenew }); return { client, extra }; }), jwtMiddleware: (options) => { var _a, _b; return (0, crowdin_client_1.default)({ config, optional: (_a = options.optional) !== null && _a !== void 0 ? _a : false, checkSubscriptionExpiration: (_b = options.checkSubscriptionExpiration) !== null && _b !== void 0 ? _b : false, moduleKey: options.moduleKey, }); } }); } function addFormSchema({ app, config }) { const allModuleConfigs = []; for (const moduleKey of Object.keys(config)) { const value = config[moduleKey]; if (Array.isArray(value)) { allModuleConfigs.push(...value); } else { allModuleConfigs.push(value); } } for (const moduleConfig of allModuleConfigs) { if ((0, form_schema_1.hasFormSchema)(moduleConfig)) { const moduleConfigWithForm = (0, form_schema_1.getLowCodeUiConfigFromModuleConfig)(moduleConfig); if (moduleConfigWithForm) { if (!moduleConfigWithForm.formGetDataUrl) { app.get(`/api/${moduleConfigWithForm.key}/form-data`, json_response_1.default, (0, crowdin_client_1.default)({ config, optional: false, checkSubscriptionExpiration: true, moduleKey: moduleConfigWithForm.key, }), (0, credentials_masker_1.getRequestCredentialsMasker)({ moduleConfig: moduleConfigWithForm }), (0, form_data_display_1.default)(config)); } if (!moduleConfigWithForm.formPostDataUrl) { app.post(`/api/${moduleConfigWithForm.key}/form-data`, (0, crowdin_client_1.default)({ config, optional: false, checkSubscriptionExpiration: true, moduleKey: moduleConfigWithForm.key, }), (0, credentials_masker_1.postRequestCredentialsMasker)({ secret: config.clientSecret, moduleConfig: moduleConfigWithForm, }), (0, form_data_save_1.default)(config)); } } } } } function convertClientConfig(clientConfig) { const baseUrl = clientConfig.baseUrl || process.env.URL; const clientId = clientConfig.clientId || process.env.CROWDIN_CLIENT_ID; const clientSecret = clientConfig.clientSecret || process.env.CROWDIN_CLIENT_SECRET; const port = clientConfig.port || process.env.PORT || 3000; const { region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION, tmpBucketName = process.env.AWS_TMP_BUCKET_NAME, } = clientConfig.awsConfig || {}; if (clientConfig.authenticationType !== types_1.AuthenticationType.NONE) { if (!clientId && !clientSecret) { throw new Error('Missing [clientId, clientSecret] parameters'); } } if (clientConfig.projectIntegration) { clientConfig.api = Object.assign({ default: true }, clientConfig.api); } clientConfig.enableStatusPage = Object.assign({ database: true, filesystem: true }, (clientConfig.enableStatusPage || {})); return Object.assign(Object.assign({}, clientConfig), { baseUrl: baseUrl ? (baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl) : undefined, clientId, clientSecret, awsConfig: { tmpBucketName, region, }, port: Number(port), dbFolder: clientConfig.dbFolder || (0, path_1.join)(process.cwd(), 'db'), imagePath: clientConfig.imagePath || (0, path_1.join)(process.cwd(), 'logo.png') }); } function handleUncaughtErrors() { process .on('unhandledRejection', (reason) => { const error = `Unhandled Rejection. Reason: ${reason.stack || reason}`; (0, logger_1.logError)(error); }) .on('uncaughtException', (reason) => { const error = `Uncaught Exception. Reason: ${reason.stack || reason}`; (0, logger_1.logError)(error); }); }