@nominalso/vibe-auth 0.2.2 → 0.2.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/AGENTS.md +22 -5
- package/README.md +17 -10
- package/dist/index.cjs +60 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -1
- package/dist/index.d.ts +114 -1
- package/dist/index.js +60 -4
- package/dist/index.js.map +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -39,11 +39,28 @@ export const auth = createVibeAuth({
|
|
|
39
39
|
render the app's provider tree there (see **Root wiring** below).
|
|
40
40
|
2. **The app root.** Wrap everything in `<auth.AuthGate>`. The app renders nothing of itself
|
|
41
41
|
until authenticated.
|
|
42
|
-
3. **The host bridge**, if embedded via `@nominalso/vibe-bridge`.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
3. **The host bridge**, if embedded via `@nominalso/vibe-bridge`. One call, at
|
|
43
|
+
module scope, from a **parent** of `AuthGate` — never a React effect, and
|
|
44
|
+
never a module reachable only through a `lazy()` chunk:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
export const bridge = new VibeAppBridge()
|
|
48
|
+
export const { hostContext, getHostContext, subscribeHostContext } =
|
|
49
|
+
auth.wireVibeApp<ContextPayload>(bridge)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`wireVibeApp` registers the context subscriber and host auth **before**
|
|
53
|
+
`connect()`, seeds the host principal `AuthGate` waits on **before**
|
|
54
|
+
publishing context, skips the handshake entirely in the callback document,
|
|
55
|
+
and resolves `null` rather than rejecting when there is no host. Pass
|
|
56
|
+
`onContextChange` / `onDataReset` / `onSubrouteRequest` as options in the
|
|
57
|
+
same call; each is a single subscriber, and routing them through here is what
|
|
58
|
+
guarantees exactly one of each.
|
|
59
|
+
|
|
60
|
+
`wireHostAuth` / `seedLastUserId` are the pre-0.2.3 hand-wiring. They are
|
|
61
|
+
still exported for compatibility, but new code should not call them — four of
|
|
62
|
+
seven migrated apps got that sequence wrong, each differently, which is why
|
|
63
|
+
`wireVibeApp` exists.
|
|
47
64
|
|
|
48
65
|
## Root wiring (SSR — React #418)
|
|
49
66
|
|
package/README.md
CHANGED
|
@@ -62,17 +62,22 @@ function Root() {
|
|
|
62
62
|
|
|
63
63
|
```ts
|
|
64
64
|
// Start the Nominal host handshake at module scope, before React mounts.
|
|
65
|
+
import { VibeAppBridge, type ContextPayload } from '@nominalso/vibe-bridge'
|
|
65
66
|
import { auth } from '@/lib/auth'
|
|
66
|
-
import { bridge } from './bridge'
|
|
67
67
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
})
|
|
68
|
+
export const bridge = new VibeAppBridge()
|
|
69
|
+
|
|
70
|
+
export const { hostContext, getHostContext, subscribeHostContext } =
|
|
71
|
+
auth.wireVibeApp<ContextPayload>(bridge)
|
|
73
72
|
```
|
|
74
73
|
|
|
75
|
-
|
|
74
|
+
One call does the whole handshake, in the one correct order: context subscriber
|
|
75
|
+
and host auth registered before `connect()`, the host principal seeded before
|
|
76
|
+
context is published, the handshake skipped in the callback document, and `null`
|
|
77
|
+
rather than a rejection when there is no host. Pass `onContextChange`,
|
|
78
|
+
`onDataReset` and `onSubrouteRequest` as options in the same call.
|
|
79
|
+
|
|
80
|
+
See [`AGENTS.md`](./AGENTS.md) for SSR root wiring and the full ordering rules.
|
|
76
81
|
Timeouts live on `VibeAuthTimeouts` in the package `.d.ts`.
|
|
77
82
|
|
|
78
83
|
## Why a client you own, not one this package creates
|
|
@@ -87,11 +92,13 @@ control**, not a preference (the `.d.ts` on `VibeAuthConfig` explains why).
|
|
|
87
92
|
|
|
88
93
|
- `ensureSession()` / `rebindSession({ userId, tenant })` — the cross-document-safe core (Web Lock
|
|
89
94
|
serialised, terminal-capped, sibling-adopting). Most apps never call these directly; the
|
|
90
|
-
gate and `
|
|
95
|
+
gate and `wireVibeApp` do.
|
|
91
96
|
- `AuthGate` / `DefaultSignInScreen` / `SilentCallback` — the three React pieces. Override
|
|
92
97
|
`signInScreen`/`loader` props on `AuthGate` for branding.
|
|
93
|
-
- `
|
|
94
|
-
identity/tenant switch).
|
|
98
|
+
- `wireVibeApp(bridge, options?)` — the Nominal-host integration in one call (handshake,
|
|
99
|
+
host context store, logout and identity/tenant switch). Call it at module scope from a
|
|
100
|
+
parent of `AuthGate`. (`wireHostAuth` / `seedLastUserId` are the pre-0.2.3 hand-wiring,
|
|
101
|
+
still exported for compatibility; new code should not use them.)
|
|
95
102
|
- Fully configurable timeouts (`VibeAuthTimeouts` in the `.d.ts`) — every value defaults to
|
|
96
103
|
what shipped after this flow's production incidents.
|
|
97
104
|
|
package/dist/index.cjs
CHANGED
|
@@ -278,6 +278,7 @@ function createSilentAuth(config, getHostPrincipal, onBound) {
|
|
|
278
278
|
ensureSession,
|
|
279
279
|
rebindSession,
|
|
280
280
|
isBoundTo,
|
|
281
|
+
stampBoundMarker: writeBoundMarker,
|
|
281
282
|
clearBoundUser,
|
|
282
283
|
completeWithResult
|
|
283
284
|
};
|
|
@@ -552,6 +553,14 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
552
553
|
let applyGen = 0;
|
|
553
554
|
let pendingSessionRead;
|
|
554
555
|
let acceptPrincipalUpdates = !hostAuth?.isHostWired();
|
|
556
|
+
let redeemedHere = false;
|
|
557
|
+
const sessionBelongsTo = (principal, sessionOk) => {
|
|
558
|
+
if (!sessionOk) return false;
|
|
559
|
+
if (silentAuth.isBoundTo(principal)) return true;
|
|
560
|
+
if (!redeemedHere) return false;
|
|
561
|
+
silentAuth.stampBoundMarker(principal);
|
|
562
|
+
return true;
|
|
563
|
+
};
|
|
555
564
|
const applyPrincipal = async (principal) => {
|
|
556
565
|
hostPrincipal = principal;
|
|
557
566
|
const gen = ++applyGen;
|
|
@@ -564,10 +573,11 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
564
573
|
sessionOk = await silentAuth.hasValidSession().catch(() => false);
|
|
565
574
|
}
|
|
566
575
|
if (cancelled || gen !== applyGen) return;
|
|
567
|
-
if (
|
|
576
|
+
if (sessionBelongsTo(principal, sessionOk)) {
|
|
568
577
|
setStatus("authenticated" /* Authenticated */);
|
|
569
578
|
return;
|
|
570
579
|
}
|
|
580
|
+
if (RETURNING_FROM_OAUTH) return;
|
|
571
581
|
const ok = await silentAuth.rebindSession(principal).catch(() => false);
|
|
572
582
|
if (cancelled || gen !== applyGen) return;
|
|
573
583
|
if (!ok) void supabase.auth.signOut().catch(() => {
|
|
@@ -582,12 +592,13 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
582
592
|
if (principal) {
|
|
583
593
|
const sessionOk = await silentAuth.hasValidSession().catch(() => false);
|
|
584
594
|
if (cancelled || gen !== applyGen) return;
|
|
585
|
-
if (!(sessionOk
|
|
595
|
+
if (!sessionBelongsTo(principal, sessionOk)) return;
|
|
586
596
|
}
|
|
587
597
|
setStatus("authenticated" /* Authenticated */);
|
|
588
598
|
};
|
|
589
599
|
async function resolve() {
|
|
590
600
|
if (RETURNING_FROM_OAUTH) {
|
|
601
|
+
acceptPrincipalUpdates = true;
|
|
591
602
|
oauthTimer = window.setTimeout(() => {
|
|
592
603
|
if (!cancelled)
|
|
593
604
|
setStatus((s) => s === "checking" /* Checking */ ? "unauthenticated" /* Unauthenticated */ : s);
|
|
@@ -632,8 +643,10 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
632
643
|
window.addEventListener("storage", onStorage);
|
|
633
644
|
const { data: sub } = supabase.auth.onAuthStateChange((event, session) => {
|
|
634
645
|
if (cancelled || event === "INITIAL_SESSION") return;
|
|
635
|
-
if (session)
|
|
636
|
-
|
|
646
|
+
if (session) {
|
|
647
|
+
if (RETURNING_FROM_OAUTH && event === "SIGNED_IN") redeemedHere = true;
|
|
648
|
+
void openIfBound();
|
|
649
|
+
} else {
|
|
637
650
|
pendingSessionRead = void 0;
|
|
638
651
|
setStatus((s) => s === "authenticated" /* Authenticated */ ? "unauthenticated" /* Unauthenticated */ : s);
|
|
639
652
|
}
|
|
@@ -719,6 +732,48 @@ function createHostAuth(config, silentAuth) {
|
|
|
719
732
|
};
|
|
720
733
|
}
|
|
721
734
|
|
|
735
|
+
// src/wireVibeApp.ts
|
|
736
|
+
function createWireVibeApp(config, hostAuth) {
|
|
737
|
+
return function wireVibeApp(bridge, options = {}) {
|
|
738
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
739
|
+
let current = null;
|
|
740
|
+
function publish(next) {
|
|
741
|
+
current = next;
|
|
742
|
+
for (const listener of [...listeners]) listener(next);
|
|
743
|
+
}
|
|
744
|
+
bridge.onContextChange((next) => {
|
|
745
|
+
publish(next);
|
|
746
|
+
options.onContextChange?.(next);
|
|
747
|
+
});
|
|
748
|
+
if (options.onSubrouteRequest) bridge.onSubrouteRequest?.(options.onSubrouteRequest);
|
|
749
|
+
hostAuth.wireHostAuth(bridge);
|
|
750
|
+
const noHost = typeof window === "undefined" || window.location.pathname === config.callbackPath;
|
|
751
|
+
const hostContext = noHost ? Promise.resolve(null) : bridge.connect().then((ctx) => {
|
|
752
|
+
const next = ctx;
|
|
753
|
+
hostAuth.seedLastUserId(next.user.id, next.tenant);
|
|
754
|
+
if (next.enableDataReset && options.onDataReset) {
|
|
755
|
+
bridge.onDataReset?.(options.onDataReset);
|
|
756
|
+
}
|
|
757
|
+
publish(next);
|
|
758
|
+
return next;
|
|
759
|
+
}).catch((error) => {
|
|
760
|
+
console.warn("[vibe-auth] host connect failed", error);
|
|
761
|
+
return null;
|
|
762
|
+
});
|
|
763
|
+
return {
|
|
764
|
+
bridge,
|
|
765
|
+
hostContext,
|
|
766
|
+
getHostContext: () => current,
|
|
767
|
+
subscribeHostContext: (listener) => {
|
|
768
|
+
listeners.add(listener);
|
|
769
|
+
return () => {
|
|
770
|
+
listeners.delete(listener);
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
|
|
722
777
|
// src/createVibeAuth.ts
|
|
723
778
|
function createVibeAuth(userConfig) {
|
|
724
779
|
const config = resolveConfig(userConfig);
|
|
@@ -749,6 +804,7 @@ function createVibeAuth(userConfig) {
|
|
|
749
804
|
AuthGate,
|
|
750
805
|
DefaultSignInScreen,
|
|
751
806
|
SilentCallback,
|
|
807
|
+
wireVibeApp: createWireVibeApp(config, hostAuth),
|
|
752
808
|
wireHostAuth,
|
|
753
809
|
seedLastUserId,
|
|
754
810
|
RESULT_MESSAGE_TYPE,
|