@nominalso/vibe-auth 0.2.1 → 0.2.3
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/index.cjs +70 -7
- 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 +70 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -147,6 +147,15 @@ interface SilentAuth {
|
|
|
147
147
|
* `rebindSession`.
|
|
148
148
|
*/
|
|
149
149
|
isBoundTo(principal: HostPrincipal): boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Record that the live session belongs to this principal.
|
|
152
|
+
*
|
|
153
|
+
* Normally `completeWithResult` does this after redeeming a code. The
|
|
154
|
+
* top-level redirect flow never routes through that function — the session
|
|
155
|
+
* arrives via supabase-js's `detectSessionInUrl` — so `AuthGate` stamps it
|
|
156
|
+
* there instead, and only for a `SIGNED_IN` it observed in that document.
|
|
157
|
+
*/
|
|
158
|
+
stampBoundMarker(principal: HostPrincipal): void;
|
|
150
159
|
clearBoundUser(): void;
|
|
151
160
|
/** @internal exposed for interactive.ts, which shares completeWithResult/parseCallbackMessage's private helpers */
|
|
152
161
|
completeWithResult(result: AuthResult): Promise<boolean>;
|
|
@@ -238,6 +247,97 @@ interface AuthGateBundle {
|
|
|
238
247
|
*/
|
|
239
248
|
declare function createAuthGate(config: ResolvedVibeAuthConfig, silentAuth: SilentAuth, interactiveAuth: InteractiveAuth, hostAuth?: HostAuth): AuthGateBundle;
|
|
240
249
|
|
|
250
|
+
/**
|
|
251
|
+
* The contract between this package and `@nominalso/vibe-bridge`, plus the
|
|
252
|
+
* shape `wireVibeApp` hands back.
|
|
253
|
+
*
|
|
254
|
+
* Its own module for the same reason as `principal.ts`: `wireVibeApp` and
|
|
255
|
+
* `createVibeAuth` both speak these types, and consumers re-export them, so
|
|
256
|
+
* putting them in the implementation would make every reader import the
|
|
257
|
+
* implementation to name a type.
|
|
258
|
+
*
|
|
259
|
+
* Everything here is **structural**. `@nominalso/vibe-auth` must keep zero
|
|
260
|
+
* dependency on `@nominalso/vibe-bridge` — they publish separately and version
|
|
261
|
+
* independently, and a hard dependency would couple their release cycles.
|
|
262
|
+
*/
|
|
263
|
+
/**
|
|
264
|
+
* The subset of `ContextPayload` this package reads. Apps get their own full
|
|
265
|
+
* `ContextPayload` back via the `Ctx` type parameter; structural assignability
|
|
266
|
+
* already allows the extra properties.
|
|
267
|
+
*
|
|
268
|
+
* Deliberately NO index signature. One here looks harmless but makes the
|
|
269
|
+
* constraint unsatisfiable by the very type it exists for: TypeScript does not
|
|
270
|
+
* give an `interface` an implicit index signature, so `ContextPayload` — an
|
|
271
|
+
* interface — failed `Ctx extends HostContextLike`.
|
|
272
|
+
*/
|
|
273
|
+
interface HostContextLike {
|
|
274
|
+
tenant: string;
|
|
275
|
+
user: {
|
|
276
|
+
id: string;
|
|
277
|
+
};
|
|
278
|
+
/** Set by the host only when it lets a user clear the app's data. */
|
|
279
|
+
enableDataReset?: boolean;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The subset of `VibeAppBridge` `wireVibeApp` needs.
|
|
283
|
+
*
|
|
284
|
+
* Deliberately does NOT extend `AuthBridgeLike`, i.e. does not require
|
|
285
|
+
* `onAuthChange`. That method is `@internal` in `@nominalso/vibe-bridge` and
|
|
286
|
+
* stripped from its published `.d.ts`, so a real `VibeAppBridge` does not
|
|
287
|
+
* satisfy a constraint that demands it — requiring it here would make
|
|
288
|
+
* `wireVibeApp(bridge)` reject the only argument it is ever passed.
|
|
289
|
+
*
|
|
290
|
+
* It still exists at runtime, and `wireVibeApp` is the sanctioned caller, so
|
|
291
|
+
* that one call casts to `AuthBridgeLike` locally.
|
|
292
|
+
*
|
|
293
|
+
* `onDataReset` / `onSubrouteRequest` are optional so an app on an older bridge
|
|
294
|
+
* still type-checks; `wireVibeApp` feature-detects both before calling them.
|
|
295
|
+
*/
|
|
296
|
+
interface VibeBridgeLike {
|
|
297
|
+
onContextChange(cb: (ctx: HostContextLike) => void): () => void;
|
|
298
|
+
connect(): Promise<HostContextLike>;
|
|
299
|
+
onDataReset?(cb: (payload: ResetPayloadLike) => void): () => void;
|
|
300
|
+
onSubrouteRequest?(cb: (subroute: string) => void): () => void;
|
|
301
|
+
}
|
|
302
|
+
/** Informational payload of a host `RESET_DATA` push. */
|
|
303
|
+
interface ResetPayloadLike {
|
|
304
|
+
reason?: string;
|
|
305
|
+
}
|
|
306
|
+
interface WireVibeAppOptions<Ctx extends HostContextLike> {
|
|
307
|
+
/**
|
|
308
|
+
* Extra work on each post-connect context push (subsidiary / period switch)
|
|
309
|
+
* — refetch scoped data here. The built-in store updates first, so
|
|
310
|
+
* `getHostContext()` already returns `next` by the time this runs.
|
|
311
|
+
*/
|
|
312
|
+
onContextChange?: (next: Ctx) => void;
|
|
313
|
+
/**
|
|
314
|
+
* Reset the app to its initial state. Wired only when the host actually
|
|
315
|
+
* enabled it (`ctx.enableDataReset`), so passing it is always safe.
|
|
316
|
+
*/
|
|
317
|
+
onDataReset?: (payload: ResetPayloadLike) => void;
|
|
318
|
+
/**
|
|
319
|
+
* Host-originated subroute pushes. Registering anything — even a no-op —
|
|
320
|
+
* suppresses the bridge's `history.pushState` fallback, which otherwise
|
|
321
|
+
* navigates a single-route iframe away and unmounts in-flight dialogs.
|
|
322
|
+
*/
|
|
323
|
+
onSubrouteRequest?: (subroute: string) => void;
|
|
324
|
+
}
|
|
325
|
+
interface VibeAppWiring<Ctx extends HostContextLike> {
|
|
326
|
+
/** The bridge you passed in, returned so one import covers the whole module. */
|
|
327
|
+
bridge: VibeBridgeLike;
|
|
328
|
+
/**
|
|
329
|
+
* Resolves the host context, or `null` when there is no host to reach — a
|
|
330
|
+
* standalone preview, the OAuth callback document, or SSR. **Never rejects:**
|
|
331
|
+
* a failed handshake is expected outside Nominal, and `AuthGate` falls back
|
|
332
|
+
* to interactive sign-in on its own.
|
|
333
|
+
*/
|
|
334
|
+
hostContext: Promise<Ctx | null>;
|
|
335
|
+
/** Current host context; `null` before connect resolves and when standalone. */
|
|
336
|
+
getHostContext(): Ctx | null;
|
|
337
|
+
/** Subscribe to context changes. Returns an unsubscribe function. */
|
|
338
|
+
subscribeHostContext(listener: (ctx: Ctx | null) => void): () => void;
|
|
339
|
+
}
|
|
340
|
+
|
|
241
341
|
interface VibeAuth {
|
|
242
342
|
/** True when a Supabase session already exists locally. */
|
|
243
343
|
hasValidSession(): Promise<boolean>;
|
|
@@ -269,11 +369,24 @@ interface VibeAuth {
|
|
|
269
369
|
DefaultSignInScreen: ReturnType<typeof createAuthGate>['DefaultSignInScreen'];
|
|
270
370
|
/** Mount this, and ONLY this, at `callbackPath` — ungated, outside `AuthGate`. */
|
|
271
371
|
SilentCallback: ReturnType<typeof createCallbackHandler>['SilentCallback'];
|
|
372
|
+
/**
|
|
373
|
+
* **The one call an embedded app needs.** Wires the bridge to this auth
|
|
374
|
+
* instance and starts the host handshake — `onContextChange`, `wireHostAuth`,
|
|
375
|
+
* the callback-document guard, `connect()`, and `seedLastUserId` — in the
|
|
376
|
+
* one correct order. Returns a context store plus the connect promise.
|
|
377
|
+
*
|
|
378
|
+
* Call it once, synchronously, at module scope of an eagerly imported module.
|
|
379
|
+
* Prefer this over calling `wireHostAuth` / `seedLastUserId` by hand.
|
|
380
|
+
*/
|
|
381
|
+
wireVibeApp<Ctx extends HostContextLike>(bridge: VibeBridgeLike, options?: WireVibeAppOptions<Ctx>): VibeAppWiring<Ctx>;
|
|
272
382
|
/**
|
|
273
383
|
* Wire the host's auth signal to this Supabase client. Call at module
|
|
274
384
|
* scope, BEFORE `bridge.connect()`, so `AuthGate` will not open on a
|
|
275
385
|
* leftover Supabase session before it knows the current Nominal user.
|
|
276
386
|
* Returns the unsubscribe function.
|
|
387
|
+
*
|
|
388
|
+
* Low-level: `wireVibeApp` already does this in the right order. Reach for
|
|
389
|
+
* this only when an app genuinely cannot hand its bridge over.
|
|
277
390
|
*/
|
|
278
391
|
wireHostAuth(bridge: AuthBridgeLike): () => void;
|
|
279
392
|
/**
|
|
@@ -339,4 +452,4 @@ declare function createVibeAuth(userConfig: VibeAuthConfig): VibeAuth;
|
|
|
339
452
|
|
|
340
453
|
declare const RESULT_MESSAGE_TYPE = "silent-auth-result";
|
|
341
454
|
|
|
342
|
-
export { type AuthBridgeLike, type AuthGateProps, type AuthResult, AuthResultKind, type HostPrincipal, RESULT_MESSAGE_TYPE, type ResolvedVibeAuthConfig, type ResolvedVibeAuthTimeouts, type SignInAuthenticated, type SignInFailed, SignInFailureReason, SignInKind, type SignInRedirecting, type SignInResult, type VibeAuth, type VibeAuthConfig, type VibeAuthTimeouts, createVibeAuth };
|
|
455
|
+
export { type AuthBridgeLike, type AuthGateProps, type AuthResult, AuthResultKind, type HostContextLike, type HostPrincipal, RESULT_MESSAGE_TYPE, type ResetPayloadLike, type ResolvedVibeAuthConfig, type ResolvedVibeAuthTimeouts, type SignInAuthenticated, type SignInFailed, SignInFailureReason, SignInKind, type SignInRedirecting, type SignInResult, type VibeAppWiring, type VibeAuth, type VibeAuthConfig, type VibeAuthTimeouts, type VibeBridgeLike, type WireVibeAppOptions, createVibeAuth };
|
package/dist/index.d.ts
CHANGED
|
@@ -147,6 +147,15 @@ interface SilentAuth {
|
|
|
147
147
|
* `rebindSession`.
|
|
148
148
|
*/
|
|
149
149
|
isBoundTo(principal: HostPrincipal): boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Record that the live session belongs to this principal.
|
|
152
|
+
*
|
|
153
|
+
* Normally `completeWithResult` does this after redeeming a code. The
|
|
154
|
+
* top-level redirect flow never routes through that function — the session
|
|
155
|
+
* arrives via supabase-js's `detectSessionInUrl` — so `AuthGate` stamps it
|
|
156
|
+
* there instead, and only for a `SIGNED_IN` it observed in that document.
|
|
157
|
+
*/
|
|
158
|
+
stampBoundMarker(principal: HostPrincipal): void;
|
|
150
159
|
clearBoundUser(): void;
|
|
151
160
|
/** @internal exposed for interactive.ts, which shares completeWithResult/parseCallbackMessage's private helpers */
|
|
152
161
|
completeWithResult(result: AuthResult): Promise<boolean>;
|
|
@@ -238,6 +247,97 @@ interface AuthGateBundle {
|
|
|
238
247
|
*/
|
|
239
248
|
declare function createAuthGate(config: ResolvedVibeAuthConfig, silentAuth: SilentAuth, interactiveAuth: InteractiveAuth, hostAuth?: HostAuth): AuthGateBundle;
|
|
240
249
|
|
|
250
|
+
/**
|
|
251
|
+
* The contract between this package and `@nominalso/vibe-bridge`, plus the
|
|
252
|
+
* shape `wireVibeApp` hands back.
|
|
253
|
+
*
|
|
254
|
+
* Its own module for the same reason as `principal.ts`: `wireVibeApp` and
|
|
255
|
+
* `createVibeAuth` both speak these types, and consumers re-export them, so
|
|
256
|
+
* putting them in the implementation would make every reader import the
|
|
257
|
+
* implementation to name a type.
|
|
258
|
+
*
|
|
259
|
+
* Everything here is **structural**. `@nominalso/vibe-auth` must keep zero
|
|
260
|
+
* dependency on `@nominalso/vibe-bridge` — they publish separately and version
|
|
261
|
+
* independently, and a hard dependency would couple their release cycles.
|
|
262
|
+
*/
|
|
263
|
+
/**
|
|
264
|
+
* The subset of `ContextPayload` this package reads. Apps get their own full
|
|
265
|
+
* `ContextPayload` back via the `Ctx` type parameter; structural assignability
|
|
266
|
+
* already allows the extra properties.
|
|
267
|
+
*
|
|
268
|
+
* Deliberately NO index signature. One here looks harmless but makes the
|
|
269
|
+
* constraint unsatisfiable by the very type it exists for: TypeScript does not
|
|
270
|
+
* give an `interface` an implicit index signature, so `ContextPayload` — an
|
|
271
|
+
* interface — failed `Ctx extends HostContextLike`.
|
|
272
|
+
*/
|
|
273
|
+
interface HostContextLike {
|
|
274
|
+
tenant: string;
|
|
275
|
+
user: {
|
|
276
|
+
id: string;
|
|
277
|
+
};
|
|
278
|
+
/** Set by the host only when it lets a user clear the app's data. */
|
|
279
|
+
enableDataReset?: boolean;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The subset of `VibeAppBridge` `wireVibeApp` needs.
|
|
283
|
+
*
|
|
284
|
+
* Deliberately does NOT extend `AuthBridgeLike`, i.e. does not require
|
|
285
|
+
* `onAuthChange`. That method is `@internal` in `@nominalso/vibe-bridge` and
|
|
286
|
+
* stripped from its published `.d.ts`, so a real `VibeAppBridge` does not
|
|
287
|
+
* satisfy a constraint that demands it — requiring it here would make
|
|
288
|
+
* `wireVibeApp(bridge)` reject the only argument it is ever passed.
|
|
289
|
+
*
|
|
290
|
+
* It still exists at runtime, and `wireVibeApp` is the sanctioned caller, so
|
|
291
|
+
* that one call casts to `AuthBridgeLike` locally.
|
|
292
|
+
*
|
|
293
|
+
* `onDataReset` / `onSubrouteRequest` are optional so an app on an older bridge
|
|
294
|
+
* still type-checks; `wireVibeApp` feature-detects both before calling them.
|
|
295
|
+
*/
|
|
296
|
+
interface VibeBridgeLike {
|
|
297
|
+
onContextChange(cb: (ctx: HostContextLike) => void): () => void;
|
|
298
|
+
connect(): Promise<HostContextLike>;
|
|
299
|
+
onDataReset?(cb: (payload: ResetPayloadLike) => void): () => void;
|
|
300
|
+
onSubrouteRequest?(cb: (subroute: string) => void): () => void;
|
|
301
|
+
}
|
|
302
|
+
/** Informational payload of a host `RESET_DATA` push. */
|
|
303
|
+
interface ResetPayloadLike {
|
|
304
|
+
reason?: string;
|
|
305
|
+
}
|
|
306
|
+
interface WireVibeAppOptions<Ctx extends HostContextLike> {
|
|
307
|
+
/**
|
|
308
|
+
* Extra work on each post-connect context push (subsidiary / period switch)
|
|
309
|
+
* — refetch scoped data here. The built-in store updates first, so
|
|
310
|
+
* `getHostContext()` already returns `next` by the time this runs.
|
|
311
|
+
*/
|
|
312
|
+
onContextChange?: (next: Ctx) => void;
|
|
313
|
+
/**
|
|
314
|
+
* Reset the app to its initial state. Wired only when the host actually
|
|
315
|
+
* enabled it (`ctx.enableDataReset`), so passing it is always safe.
|
|
316
|
+
*/
|
|
317
|
+
onDataReset?: (payload: ResetPayloadLike) => void;
|
|
318
|
+
/**
|
|
319
|
+
* Host-originated subroute pushes. Registering anything — even a no-op —
|
|
320
|
+
* suppresses the bridge's `history.pushState` fallback, which otherwise
|
|
321
|
+
* navigates a single-route iframe away and unmounts in-flight dialogs.
|
|
322
|
+
*/
|
|
323
|
+
onSubrouteRequest?: (subroute: string) => void;
|
|
324
|
+
}
|
|
325
|
+
interface VibeAppWiring<Ctx extends HostContextLike> {
|
|
326
|
+
/** The bridge you passed in, returned so one import covers the whole module. */
|
|
327
|
+
bridge: VibeBridgeLike;
|
|
328
|
+
/**
|
|
329
|
+
* Resolves the host context, or `null` when there is no host to reach — a
|
|
330
|
+
* standalone preview, the OAuth callback document, or SSR. **Never rejects:**
|
|
331
|
+
* a failed handshake is expected outside Nominal, and `AuthGate` falls back
|
|
332
|
+
* to interactive sign-in on its own.
|
|
333
|
+
*/
|
|
334
|
+
hostContext: Promise<Ctx | null>;
|
|
335
|
+
/** Current host context; `null` before connect resolves and when standalone. */
|
|
336
|
+
getHostContext(): Ctx | null;
|
|
337
|
+
/** Subscribe to context changes. Returns an unsubscribe function. */
|
|
338
|
+
subscribeHostContext(listener: (ctx: Ctx | null) => void): () => void;
|
|
339
|
+
}
|
|
340
|
+
|
|
241
341
|
interface VibeAuth {
|
|
242
342
|
/** True when a Supabase session already exists locally. */
|
|
243
343
|
hasValidSession(): Promise<boolean>;
|
|
@@ -269,11 +369,24 @@ interface VibeAuth {
|
|
|
269
369
|
DefaultSignInScreen: ReturnType<typeof createAuthGate>['DefaultSignInScreen'];
|
|
270
370
|
/** Mount this, and ONLY this, at `callbackPath` — ungated, outside `AuthGate`. */
|
|
271
371
|
SilentCallback: ReturnType<typeof createCallbackHandler>['SilentCallback'];
|
|
372
|
+
/**
|
|
373
|
+
* **The one call an embedded app needs.** Wires the bridge to this auth
|
|
374
|
+
* instance and starts the host handshake — `onContextChange`, `wireHostAuth`,
|
|
375
|
+
* the callback-document guard, `connect()`, and `seedLastUserId` — in the
|
|
376
|
+
* one correct order. Returns a context store plus the connect promise.
|
|
377
|
+
*
|
|
378
|
+
* Call it once, synchronously, at module scope of an eagerly imported module.
|
|
379
|
+
* Prefer this over calling `wireHostAuth` / `seedLastUserId` by hand.
|
|
380
|
+
*/
|
|
381
|
+
wireVibeApp<Ctx extends HostContextLike>(bridge: VibeBridgeLike, options?: WireVibeAppOptions<Ctx>): VibeAppWiring<Ctx>;
|
|
272
382
|
/**
|
|
273
383
|
* Wire the host's auth signal to this Supabase client. Call at module
|
|
274
384
|
* scope, BEFORE `bridge.connect()`, so `AuthGate` will not open on a
|
|
275
385
|
* leftover Supabase session before it knows the current Nominal user.
|
|
276
386
|
* Returns the unsubscribe function.
|
|
387
|
+
*
|
|
388
|
+
* Low-level: `wireVibeApp` already does this in the right order. Reach for
|
|
389
|
+
* this only when an app genuinely cannot hand its bridge over.
|
|
277
390
|
*/
|
|
278
391
|
wireHostAuth(bridge: AuthBridgeLike): () => void;
|
|
279
392
|
/**
|
|
@@ -339,4 +452,4 @@ declare function createVibeAuth(userConfig: VibeAuthConfig): VibeAuth;
|
|
|
339
452
|
|
|
340
453
|
declare const RESULT_MESSAGE_TYPE = "silent-auth-result";
|
|
341
454
|
|
|
342
|
-
export { type AuthBridgeLike, type AuthGateProps, type AuthResult, AuthResultKind, type HostPrincipal, RESULT_MESSAGE_TYPE, type ResolvedVibeAuthConfig, type ResolvedVibeAuthTimeouts, type SignInAuthenticated, type SignInFailed, SignInFailureReason, SignInKind, type SignInRedirecting, type SignInResult, type VibeAuth, type VibeAuthConfig, type VibeAuthTimeouts, createVibeAuth };
|
|
455
|
+
export { type AuthBridgeLike, type AuthGateProps, type AuthResult, AuthResultKind, type HostContextLike, type HostPrincipal, RESULT_MESSAGE_TYPE, type ResetPayloadLike, type ResolvedVibeAuthConfig, type ResolvedVibeAuthTimeouts, type SignInAuthenticated, type SignInFailed, SignInFailureReason, SignInKind, type SignInRedirecting, type SignInResult, type VibeAppWiring, type VibeAuth, type VibeAuthConfig, type VibeAuthTimeouts, type VibeBridgeLike, type WireVibeAppOptions, createVibeAuth };
|
package/dist/index.js
CHANGED
|
@@ -60,7 +60,7 @@ function parseCallbackMessage(data) {
|
|
|
60
60
|
}
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
|
-
function createSilentAuth(config, getHostPrincipal) {
|
|
63
|
+
function createSilentAuth(config, getHostPrincipal, onBound) {
|
|
64
64
|
const { supabase, provider, callbackPath, timeouts, lockName, boundUserKey } = config;
|
|
65
65
|
async function getSessionUserId() {
|
|
66
66
|
const { data } = await supabase.auth.getSession();
|
|
@@ -98,7 +98,10 @@ function createSilentAuth(config, getHostPrincipal) {
|
|
|
98
98
|
if (priorUserId !== null && sessionUserId === priorUserId) return false;
|
|
99
99
|
}
|
|
100
100
|
const host = getHostPrincipal?.();
|
|
101
|
-
if (host)
|
|
101
|
+
if (host) {
|
|
102
|
+
writeBoundMarker(host);
|
|
103
|
+
onBound?.(host);
|
|
104
|
+
}
|
|
102
105
|
return true;
|
|
103
106
|
}
|
|
104
107
|
function runHiddenAuthFrame(url) {
|
|
@@ -246,6 +249,7 @@ function createSilentAuth(config, getHostPrincipal) {
|
|
|
246
249
|
ensureSession,
|
|
247
250
|
rebindSession,
|
|
248
251
|
isBoundTo,
|
|
252
|
+
stampBoundMarker: writeBoundMarker,
|
|
249
253
|
clearBoundUser,
|
|
250
254
|
completeWithResult
|
|
251
255
|
};
|
|
@@ -520,6 +524,14 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
520
524
|
let applyGen = 0;
|
|
521
525
|
let pendingSessionRead;
|
|
522
526
|
let acceptPrincipalUpdates = !hostAuth?.isHostWired();
|
|
527
|
+
let redeemedHere = false;
|
|
528
|
+
const sessionBelongsTo = (principal, sessionOk) => {
|
|
529
|
+
if (!sessionOk) return false;
|
|
530
|
+
if (silentAuth.isBoundTo(principal)) return true;
|
|
531
|
+
if (!redeemedHere) return false;
|
|
532
|
+
silentAuth.stampBoundMarker(principal);
|
|
533
|
+
return true;
|
|
534
|
+
};
|
|
523
535
|
const applyPrincipal = async (principal) => {
|
|
524
536
|
hostPrincipal = principal;
|
|
525
537
|
const gen = ++applyGen;
|
|
@@ -532,10 +544,11 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
532
544
|
sessionOk = await silentAuth.hasValidSession().catch(() => false);
|
|
533
545
|
}
|
|
534
546
|
if (cancelled || gen !== applyGen) return;
|
|
535
|
-
if (
|
|
547
|
+
if (sessionBelongsTo(principal, sessionOk)) {
|
|
536
548
|
setStatus("authenticated" /* Authenticated */);
|
|
537
549
|
return;
|
|
538
550
|
}
|
|
551
|
+
if (RETURNING_FROM_OAUTH) return;
|
|
539
552
|
const ok = await silentAuth.rebindSession(principal).catch(() => false);
|
|
540
553
|
if (cancelled || gen !== applyGen) return;
|
|
541
554
|
if (!ok) void supabase.auth.signOut().catch(() => {
|
|
@@ -550,12 +563,13 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
550
563
|
if (principal) {
|
|
551
564
|
const sessionOk = await silentAuth.hasValidSession().catch(() => false);
|
|
552
565
|
if (cancelled || gen !== applyGen) return;
|
|
553
|
-
if (!(sessionOk
|
|
566
|
+
if (!sessionBelongsTo(principal, sessionOk)) return;
|
|
554
567
|
}
|
|
555
568
|
setStatus("authenticated" /* Authenticated */);
|
|
556
569
|
};
|
|
557
570
|
async function resolve() {
|
|
558
571
|
if (RETURNING_FROM_OAUTH) {
|
|
572
|
+
acceptPrincipalUpdates = true;
|
|
559
573
|
oauthTimer = window.setTimeout(() => {
|
|
560
574
|
if (!cancelled)
|
|
561
575
|
setStatus((s) => s === "checking" /* Checking */ ? "unauthenticated" /* Unauthenticated */ : s);
|
|
@@ -600,8 +614,10 @@ function createAuthGate(config, silentAuth, interactiveAuth, hostAuth) {
|
|
|
600
614
|
window.addEventListener("storage", onStorage);
|
|
601
615
|
const { data: sub } = supabase.auth.onAuthStateChange((event, session) => {
|
|
602
616
|
if (cancelled || event === "INITIAL_SESSION") return;
|
|
603
|
-
if (session)
|
|
604
|
-
|
|
617
|
+
if (session) {
|
|
618
|
+
if (RETURNING_FROM_OAUTH && event === "SIGNED_IN") redeemedHere = true;
|
|
619
|
+
void openIfBound();
|
|
620
|
+
} else {
|
|
605
621
|
pendingSessionRead = void 0;
|
|
606
622
|
setStatus((s) => s === "authenticated" /* Authenticated */ ? "unauthenticated" /* Unauthenticated */ : s);
|
|
607
623
|
}
|
|
@@ -687,12 +703,58 @@ function createHostAuth(config, silentAuth) {
|
|
|
687
703
|
};
|
|
688
704
|
}
|
|
689
705
|
|
|
706
|
+
// src/wireVibeApp.ts
|
|
707
|
+
function createWireVibeApp(config, hostAuth) {
|
|
708
|
+
return function wireVibeApp(bridge, options = {}) {
|
|
709
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
710
|
+
let current = null;
|
|
711
|
+
function publish(next) {
|
|
712
|
+
current = next;
|
|
713
|
+
for (const listener of [...listeners]) listener(next);
|
|
714
|
+
}
|
|
715
|
+
bridge.onContextChange((next) => {
|
|
716
|
+
publish(next);
|
|
717
|
+
options.onContextChange?.(next);
|
|
718
|
+
});
|
|
719
|
+
if (options.onSubrouteRequest) bridge.onSubrouteRequest?.(options.onSubrouteRequest);
|
|
720
|
+
hostAuth.wireHostAuth(bridge);
|
|
721
|
+
const noHost = typeof window === "undefined" || window.location.pathname === config.callbackPath;
|
|
722
|
+
const hostContext = noHost ? Promise.resolve(null) : bridge.connect().then((ctx) => {
|
|
723
|
+
const next = ctx;
|
|
724
|
+
hostAuth.seedLastUserId(next.user.id, next.tenant);
|
|
725
|
+
if (next.enableDataReset && options.onDataReset) {
|
|
726
|
+
bridge.onDataReset?.(options.onDataReset);
|
|
727
|
+
}
|
|
728
|
+
publish(next);
|
|
729
|
+
return next;
|
|
730
|
+
}).catch((error) => {
|
|
731
|
+
console.warn("[vibe-auth] host connect failed", error);
|
|
732
|
+
return null;
|
|
733
|
+
});
|
|
734
|
+
return {
|
|
735
|
+
bridge,
|
|
736
|
+
hostContext,
|
|
737
|
+
getHostContext: () => current,
|
|
738
|
+
subscribeHostContext: (listener) => {
|
|
739
|
+
listeners.add(listener);
|
|
740
|
+
return () => {
|
|
741
|
+
listeners.delete(listener);
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
690
748
|
// src/createVibeAuth.ts
|
|
691
749
|
function createVibeAuth(userConfig) {
|
|
692
750
|
const config = resolveConfig(userConfig);
|
|
693
751
|
assertPkce(config.supabase);
|
|
694
752
|
let hostAuth;
|
|
695
|
-
const silentAuth = createSilentAuth(
|
|
753
|
+
const silentAuth = createSilentAuth(
|
|
754
|
+
config,
|
|
755
|
+
() => hostAuth.getPrincipal(),
|
|
756
|
+
(principal) => hostAuth.seedLastUserId(principal.userId, principal.tenant)
|
|
757
|
+
);
|
|
696
758
|
const interactiveAuth = createInteractiveAuth(config, silentAuth);
|
|
697
759
|
const { SilentCallback } = createCallbackHandler();
|
|
698
760
|
hostAuth = createHostAuth(config, silentAuth);
|
|
@@ -713,6 +775,7 @@ function createVibeAuth(userConfig) {
|
|
|
713
775
|
AuthGate,
|
|
714
776
|
DefaultSignInScreen,
|
|
715
777
|
SilentCallback,
|
|
778
|
+
wireVibeApp: createWireVibeApp(config, hostAuth),
|
|
716
779
|
wireHostAuth,
|
|
717
780
|
seedLastUserId,
|
|
718
781
|
RESULT_MESSAGE_TYPE,
|