@crowdin/app-project-module 0.20.10 → 0.21.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.
package/README.md CHANGED
@@ -340,6 +340,42 @@ configuration.projectIntegration.oauthLogin = {
340
340
  }
341
341
  ```
342
342
 
343
+ In addition you can specify extra fields on login screen that you can use to dynamically build authorization url.
344
+
345
+ ```javascript
346
+ configuration.projectIntegration.oauthLogin.loginFields = [
347
+ {
348
+ key: 'region',
349
+ label: 'Region',
350
+ type: 'select',
351
+ defaultValue: 'NA',
352
+ options: [
353
+ {
354
+ value: 'NA',
355
+ label: 'North American'
356
+ },
357
+ {
358
+ value: 'EU',
359
+ label: 'Europe'
360
+ },
361
+ {
362
+ value: 'AZURE_NA',
363
+ label: 'Azure North American'
364
+ }
365
+ ]
366
+ }
367
+ ];
368
+
369
+ configuration.projectIntegration.oauthLogin.getAuthorizationUrl = (redirectUrl, loginForm) => {
370
+ const region = loginForm.region;
371
+ if (region === 'EU') {
372
+ return `https://eu-region.com?client_id=<client-id>&redirect_uri=${redirectUrl}`;
373
+ } else {
374
+ return `https://default-region.com?client_id=<client-id>&redirect_uri=${redirectUrl}`;
375
+ }
376
+ };
377
+ ```
378
+
343
379
  Please refer to jsdoc for more details.
344
380
 
345
381
  ## Storage
@@ -40,7 +40,10 @@ function handleBuildFile(baseUrl, dataFolder, config, req, client, context, proj
40
40
  strings = req.strings;
41
41
  }
42
42
  else {
43
- strings = JSON.parse((yield axios_1.default.get(req.stringsUrl)).data);
43
+ // the response is presented in the ndjson format
44
+ const response = (yield axios_1.default.get(req.stringsUrl)).data;
45
+ const jsonRows = response.split(/\n|\n\r/).filter(Boolean);
46
+ strings = jsonRows.map((jsonStringRow) => JSON.parse(jsonStringRow));
44
47
  }
45
48
  let res;
46
49
  if ((req.file.id || !file) && config.exportStrings) {
@@ -12,20 +12,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const util_1 = require("../util");
13
13
  const connection_1 = require("../util/connection");
14
14
  const defaults_1 = require("../util/defaults");
15
- function constructOauthUrl(config, integration) {
16
- var _a, _b, _c;
17
- const oauth = integration.oauthLogin;
18
- let url = (oauth === null || oauth === void 0 ? void 0 : oauth.authorizationUrl) || '';
19
- 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}`;
20
- url += `&${((_b = oauth === null || oauth === void 0 ? void 0 : oauth.fieldsMapping) === null || _b === void 0 ? void 0 : _b.redirectUri) || 'redirect_uri'}=${config.baseUrl}${(0, defaults_1.getOauthRoute)(integration)}`;
21
- if (oauth === null || oauth === void 0 ? void 0 : oauth.scope) {
22
- 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}`;
23
- }
24
- if (oauth === null || oauth === void 0 ? void 0 : oauth.extraAutorizationUrlParameters) {
25
- Object.entries(oauth === null || oauth === void 0 ? void 0 : oauth.extraAutorizationUrlParameters).forEach(([key, value]) => (url += `&${key}=${value}`));
26
- }
27
- return url;
28
- }
29
15
  function handle(config, integration) {
30
16
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
31
17
  var _a;
@@ -42,8 +28,11 @@ function handle(config, integration) {
42
28
  view = 'login';
43
29
  options.loginFields = (_a = integration.loginForm) === null || _a === void 0 ? void 0 : _a.fields;
44
30
  if (integration.oauthLogin) {
45
- options.oauthUrl = constructOauthUrl(config, integration);
46
- (0, util_1.log)(`Adding oauth url ${options.oauthUrl}`, config.logger);
31
+ options.loginFields = integration.oauthLogin.loginFields || [];
32
+ options.oauthUrl = integration.oauthLogin.authorizationUrl
33
+ ? (0, defaults_1.constructOauthUrl)(config, integration)
34
+ : undefined;
35
+ options.oauthLogin = true;
47
36
  }
48
37
  }
49
38
  else if (integration.getConfiguration) {
@@ -44,7 +44,7 @@ function handle(config, integration) {
44
44
  headers: { Accept: 'application/json' },
45
45
  })).data;
46
46
  }
47
- const oauthCredentials = {};
47
+ const oauthCredentials = { originalUrl: req.originalUrl };
48
48
  oauthCredentials.accessToken = credentials[((_h = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _h === void 0 ? void 0 : _h.accessToken) || 'access_token'];
49
49
  if (oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.refresh) {
50
50
  oauthCredentials.refreshToken = credentials[((_j = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _j === void 0 ? void 0 : _j.refreshToken) || 'refresh_token'];
@@ -0,0 +1,4 @@
1
+ /// <reference types="qs" />
2
+ import { Response } from 'express';
3
+ import { Config, IntegrationLogic } from '../models';
4
+ export default function handle(config: Config, integration: IntegrationLogic): (req: 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,30 @@
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 util_1 = require("../util");
13
+ const defaults_1 = require("../util/defaults");
14
+ function handle(config, integration) {
15
+ return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
16
+ (0, util_1.log)('Recieved OAuth login url request', config.logger);
17
+ const { oauthLogin } = integration;
18
+ if (!oauthLogin) {
19
+ (0, util_1.log)('OAuth login url request is not supported', config.logger);
20
+ res.status(400).end();
21
+ return;
22
+ }
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);
27
+ res.send({ url });
28
+ }), config.onError);
29
+ }
30
+ exports.default = handle;
package/out/index.js CHANGED
@@ -51,6 +51,7 @@ const integration_update_1 = __importDefault(require("./handlers/integration-upd
51
51
  const main_1 = __importDefault(require("./handlers/main"));
52
52
  const manifest_1 = __importDefault(require("./handlers/manifest"));
53
53
  const oauth_login_1 = __importDefault(require("./handlers/oauth-login"));
54
+ const oauth_url_1 = __importDefault(require("./handlers/oauth-url"));
54
55
  const settings_save_1 = __importDefault(require("./handlers/settings-save"));
55
56
  const subscription_info_1 = __importDefault(require("./handlers/subscription-info"));
56
57
  const subscription_paid_1 = __importDefault(require("./handlers/subscription-paid"));
@@ -117,6 +118,7 @@ function addCrowdinEndpoints(app, config) {
117
118
  app.post('/api/sync-settings', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, sync_settings_save_1.default)(config));
118
119
  if (integrationLogic.oauthLogin) {
119
120
  app.get((0, defaults_1.getOauthRoute)(integrationLogic), (0, oauth_login_1.default)(config, integrationLogic));
121
+ app.post('/api/oauth-url', json_response_1.default, (0, crowdin_client_1.default)(config, false, false), (0, oauth_url_1.default)(config, integrationLogic));
120
122
  }
121
123
  if (integrationLogic.cronJobs) {
122
124
  integrationLogic.cronJobs.forEach(job => {
@@ -238,10 +238,18 @@ export interface LoginForm {
238
238
  fields: FormField[];
239
239
  }
240
240
  export interface OAuthLogin {
241
+ /**
242
+ * Extra field for login form
243
+ */
244
+ loginFields?: FormField[];
241
245
  /**
242
246
  * Authorization url (e.g. https://github.com/login/oauth/authorize or https://accounts.google.com/o/oauth2/v2/auth)
243
247
  */
244
- authorizationUrl: string;
248
+ authorizationUrl?: string;
249
+ /**
250
+ * Authorization url getter
251
+ */
252
+ getAuthorizationUrl?: (redirectUrl: string, loginForm?: any) => string;
245
253
  /**
246
254
  * Access token url (e.g. https://github.com/login/oauth/access_token)
247
255
  */
@@ -227,13 +227,13 @@ class MySQLStorage {
227
227
  saveMetadata(id, metadata) {
228
228
  return __awaiter(this, void 0, void 0, function* () {
229
229
  yield this.dbPromise;
230
- yield this.executeQuery(connection => connection.execute('INSERT INTO app_metadata(id, data) VALUES (?, ?)', [id, metadata]));
230
+ yield this.executeQuery(connection => connection.execute('INSERT INTO app_metadata(id, data) VALUES (?, ?)', [id, JSON.stringify(metadata)]));
231
231
  });
232
232
  }
233
233
  updateMetadata(id, metadata) {
234
234
  return __awaiter(this, void 0, void 0, function* () {
235
235
  yield this.dbPromise;
236
- yield this.executeQuery(connection => connection.execute('UPDATE app_metadata SET data = ? WHERE id = ?', [id, metadata]));
236
+ yield this.executeQuery(connection => connection.execute('UPDATE app_metadata SET data = ? WHERE id = ?', [id, JSON.stringify(metadata)]));
237
237
  });
238
238
  }
239
239
  getMetadata(id) {
@@ -223,13 +223,13 @@ class PostgreStorage {
223
223
  saveMetadata(id, metadata) {
224
224
  return __awaiter(this, void 0, void 0, function* () {
225
225
  yield this.dbPromise;
226
- yield this.executeQuery(client => client.query('INSERT INTO app_metadata(id, data) VALUES ($1, $2)', [id, metadata]));
226
+ yield this.executeQuery(client => client.query('INSERT INTO app_metadata(id, data) VALUES ($1, $2)', [id, JSON.stringify(metadata)]));
227
227
  });
228
228
  }
229
229
  updateMetadata(id, metadata) {
230
230
  return __awaiter(this, void 0, void 0, function* () {
231
231
  yield this.dbPromise;
232
- yield this.executeQuery(client => client.query('UPDATE app_metadata SET data = $1 WHERE id = $2', [id, metadata]));
232
+ yield this.executeQuery(client => client.query('UPDATE app_metadata SET data = $1 WHERE id = $2', [id, JSON.stringify(metadata)]));
233
233
  });
234
234
  }
235
235
  getMetadata(id) {
@@ -3,3 +3,4 @@ import { Config, IntegrationLogic } from '../models';
3
3
  export declare function getRootFolder(config: Config, integration: IntegrationLogic, client: Crowdin, projectId: number): Promise<SourceFilesModel.Directory | undefined>;
4
4
  export declare function getOauthRoute(integration: IntegrationLogic): string;
5
5
  export declare function applyDefaults(config: Config, integration: IntegrationLogic): void;
6
+ export declare function constructOauthUrl(config: Config, integration: IntegrationLogic): string;
@@ -28,7 +28,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
28
28
  });
29
29
  };
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
- exports.applyDefaults = exports.getOauthRoute = exports.getRootFolder = void 0;
31
+ exports.constructOauthUrl = exports.applyDefaults = exports.getOauthRoute = exports.getRootFolder = void 0;
32
32
  const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions"));
33
33
  function getRootFolder(config, integration, client, projectId) {
34
34
  return __awaiter(this, void 0, void 0, function* () {
@@ -151,3 +151,18 @@ function applyDefaults(config, integration) {
151
151
  }
152
152
  }
153
153
  exports.applyDefaults = applyDefaults;
154
+ function constructOauthUrl(config, integration) {
155
+ var _a, _b, _c;
156
+ const oauth = integration.oauthLogin;
157
+ let url = (oauth === null || oauth === void 0 ? void 0 : oauth.authorizationUrl) || '';
158
+ 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}`;
159
+ 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)}`;
160
+ if (oauth === null || oauth === void 0 ? void 0 : oauth.scope) {
161
+ 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}`;
162
+ }
163
+ if (oauth === null || oauth === void 0 ? void 0 : oauth.extraAutorizationUrlParameters) {
164
+ Object.entries(oauth === null || oauth === void 0 ? void 0 : oauth.extraAutorizationUrlParameters).forEach(([key, value]) => (url += `&${key}=${value}`));
165
+ }
166
+ return url;
167
+ }
168
+ exports.constructOauthUrl = constructOauthUrl;
@@ -8,70 +8,67 @@
8
8
  <crowdin-card is-shadowed is-padding-lg class="login">
