@lushly-dev/afd-auth 1.0.0 → 2.1.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/README.md +201 -0
- package/dist/adapters/better-auth.d.ts +45 -16
- package/dist/adapters/better-auth.d.ts.map +1 -1
- package/dist/adapters/better-auth.js +76 -28
- package/dist/adapters/better-auth.js.map +1 -1
- package/dist/adapters/convex.d.ts +29 -6
- package/dist/adapters/convex.d.ts.map +1 -1
- package/dist/adapters/convex.js +139 -56
- package/dist/adapters/convex.js.map +1 -1
- package/dist/adapters/mock.d.ts +16 -4
- package/dist/adapters/mock.d.ts.map +1 -1
- package/dist/adapters/mock.js +13 -11
- package/dist/adapters/mock.js.map +1 -1
- package/dist/adapters/provider-errors.d.ts +16 -0
- package/dist/adapters/provider-errors.d.ts.map +1 -0
- package/dist/adapters/provider-errors.js +27 -0
- package/dist/adapters/provider-errors.js.map +1 -0
- package/dist/command-names.d.ts +13 -0
- package/dist/command-names.d.ts.map +1 -0
- package/dist/command-names.js +13 -0
- package/dist/command-names.js.map +1 -0
- package/dist/commands.d.ts +11 -2
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +141 -9
- package/dist/commands.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -6
- package/dist/index.js.map +1 -1
- package/dist/listeners.d.ts +39 -0
- package/dist/listeners.d.ts.map +1 -0
- package/dist/listeners.js +72 -0
- package/dist/listeners.js.map +1 -0
- package/dist/middleware.d.ts +22 -3
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +32 -14
- package/dist/middleware.js.map +1 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +19 -5
- package/dist/react.js.map +1 -1
- package/dist/session-state.d.ts +19 -0
- package/dist/session-state.d.ts.map +1 -0
- package/dist/session-state.js +42 -0
- package/dist/session-state.js.map +1 -0
- package/dist/session-sync-message.d.ts +29 -0
- package/dist/session-sync-message.d.ts.map +1 -0
- package/dist/session-sync-message.js +27 -0
- package/dist/session-sync-message.js.map +1 -0
- package/dist/session-sync.d.ts +35 -8
- package/dist/session-sync.d.ts.map +1 -1
- package/dist/session-sync.js +178 -44
- package/dist/session-sync.js.map +1 -1
- package/dist/types.d.ts +35 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +27 -16
package/dist/commands.js
CHANGED
|
@@ -3,20 +3,24 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Requires optional peer dependencies: @lushly-dev/afd-server, zod
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import { defineCommand } from '@lushly-dev/afd-server';
|
|
6
|
+
// The transport-free entry keeps this module free of the MCP SDK and Node builtins.
|
|
7
|
+
import { defineCommand, failure, success, } from '@lushly-dev/afd-server/define';
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import { AuthAdapterError } from './errors.js';
|
|
10
|
+
import { areSessionStatesEqual } from './session-state.js';
|
|
11
|
+
const DEFAULT_SIGN_IN_TIMEOUT_MS = 10_000;
|
|
12
|
+
const SESSION_GET_SUGGESTION = 'Call auth-session-get to check whether the session is active';
|
|
10
13
|
/**
|
|
11
14
|
* Create AFD command definitions for auth operations.
|
|
12
15
|
*
|
|
13
16
|
* Returns ZodCommandDefinition[] that can be passed directly to `createMcpServer`.
|
|
14
17
|
* Requires `@lushly-dev/afd-server` and `zod` as peer dependencies.
|
|
15
18
|
*/
|
|
16
|
-
export function createAuthCommands(adapter) {
|
|
19
|
+
export function createAuthCommands(adapter, options = {}) {
|
|
20
|
+
const signInTimeoutMs = options.signInTimeoutMs ?? DEFAULT_SIGN_IN_TIMEOUT_MS;
|
|
17
21
|
const signIn = defineCommand({
|
|
18
22
|
name: 'auth-sign-in',
|
|
19
|
-
description: 'Sign in with credentials or OAuth provider',
|
|
23
|
+
description: 'Sign in with credentials or OAuth provider. Returns the new session, or the current state with a warning when sign-in continues at the provider (redirect) or has not completed yet.',
|
|
20
24
|
category: 'auth',
|
|
21
25
|
tags: ['auth', 'session'],
|
|
22
26
|
mutation: true,
|
|
@@ -35,12 +39,16 @@ export function createAuthCommands(adapter) {
|
|
|
35
39
|
}),
|
|
36
40
|
]),
|
|
37
41
|
async handler(input) {
|
|
42
|
+
const before = adapter.getSession();
|
|
43
|
+
// Subscribe before signIn() so a state change it triggers is not missed.
|
|
44
|
+
const watcher = watchAuthState(adapter);
|
|
38
45
|
try {
|
|
39
|
-
await adapter.signIn(input);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
const outcome = toOutcome(await adapter.signIn(input));
|
|
47
|
+
if (outcome?.kind === 'redirect' || outcome?.kind === 'pending') {
|
|
48
|
+
return incompleteSignIn(adapter.getSession(), input, outcome);
|
|
49
|
+
}
|
|
50
|
+
const settled = await watcher.settle(before, signInTimeoutMs);
|
|
51
|
+
return settledSignIn(adapter.getSession(), settled, input, signInTimeoutMs);
|
|
44
52
|
}
|
|
45
53
|
catch (error) {
|
|
46
54
|
if (error instanceof AuthAdapterError) {
|
|
@@ -53,6 +61,9 @@ export function createAuthCommands(adapter) {
|
|
|
53
61
|
}
|
|
54
62
|
throw error;
|
|
55
63
|
}
|
|
64
|
+
finally {
|
|
65
|
+
watcher.stop();
|
|
66
|
+
}
|
|
56
67
|
},
|
|
57
68
|
});
|
|
58
69
|
const signOut = defineCommand({
|
|
@@ -103,4 +114,125 @@ export function createAuthCommands(adapter) {
|
|
|
103
114
|
// Cast needed for heterogeneous generic command arrays (same pattern as todo example)
|
|
104
115
|
return [signIn, signOut, sessionGet];
|
|
105
116
|
}
|
|
117
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
118
|
+
// SIGN-IN RESULT
|
|
119
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
120
|
+
function describeMethod(input) {
|
|
121
|
+
return input.method === 'oauth' ? input.provider : 'credentials';
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Read the adapter's sign-in outcome. Adapters written before SignInOutcome
|
|
125
|
+
* resolve with nothing, and untyped ones may resolve with a provider
|
|
126
|
+
* response; neither counts as an outcome.
|
|
127
|
+
*/
|
|
128
|
+
function toOutcome(value) {
|
|
129
|
+
if (typeof value !== 'object' || value === null || !('kind' in value))
|
|
130
|
+
return undefined;
|
|
131
|
+
switch (value.kind) {
|
|
132
|
+
case 'signed-in':
|
|
133
|
+
return { kind: 'signed-in' };
|
|
134
|
+
case 'pending':
|
|
135
|
+
return { kind: 'pending' };
|
|
136
|
+
case 'redirect':
|
|
137
|
+
return 'url' in value && typeof value.url === 'string'
|
|
138
|
+
? { kind: 'redirect', url: value.url }
|
|
139
|
+
: { kind: 'redirect' };
|
|
140
|
+
default:
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function incompleteSignIn(state, input, outcome) {
|
|
145
|
+
const via = describeMethod(input);
|
|
146
|
+
if (outcome.kind === 'redirect') {
|
|
147
|
+
return success(state, {
|
|
148
|
+
reasoning: `Not signed in yet: sign-in via ${via} continues at the provider, and the session starts after the provider redirects back`,
|
|
149
|
+
warnings: [
|
|
150
|
+
{
|
|
151
|
+
code: 'SIGN_IN_REDIRECT',
|
|
152
|
+
message: `Sign-in via ${via} continues at the provider`,
|
|
153
|
+
severity: 'info',
|
|
154
|
+
...(outcome.url === undefined ? {} : { details: { url: outcome.url } }),
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
suggestions: [
|
|
158
|
+
'Complete sign-in at the provider',
|
|
159
|
+
'After the provider redirects back, call auth-session-get to confirm the session',
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return success(state, {
|
|
164
|
+
reasoning: `Not signed in yet: sign-in via ${via} needs another step, such as email verification, before a session exists`,
|
|
165
|
+
warnings: [
|
|
166
|
+
{
|
|
167
|
+
code: 'SIGN_IN_PENDING',
|
|
168
|
+
message: `Sign-in via ${via} needs another step before a session exists`,
|
|
169
|
+
severity: 'warning',
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
suggestions: ["Complete the provider's verification step", SESSION_GET_SUGGESTION],
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function settledSignIn(current, settled, input, timeoutMs) {
|
|
176
|
+
const via = describeMethod(input);
|
|
177
|
+
const state = settled ?? current;
|
|
178
|
+
if (state.status === 'authenticated') {
|
|
179
|
+
return success(state, {
|
|
180
|
+
reasoning: `Signed in via ${via} as ${state.user.email}`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (settled?.status === 'unauthenticated') {
|
|
184
|
+
return failure({
|
|
185
|
+
code: 'PROVIDER_ERROR',
|
|
186
|
+
message: `Sign-in via ${via} completed, but the provider reports no active session`,
|
|
187
|
+
suggestion: 'Check that the provider can store its session (cookies or tokens) in this environment, then sign in again',
|
|
188
|
+
retryable: false,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return success(current, {
|
|
192
|
+
reasoning: `Not signed in yet: the sign-in via ${via} was accepted, but no session was confirmed within ${timeoutMs} ms (status: ${current.status})`,
|
|
193
|
+
warnings: [
|
|
194
|
+
{
|
|
195
|
+
code: 'SIGN_IN_PENDING',
|
|
196
|
+
message: `No session confirmed within ${timeoutMs} ms of sign-in`,
|
|
197
|
+
severity: 'warning',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
suggestions: [SESSION_GET_SUGGESTION],
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function watchAuthState(adapter) {
|
|
204
|
+
let changed = false;
|
|
205
|
+
let onSettled = null;
|
|
206
|
+
const subscription = adapter.onAuthStateChange((state) => {
|
|
207
|
+
changed = true;
|
|
208
|
+
if (state.status !== 'loading' && onSettled) {
|
|
209
|
+
const resolve = onSettled;
|
|
210
|
+
onSettled = null;
|
|
211
|
+
resolve(state);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return {
|
|
215
|
+
settle(before, timeoutMs) {
|
|
216
|
+
const current = adapter.getSession();
|
|
217
|
+
if (current.status === 'authenticated' &&
|
|
218
|
+
(changed || !areSessionStatesEqual(before, current))) {
|
|
219
|
+
return Promise.resolve(current);
|
|
220
|
+
}
|
|
221
|
+
return new Promise((resolve) => {
|
|
222
|
+
const timer = setTimeout(() => {
|
|
223
|
+
onSettled = null;
|
|
224
|
+
resolve(null);
|
|
225
|
+
}, timeoutMs);
|
|
226
|
+
onSettled = (state) => {
|
|
227
|
+
clearTimeout(timer);
|
|
228
|
+
resolve(state);
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
stop() {
|
|
233
|
+
onSettled = null;
|
|
234
|
+
subscription.unsubscribe();
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
106
238
|
//# sourceMappingURL=commands.js.map
|
package/dist/commands.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,16 +5,17 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export type { BetterAuthAdapterOptions } from './adapters/better-auth.js';
|
|
7
7
|
export { BetterAuthAdapter } from './adapters/better-auth.js';
|
|
8
|
-
export type { ConvexAuthAdapterOptions } from './adapters/convex.js';
|
|
9
|
-
export { useConvexAuthAdapter } from './adapters/convex.js';
|
|
10
8
|
export type { MockAuthAdapterOptions } from './adapters/mock.js';
|
|
11
9
|
export { MockAuthAdapter } from './adapters/mock.js';
|
|
12
|
-
export {
|
|
10
|
+
export { AUTH_COMMAND_NAMES } from './command-names.js';
|
|
13
11
|
export type { AuthErrorCode } from './errors.js';
|
|
14
12
|
export { AuthAdapterError } from './errors.js';
|
|
13
|
+
export type { ListenerErrorHandler } from './listeners.js';
|
|
14
|
+
export type { AuthMiddlewareOptions } from './middleware.js';
|
|
15
15
|
export { createAuthMiddleware } from './middleware.js';
|
|
16
16
|
export type { SessionSyncOptions } from './session-sync.js';
|
|
17
17
|
export { SessionSync } from './session-sync.js';
|
|
18
|
-
export type {
|
|
18
|
+
export type { SessionSyncMessage, SessionSyncMessageType } from './session-sync-message.js';
|
|
19
|
+
export type { AuthAdapter, AuthenticatedSessionState, AuthSessionState, CredentialsSignInOptions, OAuthSignInOptions, Provider, Session, SignInOptions, SignInOutcome, User, } from './types.js';
|
|
19
20
|
export { LOADING, UNAUTHENTICATED } from './types.js';
|
|
20
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,YAAY,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAE5F,YAAY,EACX,WAAW,EACX,yBAAyB,EACzB,gBAAgB,EAChB,wBAAwB,EACxB,kBAAkB,EAClB,QAAQ,EACR,OAAO,EACP,aAAa,EACb,aAAa,EACb,IAAI,GACJ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,15 +4,11 @@
|
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
6
|
export { BetterAuthAdapter } from './adapters/better-auth.js';
|
|
7
|
-
export { useConvexAuthAdapter } from './adapters/convex.js';
|
|
8
|
-
// Adapters
|
|
9
7
|
export { MockAuthAdapter } from './adapters/mock.js';
|
|
10
|
-
//
|
|
11
|
-
export {
|
|
8
|
+
// Built-in command names (always allowed by the middleware)
|
|
9
|
+
export { AUTH_COMMAND_NAMES } from './command-names.js';
|
|
12
10
|
export { AuthAdapterError } from './errors.js';
|
|
13
|
-
// Middleware
|
|
14
11
|
export { createAuthMiddleware } from './middleware.js';
|
|
15
|
-
// Session sync
|
|
16
12
|
export { SessionSync } from './session-sync.js';
|
|
17
13
|
export { LOADING, UNAUTHENTICATED } from './types.js';
|
|
18
14
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,4DAA4D;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAK/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAGvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAehD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Listener fan-out with per-listener error isolation
|
|
3
|
+
*
|
|
4
|
+
* Shared by every adapter and by SessionSync so that one throwing subscriber
|
|
5
|
+
* cannot stop later subscribers from seeing a state change (for example a
|
|
6
|
+
* sign-out), and cannot make the operation that triggered the change reject.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Receives an error thrown by a subscriber. When no handler is configured the
|
|
10
|
+
* error is rethrown in a microtask, so it still reaches the host's uncaught
|
|
11
|
+
* error reporting (`window.onerror`, `process.on('uncaughtException')`).
|
|
12
|
+
*/
|
|
13
|
+
export type ListenerErrorHandler = (error: unknown) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Report a subscriber error without interrupting the caller.
|
|
16
|
+
*
|
|
17
|
+
* The error goes to `onListenerError` when one is given. If there is none, or
|
|
18
|
+
* the handler itself throws, the error is rethrown asynchronously.
|
|
19
|
+
*/
|
|
20
|
+
export declare function reportListenerError(error: unknown, onListenerError?: ListenerErrorHandler): void;
|
|
21
|
+
/**
|
|
22
|
+
* A set of subscribers that are each called in isolation.
|
|
23
|
+
*/
|
|
24
|
+
export declare class ListenerSet<T> {
|
|
25
|
+
private readonly listeners;
|
|
26
|
+
private readonly onListenerError;
|
|
27
|
+
constructor(onListenerError?: ListenerErrorHandler);
|
|
28
|
+
get size(): number;
|
|
29
|
+
add(listener: (value: T) => void): {
|
|
30
|
+
unsubscribe: () => void;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Call every subscriber with `value`. A subscriber that throws is reported
|
|
34
|
+
* and skipped; the remaining subscribers still run and `emit` never throws.
|
|
35
|
+
*/
|
|
36
|
+
emit(value: T): void;
|
|
37
|
+
clear(): void;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=listeners.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listeners.d.ts","sourceRoot":"","sources":["../src/listeners.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;AAE5D;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,eAAe,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAahG;AAED;;GAEG;AACH,qBAAa,WAAW,CAAC,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAC3D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmC;gBAEvD,eAAe,CAAC,EAAE,oBAAoB;IAIlD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,GAAG,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG;QAAE,WAAW,EAAE,MAAM,IAAI,CAAA;KAAE;IAS9D;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAcpB,KAAK,IAAI,IAAI;CAGb"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Listener fan-out with per-listener error isolation
|
|
3
|
+
*
|
|
4
|
+
* Shared by every adapter and by SessionSync so that one throwing subscriber
|
|
5
|
+
* cannot stop later subscribers from seeing a state change (for example a
|
|
6
|
+
* sign-out), and cannot make the operation that triggered the change reject.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Report a subscriber error without interrupting the caller.
|
|
10
|
+
*
|
|
11
|
+
* The error goes to `onListenerError` when one is given. If there is none, or
|
|
12
|
+
* the handler itself throws, the error is rethrown asynchronously.
|
|
13
|
+
*/
|
|
14
|
+
export function reportListenerError(error, onListenerError) {
|
|
15
|
+
let unhandled = error;
|
|
16
|
+
if (onListenerError) {
|
|
17
|
+
try {
|
|
18
|
+
onListenerError(error);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
catch (handlerError) {
|
|
22
|
+
unhandled = handlerError;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
queueMicrotask(() => {
|
|
26
|
+
throw unhandled;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A set of subscribers that are each called in isolation.
|
|
31
|
+
*/
|
|
32
|
+
export class ListenerSet {
|
|
33
|
+
listeners = new Set();
|
|
34
|
+
onListenerError;
|
|
35
|
+
constructor(onListenerError) {
|
|
36
|
+
this.onListenerError = onListenerError;
|
|
37
|
+
}
|
|
38
|
+
get size() {
|
|
39
|
+
return this.listeners.size;
|
|
40
|
+
}
|
|
41
|
+
add(listener) {
|
|
42
|
+
this.listeners.add(listener);
|
|
43
|
+
return {
|
|
44
|
+
unsubscribe: () => {
|
|
45
|
+
this.listeners.delete(listener);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Call every subscriber with `value`. A subscriber that throws is reported
|
|
51
|
+
* and skipped; the remaining subscribers still run and `emit` never throws.
|
|
52
|
+
*/
|
|
53
|
+
emit(value) {
|
|
54
|
+
// Iterate a snapshot so a subscriber added during this emit waits for the
|
|
55
|
+
// next one, and re-check membership so a subscriber removed by an earlier
|
|
56
|
+
// one is not called.
|
|
57
|
+
for (const listener of Array.from(this.listeners)) {
|
|
58
|
+
if (!this.listeners.has(listener))
|
|
59
|
+
continue;
|
|
60
|
+
try {
|
|
61
|
+
listener(value);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
reportListenerError(error, this.onListenerError);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
clear() {
|
|
69
|
+
this.listeners.clear();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=listeners.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listeners.js","sourceRoot":"","sources":["../src/listeners.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAc,EAAE,eAAsC;IACzF,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,eAAe,EAAE,CAAC;QACrB,IAAI,CAAC;YACJ,eAAe,CAAC,KAAK,CAAC,CAAC;YACvB,OAAO;QACR,CAAC;QAAC,OAAO,YAAY,EAAE,CAAC;YACvB,SAAS,GAAG,YAAY,CAAC;QAC1B,CAAC;IACF,CAAC;IACD,cAAc,CAAC,GAAG,EAAE;QACnB,MAAM,SAAS,CAAC;IACjB,CAAC,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,WAAW;IACN,SAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC1C,eAAe,CAAmC;IAEnE,YAAY,eAAsC;QACjD,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACxC,CAAC;IAED,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,GAAG,CAAC,QAA4B;QAC/B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO;YACN,WAAW,EAAE,GAAG,EAAE;gBACjB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACjC,CAAC;SACD,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,IAAI,CAAC,KAAQ;QACZ,0EAA0E;QAC1E,0EAA0E;QAC1E,qBAAqB;QACrB,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACnD,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC5C,IAAI,CAAC;gBACJ,QAAQ,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;YAClD,CAAC;QACF,CAAC;IACF,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;CACD"}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -2,9 +2,24 @@
|
|
|
2
2
|
* @fileoverview Auth middleware for gating commands behind authentication
|
|
3
3
|
*/
|
|
4
4
|
import type { CommandMiddleware } from '@lushly-dev/afd-core';
|
|
5
|
-
import type { AuthAdapter } from './types.js';
|
|
5
|
+
import type { AuthAdapter, AuthenticatedSessionState } from './types.js';
|
|
6
|
+
declare module '@lushly-dev/afd-core' {
|
|
7
|
+
interface CommandContext {
|
|
8
|
+
/**
|
|
9
|
+
* The verified, unexpired session, set by `createAuthMiddleware`.
|
|
10
|
+
*
|
|
11
|
+
* The middleware sets or deletes this on every call, excluded commands
|
|
12
|
+
* included, so a value supplied by the caller never reaches a handler.
|
|
13
|
+
*/
|
|
14
|
+
auth?: AuthenticatedSessionState;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
6
17
|
export interface AuthMiddlewareOptions {
|
|
7
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Command names that bypass auth checks. The built-in auth commands
|
|
20
|
+
* (`auth-sign-in`, `auth-sign-out`, `auth-session-get`) always bypass them
|
|
21
|
+
* and do not need to be listed.
|
|
22
|
+
*/
|
|
8
23
|
exclude?: string[];
|
|
9
24
|
}
|
|
10
25
|
/**
|
|
@@ -12,7 +27,11 @@ export interface AuthMiddlewareOptions {
|
|
|
12
27
|
*
|
|
13
28
|
* - Unauthenticated → failure with UNAUTHORIZED
|
|
14
29
|
* - Loading → failure with UNAUTHORIZED (retryable)
|
|
15
|
-
* - Authenticated
|
|
30
|
+
* - Authenticated but `expiresAt` reached → failure with TOKEN_EXPIRED (retryable)
|
|
31
|
+
* - Authenticated → sets `context.auth` and calls next()
|
|
32
|
+
*
|
|
33
|
+
* Excluded commands, and the built-in auth commands, always run. They see
|
|
34
|
+
* `context.auth` only when the adapter holds an unexpired session.
|
|
16
35
|
*/
|
|
17
36
|
export declare function createAuthMiddleware(adapter: AuthAdapter, options?: AuthMiddlewareOptions): CommandMiddleware;
|
|
18
37
|
//# sourceMappingURL=middleware.d.ts.map
|
package/dist/middleware.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAI9D,OAAO,KAAK,EAAE,WAAW,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAEzE,OAAO,QAAQ,sBAAsB,CAAC;IACrC,UAAU,cAAc;QACvB;;;;;WAKG;QACH,IAAI,CAAC,EAAE,yBAAyB,CAAC;KACjC;CACD;AAED,MAAM,WAAW,qBAAqB;IACrC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,qBAA0B,GACjC,iBAAiB,CA6CnB"}
|
package/dist/middleware.js
CHANGED
|
@@ -2,27 +2,33 @@
|
|
|
2
2
|
* @fileoverview Auth middleware for gating commands behind authentication
|
|
3
3
|
*/
|
|
4
4
|
import { failure } from '@lushly-dev/afd-core';
|
|
5
|
+
import { AUTH_COMMAND_NAMES } from './command-names.js';
|
|
6
|
+
import { isSessionExpired } from './session-state.js';
|
|
5
7
|
/**
|
|
6
8
|
* Create middleware that gates commands behind authentication.
|
|
7
9
|
*
|
|
8
10
|
* - Unauthenticated → failure with UNAUTHORIZED
|
|
9
11
|
* - Loading → failure with UNAUTHORIZED (retryable)
|
|
10
|
-
* - Authenticated
|
|
12
|
+
* - Authenticated but `expiresAt` reached → failure with TOKEN_EXPIRED (retryable)
|
|
13
|
+
* - Authenticated → sets `context.auth` and calls next()
|
|
14
|
+
*
|
|
15
|
+
* Excluded commands, and the built-in auth commands, always run. They see
|
|
16
|
+
* `context.auth` only when the adapter holds an unexpired session.
|
|
11
17
|
*/
|
|
12
18
|
export function createAuthMiddleware(adapter, options = {}) {
|
|
13
|
-
const excluded = new Set(options.exclude ?? []);
|
|
19
|
+
const excluded = new Set([...AUTH_COMMAND_NAMES, ...(options.exclude ?? [])]);
|
|
14
20
|
return async (commandName, _input, context, next) => {
|
|
15
|
-
if (excluded.has(commandName)) {
|
|
16
|
-
return next();
|
|
17
|
-
}
|
|
18
21
|
const state = adapter.getSession();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const verified = state.status === 'authenticated' && !isSessionExpired(state.session) ? state : undefined;
|
|
23
|
+
// Overwrite or remove whatever the caller put in the context.
|
|
24
|
+
if (verified) {
|
|
25
|
+
context.auth = verified;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
delete context.auth;
|
|
29
|
+
}
|
|
30
|
+
if (excluded.has(commandName) || verified) {
|
|
31
|
+
return next();
|
|
26
32
|
}
|
|
27
33
|
if (state.status === 'loading') {
|
|
28
34
|
return failure({
|
|
@@ -32,8 +38,20 @@ export function createAuthMiddleware(adapter, options = {}) {
|
|
|
32
38
|
retryable: true,
|
|
33
39
|
});
|
|
34
40
|
}
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
if (state.status === 'authenticated') {
|
|
42
|
+
return failure({
|
|
43
|
+
code: 'TOKEN_EXPIRED',
|
|
44
|
+
message: 'Session has expired',
|
|
45
|
+
suggestion: 'Try again once the auth provider has refreshed the session, or sign in again with auth-sign-in',
|
|
46
|
+
retryable: true,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return failure({
|
|
50
|
+
code: 'UNAUTHORIZED',
|
|
51
|
+
message: 'Authentication required',
|
|
52
|
+
suggestion: 'Sign in with auth-sign-in to access this command',
|
|
53
|
+
retryable: false,
|
|
54
|
+
});
|
|
37
55
|
};
|
|
38
56
|
}
|
|
39
57
|
//# sourceMappingURL=middleware.js.map
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAwBtD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CACnC,OAAoB,EACpB,UAAiC,EAAE;IAEnC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,CAAC,GAAG,kBAAkB,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAEtF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACnD,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;QACnC,MAAM,QAAQ,GACb,KAAK,CAAC,MAAM,KAAK,eAAe,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QAE1F,8DAA8D;QAC9D,IAAI,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;QACzB,CAAC;aAAM,CAAC;YACP,OAAO,OAAO,CAAC,IAAI,CAAC;QACrB,CAAC;QAED,IAAI,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,QAAQ,EAAE,CAAC;YAC3C,OAAO,IAAI,EAAE,CAAC;QACf,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,OAAO,CAAC;gBACd,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,iCAAiC;gBAC1C,UAAU,EAAE,mDAAmD;gBAC/D,SAAS,EAAE,IAAI;aACf,CAAC,CAAC;QACJ,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;YACtC,OAAO,OAAO,CAAC;gBACd,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,qBAAqB;gBAC9B,UAAU,EACT,gGAAgG;gBACjG,SAAS,EAAE,IAAI;aACf,CAAC,CAAC;QACJ,CAAC;QAED,OAAO,OAAO,CAAC;YACd,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,yBAAyB;YAClC,UAAU,EAAE,kDAAkD;YAC9D,SAAS,EAAE,KAAK;SAChB,CAAC,CAAC;IACJ,CAAC,CAAC;AACH,CAAC"}
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGtE,YAAY,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,WAAW,SAAS;IACzB,OAAO,EAAE,MAAM,WAAW,CAAC;IAC3B,UAAU,EAAE,MAAM,gBAAgB,CAAC;IACnC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,CAyC/D"}
|
package/dist/react.js
CHANGED
|
@@ -3,26 +3,40 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Sub-path export: import from '@lushly-dev/afd-auth/react'
|
|
5
5
|
*/
|
|
6
|
-
import { useCallback,
|
|
6
|
+
import { useCallback, useSyncExternalStore } from 'react';
|
|
7
|
+
import { areSessionStatesEqual } from './session-state.js';
|
|
7
8
|
import { LOADING } from './types.js';
|
|
8
9
|
export { useConvexAuthAdapter } from './adapters/convex.js';
|
|
9
10
|
/**
|
|
10
11
|
* Create React hooks bound to an auth adapter instance.
|
|
11
12
|
*/
|
|
12
13
|
export function createAuthHooks(adapter) {
|
|
14
|
+
// Providers may return a new object from getSession() on every read, but
|
|
15
|
+
// React's external-store contract requires the snapshot itself to be cached,
|
|
16
|
+
// so compare the session's values before replacing it.
|
|
17
|
+
let cachedSnapshot;
|
|
18
|
+
const readSnapshot = () => {
|
|
19
|
+
const nextSnapshot = adapter.getSession();
|
|
20
|
+
if (cachedSnapshot && areSessionStatesEqual(cachedSnapshot, nextSnapshot)) {
|
|
21
|
+
return cachedSnapshot;
|
|
22
|
+
}
|
|
23
|
+
cachedSnapshot = nextSnapshot;
|
|
24
|
+
return nextSnapshot;
|
|
25
|
+
};
|
|
13
26
|
function useAuth() {
|
|
14
27
|
return adapter;
|
|
15
28
|
}
|
|
16
29
|
function useSession() {
|
|
17
|
-
const snapshotRef = useRef(adapter.getSession());
|
|
18
30
|
const subscribe = useCallback((onStoreChange) => {
|
|
19
|
-
const { unsubscribe } = adapter.onAuthStateChange((
|
|
20
|
-
snapshotRef.current = state;
|
|
31
|
+
const { unsubscribe } = adapter.onAuthStateChange(() => {
|
|
21
32
|
onStoreChange();
|
|
22
33
|
});
|
|
23
34
|
return unsubscribe;
|
|
24
35
|
}, []);
|
|
25
|
-
|
|
36
|
+
// Read the adapter at snapshot time. useSyncExternalStore checks this
|
|
37
|
+
// value again immediately after subscribing, which closes the render to
|
|
38
|
+
// subscription race where an auth event arrives between those phases.
|
|
39
|
+
const getSnapshot = useCallback(readSnapshot, []);
|
|
26
40
|
const getServerSnapshot = useCallback(() => LOADING, []);
|
|
27
41
|
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
28
42
|
}
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAGrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAQ5D;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,OAAoB;IACnD,yEAAyE;IACzE,6EAA6E;IAC7E,uDAAuD;IACvD,IAAI,cAA4C,CAAC;IACjD,MAAM,YAAY,GAAG,GAAqB,EAAE;QAC3C,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;QAC1C,IAAI,cAAc,IAAI,qBAAqB,CAAC,cAAc,EAAE,YAAY,CAAC,EAAE,CAAC;YAC3E,OAAO,cAAc,CAAC;QACvB,CAAC;QACD,cAAc,GAAG,YAAY,CAAC;QAC9B,OAAO,YAAY,CAAC;IACrB,CAAC,CAAC;IAEF,SAAS,OAAO;QACf,OAAO,OAAO,CAAC;IAChB,CAAC;IAED,SAAS,UAAU;QAClB,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,aAAyB,EAAE,EAAE;YAC3D,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG,EAAE;gBACtD,aAAa,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;YACH,OAAO,WAAW,CAAC;QACpB,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,sEAAsE;QACtE,wEAAwE;QACxE,sEAAsE;QACtE,MAAM,WAAW,GAAG,WAAW,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAClD,MAAM,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAEzD,OAAO,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC;IACxE,CAAC;IAED,SAAS,OAAO;QACf,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;QAC7B,OAAO,OAAO,CAAC,MAAM,KAAK,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Session state comparison and expiry helpers shared by the
|
|
3
|
+
* adapters, the middleware and the React hooks.
|
|
4
|
+
*/
|
|
5
|
+
import type { AuthSessionState, Session, User } from './types.js';
|
|
6
|
+
export declare function areUsersEqual(left: User, right: User): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Compare two session states by value. Providers may allocate a new state
|
|
9
|
+
* object on every read, so identity is not a usable signal for "changed".
|
|
10
|
+
*/
|
|
11
|
+
export declare function areSessionStatesEqual(left: AuthSessionState, right: AuthSessionState): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Whether a session has reached its expiry. A session without `expiresAt`
|
|
14
|
+
* does not expire here, because its provider manages the token lifetime. An
|
|
15
|
+
* `expiresAt` that is not a valid date counts as expired, so a malformed value
|
|
16
|
+
* fails closed.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isSessionExpired(session: Session, now?: number): boolean;
|
|
19
|
+
//# sourceMappingURL=session-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-state.d.ts","sourceRoot":"","sources":["../src/session-state.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElE,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,OAAO,CAO9D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAS9F;AASD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAIpF"}
|