@crowdin/app-project-module 1.3.1 → 1.4.1
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/out/middlewares/integration-credentials.d.ts +6 -1
- package/out/middlewares/integration-credentials.js +39 -2
- package/out/middlewares/render-ui-module.js +9 -2
- package/out/modules/api/api.js +13 -13
- package/out/modules/integration/handlers/integration-logout.js +2 -1
- package/out/modules/integration/index.js +13 -13
- package/out/modules/integration/types.d.ts +1 -0
- package/out/modules/integration/util/cron.js +12 -4
- package/out/modules/integration/util/webhooks.js +6 -3
- package/out/modules/manifest.js +3 -1
- package/out/modules/workflow-step-type/index.js +1 -1
- package/out/static/ui/error.bundle.js +2 -2
- package/out/static/ui/error.bundle.js.map +1 -1
- package/out/types.d.ts +4 -0
- package/out/util/connection.js +55 -20
- package/out/util/index.d.ts +1 -1
- package/out/util/index.js +56 -3
- package/out/views/AboutPage.js +14 -2
- package/package.json +1 -1
package/out/types.d.ts
CHANGED
|
@@ -524,6 +524,10 @@ export interface ImagePath {
|
|
|
524
524
|
* path to app logo (e.g. {@example join(__dirname, 'logo.png')})
|
|
525
525
|
*/
|
|
526
526
|
imagePath?: string;
|
|
527
|
+
/**
|
|
528
|
+
* URL to app logo that can be used instead of hosting it in the app (e.g. 'https://example.com/logo.png')
|
|
529
|
+
*/
|
|
530
|
+
imageUrl?: string;
|
|
527
531
|
}
|
|
528
532
|
export interface Logger {
|
|
529
533
|
enabled: boolean;
|
package/out/util/connection.js
CHANGED
|
@@ -270,7 +270,7 @@ function prepareIntegrationCredentials(config, integration, integrationCredentia
|
|
|
270
270
|
const performRefreshTokenRequest = (oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.performRefreshTokenRequest) || (integrationLogin === null || integrationLogin === void 0 ? void 0 : integrationLogin.performRefreshTokenRequest);
|
|
271
271
|
credentials.tokenProvider = {
|
|
272
272
|
getToken: (...args_1) => __awaiter(this, [...args_1], void 0, function* (force = false) {
|
|
273
|
-
var _a, _b, _c, _d
|
|
273
|
+
var _a, _b, _c, _d;
|
|
274
274
|
const { expireIn } = credentials;
|
|
275
275
|
//2 min as an extra buffer
|
|
276
276
|
const isExpired = !expireIn || expireIn - 120 < Date.now() / 1000;
|
|
@@ -280,33 +280,68 @@ function prepareIntegrationCredentials(config, integration, integrationCredentia
|
|
|
280
280
|
(0, logger_1.log)(force
|
|
281
281
|
? 'Force refreshing integration credentials'
|
|
282
282
|
: 'Integration credentials have expired during operation. Requesting a new credentials');
|
|
283
|
+
const doRefresh = (currentCredentials) => __awaiter(this, void 0, void 0, function* () {
|
|
284
|
+
var _a, _b, _c;
|
|
285
|
+
if (performRefreshTokenRequest) {
|
|
286
|
+
const loginForm = yield (0, storage_1.getStorage)().getMetadata((0, defaults_1.getOAuthLoginFormId)(integrationCredentials.id));
|
|
287
|
+
return performRefreshTokenRequest({ credentials: currentCredentials, loginForm });
|
|
288
|
+
}
|
|
289
|
+
else if (oauthLogin) {
|
|
290
|
+
const url = oauthLogin.refreshTokenUrl || oauthLogin.accessTokenUrl;
|
|
291
|
+
const request = {};
|
|
292
|
+
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;
|
|
293
|
+
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;
|
|
294
|
+
request[((_c = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _c === void 0 ? void 0 : _c.refreshToken) || 'refresh_token'] =
|
|
295
|
+
currentCredentials.refreshToken;
|
|
296
|
+
if (oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.extraRefreshTokenParameters) {
|
|
297
|
+
Object.entries(oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.extraRefreshTokenParameters).forEach(([key, value]) => (request[key] = value));
|
|
298
|
+
}
|
|
299
|
+
return (yield axios_1.default.post(url || '', request, {
|
|
300
|
+
headers: { Accept: 'application/json' },
|
|
301
|
+
})).data;
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
});
|
|
283
305
|
let newCredentials;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
newCredentials = yield performRefreshTokenRequest({ credentials, loginForm });
|
|
306
|
+
try {
|
|
307
|
+
newCredentials = yield doRefresh(credentials);
|
|
287
308
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
309
|
+
catch (e) {
|
|
310
|
+
// In-memory refresh token may be stale (rotated by another process/worker,
|
|
311
|
+
// or the token itself expired). Try fetching fresh credentials from DB.
|
|
312
|
+
(0, logger_1.log)('Token refresh failed. Attempting to fetch fresh credentials from the database');
|
|
313
|
+
const freshStoredCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(integrationCredentials.id);
|
|
314
|
+
if (freshStoredCredentials) {
|
|
315
|
+
const freshCredentials = JSON.parse((0, _1.decryptData)(config, freshStoredCredentials.credentials));
|
|
316
|
+
if (freshCredentials.refreshToken &&
|
|
317
|
+
freshCredentials.refreshToken !== credentials.refreshToken) {
|
|
318
|
+
(0, logger_1.log)('Found a newer refresh token in the database, retrying token refresh');
|
|
319
|
+
try {
|
|
320
|
+
newCredentials = yield doRefresh(freshCredentials);
|
|
321
|
+
credentials.refreshToken = freshCredentials.refreshToken;
|
|
322
|
+
}
|
|
323
|
+
catch (retryError) {
|
|
324
|
+
(0, logger_1.logError)(retryError);
|
|
325
|
+
throw retryError;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
throw e;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
throw e;
|
|
296
334
|
}
|
|
297
|
-
newCredentials = (yield axios_1.default.post(url || '', request, {
|
|
298
|
-
headers: { Accept: 'application/json' },
|
|
299
|
-
})).data;
|
|
300
335
|
}
|
|
301
|
-
|
|
336
|
+
if (!newCredentials) {
|
|
302
337
|
return credentials.accessToken;
|
|
303
338
|
}
|
|
304
|
-
credentials.accessToken = newCredentials[((
|
|
339
|
+
credentials.accessToken = newCredentials[((_a = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _a === void 0 ? void 0 : _a.accessToken) || 'access_token'];
|
|
305
340
|
credentials.expireIn =
|
|
306
|
-
Number(newCredentials[((
|
|
307
|
-
if (newCredentials[((
|
|
341
|
+
Number(newCredentials[((_b = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _b === void 0 ? void 0 : _b.expiresIn) || 'expires_in']) + Date.now() / 1000;
|
|
342
|
+
if (newCredentials[((_c = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _c === void 0 ? void 0 : _c.refreshToken) || 'refresh_token']) {
|
|
308
343
|
credentials.refreshToken =
|
|
309
|
-
newCredentials[((
|
|
344
|
+
newCredentials[((_d = oauthLogin === null || oauthLogin === void 0 ? void 0 : oauthLogin.fieldsMapping) === null || _d === void 0 ? void 0 : _d.refreshToken) || 'refresh_token'];
|
|
310
345
|
}
|
|
311
346
|
(0, logger_1.log)('Saving updated integration credentials in the database');
|
|
312
347
|
yield (0, storage_1.getStorage)().updateIntegrationCredentials(integrationCredentials.id, (0, _1.encryptData)(config, JSON.stringify(credentials)));
|
package/out/util/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare function getLogoUrl(config: Config | UnauthorizedConfig, moduleCo
|
|
|
14
14
|
* Logo middleware with backwards compatibility
|
|
15
15
|
* Serves both /logo.png (backwards-compatible) and actual file name
|
|
16
16
|
* @param config - App configuration (required for Cloudflare Workers Assets support)
|
|
17
|
-
* @param moduleConfig - Module configuration with imagePath (optional, falls back to config.imagePath)
|
|
17
|
+
* @param moduleConfig - Module configuration with imagePath or imageUrl (optional, falls back to config.imagePath)
|
|
18
18
|
* @returns Express middleware
|
|
19
19
|
*/
|
|
20
20
|
export declare function serveLogo(config: Config | UnauthorizedConfig, moduleConfig?: ImagePath): (req: Request, res: Response, next: Function) => Promise<void>;
|
package/out/util/index.js
CHANGED
|
@@ -177,8 +177,30 @@ function executeWithRetry(func_1) {
|
|
|
177
177
|
});
|
|
178
178
|
}
|
|
179
179
|
function getLogoUrl(config, moduleConfig, modulePath) {
|
|
180
|
+
if (moduleConfig) {
|
|
181
|
+
if (moduleConfig.imageUrl) {
|
|
182
|
+
if (modulePath) {
|
|
183
|
+
return `/logo${modulePath}/logo.png`;
|
|
184
|
+
}
|
|
185
|
+
return '/logo.png';
|
|
186
|
+
}
|
|
187
|
+
else if (moduleConfig.imagePath) {
|
|
188
|
+
const imagePath = moduleConfig.imagePath;
|
|
189
|
+
const fileName = basename(imagePath);
|
|
190
|
+
if (!modulePath) {
|
|
191
|
+
return `/${fileName}`;
|
|
192
|
+
}
|
|
193
|
+
return `/logo${modulePath}/${fileName}`;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (config.imageUrl) {
|
|
197
|
+
if (modulePath) {
|
|
198
|
+
return `/logo${modulePath}/logo.png`;
|
|
199
|
+
}
|
|
200
|
+
return '/logo.png';
|
|
201
|
+
}
|
|
180
202
|
// Extract file name from imagePath with fallback to config.imagePath
|
|
181
|
-
const imagePath =
|
|
203
|
+
const imagePath = config.imagePath;
|
|
182
204
|
const fileName = basename(imagePath);
|
|
183
205
|
if (!modulePath) {
|
|
184
206
|
return `/${fileName}`;
|
|
@@ -189,11 +211,42 @@ function getLogoUrl(config, moduleConfig, modulePath) {
|
|
|
189
211
|
* Logo middleware with backwards compatibility
|
|
190
212
|
* Serves both /logo.png (backwards-compatible) and actual file name
|
|
191
213
|
* @param config - App configuration (required for Cloudflare Workers Assets support)
|
|
192
|
-
* @param moduleConfig - Module configuration with imagePath (optional, falls back to config.imagePath)
|
|
214
|
+
* @param moduleConfig - Module configuration with imagePath or imageUrl (optional, falls back to config.imagePath)
|
|
193
215
|
* @returns Express middleware
|
|
194
216
|
*/
|
|
195
217
|
function serveLogo(config, moduleConfig) {
|
|
196
|
-
|
|
218
|
+
if (moduleConfig) {
|
|
219
|
+
if (moduleConfig.imageUrl) {
|
|
220
|
+
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
221
|
+
if (req.path === '/logo.png') {
|
|
222
|
+
res.redirect(moduleConfig.imageUrl);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
next();
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
else if (moduleConfig.imagePath) {
|
|
229
|
+
const fileHandler = (0, static_files_1.serveFile)(config, moduleConfig.imagePath);
|
|
230
|
+
const fileName = basename(moduleConfig.imagePath);
|
|
231
|
+
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
232
|
+
// Match exact paths: /logo.png (backwards-compatible) or /{actual-file-name}
|
|
233
|
+
if (req.path === '/logo.png' || req.path === `/${fileName}`) {
|
|
234
|
+
return fileHandler(req, res, next);
|
|
235
|
+
}
|
|
236
|
+
next();
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (config.imageUrl) {
|
|
241
|
+
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
242
|
+
if (req.path === '/logo.png') {
|
|
243
|
+
res.redirect(config.imageUrl);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
next();
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
const imagePath = config.imagePath;
|
|
197
250
|
const fileName = basename(imagePath);
|
|
198
251
|
const fileHandler = (0, static_files_1.serveFile)(config, imagePath);
|
|
199
252
|
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
package/out/views/AboutPage.js
CHANGED
|
@@ -58,7 +58,8 @@ const AboutPage = ({ name, logo, manifest, storeLink, showDebugLink }) => (react
|
|
|
58
58
|
react_1.default.createElement("div", { className: "mb-3" },
|
|
59
59
|
react_1.default.createElement("label", { htmlFor: "manifest-url", className: "form-label" }, "Paste the following manifest URL when prompted during manual installation:"),
|
|
60
60
|
react_1.default.createElement("div", { className: "input-group" },
|
|
61
|
-
react_1.default.createElement("input", { type: "text", className: "form-control", id: "manifest-url", value: manifest, readOnly: true })
|
|
61
|
+
react_1.default.createElement("input", { type: "text", className: "form-control", id: "manifest-url", value: manifest, readOnly: true }),
|
|
62
|
+
react_1.default.createElement("button", { className: "btn btn-outline-secondary", type: "button", id: "copy-button" }, "Copy"))),
|
|
62
63
|
storeLink && (react_1.default.createElement("div", null,
|
|
63
64
|
"Read more about",
|
|
64
65
|
' ',
|
|
@@ -72,5 +73,16 @@ const AboutPage = ({ name, logo, manifest, storeLink, showDebugLink }) => (react
|
|
|
72
73
|
' ',
|
|
73
74
|
react_1.default.createElement("a", { href: "/__debug", target: "_blank", title: "Open developer console in a new tab" }, "Developer Console"),
|
|
74
75
|
' ',
|
|
75
|
-
"to inspect requests, debug issues, and monitor application behavior.")))))))
|
|
76
|
+
"to inspect requests, debug issues, and monitor application behavior."))))))),
|
|
77
|
+
react_1.default.createElement("script", { dangerouslySetInnerHTML: {
|
|
78
|
+
__html: `
|
|
79
|
+
document.getElementById('copy-button').addEventListener('click', async function() {
|
|
80
|
+
var text = document.getElementById('manifest-url').value;
|
|
81
|
+
await navigator.clipboard.writeText(text);
|
|
82
|
+
var btn = document.getElementById('copy-button');
|
|
83
|
+
btn.textContent = 'Copied!';
|
|
84
|
+
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
|
|
85
|
+
});
|
|
86
|
+
`.trim(),
|
|
87
|
+
} }))));
|
|
76
88
|
exports.AboutPage = AboutPage;
|
package/package.json
CHANGED