@nebulr-group/bridge-svelte 0.8.0-beta.1 → 0.8.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/dist/auth/guard-cache.d.ts +30 -0
- package/dist/auth/guard-cache.js +102 -0
- package/dist/auth/route-guard.d.ts +10 -1
- package/dist/auth/route-guard.js +126 -22
- package/dist/client/BridgeBootstrap.d.ts +24 -1
- package/dist/client/BridgeBootstrap.js +198 -75
- package/dist/client/BridgeBootstrap.svelte +30 -0
- package/dist/client/components/developer/RealtimeDevBadge.svelte +6 -0
- package/dist/client/components/sdk-auth/ForgotPassword.svelte +3 -2
- package/dist/client/components/sdk-auth/LoginForm.svelte +42 -8
- package/dist/client/components/sdk-auth/MagicLink.svelte +2 -1
- package/dist/client/components/sdk-auth/MfaChallenge.svelte +4 -3
- package/dist/client/components/sdk-auth/MfaSetup.svelte +4 -3
- package/dist/client/components/sdk-auth/PasskeyLogin.svelte +2 -1
- package/dist/client/components/sdk-auth/PasskeyRequestSetupLink.svelte +2 -1
- package/dist/client/components/sdk-auth/PasskeySetup.svelte +2 -1
- package/dist/client/components/sdk-auth/SignupForm.svelte +2 -1
- package/dist/client/components/sdk-auth/SsoButton.svelte +2 -1
- package/dist/client/components/sdk-auth/TenantSelector.svelte +2 -1
- package/dist/client/components/sdk-auth/WorkspaceSelector.svelte +3 -2
- package/dist/client/components/sdk-auth/shared/auth-error.d.ts +28 -0
- package/dist/client/components/sdk-auth/shared/auth-error.js +67 -0
- package/dist/core/bridge-runtime.d.ts +13 -0
- package/dist/core/bridge-runtime.js +192 -8
- package/dist/core/realtime-dev-badge.d.ts +2 -0
- package/dist/core/realtime-dev-badge.js +13 -1
- package/dist/core/realtime-status.d.ts +7 -0
- package/dist/core/realtime-status.js +15 -1
- package/dist/core/snapshot-stores.d.ts +11 -0
- package/dist/core/snapshot-stores.js +30 -1
- package/dist/shared/allowed-origins.d.ts +14 -0
- package/dist/shared/allowed-origins.js +19 -0
- package/package.json +4 -2
- package/dist/auth/route-guard.test.d.ts +0 -1
- package/dist/auth/route-guard.test.js +0 -48
- package/dist/client/components/sdk-auth/auth-form-description.test.d.ts +0 -1
- package/dist/client/components/sdk-auth/auth-form-description.test.js +0 -470
- package/dist/client/components/sdk-auth/login-settling.test.d.ts +0 -27
- package/dist/client/components/sdk-auth/login-settling.test.js +0 -219
- package/dist/client/components/sdk-auth/selector-i18n.test.d.ts +0 -1
- package/dist/client/components/sdk-auth/selector-i18n.test.js +0 -248
- package/dist/client/components/subscription/billing-manage-route.test.d.ts +0 -1
- package/dist/client/components/subscription/billing-manage-route.test.js +0 -258
- package/dist/client/components/subscription/billing-notice-gate.test.d.ts +0 -1
- package/dist/client/components/subscription/billing-notice-gate.test.js +0 -83
- package/dist/client/components/subscription/plan-selector.test.d.ts +0 -1
- package/dist/client/components/subscription/plan-selector.test.js +0 -391
- package/dist/client/tracking/pii-hashing.spec.d.ts +0 -1
- package/dist/client/tracking/pii-hashing.spec.js +0 -47
- package/dist/client/tracking/reddit-tracking.spec.d.ts +0 -1
- package/dist/client/tracking/reddit-tracking.spec.js +0 -126
- package/dist/core/bridge-runtime.test.d.ts +0 -1
- package/dist/core/bridge-runtime.test.js +0 -507
- package/dist/core/bridge.test.d.ts +0 -1
- package/dist/core/bridge.test.js +0 -308
- package/dist/core/events.test.d.ts +0 -1
- package/dist/core/events.test.js +0 -116
- package/dist/core/lazy-slice.test.d.ts +0 -1
- package/dist/core/lazy-slice.test.js +0 -144
- package/dist/core/realtime-dev-badge.test.d.ts +0 -1
- package/dist/core/realtime-dev-badge.test.js +0 -145
- package/dist/core/use-bridge.test.d.ts +0 -1
- package/dist/core/use-bridge.test.js +0 -24
- package/dist/flags/bootstrap.test.d.ts +0 -1
- package/dist/flags/bootstrap.test.js +0 -200
- package/dist/flags/index.test.d.ts +0 -1
- package/dist/flags/index.test.js +0 -81
- package/dist/flags/us13-integration.test.d.ts +0 -1
- package/dist/flags/us13-integration.test.js +0 -168
- package/dist/shared/types/published-config-type.test.d.ts +0 -1
- package/dist/shared/types/published-config-type.test.js +0 -92
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long a route decision waits for the token refresh a plan, entitlements or
|
|
3
|
+
* user-state change started. Past it the guard decides with the token it has,
|
|
4
|
+
* which for a protected route is the old (fail-closed) verdict.
|
|
5
|
+
*/
|
|
6
|
+
export declare const AUTHORIZATION_CHANGE_WAIT_MS = 3000;
|
|
7
|
+
/** Drop the route-guard flag cache so the next route evaluation asks the server again. */
|
|
8
|
+
export declare function invalidateRouteGuardCache(): void;
|
|
9
|
+
/** Current cache generation; changes on every `invalidateRouteGuardCache()`. */
|
|
10
|
+
export declare function guardCacheGeneration(): number;
|
|
11
|
+
/**
|
|
12
|
+
* Drop the underlying cache WITHOUT bumping the generation — used to discard a
|
|
13
|
+
* result that landed after an invalidation, which is not itself a new change.
|
|
14
|
+
*/
|
|
15
|
+
export declare function dropFlagCache(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Register the token refresh started by an authorization-affecting event.
|
|
18
|
+
* The tracked promise never rejects; it clears itself once settled.
|
|
19
|
+
*/
|
|
20
|
+
export declare function trackAuthorizationChange(refresh: Promise<unknown>): Promise<void>;
|
|
21
|
+
/** The refresh in flight for an authorization change, or `null` when there is none. */
|
|
22
|
+
export declare function pendingAuthorizationChange(): Promise<void> | null;
|
|
23
|
+
/** Forget any pending change (runtime stop / test reset). */
|
|
24
|
+
export declare function clearPendingAuthorizationChange(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Wait until no authorization-change refresh is in flight, or until `deadline`
|
|
27
|
+
* (epoch ms). Never throws. Returns immediately — without yielding — when
|
|
28
|
+
* nothing is pending, so a signed-out visitor's navigation is not delayed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function settleAuthorizationChange(deadline: number): Promise<void>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Route-guard cache invalidation (TBP-654). Internal — not re-exported.
|
|
2
|
+
//
|
|
3
|
+
// Route guards read auth-core's FeatureFlagService, a cache with a 5-minute
|
|
4
|
+
// TTL. A plan-targeted rule's verdict depends on the user's plan and token, not
|
|
5
|
+
// on the flag definition, so anything that can change those has to drop the
|
|
6
|
+
// cache — otherwise an upgraded user stays locked out of the page they just
|
|
7
|
+
// paid for until the TTL runs out.
|
|
8
|
+
//
|
|
9
|
+
// The generation counter exists for one race: an evaluation that was already
|
|
10
|
+
// in flight when the cache was invalidated writes its (pre-change) result back
|
|
11
|
+
// into the cache when it lands, with a fresh timestamp. Callers compare the
|
|
12
|
+
// generation before and after reading the cache and re-read when it moved.
|
|
13
|
+
//
|
|
14
|
+
// The pending-change promise exists for a second race: the page learns about a
|
|
15
|
+
// plan change (and shows the new plan) a few hundred ms before the token that
|
|
16
|
+
// carries it arrives. A route decision taken in that window is evaluated with
|
|
17
|
+
// the old token and refuses the page the user can already see they have. The
|
|
18
|
+
// runtime registers the token refresh an authorization-affecting event starts;
|
|
19
|
+
// guards wait for it, bounded, before deciding.
|
|
20
|
+
import { getBridgeAuth } from '../core/bridge-instance.js';
|
|
21
|
+
/**
|
|
22
|
+
* How long a route decision waits for the token refresh a plan, entitlements or
|
|
23
|
+
* user-state change started. Past it the guard decides with the token it has,
|
|
24
|
+
* which for a protected route is the old (fail-closed) verdict.
|
|
25
|
+
*/
|
|
26
|
+
export const AUTHORIZATION_CHANGE_WAIT_MS = 3_000;
|
|
27
|
+
let _generation = 0;
|
|
28
|
+
let _pendingChange = null;
|
|
29
|
+
/** Drop the route-guard flag cache so the next route evaluation asks the server again. */
|
|
30
|
+
export function invalidateRouteGuardCache() {
|
|
31
|
+
_generation += 1;
|
|
32
|
+
dropFlagCache();
|
|
33
|
+
}
|
|
34
|
+
/** Current cache generation; changes on every `invalidateRouteGuardCache()`. */
|
|
35
|
+
export function guardCacheGeneration() {
|
|
36
|
+
return _generation;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Drop the underlying cache WITHOUT bumping the generation — used to discard a
|
|
40
|
+
* result that landed after an invalidation, which is not itself a new change.
|
|
41
|
+
*/
|
|
42
|
+
export function dropFlagCache() {
|
|
43
|
+
try {
|
|
44
|
+
getBridgeAuth().invalidateFeatureFlagCache();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// BridgeAuth not initialised yet — there is no cache to drop.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Register the token refresh started by an authorization-affecting event.
|
|
52
|
+
* The tracked promise never rejects; it clears itself once settled.
|
|
53
|
+
*/
|
|
54
|
+
export function trackAuthorizationChange(refresh) {
|
|
55
|
+
const tracked = refresh.then(() => undefined, () => undefined).finally(() => {
|
|
56
|
+
if (_pendingChange === tracked)
|
|
57
|
+
_pendingChange = null;
|
|
58
|
+
});
|
|
59
|
+
_pendingChange = tracked;
|
|
60
|
+
return tracked;
|
|
61
|
+
}
|
|
62
|
+
/** The refresh in flight for an authorization change, or `null` when there is none. */
|
|
63
|
+
export function pendingAuthorizationChange() {
|
|
64
|
+
return _pendingChange;
|
|
65
|
+
}
|
|
66
|
+
/** Forget any pending change (runtime stop / test reset). */
|
|
67
|
+
export function clearPendingAuthorizationChange() {
|
|
68
|
+
_pendingChange = null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Wait until no authorization-change refresh is in flight, or until `deadline`
|
|
72
|
+
* (epoch ms). Never throws. Returns immediately — without yielding — when
|
|
73
|
+
* nothing is pending, so a signed-out visitor's navigation is not delayed.
|
|
74
|
+
*/
|
|
75
|
+
export async function settleAuthorizationChange(deadline) {
|
|
76
|
+
let timer;
|
|
77
|
+
try {
|
|
78
|
+
// A change that lands while we wait (a second event after the first
|
|
79
|
+
// refresh finished) starts a new refresh; wait for that too, within the
|
|
80
|
+
// same deadline.
|
|
81
|
+
for (let pending = _pendingChange; pending; pending = _pendingChange) {
|
|
82
|
+
const remaining = deadline - Date.now();
|
|
83
|
+
if (remaining <= 0)
|
|
84
|
+
return;
|
|
85
|
+
const expired = await Promise.race([
|
|
86
|
+
pending.then(() => false),
|
|
87
|
+
new Promise((resolve) => {
|
|
88
|
+
timer = setTimeout(() => resolve(true), remaining);
|
|
89
|
+
}),
|
|
90
|
+
]);
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
if (expired)
|
|
93
|
+
return;
|
|
94
|
+
if (_pendingChange === pending)
|
|
95
|
+
return; // settled but not yet cleared
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
if (timer)
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
import type { NavigationDecision } from '@nebulr-group/bridge-auth-core';
|
|
1
2
|
export type { FlagRequirement, NavigationDecision, RouteGuard, RouteGuardConfig, RouteRule } from '@nebulr-group/bridge-auth-core';
|
|
2
|
-
export declare function createRouteGuard(flagsReady?: Promise<void>):
|
|
3
|
+
export declare function createRouteGuard(flagsReady?: Promise<void>): {
|
|
4
|
+
checkRouteRestrictions(pathname: string): Promise<string | null>;
|
|
5
|
+
getNavigationDecision(pathname: string, attempted?: string): Promise<NavigationDecision>;
|
|
6
|
+
isPublicRoute(pathname: string): boolean;
|
|
7
|
+
isProtectedRoute(pathname: string): boolean;
|
|
8
|
+
shouldRedirectToLogin(pathname: string): boolean;
|
|
9
|
+
getLoginRedirect(): string;
|
|
10
|
+
resolveReturnTo(attempted: string | null | undefined): string | null;
|
|
11
|
+
};
|
|
3
12
|
/**
|
|
4
13
|
* Does any route rule's `featureFlag` requirement mention this key?
|
|
5
14
|
* (TBP-575.)
|
package/dist/auth/route-guard.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// src/lib/auth/route-guard.ts — thin wrapper delegating to auth-core via bridge-instance
|
|
2
2
|
import { getBridgeAuth } from '../core/bridge-instance.js';
|
|
3
3
|
import { getConfig, getRouteGuardConfig } from '../client/stores/config.store.js';
|
|
4
|
+
import { logger } from '../shared/logger.js';
|
|
5
|
+
import { AUTHORIZATION_CHANGE_WAIT_MS, dropFlagCache, guardCacheGeneration, settleAuthorizationChange, } from './guard-cache.js';
|
|
6
|
+
// How many times a restriction check is re-run when the cache was invalidated
|
|
7
|
+
// underneath it (TBP-654). Bounded so a burst of invalidations cannot spin.
|
|
8
|
+
const MAX_FRESH_READS = 3;
|
|
4
9
|
export function createRouteGuard(flagsReady) {
|
|
5
10
|
const config = getRouteGuardConfig();
|
|
6
11
|
// TBP-629 — feed the app's own loginRoute into the guard so it can refuse to
|
|
@@ -17,38 +22,137 @@ export function createRouteGuard(flagsReady) {
|
|
|
17
22
|
},
|
|
18
23
|
};
|
|
19
24
|
const guard = getBridgeAuth().createRouteGuard(guardConfig);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
25
|
+
// TBP-654 — a restriction check reads the flag cache. If the cache was
|
|
26
|
+
// invalidated while the check was in flight (plan change, token refresh),
|
|
27
|
+
// the answer may predate the change AND has just been written back into the
|
|
28
|
+
// cache. Discard it and ask again.
|
|
29
|
+
//
|
|
30
|
+
// Each read first waits (bounded by `deadline`) for a token refresh that a
|
|
31
|
+
// plan / entitlements / user-state change started: the page shows the new
|
|
32
|
+
// plan a few hundred ms before the token carrying it lands, and a verdict
|
|
33
|
+
// taken in between would be evaluated with the old one.
|
|
34
|
+
async function checkRestrictionsFresh(pathname, deadline) {
|
|
35
|
+
for (let attempt = 1;; attempt++) {
|
|
36
|
+
await settleAuthorizationChange(deadline);
|
|
37
|
+
const generation = guardCacheGeneration();
|
|
38
|
+
const redirectTo = await guard.checkRouteRestrictions(pathname);
|
|
39
|
+
if (generation === guardCacheGeneration())
|
|
40
|
+
return redirectTo;
|
|
41
|
+
dropFlagCache();
|
|
42
|
+
if (attempt >= MAX_FRESH_READS)
|
|
43
|
+
return redirectTo;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function loginDecision(pathname, attempted) {
|
|
47
|
+
// TBP-629 — the attempted target (path + query) rides along on every
|
|
48
|
+
// login decision, including the fail-closed ones below.
|
|
49
|
+
let returnTo = null;
|
|
50
|
+
try {
|
|
51
|
+
returnTo = guard.resolveReturnTo(attempted ?? pathname);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
returnTo = null;
|
|
55
|
+
}
|
|
56
|
+
let loginUrl = '';
|
|
57
|
+
try {
|
|
58
|
+
loginUrl = guard.getLoginRedirect();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// SDK mode never reads loginUrl; hosted mode rebuilds it at redirect time.
|
|
62
|
+
}
|
|
63
|
+
return { type: 'login', loginUrl, ...(returnTo ? { returnTo } : {}) };
|
|
64
|
+
}
|
|
65
|
+
// TBP-653 — the guard could not reach a decision (network error on a flag
|
|
66
|
+
// check, malformed config, an exception anywhere in the chain). That must
|
|
67
|
+
// never let a restricted route through:
|
|
68
|
+
// - a public route with no flag/billing requirement stays reachable;
|
|
69
|
+
// - a signed-out visitor is sent to login;
|
|
70
|
+
// - a signed-in user is treated as failing the route's requirement and
|
|
71
|
+
// sent where the rule says a failing user goes.
|
|
72
|
+
function failClosed(pathname, attempted, err) {
|
|
73
|
+
logger.error('[route-guard] could not evaluate route; denying access', pathname, err);
|
|
74
|
+
let rule = null;
|
|
75
|
+
try {
|
|
76
|
+
rule = findMatchingRule(pathname, config?.rules ?? []);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
rule = null;
|
|
80
|
+
}
|
|
81
|
+
const restricted = !!(rule?.featureFlag || rule?.billing === 'hard');
|
|
82
|
+
let isPublic = false;
|
|
83
|
+
try {
|
|
84
|
+
isPublic = guard.isPublicRoute(pathname);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
isPublic = false;
|
|
88
|
+
}
|
|
89
|
+
if (isPublic && !restricted)
|
|
90
|
+
return { type: 'allow' };
|
|
91
|
+
let authenticated = false;
|
|
92
|
+
try {
|
|
93
|
+
authenticated = getBridgeAuth().isAuthenticated();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
authenticated = false;
|
|
97
|
+
}
|
|
98
|
+
if (authenticated) {
|
|
99
|
+
const to = rule?.redirectTo ?? '/';
|
|
100
|
+
if (to !== pathname)
|
|
101
|
+
return { type: 'redirect', to };
|
|
102
|
+
}
|
|
103
|
+
return loginDecision(pathname, attempted);
|
|
104
|
+
}
|
|
23
105
|
return {
|
|
24
106
|
...guard,
|
|
25
107
|
async checkRouteRestrictions(pathname) {
|
|
108
|
+
const deadline = Date.now() + AUTHORIZATION_CHANGE_WAIT_MS;
|
|
26
109
|
await flagsReady;
|
|
27
|
-
return
|
|
110
|
+
return checkRestrictionsFresh(pathname, deadline);
|
|
28
111
|
},
|
|
29
112
|
async getNavigationDecision(pathname, attempted) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
113
|
+
// TBP-654 — one bound for the whole decision, however many reads it takes.
|
|
114
|
+
const deadline = Date.now() + AUTHORIZATION_CHANGE_WAIT_MS;
|
|
115
|
+
try {
|
|
116
|
+
// A refresh that fails can sign the session out, so let it land before
|
|
117
|
+
// the signed-in check too. Nothing pending (always the case for a
|
|
118
|
+
// signed-out visitor) → no wait at all.
|
|
119
|
+
await settleAuthorizationChange(deadline);
|
|
120
|
+
if (guard.shouldRedirectToLogin(pathname)) {
|
|
121
|
+
// TBP-629 — this branch short-circuits before flagsReady on purpose
|
|
122
|
+
// (an unauthenticated visitor needs no flag evaluation), which is
|
|
123
|
+
// exactly why `attempted` has to be threaded through here too.
|
|
124
|
+
return loginDecision(pathname, attempted);
|
|
125
|
+
}
|
|
126
|
+
await flagsReady;
|
|
127
|
+
const redirectTo = await checkRestrictionsFresh(pathname, deadline);
|
|
128
|
+
if (redirectTo) {
|
|
129
|
+
return { type: 'redirect', to: redirectTo };
|
|
130
|
+
}
|
|
131
|
+
return { type: 'allow' };
|
|
42
132
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (redirectTo) {
|
|
46
|
-
return { type: 'redirect', to: redirectTo };
|
|
133
|
+
catch (err) {
|
|
134
|
+
return failClosed(pathname, attempted, err);
|
|
47
135
|
}
|
|
48
|
-
|
|
49
|
-
}
|
|
136
|
+
},
|
|
50
137
|
};
|
|
51
138
|
}
|
|
139
|
+
// Same matching semantics as auth-core's route guard: a RegExp is tested as-is,
|
|
140
|
+
// a string is an exact match unless it contains `*` wildcards. First match wins.
|
|
141
|
+
function toRegExp(pattern) {
|
|
142
|
+
if (pattern instanceof RegExp)
|
|
143
|
+
return pattern;
|
|
144
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
145
|
+
if (!pattern.includes('*'))
|
|
146
|
+
return new RegExp(`^${escaped}$`);
|
|
147
|
+
return new RegExp(`^${escaped.replace(/\\\*/g, '.*')}$`);
|
|
148
|
+
}
|
|
149
|
+
function findMatchingRule(pathname, rules) {
|
|
150
|
+
for (const rule of rules) {
|
|
151
|
+
if (toRegExp(rule.match).test(pathname))
|
|
152
|
+
return rule;
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
52
156
|
/**
|
|
53
157
|
* Does any route rule's `featureFlag` requirement mention this key?
|
|
54
158
|
* (TBP-575.)
|
|
@@ -3,6 +3,29 @@ import { waitForBridge as _waitForBridge } from '../core/bridge-instance.js';
|
|
|
3
3
|
import type { BridgeConfig } from '../shared/types/config.js';
|
|
4
4
|
export declare function bridgeBootstrap(url: URL, config: BridgeConfig | string, routeConfig?: RouteGuardConfig, kitFetch?: typeof globalThis.fetch): Promise<{
|
|
5
5
|
flagsReady: Promise<void>;
|
|
6
|
-
}
|
|
6
|
+
}>;
|
|
7
|
+
/**
|
|
8
|
+
* Re-check Bridge's route rules for `url` from any `load` function, and throw
|
|
9
|
+
* the same redirect `bridgeBootstrap` would: to your `loginRoute` (or the hosted
|
|
10
|
+
* login page) for a signed-out visitor on a protected route, or to the rule's
|
|
11
|
+
* `redirectTo` when a feature-flag or billing requirement is not met.
|
|
12
|
+
*
|
|
13
|
+
* `bridgeBootstrap` in your root `+layout.ts` already guards every navigation.
|
|
14
|
+
* Use this as a second line of defence on pages that must never render for the
|
|
15
|
+
* wrong visitor. It fails closed: if the decision cannot be made, a protected
|
|
16
|
+
* route is denied.
|
|
17
|
+
*
|
|
18
|
+
* Route guards control what the browser renders. They are not authorization —
|
|
19
|
+
* your API must still verify the user's token on every request.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // src/routes/admin/+page.ts
|
|
23
|
+
* import { assertAuthorized } from '@nebulr-group/bridge-svelte';
|
|
24
|
+
* export const load = async ({ url }) => {
|
|
25
|
+
* await assertAuthorized(url);
|
|
26
|
+
* // ...
|
|
27
|
+
* };
|
|
28
|
+
*/
|
|
29
|
+
export declare function assertAuthorized(url: URL): Promise<void>;
|
|
7
30
|
export declare const bridgeReady: import("svelte/store").Readable<boolean>;
|
|
8
31
|
export { _waitForBridge as waitForBridge };
|