@crowdin/app-project-module
Version:
Module that generates for you all common endpoints for serving standalone Crowdin App
345 lines (344 loc) • 18.9 kB
JavaScript
;
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.prepareIntegrationCredentials = exports.prepareCrowdinClient = void 0;
const crowdin_api_client_1 = __importDefault(require("@crowdin/crowdin-api-client"));
const crowdin_apps_functions_1 = require("@crowdin/crowdin-apps-functions");
const axios_1 = __importDefault(require("axios"));
const _1 = require(".");
const storage_1 = require("../storage");
const types_1 = require("../types");
const axios_2 = require("./axios");
const logger_1 = require("./logger");
const os = __importStar(require("os"));
const defaults_1 = require("../modules/integration/util/defaults");
const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions"));
const axiosCustom = new axios_2.AxiosProvider().axios;
function prepareCrowdinClient({ config, credentials, autoRenew = false, context, }) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
let userAgent;
if (config.crowdinApiUserAgent) {
userAgent = config.crowdinApiUserAgent;
}
else {
userAgent = `app/${config.name} node/v${process.versions.node} ${os.type()}/${os.platform()}/${os.release()}`;
}
//2 min as an extra buffer
const isExpired = +credentials.expire - 120 < Date.now() / 1000;
const organization = credentials.type === types_1.AccountType.ENTERPRISE ? credentials.id : undefined;
let token;
let crowdinCreds;
if (!isExpired) {
token = (0, _1.decryptData)(config, credentials.accessToken);
crowdinCreds = credentials;
}
else {
const res = yield refreshCrowdinCreds({ config, credentials });
token = res.token;
crowdinCreds = res.credentials;
}
if (!autoRenew) {
return {
client: new crowdin_api_client_1.default({ token, organization, baseUrl: (_a = config.crowdinUrls) === null || _a === void 0 ? void 0 : _a.apiUrl }, { userAgent }),
token,
};
}
const getHttpConfig = (config) => {
if (!config) {
return;
}
const headers = Object.assign(Object.assign({}, config.headers), { Authorization: `Bearer ${token}` });
if (userAgent) {
headers['User-Agent'] = userAgent;
}
return { headers };
};
const renewToken = () => __awaiter(this, void 0, void 0, function* () {
const res = yield refreshCrowdinCreds({ config, credentials: crowdinCreds });
token = res.token;
crowdinCreds = res.credentials;
});
const isExpiredCause = (error) => {
var _a, _b;
const errorObj = (_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error;
return (errorObj === null || errorObj === void 0 ? void 0 : errorObj.code) === 401 && (errorObj === null || errorObj === void 0 ? void 0 : errorObj.message) === 'Unauthorized';
};
//providing custom http client to handle expirations and renew token on the fly
//needed for long running jobs with single Crowdin client instance
return {
client: new crowdin_api_client_1.default({
token,
organization,
baseUrl: (_b = config.crowdinUrls) === null || _b === void 0 ? void 0 : _b.apiUrl,
}, {
httpClient: {
get(url, httpConfig) {
return __awaiter(this, void 0, void 0, function* () {
try {
const res = yield axiosCustom.get(url, getHttpConfig(httpConfig));
return res.data;
}
catch (e) {
if (isExpiredCause(e)) {
yield renewToken();
const res = yield axiosCustom.get(url, getHttpConfig(httpConfig));
return res.data;
}
(0, logger_1.logError)(e, context);
throw e;
}
});
},
delete(url, httpConfig) {
return __awaiter(this, void 0, void 0, function* () {
try {
const res = yield axiosCustom.delete(url, getHttpConfig(httpConfig));
return res.data;
}
catch (e) {
if (isExpiredCause(e)) {
yield renewToken();
const res = yield axiosCustom.delete(url, getHttpConfig(httpConfig));
return res.data;
}
(0, logger_1.logError)(e, context);
throw e;
}
});
},
head(url, httpConfig) {
return __awaiter(this, void 0, void 0, function* () {
try {
const res = yield axiosCustom.head(url, getHttpConfig(httpConfig));
return res.data;
}
catch (e) {
if (isExpiredCause(e)) {
yield renewToken();
const res = yield axiosCustom.head(url, getHttpConfig(httpConfig));
return res.data;
}
(0, logger_1.logError)(e, context);
throw e;
}
});
},
patch(url, data, httpConfig) {
return __awaiter(this, void 0, void 0, function* () {
try {
const res = yield axiosCustom.patch(url, data, getHttpConfig(httpConfig));
return res.data;
}
catch (e) {
if (isExpiredCause(e)) {
yield renewToken();
const res = yield axiosCustom.patch(url, data, getHttpConfig(httpConfig));
return res.data;
}
(0, logger_1.logError)(e, context);
throw e;
}
});
},
post(url, data, httpConfig) {
return __awaiter(this, void 0, void 0, function* () {
try {
const res = yield axiosCustom.post(url, data, getHttpConfig(httpConfig));
return res.data;
}
catch (e) {
if (isExpiredCause(e)) {
yield renewToken();
const res = yield axiosCustom.post(url, data, getHttpConfig(httpConfig));
return res.data;
}
(0, logger_1.logError)(e, context);
throw e;
}
});
},
put(url, data, httpConfig) {
return __awaiter(this, void 0, void 0, function* () {
try {
const res = yield axiosCustom.put(url, data, getHttpConfig(httpConfig));
return res.data;
}
catch (e) {
if (isExpiredCause(e)) {
yield renewToken();
const res = yield axiosCustom.put(url, data, getHttpConfig(httpConfig));
return res.data;
}
(0, logger_1.logError)(e, context);
throw e;
}
});
},
},
userAgent,
}),
token,
};
});
}
exports.prepareCrowdinClient = prepareCrowdinClient;
function prepareIntegrationCredentials(config, integration, integrationCredentials) {
var _a, _b, _c, _d, _e, _f, _g;
return __awaiter(this, void 0, void 0, function* () {
const credentials = JSON.parse((0, _1.decryptData)(config, integrationCredentials.credentials));
credentials.ownerId = crowdinAppFunctions.parseCrowdinId(integrationCredentials.id).userId;
const oauthLogin = integration.oauthLogin;
const integrationLogin = integration.loginForm;
if ((oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.refresh) || (integrationLogin === null || integrationLogin === void 0 ? void 0 : integrationLogin.refresh)) {
(0, logger_1.log)('Checking if integration credentials need to be refreshed');
const { expireIn } = credentials;
//2 min as an extra buffer
const isExpired = !expireIn || expireIn - 120 < Date.now() / 1000;
const performRefreshTokenRequest = (oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.performRefreshTokenRequest) || (integrationLogin === null || integrationLogin === void 0 ? void 0 : integrationLogin.performRefreshTokenRequest);
if (isExpired) {
(0, logger_1.log)('Integration credentials have expired. Requesting a new credentials');
let newCredentials;
if (performRefreshTokenRequest) {
const loginForm = yield (0, storage_1.getStorage)().getMetadata((0, defaults_1.getOAuthLoginFormId)(integrationCredentials.id));
newCredentials = yield performRefreshTokenRequest(credentials, loginForm);
}
else if (oauthLogin) {
const url = oauthLogin.refreshTokenUrl || oauthLogin.accessTokenUrl;
const request = {};
request[((_a = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _a === void 0 ? void 0 : _a.clientId) || 'client_id'] = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.clientId;
request[((_b = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _b === void 0 ? void 0 : _b.clientSecret) || 'client_secret'] = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.clientSecret;
request[((_c = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _c === void 0 ? void 0 : _c.refreshToken) || 'refresh_token'] = credentials.refreshToken;
if (oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.extraRefreshTokenParameters) {
Object.entries(oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.extraRefreshTokenParameters).forEach(([key, value]) => (request[key] = value));
}
newCredentials = (yield axios_1.default.post(url || '', request, {
headers: { Accept: 'application/json' },
})).data;
}
else {
return credentials;
}
credentials.accessToken = newCredentials[((_d = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _d === void 0 ? void 0 : _d.accessToken) || 'access_token'];
credentials.expireIn =
Number(newCredentials[((_e = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _e === void 0 ? void 0 : _e.expiresIn) || 'expires_in']) + Date.now() / 1000;
if (newCredentials[((_f = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _f === void 0 ? void 0 : _f.refreshToken) || 'refresh_token']) {
credentials.refreshToken = newCredentials[((_g = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _g === void 0 ? void 0 : _g.refreshToken) || 'refresh_token'];
}
(0, logger_1.log)('Saving updated integration credentials in the database');
yield (0, storage_1.getStorage)().updateIntegrationCredentials(integrationCredentials.id, (0, _1.encryptData)(config, JSON.stringify(credentials)));
}
}
return credentials;
});
}
exports.prepareIntegrationCredentials = prepareIntegrationCredentials;
function refreshCrowdinCreds({ config, credentials }) {
return __awaiter(this, void 0, void 0, function* () {
(0, logger_1.log)('Crowdin credentials have expired. Requesting a new credentials');
const newCredentials = yield refreshToken(config, credentials);
(0, logger_1.log)('Saving updated crowdin credentials in the database');
const newCrowdinCredentials = {
id: credentials.id,
appSecret: credentials.appSecret,
domain: credentials.domain,
userId: credentials.userId,
agentId: credentials.agentId,
organizationId: credentials.organizationId,
baseUrl: credentials.baseUrl,
refreshToken: newCredentials.refreshToken,
accessToken: newCredentials.accessToken,
expire: (new Date().getTime() / 1000 + newCredentials.expiresIn).toString(),
type: credentials.type,
};
yield (0, storage_1.getStorage)().updateCrowdinCredentials(newCrowdinCredentials);
const token = (0, _1.decryptData)(config, newCredentials.accessToken);
return {
credentials: newCrowdinCredentials,
token,
};
});
}
function refreshToken(config, credentials) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
if (config.authenticationType === types_1.AuthenticationType.CODE) {
const token = yield (0, crowdin_apps_functions_1.refreshOAuthToken)({
clientId: config.clientId,
clientSecret: config.clientSecret,
refreshToken: (0, _1.decryptData)(config, credentials.refreshToken),
url: (_a = config.crowdinUrls) === null || _a === void 0 ? void 0 : _a.accountUrl,
});
return {
accessToken: (0, _1.encryptData)(config, token.accessToken),
refreshToken: (0, _1.encryptData)(config, token.refreshToken),
expiresIn: token.expiresIn,
};
}
if (config.authenticationType === types_1.AuthenticationType.AGENT) {
const token = yield (0, crowdin_apps_functions_1.fetchAgentToken)({
appId: config.identifier,
appSecret: credentials.appSecret,
clientId: config.clientId,
clientSecret: config.clientSecret,
domain: credentials.domain || '',
userId: credentials.userId,
agentId: credentials.agentId,
url: (_b = config.crowdinUrls) === null || _b === void 0 ? void 0 : _b.accountUrl,
});
return {
accessToken: (0, _1.encryptData)(config, token.accessToken),
expiresIn: token.expiresIn,
refreshToken: '',
};
}
const token = yield (0, crowdin_apps_functions_1.fetchAppToken)({
appId: config.identifier,
appSecret: credentials.appSecret,
clientId: config.clientId,
clientSecret: config.clientSecret,
domain: credentials.domain || '',
userId: credentials.userId,
url: (_c = config.crowdinUrls) === null || _c === void 0 ? void 0 : _c.accountUrl,
});
return {
accessToken: (0, _1.encryptData)(config, token.accessToken),
expiresIn: token.expiresIn,
refreshToken: '',
};
});
}