9
9
  <img alt='{{ name }} logo' src='logo.png' />
10
10
  <crowdin-h4 id="integration-name">{{ name }}</crowdin-h4>
11
- {{#if oauthUrl}}
12
- {{else}}
13
- <div class="inputs">
14
- {{#each loginFields}}
15
- {{#ifeq type "checkbox"}}
16
- <crowdin-checkbox
11
+ <div class="inputs">
12
+ {{#each loginFields}}
13
+ {{#ifeq type "checkbox"}}
14
+ <crowdin-checkbox
15
+ id="{{key}}"
16
+ label="{{label}}"
17
+ value="false"
18
+ use-switch
19
+ {{#if helpText}}
20
+ help-text="{{helpText}}"
21
+ {{/if}}
22
+ {{#if helpTextHtml}}
23
+ help-text-html="{{helpTextHtml}}"
24
+ {{/if}}
25
+ {{#ifeq defaultValue true}}
26
+ checked="{{defaultValue}}"
27
+ {{/ifeq}}
28
+ >
29
+ </crowdin-checkbox>
30
+ {{else}}
31
+ {{#ifeq type "select"}}
32
+ <crowdin-select
33
+ {{#if isMulti}}
34
+ is-multi
35
+ close-on-select="false"
36
+ {{/if}}
17
37
  id="{{key}}"
18
38
  label="{{label}}"
19
- value="false"
20
- use-switch
21
39
  {{#if helpText}}
22
40
  help-text="{{helpText}}"
23
41
  {{/if}}
24
42
  {{#if helpTextHtml}}
25
43
  help-text-html="{{helpTextHtml}}"
26
44
  {{/if}}
27
- {{#ifeq defaultValue true}}
28
- checked="{{defaultValue}}"
29
- {{/ifeq}}
30
45
  >
31
- </crowdin-checkbox>
46
+ {{#each options}}
47
+ <option {{#ifeq ../defaultValue value}} selected {{/ifeq}} value="{{value}}">{{label}}</option>
48
+ {{/each}}
49
+ </crowdin-select>
32
50
  {{else}}
33
- {{#ifeq type "select"}}
34
- <crowdin-select
35
- {{#if isMulti}}
36
- is-multi
37
- close-on-select="false"
38
- {{/if}}
39
- id="{{key}}"
40
- label="{{label}}"
41
- {{#if helpText}}
42
- help-text="{{helpText}}"
43
- {{/if}}
44
- {{#if helpTextHtml}}
45
- help-text-html="{{helpTextHtml}}"
46
- {{/if}}
47
- >
48
- {{#each options}}
49
- <option {{#ifeq ../defaultValue value}} selected {{/ifeq}} value="{{value}}">{{label}}</option>
50
- {{/each}}
51
- </crowdin-select>
52
- {{else}}
53
- <crowdin-input
54
- id="{{key}}"
55
- label="{{label}}"
56
- {{#if helpText}}
57
- help-text="{{helpText}}"
58
- {{/if}}
59
- {{#if helpTextHtml}}
60
- help-text-html="{{helpTextHtml}}"
61
- {{/if}}
62
- {{#if type}}
63
- type="{{type}}"
64
- {{/if}}
65
- value="{{#if defaultValue}}{{defaultValue}}{{/if}}">
66
- </crowdin-input>
67
- {{/ifeq}}
51
+ <crowdin-input
52
+ id="{{key}}"
53
+ label="{{label}}"
54
+ {{#if helpText}}
55
+ help-text="{{helpText}}"
56
+ {{/if}}
57
+ {{#if helpTextHtml}}
58
+ help-text-html="{{helpTextHtml}}"
59
+ {{/if}}
60
+ {{#if type}}
61
+ type="{{type}}"
62
+ {{/if}}
63
+ value="{{#if defaultValue}}{{defaultValue}}{{/if}}">
64
+ </crowdin-input>
68
65
  {{/ifeq}}
66
+ {{/ifeq}}
69
67
  {{/each}}
70
68
  </div>
71
- {{/if}}
72
69
  <crowdin-button
73
70
  outlined icon-after="arrow_forward"
74
- {{#if oauthUrl}}
71
+ {{#if oauthLogin}}
75
72
  onclick="oauthLogin()"
76
73
  {{else}}
77
74
  onclick="integrationLogin()"
@@ -88,7 +85,30 @@
88
85
  const loginButton = document.querySelector('crowdin-button');
89
86
 
90
87
  function oauthLogin() {
88
+ {{#if oauthUrl}}
91
89
  const url = '{{{ oauthUrl }}}';
90
+ {{else}}
91
+ const url = undefined;
92
+ {{/if}}
93
+ if (url) {
94
+ openOAuthPopup(url);
95
+ return;
96
+ }
97
+ loginButton.setAttribute('disabled', true);
98
+ loginButton.setAttribute('is-loading', true);
99
+ checkOrigin()
100
+ .then(queryParams =>
101
+ fetch(`api/oauth-url${queryParams}`, {
102
+ method: 'POST',
103
+ headers: { 'Content-Type': 'application/json' },
104
+ body: JSON.stringify({ loginForm: getLoginForm() })
105
+ })
106
+ )
107
+ .then(checkResponse)
108
+ .then(res => openOAuthPopup(res.url));
109
+ }
110
+
111
+ function openOAuthPopup(url) {
92
112
  const oauthWindow = window.open(url, '{{ name }}', 'location=0,status=0,width=800,height=400');
93
113
  postPromises['oauth_popup'] = {
94
114
  resolve: (data) => {
@@ -107,15 +127,7 @@
107
127
  }
108
128
 
109
129
  function integrationLogin(oauthCredentials) {
110
- const credentials = oauthCredentials || {
111
- {{#each loginFields}}
112
- {{#ifeq type "checkbox"}}
113
- '{{key}}': !!document.querySelector('#{{key}}').checked,
114
- {{else}}
115
- '{{key}}': document.querySelector('#{{key}}').getAttribute('value'),
116
- {{/ifeq}}
117
- {{/each}}
118
- };
130
+ const credentials = oauthCredentials || getLoginForm();
119
131
  loginButton.setAttribute('disabled', true);
120
132
  loginButton.setAttribute('is-loading', true);
121
133
  checkOrigin()
@@ -134,6 +146,18 @@
134
146
  loginButton.setAttribute('is-loading', false);
135
147
  });
136
148
  }
149
+
150
+ function getLoginForm() {
151
+ return {
152
+ {{#each loginFields}}
153
+ {{#ifeq type "checkbox"}}
154
+ '{{key}}': !!document.querySelector('#{{key}}').checked,
155
+ {{else}}
156
+ '{{key}}': document.querySelector('#{{key}}').getAttribute('value'),
157
+ {{/ifeq}}
158
+ {{/each}}
159
+ };
160
+ }
137
161
  </script>
138
162
 
139
163
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.20.10",
3
+ "version": "0.21.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",