@nominalso/vibe-auth 0.2.0 → 0.2.2
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/AGENTS.md +3 -2
- package/README.md +5 -3
- package/dist/index.cjs +50 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -6
- package/dist/index.d.ts +10 -6
- package/dist/index.js +50 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -222,7 +222,7 @@ interface AuthGateProps {
|
|
|
222
222
|
children: ReactNode;
|
|
223
223
|
/** Override the default sign-in screen (branding). Defaults to `DefaultSignInScreen`. */
|
|
224
224
|
signInScreen?: ReactNode;
|
|
225
|
-
/** Override the default full-screen loader. Defaults to a bare "
|
|
225
|
+
/** Override the default full-screen loader. Defaults to a bare "Fetching your data…" spinner-less div. */
|
|
226
226
|
loader?: ReactNode;
|
|
227
227
|
}
|
|
228
228
|
interface AuthGateBundle {
|
|
@@ -278,8 +278,10 @@ interface VibeAuth {
|
|
|
278
278
|
wireHostAuth(bridge: AuthBridgeLike): () => void;
|
|
279
279
|
/**
|
|
280
280
|
* Seed `{userId, tenant}` from `connect()`. Call from a **parent** of
|
|
281
|
-
* `AuthGate` after `connect()` resolves.
|
|
282
|
-
*
|
|
281
|
+
* `AuthGate` after `connect()` resolves. Start `connect()` at module scope,
|
|
282
|
+
* not in a React effect, so the gate receives the live host principal as
|
|
283
|
+
* early as possible. It then opens without rebind when the bound-user
|
|
284
|
+
* marker already matches.
|
|
283
285
|
*/
|
|
284
286
|
seedLastUserId(userId: string, tenant: string): void;
|
|
285
287
|
/** The postMessage type relayed by `SilentCallback` — do not change if migrating an existing app. */
|
|
@@ -325,10 +327,12 @@ interface VibeAuth {
|
|
|
325
327
|
* ```
|
|
326
328
|
*
|
|
327
329
|
* ```ts
|
|
328
|
-
* //
|
|
330
|
+
* // Start the host handshake at module scope, before React mounts.
|
|
329
331
|
* const unsub = auth.wireHostAuth(bridge)
|
|
330
|
-
* const
|
|
331
|
-
*
|
|
332
|
+
* export const hostContext = bridge.connect().then((ctx) => {
|
|
333
|
+
* auth.seedLastUserId(ctx.user.id, ctx.tenant)
|
|
334
|
+
* return ctx
|
|
335
|
+
* })
|
|
332
336
|
* ```
|
|
333
337
|
*/
|
|
334
338
|
declare function createVibeAuth(userConfig: VibeAuthConfig): VibeAuth;
|
package/dist/index.d.ts
CHANGED
|
@@ -222,7 +222,7 @@ interface AuthGateProps {
|
|
|
222
222
|
children: ReactNode;
|
|
223
223
|
/** Override the default sign-in screen (branding). Defaults to `DefaultSignInScreen`. */
|
|
224
224
|
signInScreen?: ReactNode;
|
|
225
|
-
/** Override the default full-screen loader. Defaults to a bare "
|
|
225
|
+
/** Override the default full-screen loader. Defaults to a bare "Fetching your data…" spinner-less div. */
|
|
226
226
|
loader?: ReactNode;
|
|
227
227
|
}
|
|
228
228
|
interface AuthGateBundle {
|
|
@@ -278,8 +278,10 @@ interface VibeAuth {
|
|
|
278
278
|
wireHostAuth(bridge: AuthBridgeLike): () => void;
|
|
279
279
|
/**
|
|
280
280
|
* Seed `{userId, tenant}` from `connect()`. Call from a **parent** of
|
|
281
|
-
* `AuthGate` after `connect()` resolves.
|
|
282
|
-
*
|
|
281
|
+
* `AuthGate` after `connect()` resolves. Start `connect()` at module scope,
|
|
282
|
+
* not in a React effect, so the gate receives the live host principal as
|
|
283
|
+
* early as possible. It then opens without rebind when the bound-user
|
|
284
|
+
* marker already matches.
|
|
283
285
|
*/
|
|
284
286
|
seedLastUserId(userId: string, tenant: string): void;
|
|
285
287
|
/** The postMessage type relayed by `SilentCallback` — do not change if migrating an existing app. */
|
|
@@ -325,10 +327,12 @@ interface VibeAuth {
|
|
|
325
327
|
* ```
|
|
326
328
|
*
|
|
327
329
|
* ```ts
|
|
328
|
-
* //
|
|
330
|
+
* // Start the host handshake at module scope, before React mounts.
|
|
329
331
|
* const unsub = auth.wireHostAuth(bridge)
|
|
330
|
-
* const
|
|
331
|
-
*
|
|
332
|
+
* export const hostContext = bridge.connect().then((ctx) => {
|
|
333
|
+
* auth.seedLastUserId(ctx.user.id, ctx.tenant)
|
|
334
|
+
* return ctx
|
|
335
|
+
* })
|
|
332
336
|
* ```
|
|
333
337
|
*/
|
|
334
338
|
declare function createVibeAuth(userConfig: VibeAuthConfig): VibeAuth;
|
package/dist/index.js
CHANGED
|
@@ -47,6 +47,7 @@ function assertPkce(supabase) {
|
|
|
47
47
|
var RESULT_MESSAGE_TYPE = "silent-auth-result";
|
|
48
48
|
|
|
49
49
|
// src/silentAuth.ts
|
|
50
|
+
var SIBLING_SESSION_SYNC_MS = 50;
|
|
50
51
|
var AuthResultKind = /* @__PURE__ */ ((AuthResultKind2) => {
|
|
51
52
|
AuthResultKind2["Code"] = "code";
|
|
52
53
|
return AuthResultKind2;
|
|
@@ -59,7 +60,7 @@ function parseCallbackMessage(data) {
|
|
|
59
60
|
}
|
|
60
61
|
return null;
|
|
61
62
|
}
|
|
62
|
-
function createSilentAuth(config, getHostPrincipal) {
|
|
63
|
+
function createSilentAuth(config, getHostPrincipal, onBound) {
|
|
63
64
|
const { supabase, provider, callbackPath, timeouts, lockName, boundUserKey } = config;
|
|
64
65
|
async function getSessionUserId() {
|
|
65
66
|
const { data } = await supabase.auth.getSession();
|
|
@@ -68,6 +69,11 @@ function createSilentAuth(config, getHostPrincipal) {
|
|
|
68
69
|
async function hasValidSession() {
|
|
69
70
|
return await getSessionUserId() !== null;
|
|
70
71
|
}
|
|
72
|
+
async function waitForSiblingSession() {
|
|
73
|
+
if (await hasValidSession()) return true;
|
|
74
|
+
await new Promise((resolve) => setTimeout(resolve, SIBLING_SESSION_SYNC_MS));
|
|
75
|
+
return hasValidSession();
|
|
76
|
+
}
|
|
71
77
|
function readBoundMarker() {
|
|
72
78
|
if (typeof window === "undefined") return null;
|
|
73
79
|
try {
|
|
@@ -92,7 +98,10 @@ function createSilentAuth(config, getHostPrincipal) {
|
|
|
92
98
|
if (priorUserId !== null && sessionUserId === priorUserId) return false;
|
|
93
99
|
}
|
|
94
100
|
const host = getHostPrincipal?.();
|
|
95
|
-
if (host)
|
|
101
|
+
if (host) {
|
|
102
|
+
writeBoundMarker(host);
|
|
103
|
+
onBound?.(host);
|
|
104
|
+
}
|
|
96
105
|
return true;
|
|
97
106
|
}
|
|
98
107
|
function runHiddenAuthFrame(url) {
|
|
@@ -190,7 +199,7 @@ function createSilentAuth(config, getHostPrincipal) {
|
|
|
190
199
|
if (await hasValidSession()) return true;
|
|
191
200
|
return withSsoLock(
|
|
192
201
|
async () => {
|
|
193
|
-
if (await
|
|
202
|
+
if (await waitForSiblingSession()) return true;
|
|
194
203
|
if (await trySilentSignIn()) return true;
|
|
195
204
|
return hasValidSession();
|
|
196
205
|
},
|
|
@@ -202,7 +211,14 @@ function createSilentAuth(config, getHostPrincipal) {
|
|
|
202
211
|
if (typeof window === "undefined") return false;
|
|
203
212
|
const siblingRebound = async () => {
|
|
204
213
|
const m = readBoundMarker();
|
|
205
|
-
|
|
214
|
+
if (m?.userId !== principal.userId) return false;
|
|
215
|
+
if (m.tenant !== principal.tenant) return false;
|
|
216
|
+
if (Date.now() - (m.at ?? 0) >= timeouts.lockWaitMs) return false;
|
|
217
|
+
if (!await waitForSiblingSession()) return false;
|
|
218
|
+
const confirmed = readBoundMarker();
|
|
219
|
+
if (confirmed?.userId !== principal.userId) return false;
|
|
220
|
+
if (confirmed.tenant !== principal.tenant) return false;
|
|
221
|
+
return Date.now() - (confirmed.at ?? 0) < timeouts.lockWaitMs;
|
|
206
222
|
};
|
|
207
223
|
return withSsoLock(
|
|
208
224
|
async () => {
|
|
@@ -460,7 +476,7 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
460
476
|
"div",
|
|
461
477
|
{
|
|
462
478
|
style: { display: "grid", placeItems: "center", height: "100vh", fontFamily: "sans-serif" },
|
|
463
|
-
children: "
|
|
479
|
+
children: "Fetching your data\u2026"
|
|
464
480
|
}
|
|
465
481
|
);
|
|
466
482
|
}
|
|
@@ -505,12 +521,19 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
505
521
|
let oauthTimer;
|
|
506
522
|
let hostPrincipal;
|
|
507
523
|
let applyGen = 0;
|
|
524
|
+
let pendingSessionRead;
|
|
508
525
|
let acceptPrincipalUpdates = !hostAuth?.isHostWired();
|
|
509
526
|
const applyPrincipal = async (principal) => {
|
|
510
527
|
hostPrincipal = principal;
|
|
511
528
|
const gen = ++applyGen;
|
|
512
529
|
setStatus("checking" /* Checking */);
|
|
513
|
-
const
|
|
530
|
+
const prefetchedSession = pendingSessionRead;
|
|
531
|
+
pendingSessionRead = void 0;
|
|
532
|
+
const inFlightPrefetch = prefetchedSession && !prefetchedSession.settled ? prefetchedSession : void 0;
|
|
533
|
+
let sessionOk = inFlightPrefetch ? await inFlightPrefetch.promise : await silentAuth.hasValidSession().catch(() => false);
|
|
534
|
+
if (inFlightPrefetch) {
|
|
535
|
+
sessionOk = await silentAuth.hasValidSession().catch(() => false);
|
|
536
|
+
}
|
|
514
537
|
if (cancelled || gen !== applyGen) return;
|
|
515
538
|
if (sessionOk && silentAuth.isBoundTo(principal)) {
|
|
516
539
|
setStatus("authenticated" /* Authenticated */);
|
|
@@ -544,9 +567,18 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
544
567
|
}
|
|
545
568
|
if (hostAuth?.isHostWired()) {
|
|
546
569
|
acceptPrincipalUpdates = true;
|
|
570
|
+
const sessionRead = {
|
|
571
|
+
promise: silentAuth.hasValidSession().catch(() => false),
|
|
572
|
+
settled: false
|
|
573
|
+
};
|
|
574
|
+
void sessionRead.promise.finally(() => {
|
|
575
|
+
sessionRead.settled = true;
|
|
576
|
+
});
|
|
577
|
+
pendingSessionRead = sessionRead;
|
|
547
578
|
const next = await hostAuth.waitForSeededPrincipal(timeouts.oauthCompletionMs);
|
|
548
579
|
if (cancelled) return;
|
|
549
580
|
if (next && applyGen === 0) await applyPrincipal(next);
|
|
581
|
+
if (pendingSessionRead === sessionRead) pendingSessionRead = void 0;
|
|
550
582
|
if (applyGen === 0) setStatus("unauthenticated" /* Unauthenticated */);
|
|
551
583
|
return;
|
|
552
584
|
}
|
|
@@ -563,14 +595,19 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
563
595
|
});
|
|
564
596
|
const onStorage = (e) => {
|
|
565
597
|
if (cancelled) return;
|
|
566
|
-
if (!e.key?.endsWith("-auth-token")
|
|
598
|
+
if (!e.key?.endsWith("-auth-token")) return;
|
|
599
|
+
pendingSessionRead = void 0;
|
|
600
|
+
if (!e.newValue) return;
|
|
567
601
|
void openIfBound();
|
|
568
602
|
};
|
|
569
603
|
window.addEventListener("storage", onStorage);
|
|
570
604
|
const { data: sub } = supabase.auth.onAuthStateChange((event, session) => {
|
|
571
605
|
if (cancelled || event === "INITIAL_SESSION") return;
|
|
572
606
|
if (session) void openIfBound();
|
|
573
|
-
else
|
|
607
|
+
else {
|
|
608
|
+
pendingSessionRead = void 0;
|
|
609
|
+
setStatus((s) => s === "authenticated" /* Authenticated */ ? "unauthenticated" /* Unauthenticated */ : s);
|
|
610
|
+
}
|
|
574
611
|
});
|
|
575
612
|
return () => {
|
|
576
613
|
cancelled = true;
|
|
@@ -658,7 +695,11 @@ function createVibeAuth(userConfig) {
|
|
|
658
695
|
const config = resolveConfig(userConfig);
|
|
659
696
|
assertPkce(config.supabase);
|
|
660
697
|
let hostAuth;
|
|
661
|
-
const silentAuth = createSilentAuth(
|
|
698
|
+
const silentAuth = createSilentAuth(
|
|
699
|
+
config,
|
|
700
|
+
() => hostAuth.getPrincipal(),
|
|
701
|
+
(principal) => hostAuth.seedLastUserId(principal.userId, principal.tenant)
|
|
702
|
+
);
|
|
662
703
|
const interactiveAuth = createInteractiveAuth(config, silentAuth);
|
|
663
704
|
const { SilentCallback } = createCallbackHandler();
|
|
664
705
|
hostAuth = createHostAuth(config, silentAuth);
|