@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
JavaScript
"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);
});
}