UNPKG

@crowdin/app-project-module

Version:

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

306 lines (305 loc) 16.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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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.addCrowdinEndpoints = exports.createApp = exports.metadataStore = exports.express = exports.UserPermissions = exports.Scope = exports.ProjectPermissions = exports.maskKey = exports.postRequestCredentialsMasker = exports.getRequestCredentialsMasker = void 0; 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 types_1 = require("./types"); const util_1 = require("./util"); const form_schema_1 = require("./util/form-schema"); const connection_1 = require("./util/connection"); const handlebars_1 = require("./util/handlebars"); 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; } }); //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 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 subscription_1 = require("./util/subscription"); var types_2 = require("./types"); Object.defineProperty(exports, "ProjectPermissions", { enumerable: true, get: function () { return types_2.ProjectPermissions; } }); Object.defineProperty(exports, "Scope", { enumerable: true, get: function () { return types_2.Scope; } }); Object.defineProperty(exports, "UserPermissions", { enumerable: true, get: function () { return types_2.UserPermissions; } }); exports.metadataStore = { getMetadata: (id) => { return storage.getStorage().getMetadata(id); }, saveMetadata: (id, metadata, crowdinId) => __awaiter(void 0, void 0, void 0, function* () { 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}`)); } exports.createApp = createApp; function addCrowdinEndpoints(app, clientConfig) { var _a, _b, _c; const config = convertClientConfig(clientConfig); if (!config.disableGlobalErrorHandling) { handleUncaughtErrors(); } storage.initialize(config); logger.initialize(config); 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', terminus_express_1.default.static((0, path_1.join)(__dirname, 'static'))); app.set('views', (0, path_1.join)(__dirname, 'views')); app.engine('handlebars', handlebars_1.engine); app.set('view engine', 'handlebars'); app.get((0, util_1.getLogoUrl)(), (req, res) => res.sendFile(config.imagePath)); 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, 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 }); contextMenuApp.register({ config, app }); //other apps only work in authorized context if (!(0, util_1.isAuthorizedConfig)(config)) { return Object.assign({}, exports.metadataStore); } 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 }); 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 }); addFormSchema({ config, app }); return Object.assign(Object.assign({}, exports.metadataStore), { storage: storage.getStorage(), establishCrowdinConnection: (authRequest, moduleKey) => { let jwtToken = ''; if (typeof authRequest === 'string') { jwtToken = authRequest; } else { jwtToken = (0, crowdin_client_1.getToken)(authRequest); } return (0, crowdin_client_1.prepareCrowdinRequest)({ jwtToken, config, optional: false, checkSubscriptionExpiration: false, moduleKey, }); }, encryptCrowdinConnection: (data) => (0, util_1.encryptData)(config, JSON.stringify(data)), dencryptCrowdinConnection: (hash, autoRenew) => __awaiter(this, void 0, void 0, function* () { 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 }; }) }); } exports.addCrowdinEndpoints = addCrowdinEndpoints; function addFormSchema({ app, config }) { for (const moduleKey of Object.keys(config)) { const moduleConfig = config[moduleKey]; if ((0, form_schema_1.hasFormSchema)(moduleConfig)) { const moduleConfigWithForm = (0, form_schema_1.getLowCodeUiConfigFromModuleConfig)(moduleConfig); if (moduleConfigWithForm) { 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)); 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)(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 (!baseUrl) { throw new Error('Missing baseUrl parameter'); } 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.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl, 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); }); }