@backstage/frontend-app-api 0.6.4-next.0 → 0.6.4-next.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/CHANGELOG.md +16 -0
- package/dist/index.esm.js +164 -0
- package/dist/index.esm.js.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @backstage/frontend-app-api
|
|
2
2
|
|
|
3
|
+
## 0.6.4-next.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c884b9a: The app is now aware of if it is being served from the `app-backend` with a separate public and protected bundles. When in protected mode the app will now continuously refresh the session cookie, as well as clear the cookie if the user signs out.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/core-app-api@1.12.4-next.0
|
|
10
|
+
- @backstage/frontend-plugin-api@0.6.4-next.1
|
|
11
|
+
- @backstage/config@1.2.0
|
|
12
|
+
- @backstage/core-components@0.14.4-next.0
|
|
13
|
+
- @backstage/core-plugin-api@1.9.1
|
|
14
|
+
- @backstage/errors@1.2.4
|
|
15
|
+
- @backstage/theme@0.5.2
|
|
16
|
+
- @backstage/types@1.1.1
|
|
17
|
+
- @backstage/version-bridge@1.0.7
|
|
18
|
+
|
|
3
19
|
## 0.6.4-next.0
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/index.esm.js
CHANGED
|
@@ -301,6 +301,13 @@ function isBackstageFeature(obj) {
|
|
|
301
301
|
return false;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
function isProtectedApp() {
|
|
305
|
+
var _a;
|
|
306
|
+
const element = document.querySelector('meta[name="backstage-app-mode"]');
|
|
307
|
+
const appMode = (_a = element == null ? void 0 : element.getAttribute("content")) != null ? _a : "public";
|
|
308
|
+
return appMode === "protected";
|
|
309
|
+
}
|
|
310
|
+
|
|
304
311
|
function resolveTheme(themeId, shouldPreferDark, themes) {
|
|
305
312
|
if (themeId !== void 0) {
|
|
306
313
|
const selectedTheme = themes.find((theme) => theme.id === themeId);
|
|
@@ -355,12 +362,134 @@ function AppThemeProvider({ children }) {
|
|
|
355
362
|
return /* @__PURE__ */ React.createElement(appTheme.Provider, { children });
|
|
356
363
|
}
|
|
357
364
|
|
|
365
|
+
const PLUGIN_ID = "app";
|
|
366
|
+
const CHANNEL_ID = `${PLUGIN_ID}-auth-cookie-expires-at`;
|
|
367
|
+
const MIN_BASE_DELAY_MS = 5 * 6e4;
|
|
368
|
+
const ERROR_BACKOFF_START = 5e3;
|
|
369
|
+
const ERROR_BACKOFF_FACTOR = 2;
|
|
370
|
+
const ERROR_BACKOFF_MAX = 5 * 6e4;
|
|
371
|
+
function startCookieAuthRefresh({
|
|
372
|
+
discoveryApi,
|
|
373
|
+
fetchApi,
|
|
374
|
+
errorApi
|
|
375
|
+
}) {
|
|
376
|
+
let stopped = false;
|
|
377
|
+
let timeout;
|
|
378
|
+
let firstError = true;
|
|
379
|
+
let errorBackoff = ERROR_BACKOFF_START;
|
|
380
|
+
const channel = "BroadcastChannel" in window ? new BroadcastChannel(CHANNEL_ID) : void 0;
|
|
381
|
+
const getDelay = (expiresAt) => {
|
|
382
|
+
const margin = (1 + 3 * Math.random()) * 6e4;
|
|
383
|
+
const delay = Math.max(expiresAt - Date.now(), MIN_BASE_DELAY_MS) - margin;
|
|
384
|
+
return delay;
|
|
385
|
+
};
|
|
386
|
+
const refresh = async () => {
|
|
387
|
+
try {
|
|
388
|
+
const baseUrl = await discoveryApi.getBaseUrl(PLUGIN_ID);
|
|
389
|
+
const requestUrl = `${baseUrl}/.backstage/auth/v1/cookie`;
|
|
390
|
+
const res = await fetchApi.fetch(requestUrl, {
|
|
391
|
+
credentials: "include"
|
|
392
|
+
});
|
|
393
|
+
if (!res.ok) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
`Request failed with status ${res.status} ${res.statusText}, see request towards ${requestUrl} for more details`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
const data = await res.json();
|
|
399
|
+
if (!data.expiresAt) {
|
|
400
|
+
throw new Error("No expiration date in response");
|
|
401
|
+
}
|
|
402
|
+
const expiresAt = Date.parse(data.expiresAt);
|
|
403
|
+
if (Number.isNaN(expiresAt)) {
|
|
404
|
+
throw new Error("Invalid expiration date in response");
|
|
405
|
+
}
|
|
406
|
+
firstError = true;
|
|
407
|
+
channel == null ? void 0 : channel.postMessage({
|
|
408
|
+
action: "COOKIE_REFRESH_SUCCESS",
|
|
409
|
+
payload: { expiresAt: new Date(expiresAt).toISOString() }
|
|
410
|
+
});
|
|
411
|
+
scheduleRefresh(getDelay(expiresAt));
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (firstError) {
|
|
414
|
+
firstError = false;
|
|
415
|
+
errorBackoff = ERROR_BACKOFF_START;
|
|
416
|
+
} else {
|
|
417
|
+
errorBackoff = Math.min(
|
|
418
|
+
ERROR_BACKOFF_MAX,
|
|
419
|
+
errorBackoff * ERROR_BACKOFF_FACTOR
|
|
420
|
+
);
|
|
421
|
+
console.error("Session cookie refresh failed", error);
|
|
422
|
+
errorApi.post(
|
|
423
|
+
new Error(
|
|
424
|
+
`Session refresh failed, see developer console for details`
|
|
425
|
+
)
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
scheduleRefresh(errorBackoff);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
const onMessage = (event) => {
|
|
432
|
+
const { data } = event;
|
|
433
|
+
if (data === null || typeof data !== "object") {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if ("action" in data && data.action === "COOKIE_REFRESH_SUCCESS") {
|
|
437
|
+
const expiresAt = Date.parse(data.payload.expiresAt);
|
|
438
|
+
if (Number.isNaN(expiresAt)) {
|
|
439
|
+
console.warn(
|
|
440
|
+
"Received invalid expiration from session refresh channel"
|
|
441
|
+
);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
scheduleRefresh(getDelay(expiresAt));
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
function scheduleRefresh(delayMs) {
|
|
448
|
+
if (stopped) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (timeout) {
|
|
452
|
+
clearTimeout(timeout);
|
|
453
|
+
}
|
|
454
|
+
timeout = setTimeout(refresh, delayMs);
|
|
455
|
+
}
|
|
456
|
+
channel == null ? void 0 : channel.addEventListener("message", onMessage);
|
|
457
|
+
refresh();
|
|
458
|
+
return () => {
|
|
459
|
+
stopped = true;
|
|
460
|
+
if (timeout) {
|
|
461
|
+
clearTimeout(timeout);
|
|
462
|
+
}
|
|
463
|
+
channel == null ? void 0 : channel.removeEventListener("message", onMessage);
|
|
464
|
+
channel == null ? void 0 : channel.close();
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
358
468
|
var __defProp$3 = Object.defineProperty;
|
|
359
469
|
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
360
470
|
var __publicField$3 = (obj, key, value) => {
|
|
361
471
|
__defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
362
472
|
return value;
|
|
363
473
|
};
|
|
474
|
+
var __accessCheck$4 = (obj, member, msg) => {
|
|
475
|
+
if (!member.has(obj))
|
|
476
|
+
throw TypeError("Cannot " + msg);
|
|
477
|
+
};
|
|
478
|
+
var __privateGet$4 = (obj, member, getter) => {
|
|
479
|
+
__accessCheck$4(obj, member, "read from private field");
|
|
480
|
+
return getter ? getter.call(obj) : member.get(obj);
|
|
481
|
+
};
|
|
482
|
+
var __privateAdd$4 = (obj, member, value) => {
|
|
483
|
+
if (member.has(obj))
|
|
484
|
+
throw TypeError("Cannot add the same private member more than once");
|
|
485
|
+
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
486
|
+
};
|
|
487
|
+
var __privateSet$4 = (obj, member, value, setter) => {
|
|
488
|
+
__accessCheck$4(obj, member, "write to private field");
|
|
489
|
+
setter ? setter.call(obj, value) : member.set(obj, value);
|
|
490
|
+
return value;
|
|
491
|
+
};
|
|
492
|
+
var _cookieAuthSignOut;
|
|
364
493
|
function mkError(thing) {
|
|
365
494
|
return new Error(
|
|
366
495
|
`Tried to access IdentityApi ${thing} before app was loaded`
|
|
@@ -378,6 +507,7 @@ class AppIdentityProxy {
|
|
|
378
507
|
__publicField$3(this, "resolveTarget", () => {
|
|
379
508
|
});
|
|
380
509
|
__publicField$3(this, "signOutTargetUrl", "/");
|
|
510
|
+
__privateAdd$4(this, _cookieAuthSignOut, void 0);
|
|
381
511
|
this.waitForTarget = new Promise((resolve) => {
|
|
382
512
|
this.resolveTarget = resolve;
|
|
383
513
|
});
|
|
@@ -435,10 +565,29 @@ class AppIdentityProxy {
|
|
|
435
565
|
});
|
|
436
566
|
}
|
|
437
567
|
async signOut() {
|
|
568
|
+
var _a;
|
|
438
569
|
await this.waitForTarget.then((target) => target.signOut());
|
|
570
|
+
await ((_a = __privateGet$4(this, _cookieAuthSignOut)) == null ? void 0 : _a.call(this));
|
|
439
571
|
window.location.href = this.signOutTargetUrl;
|
|
440
572
|
}
|
|
573
|
+
enableCookieAuth(ctx) {
|
|
574
|
+
if (__privateGet$4(this, _cookieAuthSignOut)) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const stopRefresh = startCookieAuthRefresh(ctx);
|
|
578
|
+
__privateSet$4(this, _cookieAuthSignOut, async () => {
|
|
579
|
+
stopRefresh();
|
|
580
|
+
const appBaseUrl = await ctx.discoveryApi.getBaseUrl("app");
|
|
581
|
+
try {
|
|
582
|
+
await ctx.fetchApi.fetch(`${appBaseUrl}/.backstage/auth/v1/cookie`, {
|
|
583
|
+
method: "DELETE"
|
|
584
|
+
});
|
|
585
|
+
} catch {
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
}
|
|
441
589
|
}
|
|
590
|
+
_cookieAuthSignOut = new WeakMap();
|
|
442
591
|
|
|
443
592
|
var __defProp$2 = Object.defineProperty;
|
|
444
593
|
var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -2743,6 +2892,21 @@ function createSpecializedApp(options) {
|
|
|
2743
2892
|
),
|
|
2744
2893
|
options == null ? void 0 : options.icons
|
|
2745
2894
|
);
|
|
2895
|
+
if (isProtectedApp()) {
|
|
2896
|
+
const discoveryApi = apiHolder.get(discoveryApiRef);
|
|
2897
|
+
const errorApi = apiHolder.get(errorApiRef);
|
|
2898
|
+
const fetchApi = apiHolder.get(fetchApiRef);
|
|
2899
|
+
if (!discoveryApi || !errorApi || !fetchApi) {
|
|
2900
|
+
throw new Error(
|
|
2901
|
+
"App is running in protected mode but missing required APIs"
|
|
2902
|
+
);
|
|
2903
|
+
}
|
|
2904
|
+
appIdentityProxy.enableCookieAuth({
|
|
2905
|
+
discoveryApi,
|
|
2906
|
+
errorApi,
|
|
2907
|
+
fetchApi
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2746
2910
|
const featureFlagApi = apiHolder.get(featureFlagsApiRef);
|
|
2747
2911
|
if (featureFlagApi) {
|
|
2748
2912
|
for (const feature of features) {
|