@crowdin/app-project-module 0.59.0 → 0.60.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.
@@ -31,10 +31,13 @@ function handle(config, integration) {
31
31
  options.loginFields = (_a = integration.loginForm) === null || _a === void 0 ? void 0 : _a.fields;
32
32
  if (integration.oauthLogin) {
33
33
  options.loginFields = integration.oauthLogin.loginFields || [];
34
- options.oauthUrl = integration.oauthLogin.authorizationUrl
35
- ? (0, defaults_1.constructOauthUrl)(config, integration)
36
- : undefined;
34
+ options.oauthUrl = (0, defaults_1.constructOauthUrl)({
35
+ config,
36
+ integration,
37
+ clientId: req.crowdinContext.clientId,
38
+ });
37
39
  options.oauthLogin = true;
40
+ options.oauthMode = integration.oauthLogin.mode;
38
41
  }
39
42
  }
40
43
  else if (integration.getConfiguration) {
@@ -16,14 +16,21 @@ const axios_1 = __importDefault(require("axios"));
16
16
  const util_1 = require("../../../util");
17
17
  const defaults_1 = require("../util/defaults");
18
18
  const logger_1 = require("../../../util/logger");
19
+ const storage_1 = require("../../../storage");
19
20
  function handle(config, integration) {
20
21
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
21
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
22
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
22
23
  const message = {
23
24
  uid: 'oauth_popup',
24
25
  };
25
26
  const code = req.query[((_b = (_a = integration.oauthLogin) === null || _a === void 0 ? void 0 : _a.fieldsMapping) === null || _b === void 0 ? void 0 : _b.code) || 'code'];
27
+ const state = ((_c = integration.oauthLogin) === null || _c === void 0 ? void 0 : _c.mode) === 'polling'
28
+ ? req.query[((_e = (_d = integration.oauthLogin) === null || _d === void 0 ? void 0 : _d.fieldsMapping) === null || _e === void 0 ? void 0 : _e.state) || 'state']
29
+ : undefined;
26
30
  (0, logger_1.log)(`Received request from OAuth login callback. Code ${code}`);
31
+ if (state) {
32
+ (0, logger_1.log)(`Received request from OAuth login callback. State ${state}`);
33
+ }
27
34
  try {
28
35
  const oauthLogin = integration.oauthLogin;
29
36
  let credentials;
@@ -34,26 +41,29 @@ function handle(config, integration) {
34
41
  else {
35
42
  const request = {};
36
43
  const oauthLogin = integration.oauthLogin;
37
- request[((_c = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _c === void 0 ? void 0 : _c.code) || 'code'] = code;
38
- request[((_d = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _d === void 0 ? void 0 : _d.clientId) || 'client_id'] = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.clientId;
39
- request[((_e = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _e === void 0 ? void 0 : _e.clientSecret) || 'client_secret'] = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.clientSecret;
40
- request[((_f = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _f === void 0 ? void 0 : _f.redirectUri) || 'redirect_uri'] = `${config.baseUrl}${(0, defaults_1.getOauthRoute)(integration)}`;
44
+ request[((_f = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _f === void 0 ? void 0 : _f.code) || 'code'] = code;
45
+ request[((_g = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _g === void 0 ? void 0 : _g.clientId) || 'client_id'] = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.clientId;
46
+ request[((_h = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _h === void 0 ? void 0 : _h.clientSecret) || 'client_secret'] = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.clientSecret;
47
+ request[((_j = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _j === void 0 ? void 0 : _j.redirectUri) || 'redirect_uri'] = `${config.baseUrl}${(0, defaults_1.getOauthRoute)(integration)}`;
41
48
  if (oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.extraAccessTokenParameters) {
42
49
  Object.entries(oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.extraAccessTokenParameters).forEach(([key, value]) => (request[key] = value));
43
50
  }
44
- credentials = (yield axios_1.default.post(((_g = integration.oauthLogin) === null || _g === void 0 ? void 0 : _g.accessTokenUrl) || '', request, {
51
+ credentials = (yield axios_1.default.post(((_k = integration.oauthLogin) === null || _k === void 0 ? void 0 : _k.accessTokenUrl) || '', request, {
45
52
  headers: { Accept: 'application/json' },
46
53
  })).data;
47
54
  }
48
55
  const oauthCredentials = { originalUrl: req.originalUrl };
49
- oauthCredentials.accessToken = credentials[((_h = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _h === void 0 ? void 0 : _h.accessToken) || 'access_token'];
56
+ oauthCredentials.accessToken = credentials[((_l = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _l === void 0 ? void 0 : _l.accessToken) || 'access_token'];
50
57
  if (oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.refresh) {
51
- oauthCredentials.refreshToken = credentials[((_j = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _j === void 0 ? void 0 : _j.refreshToken) || 'refresh_token'];
58
+ oauthCredentials.refreshToken = credentials[((_m = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _m === void 0 ? void 0 : _m.refreshToken) || 'refresh_token'];
52
59
  oauthCredentials.expireIn =
53
- Number(credentials[((_k = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _k === void 0 ? void 0 : _k.expiresIn) || 'expires_in']) + Date.now() / 1000;
60
+ Number(credentials[((_o = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _o === void 0 ? void 0 : _o.expiresIn) || 'expires_in']) + Date.now() / 1000;
54
61
  }
55
62
  message.data = oauthCredentials;
56
- return res.render('oauth', { message: JSON.stringify(message) });
63
+ if (((_p = integration.oauthLogin) === null || _p === void 0 ? void 0 : _p.mode) === 'polling' && state) {
64
+ yield (0, storage_1.getStorage)().saveMetadata(state, oauthCredentials);
65
+ }
66
+ return res.render('oauth', { message: JSON.stringify(message), oauthMode: (_q = integration.oauthLogin) === null || _q === void 0 ? void 0 : _q.mode });
57
67
  }
58
68
  catch (e) {
59
69
  (0, logger_1.logError)(e);
@@ -0,0 +1,5 @@
1
+ /// <reference types="qs" />
2
+ import { Response } from 'express';
3
+ import { CrowdinClientRequest } from '../../../types';
4
+ import { IntegrationLogic } from '../types';
5
+ export default function handle(integration: IntegrationLogic): (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,32 @@
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 storage_1 = require("../../../storage");
13
+ const util_1 = require("../../../util");
14
+ const defaults_1 = require("../util/defaults");
15
+ function handle(integration) {
16
+ return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
17
+ req.logInfo('Received OAuth polling request');
18
+ const { oauthLogin } = integration;
19
+ if (!oauthLogin) {
20
+ req.logInfo('OAuth login url request is not supported');
21
+ res.status(400).end();
22
+ return;
23
+ }
24
+ const oauthId = (0, defaults_1.getOAuthPollingId)(req.crowdinContext.clientId);
25
+ const oauthCreds = yield (0, storage_1.getStorage)().getMetadata(oauthId);
26
+ if (oauthCreds) {
27
+ yield (0, storage_1.getStorage)().deleteMetadata(oauthId);
28
+ }
29
+ res.send({ oauthCreds });
30
+ }));
31
+ }
32
+ exports.default = handle;
@@ -21,9 +21,7 @@ function handle(config, integration) {
21
21
  return;
22
22
  }
23
23
  const { loginForm } = req.body;
24
- const url = oauthLogin.getAuthorizationUrl
25
- ? oauthLogin.getAuthorizationUrl(`${config.baseUrl}${(0, defaults_1.getOauthRoute)(integration)}`, loginForm)
26
- : (0, defaults_1.constructOauthUrl)(config, integration);
24
+ const url = (0, defaults_1.constructOauthUrl)({ config, integration, clientId: req.crowdinContext.clientId, loginForm });
27
25
  res.send({ url });
28
26
  }));
29
27
  }
@@ -49,6 +49,7 @@ const job_info_1 = __importDefault(require("./handlers/job-info"));
49
49
  const main_1 = __importDefault(require("./handlers/main"));
50
50
  const oauth_login_1 = __importDefault(require("./handlers/oauth-login"));
51
51
  const oauth_url_1 = __importDefault(require("./handlers/oauth-url"));
52
+ const oauth_polling_1 = __importDefault(require("./handlers/oauth-polling"));
52
53
  const settings_save_1 = __importDefault(require("./handlers/settings-save"));
53
54
  const subscription_info_1 = __importDefault(require("./handlers/subscription-info"));
54
55
  const sync_settings_1 = __importDefault(require("./handlers/sync-settings"));
@@ -81,6 +82,9 @@ function register({ config, app }) {
81
82
  if (integrationLogic.oauthLogin) {
82
83
  app.get((0, defaults_1.getOauthRoute)(integrationLogic), (0, oauth_login_1.default)(config, integrationLogic));
83
84
  app.post('/api/oauth-url', json_response_1.default, (0, crowdin_client_1.default)(config, false, false), (0, oauth_url_1.default)(config, integrationLogic));
85
+ if (integrationLogic.oauthLogin.mode === 'polling') {
86
+ app.post('/api/oauth-polling', json_response_1.default, (0, crowdin_client_1.default)(config, false, false), (0, oauth_polling_1.default)(integrationLogic));
87
+ }
84
88
  }
85
89
  if (integrationLogic.cronJobs) {
86
90
  integrationLogic.cronJobs.forEach((job) => {
@@ -179,6 +179,7 @@ export interface OAuthLogin {
179
179
  * Url to refresh token, default will use {@link accessTokenUrl}. Needed when {@link refresh} is enabled
180
180
  */
181
181
  refreshTokenUrl?: string;
182
+ mode?: 'standard' | 'polling';
182
183
  /**
183
184
  * The scopes of access, usually expressed as a list of space-delimited, case-sensitive strings
184
185
  */
@@ -231,6 +232,10 @@ export interface OAuthLogin {
231
232
  * default 'expires_in'
232
233
  */
233
234
  expiresIn?: string;
235
+ /**
236
+ * default 'state', used for `polling' mode
237
+ */
238
+ state?: string;
234
239
  };
235
240
  /**
236
241
  * default 'false' which means that the access token has no expiration date
@@ -4,4 +4,10 @@ import { IntegrationLogic } from '../types';
4
4
  export declare function getRootFolder(config: Config, integration: IntegrationLogic, client: Crowdin, projectId: number): Promise<SourceFilesModel.Directory | undefined>;
5
5
  export declare function getOauthRoute(integration: IntegrationLogic): string;
6
6
  export declare function applyIntegrationModuleDefaults(config: Config, integration: IntegrationLogic): void;
7
- export declare function constructOauthUrl(config: Config, integration: IntegrationLogic): string;
7
+ export declare function constructOauthUrl({ config, integration, clientId, loginForm, }: {
8
+ config: Config;
9
+ integration: IntegrationLogic;
10
+ clientId: string;
11
+ loginForm?: any;
12
+ }): string | undefined;
13
+ export declare function getOAuthPollingId(clientId: string): string;
@@ -32,7 +32,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
32
32
  });
33
33
  };
34
34
  Object.defineProperty(exports, "__esModule", { value: true });
35
- exports.constructOauthUrl = exports.applyIntegrationModuleDefaults = exports.getOauthRoute = exports.getRootFolder = void 0;
35
+ exports.getOAuthPollingId = exports.constructOauthUrl = exports.applyIntegrationModuleDefaults = exports.getOauthRoute = exports.getRootFolder = void 0;
36
36
  const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions"));
37
37
  function getRootFolder(config, integration, client, projectId) {
38
38
  return __awaiter(this, void 0, void 0, function* () {
@@ -257,18 +257,34 @@ function applyIntegrationModuleDefaults(config, integration) {
257
257
  }
258
258
  }
259
259
  exports.applyIntegrationModuleDefaults = applyIntegrationModuleDefaults;
260
- function constructOauthUrl(config, integration) {
261
- var _a, _b, _c;
260
+ function constructOauthUrl({ config, integration, clientId, loginForm, }) {
261
+ var _a, _b, _c, _d;
262
262
  const oauth = integration.oauthLogin;
263
- let url = (oauth === null || oauth === void 0 ? void 0 : oauth.authorizationUrl) || '';
264
- url += `?${((_a = oauth === null || oauth === void 0 ? void 0 : oauth.fieldsMapping) === null || _a === void 0 ? void 0 : _a.clientId) || 'client_id'}=${oauth === null || oauth === void 0 ? void 0 : oauth.clientId}`;
265
- url += `&${((_b = oauth === null || oauth === void 0 ? void 0 : oauth.fieldsMapping) === null || _b === void 0 ? void 0 : _b.redirectUri) || 'redirect_uri'}=${config.baseUrl}${getOauthRoute(integration)}`;
266
- if (oauth === null || oauth === void 0 ? void 0 : oauth.scope) {
267
- url += `&${((_c = oauth === null || oauth === void 0 ? void 0 : oauth.fieldsMapping) === null || _c === void 0 ? void 0 : _c.scope) || 'scope'}=${oauth === null || oauth === void 0 ? void 0 : oauth.scope}`;
263
+ if (!oauth) {
264
+ return;
268
265
  }
269
- if (oauth === null || oauth === void 0 ? void 0 : oauth.extraAutorizationUrlParameters) {
270
- Object.entries(oauth === null || oauth === void 0 ? void 0 : oauth.extraAutorizationUrlParameters).forEach(([key, value]) => (url += `&${key}=${value}`));
266
+ if (oauth.getAuthorizationUrl) {
267
+ return oauth.getAuthorizationUrl(`${config.baseUrl}${getOauthRoute(integration)}`, loginForm);
268
+ }
269
+ if (!oauth.authorizationUrl) {
270
+ return;
271
+ }
272
+ let url = oauth.authorizationUrl || '';
273
+ url += `?${((_a = oauth.fieldsMapping) === null || _a === void 0 ? void 0 : _a.clientId) || 'client_id'}=${oauth.clientId}`;
274
+ url += `&${((_b = oauth.fieldsMapping) === null || _b === void 0 ? void 0 : _b.redirectUri) || 'redirect_uri'}=${config.baseUrl}${getOauthRoute(integration)}`;
275
+ if (oauth.scope) {
276
+ url += `&${((_c = oauth.fieldsMapping) === null || _c === void 0 ? void 0 : _c.scope) || 'scope'}=${oauth.scope}`;
277
+ }
278
+ if (oauth.mode === 'polling') {
279
+ url += `&${((_d = oauth.fieldsMapping) === null || _d === void 0 ? void 0 : _d.state) || 'state'}=${getOAuthPollingId(clientId)}`;
280
+ }
281
+ if (oauth.extraAutorizationUrlParameters) {
282
+ Object.entries(oauth.extraAutorizationUrlParameters).forEach(([key, value]) => (url += `&${key}=${value}`));
271
283
  }
272
284
  return url;
273
285
  }
274
286
  exports.constructOauthUrl = constructOauthUrl;
287
+ function getOAuthPollingId(clientId) {
288
+ return `oauth_${clientId}`;
289
+ }
290
+ exports.getOAuthPollingId = getOAuthPollingId;
@@ -178,6 +178,7 @@
178
178
  </body>
179
179
  <script type="text/javascript">
180
180
  const loginButton = document.querySelector('#login-button');
181
+ let refreshIntervalId;
181
182
 
182
183
  function oauthLogin() {
183
184
  {{#if oauthUrl}}
@@ -209,7 +210,41 @@
209
210
  }
210
211
 
211
212
  function openOAuthPopup(url) {
213
+ {{#if oauthMode}}
214
+ const oauthMode = '{{{ oauthMode }}}';
215
+ {{else}}
216
+ const oauthMode = undefined;
217
+ {{/if}}
212
218
  const oauthWindow = window.open(url, '{{ name }}', 'location=0,status=0,width=800,height=400');
219
+
220
+ if (oauthMode === 'polling') {
221
+ if (refreshIntervalId) {
222
+ clearInterval(refreshIntervalId);
223
+ }
224
+ refreshIntervalId = setInterval(() =>
225
+ checkOrigin()
226
+ .then(queryParams =>
227
+ fetch(`api/oauth-polling${queryParams}`, {
228
+ method: 'POST',
229
+ })
230
+ )
231
+ .then(checkResponse)
232
+ .then(res => {
233
+ if (res.oauthCreds) {
234
+ integrationLogin({
235
+ refreshToken: res.oauthCreds.refreshToken,
236
+ accessToken: res.oauthCreds.accessToken,
237
+ expireIn: res.oauthCreds.expireIn,
238
+ timestamp: res.oauthCreds.timestamp
239
+ });
240
+ if (refreshIntervalId) {
241
+ clearInterval(refreshIntervalId);
242
+ }
243
+ }
244
+ }), 2000);
245
+ return;
246
+ }
247
+
213
248
  postPromises['oauth_popup'] = {
214
249
  resolve: (data) => {
215
250
  if (data.error) {
@@ -1,4 +1,11 @@
1
1
  <script type="text/javascript">
2
- window.opener.postMessage('{{{message}}}');
2
+ {{#if oauthMode}}
3
+ const oauthMode = '{{{ oauthMode }}}';
4
+ {{else}}
5
+ const oauthMode = undefined;
6
+ {{/if}}
7
+ if (oauthMode !== 'polling') {
8
+ window.opener.postMessage('{{{message}}}');
9
+ }
3
10
  window.close();
4
11
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.59.0",
3
+ "version": "0.60.0",
4
4
  "description": "Module that generates for you all common endpoints for serving standalone Crowdin App",
5
5
  "main": "out/index.js",
6
6
  "types": "out/index.d.ts",