@crowdin/app-project-module 0.15.4 → 0.16.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
@@ -186,7 +186,8 @@ configuration.pricing = {
186
186
  trial: 14, //amount of days to use app for free
187
187
  trialCrowdin: 14, //amount of days specifically in crowdin workspace
188
188
  trialEnterprise: 30, //amount of days specifically for enterprise
189
- cachingSeconds: 12 * 60 * 60 //time in seconds of how long to cache subscription info
189
+ cachingSeconds: 12 * 60 * 60, //time in seconds of how long to cache subscription info
190
+ infoDisplayDaysThreshold: 14 //number of days threshold to check if subscription info should be displayed (if not defined then info will be always visible)
190
191
  };
191
192
  ```
192
193
 
@@ -326,6 +327,9 @@ It is also possible to define settings window for your app where users can custo
326
327
  ```javascript
327
328
  configuration.projectIntegration.getConfiguration = (projectId, crowdinClient, integrationCredentials) => {
328
329
  return [
330
+ {
331
+ label: 'GENERAL'
332
+ },
329
333
  {
330
334
  key: 'flag',
331
335
  label: 'Checkbox',
@@ -350,6 +354,9 @@ configuration.projectIntegration.getConfiguration = (projectId, crowdinClient, i
350
354
  }
351
355
  ]
352
356
  }
357
+
358
+ //auto reload on settings updates
359
+ configuration.projectIntegration.reloadOnConfigSave = true;
353
360
  ```
354
361
 
355
362
  ## Info window
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const util_1 = require("../util");
13
+ const connection_1 = require("../util/connection");
13
14
  const defaults_1 = require("../util/defaults");
14
15
  function constructOauthUrl(config, integration) {
15
16
  var _a, _b, _c;
@@ -49,11 +50,13 @@ function handle(config, integration) {
49
50
  const configurationFields = yield integration.getConfiguration(req.crowdinContext.jwtPayload.context.project_id, req.crowdinApiClient, req.integrationCredentials);
50
51
  options.configurationFields = configurationFields;
51
52
  options.config = JSON.stringify(req.integrationSettings || {});
53
+ options.reloadOnConfigSave = !!integration.reloadOnConfigSave;
52
54
  (0, util_1.log)(`Adding configuration fields ${JSON.stringify(configurationFields, null, 2)}`, config.logger);
53
55
  }
54
56
  options.infoModal = integration.infoModal;
55
57
  options.withCronSync = integration.withCronSync;
56
58
  options.withWebhookSync = integration.withWebhookSync;
59
+ options.checkSubscription = !(0, connection_1.isAppFree)(config);
57
60
  (0, util_1.log)(`Routing user to ${view} view`, config.logger);
58
61
  return res.render(view, options);
59
62
  }), config.onError);
@@ -0,0 +1,3 @@
1
+ import { Request, Response } from 'express';
2
+ import { Config } from '../models';
3
+ export default function handle(config: Config): (req: Request, res: Response) => void;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ function handle(config) {
4
+ return (req, res) => {
5
+ var _a;
6
+ const subscriptionInfo = req.subscriptionInfo;
7
+ let showInfo = true;
8
+ if ((_a = config.pricing) === null || _a === void 0 ? void 0 : _a.infoDisplayDaysThreshold) {
9
+ showInfo = config.pricing.infoDisplayDaysThreshold >= ((subscriptionInfo === null || subscriptionInfo === void 0 ? void 0 : subscriptionInfo.daysLeft) || 0);
10
+ }
11
+ res.send(Object.assign(Object.assign({}, (subscriptionInfo || {})), { showInfo }));
12
+ };
13
+ }
14
+ exports.default = handle;
package/out/index.js CHANGED
@@ -52,6 +52,7 @@ 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
54
  const settings_save_1 = __importDefault(require("./handlers/settings-save"));
55
+ const subscription_info_1 = __importDefault(require("./handlers/subscription-info"));
55
56
  const sync_settings_1 = __importDefault(require("./handlers/sync-settings"));
56
57
  const sync_settings_save_1 = __importDefault(require("./handlers/sync-settings-save"));
57
58
  const uninstall_1 = __importDefault(require("./handlers/uninstall"));
@@ -97,6 +98,7 @@ function addCrowdinEndpoints(app, config) {
97
98
  (0, defaults_1.applyDefaults)(config, integrationLogic);
98
99
  app.get('/logo/integration/logo.png', (req, res) => res.sendFile(integrationLogic.imagePath || config.imagePath || (0, path_1.join)(__dirname, 'logo.png')));
99
100
  app.get('/', (0, crowdin_client_1.default)(config, true, false), (0, integration_credentials_1.default)(config, integrationLogic, true), (0, main_1.default)(config, integrationLogic));
101
+ app.get('/api/subscription-info', json_response_1.default, (0, crowdin_client_1.default)(config), (0, subscription_info_1.default)(config));
100
102
  app.post('/api/settings', (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, settings_save_1.default)(config));
101
103
  app.post('/api/login', (0, crowdin_client_1.default)(config, false, false), (0, integration_login_1.default)(config, integrationLogic));
102
104
  app.post('/api/logout', (0, crowdin_client_1.default)(config, false, false), (0, integration_logout_1.default)(config));
@@ -1,9 +1,10 @@
1
1
  /// <reference types="qs" />
2
2
  import Crowdin from '@crowdin/crowdin-api-client';
3
3
  import { Response } from 'express';
4
- import { Config, CrowdinContextInfo } from '../models';
4
+ import { Config, CrowdinContextInfo, SubscriptionInfo } from '../models';
5
5
  export declare function prepareCrowdinRequest(jwtToken: string, config: Config, optional?: boolean, checkSubscriptionExpiration?: boolean): Promise<{
6
6
  context: CrowdinContextInfo;
7
7
  client?: Crowdin;
8
+ subscriptionInfo?: SubscriptionInfo;
8
9
  }>;
9
10
  export default function handle(config: Config, optional?: boolean, checkSubscriptionExpiration?: boolean): (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;
@@ -44,13 +44,14 @@ function prepareCrowdinRequest(jwtToken, config, optional = false, checkSubscrip
44
44
  }
45
45
  (0, util_1.log)('Building crowdin client instance', config.logger);
46
46
  const { client, token } = yield (0, connection_1.prepareCrowdinClient)(config, credentials);
47
+ let subscriptionInfo;
47
48
  if (checkSubscriptionExpiration) {
48
- const { expired, subscribeLink } = yield (0, connection_1.checkSubscription)(config, token, credentials.id, credentials.type);
49
- if (expired) {
50
- throw new util_1.CodeError(subscribeLink || '', 402);
49
+ subscriptionInfo = yield (0, connection_1.checkSubscription)(config, token, credentials.id, credentials.type);
50
+ if (subscriptionInfo.expired) {
51
+ throw new util_1.CodeError(subscriptionInfo.subscribeLink || '', 402);
51
52
  }
52
53
  }
53
- return { context, client };
54
+ return { context, client, subscriptionInfo };
54
55
  });
55
56
  }
56
57
  exports.prepareCrowdinRequest = prepareCrowdinRequest;
@@ -66,6 +67,7 @@ function handle(config, optional = false, checkSubscriptionExpiration = true) {
66
67
  if (data.client) {
67
68
  req.crowdinApiClient = data.client;
68
69
  }
70
+ req.subscriptionInfo = data.subscriptionInfo;
69
71
  next();
70
72
  }
71
73
  catch (e) {
@@ -150,7 +150,11 @@ export interface IntegrationLogic {
150
150
  /**
151
151
  * function to define configuration(settings) modal for you app (by default app will not have any custom settings)
152
152
  */
153
- getConfiguration?: (projectId: number, client: Crowdin, apiCredentials: any) => Promise<ConfigurationField[]>;
153
+ getConfiguration?: (projectId: number, client: Crowdin, apiCredentials: any) => Promise<ConfigurationModalEntity[]>;
154
+ /**
155
+ * flag to turn on auto reload of the tree whenever user updates the configuration
156
+ */
157
+ reloadOnConfigSave?: boolean;
154
158
  /**
155
159
  * define info modal (help section) for you app (by default app will not have own info section)
156
160
  */
@@ -171,6 +175,7 @@ export interface IntegrationLogic {
171
175
  integration: boolean;
172
176
  };
173
177
  }
178
+ export declare type ConfigurationModalEntity = ConfigurationField | ConfigurationDelimeter;
174
179
  export interface ConfigurationField {
175
180
  key: string;
176
181
  label: string;
@@ -188,6 +193,9 @@ export interface ConfigurationField {
188
193
  value: string;
189
194
  }[];
190
195
  }
196
+ export interface ConfigurationDelimeter {
197
+ label: string;
198
+ }
191
199
  export interface LoginForm {
192
200
  fields: FormField[];
193
201
  }
@@ -314,6 +322,7 @@ export interface IntegrationRequest extends CrowdinClientRequest {
314
322
  export interface CrowdinClientRequest extends Request {
315
323
  crowdinApiClient: Crowdin;
316
324
  crowdinContext: CrowdinContextInfo;
325
+ subscriptionInfo?: SubscriptionInfo;
317
326
  }
318
327
  export interface CrowdinCredentials {
319
328
  id: string;
@@ -331,6 +340,11 @@ export interface CrowdinContextInfo {
331
340
  crowdinId: string;
332
341
  clientId: string;
333
342
  }
343
+ export interface SubscriptionInfo {
344
+ expired: boolean;
345
+ subscribeLink?: string;
346
+ daysLeft?: number;
347
+ }
334
348
  export interface IntegrationCredentials {
335
349
  id: string;
336
350
  credentials: any;
@@ -490,5 +504,6 @@ export interface Pricing {
490
504
  trialCrowdin?: number;
491
505
  trialEnterprise?: number;
492
506
  cachingSeconds?: number;
507
+ infoDisplayDaysThreshold?: number;
493
508
  }
494
509
  export {};
@@ -5,11 +5,9 @@
5
5
  }
6
6
 
7
7
  .center {
8
- text-align: center;
9
- min-height: calc(100vh - 64px);
10
8
  display: flex;
11
9
  align-items: center;
12
- justify-content: space-around;
10
+ justify-content: center;
13
11
  }
14
12
 
15
13
  .top {
@@ -41,3 +39,11 @@
41
39
  .login crowdin-h4 {
42
40
  margin: 8px 0 16px;
43
41
  }
42
+
43
+ .ml-1 {
44
+ margin-left: 8px;
45
+ }
46
+
47
+ .m-0 {
48
+ margin: 0;
49
+ }
@@ -1,12 +1,10 @@
1
1
  import Crowdin from '@crowdin/crowdin-api-client';
2
- import { AccountType, Config, CrowdinCredentials, IntegrationCredentials, IntegrationLogic } from '../models';
2
+ import { AccountType, Config, CrowdinCredentials, IntegrationCredentials, IntegrationLogic, SubscriptionInfo } from '../models';
3
3
  export declare function prepareCrowdinClient(config: Config, credentials: CrowdinCredentials): Promise<{
4
4
  client: Crowdin;
5
5
  token: string;
6
6
  }>;
7
7
  export declare function prepareIntegrationCredentials(config: Config, integration: IntegrationLogic, integrationCredentials: IntegrationCredentials): Promise<any>;
8
8
  export declare function clearCache(organization: string): void;
9
- export declare function checkSubscription(config: Config, token: string, organization: string, accountType: AccountType): Promise<{
10
- expired: boolean;
11
- subscribeLink?: string;
12
- }>;
9
+ export declare function isAppFree(config: Config): boolean;
10
+ export declare function checkSubscription(config: Config, token: string, organization: string, accountType: AccountType): Promise<SubscriptionInfo>;
@@ -31,7 +31,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
31
31
  return (mod && mod.__esModule) ? mod : { "default": mod };
32
32
  };
33
33
  Object.defineProperty(exports, "__esModule", { value: true });
34
- exports.checkSubscription = exports.clearCache = exports.prepareIntegrationCredentials = exports.prepareCrowdinClient = void 0;
34
+ exports.checkSubscription = exports.isAppFree = exports.clearCache = exports.prepareIntegrationCredentials = exports.prepareCrowdinClient = void 0;
35
35
  const crowdin_api_client_1 = __importDefault(require("@crowdin/crowdin-api-client"));
36
36
  const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions"));
37
37
  const axios_1 = __importDefault(require("axios"));
@@ -133,19 +133,29 @@ function clearCache(organization) {
133
133
  delete subscriptionCache[organization];
134
134
  }
135
135
  exports.clearCache = clearCache;
136
+ function isAppFree(config) {
137
+ return !config.pricing || config.pricing.planType === 'free';
138
+ }
139
+ exports.isAppFree = isAppFree;
140
+ function validateSubscription(date) {
141
+ const expired = date.getTime() < Date.now();
142
+ const daysLeft = Math.round((date.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
143
+ return { expired, daysLeft };
144
+ }
136
145
  function checkSubscription(config, token, organization, accountType) {
146
+ var _a, _b, _c, _d, _e, _f;
137
147
  return __awaiter(this, void 0, void 0, function* () {
138
- if (!config.pricing || config.pricing.planType === 'free') {
148
+ if (isAppFree(config)) {
139
149
  return { expired: false };
140
150
  }
141
151
  //default 2 weeks
142
152
  const defaultSubscriptionPlan = 14;
143
153
  let days;
144
154
  if (organization) {
145
- days = config.pricing.trialEnterprise || config.pricing.trial || defaultSubscriptionPlan;
155
+ days = ((_a = config.pricing) === null || _a === void 0 ? void 0 : _a.trialEnterprise) || ((_b = config.pricing) === null || _b === void 0 ? void 0 : _b.trial) || defaultSubscriptionPlan;
146
156
  }
147
157
  else {
148
- days = config.pricing.trialCrowdin || config.pricing.trial || defaultSubscriptionPlan;
158
+ days = ((_c = config.pricing) === null || _c === void 0 ? void 0 : _c.trialCrowdin) || ((_d = config.pricing) === null || _d === void 0 ? void 0 : _d.trial) || defaultSubscriptionPlan;
149
159
  }
150
160
  (0, _1.log)(`Checking subscription plan. Subscriptino plan ${days} days`, config.logger);
151
161
  const appIdentifier = config.identifier;
@@ -154,9 +164,9 @@ function checkSubscription(config, token, organization, accountType) {
154
164
  const { cacheValidUntil, validUntil, subscribeLink } = cacheEntry;
155
165
  if (cacheValidUntil.getTime() > Date.now()) {
156
166
  (0, _1.log)(`Loaded data from cache. Subscription is vali until ${validUntil.toISOString()}`, config.logger);
157
- const expired = new Date(validUntil).getTime() < Date.now();
167
+ const { expired, daysLeft } = validateSubscription(new Date(validUntil));
158
168
  (0, _1.log)(`expired ${expired}`, config.logger);
159
- return { expired, subscribeLink };
169
+ return { expired, subscribeLink, daysLeft };
160
170
  }
161
171
  }
162
172
  try {
@@ -166,10 +176,10 @@ function checkSubscription(config, token, organization, accountType) {
166
176
  token,
167
177
  });
168
178
  (0, _1.log)(`Recieved subscription info. ${JSON.stringify(subscription)}`, config.logger);
169
- const expired = new Date(subscription.expires).getTime() < Date.now();
179
+ const { expired, daysLeft } = validateSubscription(new Date(subscription.expires));
170
180
  (0, _1.log)(`expired ${expired}`, config.logger);
171
- addToCache(organization, appIdentifier, new Date(subscription.expires), config.pricing.cachingSeconds);
172
- return { expired };
181
+ addToCache(organization, appIdentifier, new Date(subscription.expires), (_e = config.pricing) === null || _e === void 0 ? void 0 : _e.cachingSeconds);
182
+ return { expired, daysLeft };
173
183
  }
174
184
  catch (e) {
175
185
  if (e instanceof crowdinAppFunctions.PaymentRequiredError) {
@@ -177,10 +187,10 @@ function checkSubscription(config, token, organization, accountType) {
177
187
  (0, _1.log)(`Recieved 402 payment error. initializedAt ${initializedAt}`, config.logger);
178
188
  const date = new Date(initializedAt);
179
189
  date.setDate(date.getDate() + days);
180
- const expired = date.getTime() < Date.now();
190
+ const { expired, daysLeft } = validateSubscription(date);
181
191
  (0, _1.log)(`expired ${expired}`, config.logger);
182
- addToCache(organization, appIdentifier, new Date(date), config.pricing.cachingSeconds, subscribeLink);
183
- return { expired, subscribeLink };
192
+ addToCache(organization, appIdentifier, new Date(date), (_f = config.pricing) === null || _f === void 0 ? void 0 : _f.cachingSeconds, subscribeLink);
193
+ return { expired, subscribeLink, daysLeft };
184
194
  }
185
195
  if (config.onError) {
186
196
  config.onError(e);
@@ -105,6 +105,9 @@ function applyDefaults(config, integration) {
105
105
  fields = yield getUserSettings(projectId, crowdinClient, integrationCredentials);
106
106
  }
107
107
  return [
108
+ {
109
+ label: 'Background synchronization',
110
+ },
108
111
  {
109
112
  key: 'schedule',
110
113
  label: 'Sync schedule',
@@ -4,7 +4,15 @@
4
4
 
5
5
  <body>
6
6
  <div class="i_w">
7
- <div class='top'>
7
+ <div class="top">
8
+ {{#if checkSubscription}}
9
+ <crowdin-alert id="subscription-info" no-icon="true" type="warning" style="display: none;">
10
+ <div class="center">
11
+ <p class="m-0"></p>
12
+ <crowdin-button class="ml-1" primary onclick="window.open(subscriptionLink,'_blank')">Subscribe</crowdin-button>
13
+ </div>
14
+ </crowdin-alert>
15
+ {{/if}}
8
16
  {{#if infoModal}}
9
17
  <crowdin-button icon-before="info" onclick="infoModal.open();">{{infoModal.title}}</crowdin-button>
10
18
  {{/if}}
@@ -59,50 +67,54 @@
59
67
  >
60
68
  <div id="modal-content">
61
69
  {{#each configurationFields}}
62
- {{#ifeq type "checkbox"}}
63
- <crowdin-checkbox
64
- use-switch
65
- label="{{label}}"
66
- value="false"
67
- id="{{key}}-settings"
68
- key="{{key}}"
69
- {{#if helpText}}
70
- help-text="{{helpText}}"
71
- {{/if}}
72
- >
73
- </crowdin-checkbox>
74
- {{/ifeq}}
75
- {{#ifeq type "select"}}
76
- <crowdin-select
77
- {{#if isMulti}}
78
- is-multi
79
- close-on-select="false"
80
- {{/if}}
81
- id="{{key}}-settings"
82
- key="{{key}}"
83
- label="{{label}}"
84
- {{#if helpText}}
85
- help-text="{{helpText}}"
86
- {{/if}}
87
- >
88
- {{#each options}}
89
- <option value="{{value}}">{{label}}</option>
90
- {{/each}}
91
- </crowdin-select>
92
- {{/ifeq}}
93
- {{#ifeq type "text"}}
94
- <crowdin-input
95
- with-fixed-height
70
+ {{#if key}}
71
+ {{#ifeq type "checkbox"}}
72
+ <crowdin-checkbox
73
+ use-switch
96
74
  label="{{label}}"
97
- value=""
75
+ value="false"
98
76
  id="{{key}}-settings"
99
77
  key="{{key}}"
100
78
  {{#if helpText}}
101
79
  help-text="{{helpText}}"
102
80
  {{/if}}
103
- >
104
- </crowdin-input>
105
- {{/ifeq}}
81
+ >
82
+ </crowdin-checkbox>
83
+ {{/ifeq}}
84
+ {{#ifeq type "select"}}
85
+ <crowdin-select
86
+ {{#if isMulti}}
87
+ is-multi
88
+ close-on-select="false"
89
+ {{/if}}
90
+ id="{{key}}-settings"
91
+ key="{{key}}"
92
+ label="{{label}}"
93
+ {{#if helpText}}
94
+ help-text="{{helpText}}"
95
+ {{/if}}
96
+ >
97
+ {{#each options}}
98
+ <option value="{{value}}">{{label}}</option>
99
+ {{/each}}
100
+ </crowdin-select>
101
+ {{/ifeq}}
102
+ {{#ifeq type "text"}}
103
+ <crowdin-input
104
+ with-fixed-height
105
+ label="{{label}}"
106
+ value=""
107
+ id="{{key}}-settings"
108
+ key="{{key}}"
109
+ {{#if helpText}}
110
+ help-text="{{helpText}}"
111
+ {{/if}}
112
+ >
113
+ </crowdin-input>
114
+ {{/ifeq}}
115
+ {{else}}
116
+ <crowdin-p>{{label}}</crowdin-p>
117
+ {{/if}}
106
118
  <div style="padding: 8px"></div>
107
119
  {{/each}}
108
120
  </div>
@@ -350,6 +362,10 @@
350
362
  .finally(() => {
351
363
  settingsSaveBtn.removeAttribute('disabled');
352
364
  settingsModal.close();
365
+ {{#if reloadOnConfigSave}}
366
+ getIntegrationData();
367
+ getCrowdinData();
368
+ {{/if}}
353
369
  });
354
370
  }
355
371
  {{/if}}
@@ -431,6 +447,25 @@
431
447
  .finally(() => (appComponent.setAttribute(`is-${provider}-loading`, false)));
432
448
  }
433
449
  {{/if}}
450
+
451
+ {{#if checkSubscription}}
452
+ const subscriptionInfo = document.getElementById('subscription-info');
453
+ const subscriptionInfoText = subscriptionInfo.getElementsByTagName('p')[0];
454
+ function getSubscriptionInfo() {
455
+ checkOrigin()
456
+ .then(restParams => fetch('api/subscription-info' + restParams))
457
+ .then(checkResponse)
458
+ .then((res) => {
459
+ if (res.showInfo) {
460
+ subscriptionLink = res.subscribeLink;
461
+ subscriptionInfoText.textContent = `Your trial expires in ${res.daysLeft} days.`
462
+ subscriptionInfo.style.display = 'block';
463
+ }
464
+ });
465
+ }
466
+
467
+ getSubscriptionInfo();
468
+ {{/if}}
434
469
  </script>
435
470
 
436
471
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.15.4",
3
+ "version": "0.16.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",