@nebulr-group/bridge-svelte 0.4.2 → 0.4.4
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/route-guard.d.ts +10 -0
- package/dist/auth/route-guard.js +24 -0
- package/dist/auth/route-guard.test.d.ts +1 -0
- package/dist/auth/route-guard.test.js +48 -0
- package/dist/client/BridgeBootstrap.svelte +40 -1
- package/dist/core/bridge-runtime.d.ts +8 -1
- package/dist/core/bridge-runtime.js +42 -0
- package/dist/core/bridge-runtime.test.js +66 -1
- package/dist/index.d.ts +1 -0
- package/package.json +2 -2
|
@@ -1,2 +1,12 @@
|
|
|
1
1
|
export type { FlagRequirement, NavigationDecision, RouteGuard, RouteGuardConfig, RouteRule } from '@nebulr-group/bridge-auth-core';
|
|
2
2
|
export declare function createRouteGuard(flagsReady?: Promise<void>): import("@nebulr-group/bridge-auth-core").RouteGuard;
|
|
3
|
+
/**
|
|
4
|
+
* Does any route rule's `featureFlag` requirement mention this key?
|
|
5
|
+
* (TBP-575.)
|
|
6
|
+
*
|
|
7
|
+
* The live re-check runs on every flag mutation the app receives, and most of
|
|
8
|
+
* them have nothing to do with routing. Without this filter, flipping any flag
|
|
9
|
+
* in a busy app would cost a `bulkEvaluate` round-trip on every connected
|
|
10
|
+
* client — so this is a cost guard, not a correctness one.
|
|
11
|
+
*/
|
|
12
|
+
export declare function routeRulesReferenceFlag(key: string): boolean;
|
package/dist/auth/route-guard.js
CHANGED
|
@@ -26,3 +26,27 @@ export function createRouteGuard(flagsReady) {
|
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Does any route rule's `featureFlag` requirement mention this key?
|
|
31
|
+
* (TBP-575.)
|
|
32
|
+
*
|
|
33
|
+
* The live re-check runs on every flag mutation the app receives, and most of
|
|
34
|
+
* them have nothing to do with routing. Without this filter, flipping any flag
|
|
35
|
+
* in a busy app would cost a `bulkEvaluate` round-trip on every connected
|
|
36
|
+
* client — so this is a cost guard, not a correctness one.
|
|
37
|
+
*/
|
|
38
|
+
export function routeRulesReferenceFlag(key) {
|
|
39
|
+
const rules = getRouteGuardConfig()?.rules ?? [];
|
|
40
|
+
return rules.some((rule) => {
|
|
41
|
+
const req = rule.featureFlag;
|
|
42
|
+
if (!req)
|
|
43
|
+
return false;
|
|
44
|
+
if (typeof req === 'string')
|
|
45
|
+
return req === key;
|
|
46
|
+
if ('any' in req)
|
|
47
|
+
return req.any.includes(key);
|
|
48
|
+
if ('all' in req)
|
|
49
|
+
return req.all.includes(key);
|
|
50
|
+
return false;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// TBP-575 — the filter that decides whether a flag change is worth re-checking
|
|
2
|
+
// the current route for.
|
|
3
|
+
//
|
|
4
|
+
// Getting this wrong is silent in both directions: too narrow and the live
|
|
5
|
+
// re-check never fires (the feature looks broken), too wide and every flag
|
|
6
|
+
// flip costs every connected client a bulkEvaluate round-trip.
|
|
7
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
8
|
+
let _rules = [];
|
|
9
|
+
vi.mock('../client/stores/config.store.js', () => ({
|
|
10
|
+
getRouteGuardConfig: () => ({ rules: _rules }),
|
|
11
|
+
}));
|
|
12
|
+
vi.mock('../core/bridge-instance.js', () => ({
|
|
13
|
+
getBridgeAuth: () => ({ createRouteGuard: () => ({}) }),
|
|
14
|
+
}));
|
|
15
|
+
const { routeRulesReferenceFlag } = await import('./route-guard.js');
|
|
16
|
+
describe('routeRulesReferenceFlag', () => {
|
|
17
|
+
it('matches a plain string requirement', () => {
|
|
18
|
+
_rules = [{ match: '/lab', featureFlag: 'holo-experimental' }];
|
|
19
|
+
expect(routeRulesReferenceFlag('holo-experimental')).toBe(true);
|
|
20
|
+
expect(routeRulesReferenceFlag('something-else')).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
it('matches inside an `any` requirement', () => {
|
|
23
|
+
_rules = [{ match: '/lab', featureFlag: { any: ['a', 'b'] } }];
|
|
24
|
+
expect(routeRulesReferenceFlag('b')).toBe(true);
|
|
25
|
+
expect(routeRulesReferenceFlag('c')).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
it('matches inside an `all` requirement', () => {
|
|
28
|
+
_rules = [{ match: '/lab', featureFlag: { all: ['a', 'b'] } }];
|
|
29
|
+
expect(routeRulesReferenceFlag('a')).toBe(true);
|
|
30
|
+
expect(routeRulesReferenceFlag('c')).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
it('ignores rules with no flag requirement', () => {
|
|
33
|
+
_rules = [{ match: '/public', public: true }, { match: '/billing', billing: 'hard' }];
|
|
34
|
+
expect(routeRulesReferenceFlag('anything')).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it('scans every rule, not just the first', () => {
|
|
37
|
+
_rules = [
|
|
38
|
+
{ match: '/a', featureFlag: 'one' },
|
|
39
|
+
{ match: '/b', public: true },
|
|
40
|
+
{ match: '/c', featureFlag: 'two' },
|
|
41
|
+
];
|
|
42
|
+
expect(routeRulesReferenceFlag('two')).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
it('survives an empty or absent rule set', () => {
|
|
45
|
+
_rules = [];
|
|
46
|
+
expect(routeRulesReferenceFlag('x')).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { beforeNavigate, goto } from '$app/navigation';
|
|
3
3
|
import { page } from '$app/stores';
|
|
4
4
|
import { onMount, onDestroy } from 'svelte';
|
|
5
|
-
import { createRouteGuard } from '../auth/route-guard.js';
|
|
5
|
+
import { createRouteGuard, routeRulesReferenceFlag } from '../auth/route-guard.js';
|
|
6
6
|
import {
|
|
7
7
|
getBridgeAuth,
|
|
8
8
|
isAuthenticated,
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { setBridgeContext } from '../core/use-bridge.js';
|
|
15
15
|
import { getConfig } from './stores/config.store.js';
|
|
16
16
|
import {
|
|
17
|
+
onBridgeFlagChange,
|
|
17
18
|
startBridgeRuntime,
|
|
18
19
|
stopBridgeRuntime,
|
|
19
20
|
type StartBridgeRuntimeOptions,
|
|
@@ -91,6 +92,31 @@
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
// TBP-575 — re-evaluate the CURRENT route when a flag it depends on changes.
|
|
96
|
+
//
|
|
97
|
+
// Route rules are only evaluated on navigation. Without this, flipping a
|
|
98
|
+
// flag off never ejects the user sitting on the route it gates — they keep
|
|
99
|
+
// the page until they happen to navigate. That makes a route flag useless as
|
|
100
|
+
// a kill switch, which is most of the reason to put a flag on a route.
|
|
101
|
+
//
|
|
102
|
+
// Debounced because one admin action can emit several flag messages, and
|
|
103
|
+
// each re-check costs a bulkEvaluate round-trip.
|
|
104
|
+
let _recheckTimer: ReturnType<typeof setTimeout> | undefined;
|
|
105
|
+
let _stopFlagWatch: (() => void) | undefined;
|
|
106
|
+
|
|
107
|
+
function scheduleRouteRecheck() {
|
|
108
|
+
if (_recheckTimer) clearTimeout(_recheckTimer);
|
|
109
|
+
_recheckTimer = setTimeout(() => {
|
|
110
|
+
_recheckTimer = undefined;
|
|
111
|
+
// No `cancel` here: there is no navigation in flight to cancel. A denied
|
|
112
|
+
// verdict redirects the user off the page they are already on.
|
|
113
|
+
handleRoute(window.location.pathname).catch(() => {
|
|
114
|
+
/* a failed re-check must never break the page; the next navigation
|
|
115
|
+
re-evaluates anyway */
|
|
116
|
+
});
|
|
117
|
+
}, 150);
|
|
118
|
+
}
|
|
119
|
+
|
|
94
120
|
// Stash a teardown for the dynamically-attached capabilities (today: flags).
|
|
95
121
|
let _capabilityStop: (() => Promise<void>) | undefined;
|
|
96
122
|
|
|
@@ -99,6 +125,13 @@
|
|
|
99
125
|
// attach, session.snapshot fanout, billing-family event dispatch.
|
|
100
126
|
startBridgeRuntime(runtime);
|
|
101
127
|
|
|
128
|
+
// TBP-575 — subscribe AFTER the runtime exists so the hook is registered
|
|
129
|
+
// on the live client. Filtered to keys the route rules actually name; a
|
|
130
|
+
// flag nothing routes on must not cost every client a round-trip.
|
|
131
|
+
_stopFlagWatch = onBridgeFlagChange((change) => {
|
|
132
|
+
if (routeRulesReferenceFlag(change.key)) scheduleRouteRecheck();
|
|
133
|
+
});
|
|
134
|
+
|
|
102
135
|
// Fetch app config outside load() so we use the correct fetch context.
|
|
103
136
|
// LoginForm also calls ensureAppConfig() — both share the same in-flight promise.
|
|
104
137
|
void ensureAppConfig();
|
|
@@ -130,6 +163,12 @@
|
|
|
130
163
|
});
|
|
131
164
|
|
|
132
165
|
onDestroy(() => {
|
|
166
|
+
if (_recheckTimer) {
|
|
167
|
+
clearTimeout(_recheckTimer);
|
|
168
|
+
_recheckTimer = undefined;
|
|
169
|
+
}
|
|
170
|
+
_stopFlagWatch?.();
|
|
171
|
+
_stopFlagWatch = undefined;
|
|
133
172
|
void (async () => {
|
|
134
173
|
if (_capabilityStop) {
|
|
135
174
|
try { await _capabilityStop(); } catch { /* ignore */ }
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
* instance. Call `stopBridgeRuntime()` (e.g. on `<BridgeBootstrap />` destroy)
|
|
42
42
|
* to flush the realtime client and unsubscribe from the token store.
|
|
43
43
|
*/
|
|
44
|
-
import { RealtimeClient, type RealtimeClientConfig, type SessionSnapshotMessage } from '@nebulr-group/bridge-auth-core';
|
|
44
|
+
import { RealtimeClient, type FlagChange, type RealtimeClientConfig, type SessionSnapshotMessage } from '@nebulr-group/bridge-auth-core';
|
|
45
45
|
/**
|
|
46
46
|
* Advanced runtime overrides. Product consumers never pass these; tests,
|
|
47
47
|
* Storybook harnesses, and the demo workspace use them to override the
|
|
@@ -96,6 +96,13 @@ export declare function getCurrentAuthToken(): string | undefined;
|
|
|
96
96
|
export declare function onBridgeRealtimeOpen(handler: () => void): () => void;
|
|
97
97
|
/** Subscribe to realtime `close` events. Returns an unsubscribe fn. */
|
|
98
98
|
export declare function onBridgeRealtimeClose(handler: () => void): () => void;
|
|
99
|
+
/**
|
|
100
|
+
* Subscribe to realtime flag mutations (TBP-575). Returns an unsubscribe fn.
|
|
101
|
+
*
|
|
102
|
+
* The route-guard cache is already invalidated before subscribers run, so a
|
|
103
|
+
* handler that re-evaluates a route will read fresh values.
|
|
104
|
+
*/
|
|
105
|
+
export declare function onBridgeFlagChange(handler: (change: FlagChange) => void): () => void;
|
|
99
106
|
/** Subscribe to `session.snapshot` messages. Returns an unsubscribe fn. */
|
|
100
107
|
export declare function onBridgeRealtimeSnapshot(handler: (msg: SessionSnapshotMessage) => void): () => void;
|
|
101
108
|
/**
|
|
@@ -55,6 +55,9 @@ let _originalFetch;
|
|
|
55
55
|
const _onOpenSubs = new Set();
|
|
56
56
|
const _onCloseSubs = new Set();
|
|
57
57
|
const _onSnapshotSubs = new Set();
|
|
58
|
+
// TBP-575 — realtime flag mutations, fanned out so the route guard can
|
|
59
|
+
// invalidate its (separate) cache and re-evaluate the current route.
|
|
60
|
+
const _onFlagChangeSubs = new Set();
|
|
58
61
|
const _onUserStateSubs = new Set();
|
|
59
62
|
/**
|
|
60
63
|
* Start the Bridge runtime. Idempotent — repeated calls are a no-op. Reads
|
|
@@ -146,6 +149,34 @@ export function startBridgeRuntime(options = {}) {
|
|
|
146
149
|
catch { /* subscriber errors swallowed */ }
|
|
147
150
|
}
|
|
148
151
|
});
|
|
152
|
+
// TBP-575 — connected, handshaken, and subscribed to nothing. Distinct from
|
|
153
|
+
// 'closed': the socket is alive, so no reconnect is coming, but nothing will
|
|
154
|
+
// ever arrive on it. Surfacing this is the whole point — this state used to
|
|
155
|
+
// report as 'open'.
|
|
156
|
+
// Guarded: bridge-svelte and auth-core version independently, so a consumer
|
|
157
|
+
// can resolve an older auth-core that has no such hook. An unguarded call
|
|
158
|
+
// would crash bootstrap — a worse failure than the missing signal.
|
|
159
|
+
_realtime.setOnDegraded?.(() => {
|
|
160
|
+
_setRealtimeStatus('degraded');
|
|
161
|
+
});
|
|
162
|
+
// TBP-575 — a flag changed on the wire. Two caches need to hear about it and
|
|
163
|
+
// only one of them was ever told:
|
|
164
|
+
// - BridgeFlags (FF 2.0) — driven by realtime already, via attach()
|
|
165
|
+
// - FeatureFlagService — what ROUTE GUARDS read, 5-min TTL, deaf
|
|
166
|
+
// Invalidating here is what makes a route flag take effect on the next
|
|
167
|
+
// navigation instead of up to five minutes later.
|
|
168
|
+
_realtime.setOnFlagChange?.((change) => {
|
|
169
|
+
try {
|
|
170
|
+
getBridgeAuth().invalidateFeatureFlagCache();
|
|
171
|
+
}
|
|
172
|
+
catch { /* auth instance may not exist yet; next hydrate covers it */ }
|
|
173
|
+
for (const fn of _onFlagChangeSubs) {
|
|
174
|
+
try {
|
|
175
|
+
fn(change);
|
|
176
|
+
}
|
|
177
|
+
catch { /* subscriber errors swallowed */ }
|
|
178
|
+
}
|
|
179
|
+
});
|
|
149
180
|
_realtime.setOnSnapshot((msg) => {
|
|
150
181
|
try {
|
|
151
182
|
applySessionSnapshot(msg.data);
|
|
@@ -300,6 +331,16 @@ export function onBridgeRealtimeClose(handler) {
|
|
|
300
331
|
_onCloseSubs.add(handler);
|
|
301
332
|
return () => _onCloseSubs.delete(handler);
|
|
302
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Subscribe to realtime flag mutations (TBP-575). Returns an unsubscribe fn.
|
|
336
|
+
*
|
|
337
|
+
* The route-guard cache is already invalidated before subscribers run, so a
|
|
338
|
+
* handler that re-evaluates a route will read fresh values.
|
|
339
|
+
*/
|
|
340
|
+
export function onBridgeFlagChange(handler) {
|
|
341
|
+
_onFlagChangeSubs.add(handler);
|
|
342
|
+
return () => _onFlagChangeSubs.delete(handler);
|
|
343
|
+
}
|
|
303
344
|
/** Subscribe to `session.snapshot` messages. Returns an unsubscribe fn. */
|
|
304
345
|
export function onBridgeRealtimeSnapshot(handler) {
|
|
305
346
|
_onSnapshotSubs.add(handler);
|
|
@@ -321,6 +362,7 @@ export function __resetBridgeRuntime() {
|
|
|
321
362
|
_onOpenSubs.clear();
|
|
322
363
|
_onCloseSubs.clear();
|
|
323
364
|
_onSnapshotSubs.clear();
|
|
365
|
+
_onFlagChangeSubs.clear();
|
|
324
366
|
_onUserStateSubs.clear();
|
|
325
367
|
_currentAuthToken = undefined;
|
|
326
368
|
if (_unsubscribeAuth) {
|
|
@@ -11,6 +11,9 @@ let _tokenStore;
|
|
|
11
11
|
let _onOpen;
|
|
12
12
|
let _onClose;
|
|
13
13
|
let _onSnapshot;
|
|
14
|
+
let _onDegraded;
|
|
15
|
+
let _invalidateCalls = 0;
|
|
16
|
+
let _onFlagChange;
|
|
14
17
|
let _onUserState;
|
|
15
18
|
const _channelScopeCalls = [];
|
|
16
19
|
const _reauthCalls = [];
|
|
@@ -20,6 +23,9 @@ let _capturedRealtimeConfig;
|
|
|
20
23
|
// Reset the spy state between tests.
|
|
21
24
|
function resetSpies() {
|
|
22
25
|
_onOpen = _onClose = _onSnapshot = _onUserState = undefined;
|
|
26
|
+
_onDegraded = undefined;
|
|
27
|
+
_onFlagChange = undefined;
|
|
28
|
+
_invalidateCalls = 0;
|
|
23
29
|
_channelScopeCalls.length = 0;
|
|
24
30
|
_reauthCalls.length = 0;
|
|
25
31
|
_startCalls = 0;
|
|
@@ -34,6 +40,7 @@ vi.mock('./bridge-instance.js', () => ({
|
|
|
34
40
|
getBridgeAuth: () => ({
|
|
35
41
|
getApiContext: () => ({ appId: 'app-1', accessToken: null }),
|
|
36
42
|
refreshTokens: async () => { },
|
|
43
|
+
invalidateFeatureFlagCache: () => { _invalidateCalls += 1; },
|
|
37
44
|
}),
|
|
38
45
|
}));
|
|
39
46
|
vi.mock('../client/stores/config.store.js', () => ({
|
|
@@ -53,6 +60,8 @@ vi.mock('@nebulr-group/bridge-auth-core', () => {
|
|
|
53
60
|
setOnOpen(fn) { _onOpen = fn; }
|
|
54
61
|
setOnClose(fn) { _onClose = fn; }
|
|
55
62
|
setOnSnapshot(fn) { _onSnapshot = fn; }
|
|
63
|
+
setOnDegraded(fn) { _onDegraded = fn; }
|
|
64
|
+
setOnFlagChange(fn) { _onFlagChange = fn; }
|
|
56
65
|
setOnUserState(fn) { _onUserState = fn; }
|
|
57
66
|
setAppId(v) { _channelScopeCalls.push({ method: 'setAppId', value: v }); }
|
|
58
67
|
setWorkspaceId(v) { _channelScopeCalls.push({ method: 'setWorkspaceId', value: v }); }
|
|
@@ -80,7 +89,7 @@ afterEach(async () => {
|
|
|
80
89
|
__resetBridgeRuntime();
|
|
81
90
|
});
|
|
82
91
|
// ── Imports under test ─────────────────────────────────────────────────────
|
|
83
|
-
import { startBridgeRuntime, stopBridgeRuntime, getBridgeRealtime, onBridgeRealtimeOpen, onBridgeRealtimeClose, onBridgeRealtimeSnapshot, onBridgeRealtimeUserState, } from './bridge-runtime.js';
|
|
92
|
+
import { startBridgeRuntime, stopBridgeRuntime, getBridgeRealtime, onBridgeRealtimeOpen, onBridgeRealtimeClose, onBridgeRealtimeSnapshot, onBridgeRealtimeUserState, onBridgeFlagChange, } from './bridge-runtime.js';
|
|
84
93
|
import { realtimeStatus } from './realtime-status.js';
|
|
85
94
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
86
95
|
function makeJwt(claims) {
|
|
@@ -201,3 +210,59 @@ describe('stopBridgeRuntime', () => {
|
|
|
201
210
|
await expect(stopBridgeRuntime()).resolves.not.toThrow();
|
|
202
211
|
});
|
|
203
212
|
});
|
|
213
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
214
|
+
// TBP-575 — route flags were never push-updated.
|
|
215
|
+
//
|
|
216
|
+
// Route guards read FeatureFlagService (a 5-minute TTL cache fed by
|
|
217
|
+
// bulkEvaluate). `<FeatureFlag>` reads BridgeFlags. Realtime only ever wrote to
|
|
218
|
+
// the second one, so a flag flip took up to five minutes to affect a route —
|
|
219
|
+
// not because the TTL was wrong, but because nothing told that cache anything
|
|
220
|
+
// had changed.
|
|
221
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
222
|
+
describe('realtime flag changes reach the route-guard cache (TBP-575)', () => {
|
|
223
|
+
it('invalidates the route-guard flag cache on a flag mutation', () => {
|
|
224
|
+
startBridgeRuntime();
|
|
225
|
+
expect(_invalidateCalls).toBe(0);
|
|
226
|
+
_onFlagChange?.({ key: 'holo-experimental', kind: 'updated' });
|
|
227
|
+
expect(_invalidateCalls).toBe(1);
|
|
228
|
+
});
|
|
229
|
+
it('invalidates on removal too — a deleted flag changes route verdicts', () => {
|
|
230
|
+
startBridgeRuntime();
|
|
231
|
+
_onFlagChange?.({ key: 'holo-experimental', kind: 'removed' });
|
|
232
|
+
expect(_invalidateCalls).toBe(1);
|
|
233
|
+
});
|
|
234
|
+
it('fans the change out to subscribers so they can re-evaluate the route', () => {
|
|
235
|
+
startBridgeRuntime();
|
|
236
|
+
const seen = [];
|
|
237
|
+
const off = onBridgeFlagChange((c) => seen.push(c));
|
|
238
|
+
_onFlagChange?.({ key: 'holo-experimental', kind: 'updated' });
|
|
239
|
+
expect(seen).toEqual([{ key: 'holo-experimental', kind: 'updated' }]);
|
|
240
|
+
off();
|
|
241
|
+
_onFlagChange?.({ key: 'other', kind: 'updated' });
|
|
242
|
+
expect(seen).toHaveLength(1);
|
|
243
|
+
});
|
|
244
|
+
it('invalidates BEFORE notifying subscribers, so a re-check reads fresh values', () => {
|
|
245
|
+
startBridgeRuntime();
|
|
246
|
+
let invalidatedWhenNotified = -1;
|
|
247
|
+
onBridgeFlagChange(() => { invalidatedWhenNotified = _invalidateCalls; });
|
|
248
|
+
_onFlagChange?.({ key: 'x', kind: 'updated' });
|
|
249
|
+
expect(invalidatedWhenNotified).toBe(1);
|
|
250
|
+
});
|
|
251
|
+
it('a throwing subscriber does not stop the others', () => {
|
|
252
|
+
startBridgeRuntime();
|
|
253
|
+
let reached = false;
|
|
254
|
+
onBridgeFlagChange(() => { throw new Error('boom'); });
|
|
255
|
+
onBridgeFlagChange(() => { reached = true; });
|
|
256
|
+
_onFlagChange?.({ key: 'x', kind: 'updated' });
|
|
257
|
+
expect(reached).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
describe('degraded realtime is reported as degraded, not open (TBP-575)', () => {
|
|
261
|
+
it('mirrors the degraded state into realtimeStatus', () => {
|
|
262
|
+
startBridgeRuntime();
|
|
263
|
+
_onDegraded?.();
|
|
264
|
+
// A socket that is connected but subscribed to nothing used to report
|
|
265
|
+
// 'open' — which is exactly how a dead transport passed for healthy.
|
|
266
|
+
expect(get(realtimeStatus)).toBe('degraded');
|
|
267
|
+
});
|
|
268
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export { sha256Email } from './client/tracking/pii-hashing.js';
|
|
|
41
41
|
export type { AuthConfigResponse, AuthResult, AuthState, BridgeAuthConfig, BridgeAuthEventName, BridgeAuthEvents, FederationConnection, MagicLinkResult, MfaResult, PasskeyAuthOptions, PasskeyRegistrationOptions, PasskeyVerificationResult, SignupResult, SsoOptions, SsoResult, TenantUser, } from '@nebulr-group/bridge-auth-core';
|
|
42
42
|
export { BridgeAuth, BridgeAuthError, HttpError, TeamService, ApiTokenService } from '@nebulr-group/bridge-auth-core';
|
|
43
43
|
export type { SessionStalePayload } from '@nebulr-group/bridge-auth-core';
|
|
44
|
+
export type { QuotaSnapshot } from '@nebulr-group/bridge-auth-core';
|
|
44
45
|
export type { ApiToken, CreateApiTokenInput, CreateApiTokenResponse, } from '@nebulr-group/bridge-auth-core';
|
|
45
46
|
export type { TeamProfile, TeamProfileUpdateInput, TeamUser, TeamUserListResult, TeamUserUpdateInput, TeamWorkspace, TeamWorkspaceUpdateInput, } from '@nebulr-group/bridge-auth-core';
|
|
46
47
|
export type { Plan, PriceOfferSdk, SubscriptionStatus, CheckoutSession, Workspace, } from '@nebulr-group/bridge-auth-core';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebulr-group/bridge-svelte",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Bridge Svelte library, This library helps you to add bridge authentication and feature flags, and payments to your svelte application.",
|
|
5
5
|
"author": "Iman Pouya",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@simplewebauthn/browser": "^13.0.0",
|
|
67
|
-
"@nebulr-group/bridge-auth-core": "0.4.
|
|
67
|
+
"@nebulr-group/bridge-auth-core": "0.4.3"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@sveltejs/kit": "^2.16.0",
|