@lushly-dev/afd-auth 1.0.0 → 2.0.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 +134 -0
- package/dist/adapters/better-auth.d.ts +34 -6
- package/dist/adapters/better-auth.d.ts.map +1 -1
- package/dist/adapters/better-auth.js +73 -25
- 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 +21 -15
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Session state comparison and expiry helpers shared by the
|
|
3
|
+
* adapters, the middleware and the React hooks.
|
|
4
|
+
*/
|
|
5
|
+
export function areUsersEqual(left, right) {
|
|
6
|
+
return (left.id === right.id &&
|
|
7
|
+
left.email === right.email &&
|
|
8
|
+
left.name === right.name &&
|
|
9
|
+
left.image === right.image);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Compare two session states by value. Providers may allocate a new state
|
|
13
|
+
* object on every read, so identity is not a usable signal for "changed".
|
|
14
|
+
*/
|
|
15
|
+
export function areSessionStatesEqual(left, right) {
|
|
16
|
+
if (left.status !== right.status)
|
|
17
|
+
return false;
|
|
18
|
+
if (left.status !== 'authenticated' || right.status !== 'authenticated')
|
|
19
|
+
return true;
|
|
20
|
+
return (left.session.id === right.session.id &&
|
|
21
|
+
isSameInstant(left.session.expiresAt, right.session.expiresAt) &&
|
|
22
|
+
areUsersEqual(left.user, right.user));
|
|
23
|
+
}
|
|
24
|
+
function isSameInstant(left, right) {
|
|
25
|
+
if (left instanceof Date && right instanceof Date) {
|
|
26
|
+
return Object.is(left.getTime(), right.getTime());
|
|
27
|
+
}
|
|
28
|
+
return Object.is(left, right);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Whether a session has reached its expiry. A session without `expiresAt`
|
|
32
|
+
* does not expire here, because its provider manages the token lifetime. An
|
|
33
|
+
* `expiresAt` that is not a valid date counts as expired, so a malformed value
|
|
34
|
+
* fails closed.
|
|
35
|
+
*/
|
|
36
|
+
export function isSessionExpired(session, now = Date.now()) {
|
|
37
|
+
if (session.expiresAt === undefined)
|
|
38
|
+
return false;
|
|
39
|
+
const expiresAt = new Date(session.expiresAt).getTime();
|
|
40
|
+
return !(expiresAt > now);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=session-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-state.js","sourceRoot":"","sources":["../src/session-state.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,UAAU,aAAa,CAAC,IAAU,EAAE,KAAW;IACpD,OAAO,CACN,IAAI,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE;QACpB,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK;QAC1B,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI;QACxB,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,CAC1B,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAsB,EAAE,KAAuB;IACpF,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,IAAI,CAAC,MAAM,KAAK,eAAe,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe;QAAE,OAAO,IAAI,CAAC;IAErF,OAAO,CACN,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,KAAK,CAAC,OAAO,CAAC,EAAE;QACpC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QAC9D,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CACpC,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,IAAsB,EAAE,KAAuB;IACrE,IAAI,IAAI,YAAY,IAAI,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;QACnD,OAAO,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgB,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IAC1E,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;IACxD,OAAO,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Messages exchanged between tabs by SessionSync
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* A session change broadcast to other tabs. `visibility-refresh` is also
|
|
6
|
+
* raised locally when a tab becomes visible after being hidden for
|
|
7
|
+
* `visibilityRefreshMs`.
|
|
8
|
+
*/
|
|
9
|
+
export type SessionSyncMessage = {
|
|
10
|
+
type: 'signed-in';
|
|
11
|
+
userId?: string;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'signed-out';
|
|
14
|
+
} | {
|
|
15
|
+
type: 'session-refreshed';
|
|
16
|
+
} | {
|
|
17
|
+
type: 'profile-updated';
|
|
18
|
+
userId?: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'visibility-refresh';
|
|
21
|
+
};
|
|
22
|
+
export type SessionSyncMessageType = SessionSyncMessage['type'];
|
|
23
|
+
/**
|
|
24
|
+
* Validate a payload received from another tab (or passed by an untyped
|
|
25
|
+
* caller). Returns a copy that holds only the known fields, or `null` when
|
|
26
|
+
* the payload is not a valid message.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseSessionSyncMessage(value: unknown): SessionSyncMessage | null;
|
|
29
|
+
//# sourceMappingURL=session-sync-message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-sync-message.d.ts","sourceRoot":"","sources":["../src/session-sync-message.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAC3B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAElC,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAEhE;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAiBjF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Messages exchanged between tabs by SessionSync
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Validate a payload received from another tab (or passed by an untyped
|
|
6
|
+
* caller). Returns a copy that holds only the known fields, or `null` when
|
|
7
|
+
* the payload is not a valid message.
|
|
8
|
+
*/
|
|
9
|
+
export function parseSessionSyncMessage(value) {
|
|
10
|
+
if (typeof value !== 'object' || value === null || !('type' in value))
|
|
11
|
+
return null;
|
|
12
|
+
const userId = 'userId' in value ? value.userId : undefined;
|
|
13
|
+
if (userId !== undefined && typeof userId !== 'string')
|
|
14
|
+
return null;
|
|
15
|
+
switch (value.type) {
|
|
16
|
+
case 'signed-in':
|
|
17
|
+
case 'profile-updated':
|
|
18
|
+
return userId === undefined ? { type: value.type } : { type: value.type, userId };
|
|
19
|
+
case 'signed-out':
|
|
20
|
+
case 'session-refreshed':
|
|
21
|
+
case 'visibility-refresh':
|
|
22
|
+
return { type: value.type };
|
|
23
|
+
default:
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=session-sync-message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-sync-message.js","sourceRoot":"","sources":["../src/session-sync-message.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgBH;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnF,MAAM,MAAM,GAAG,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5D,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEpE,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,WAAW,CAAC;QACjB,KAAK,iBAAiB;YACrB,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QACnF,KAAK,YAAY,CAAC;QAClB,KAAK,mBAAmB,CAAC;QACzB,KAAK,oBAAoB;YACxB,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B;YACC,OAAO,IAAI,CAAC;IACd,CAAC;AACF,CAAC"}
|
package/dist/session-sync.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Uses BroadcastChannel (primary) with localStorage fallback.
|
|
5
5
|
* All browser APIs are guarded with typeof checks for SSR safety.
|
|
6
6
|
*/
|
|
7
|
+
import { type SessionSyncMessage } from './session-sync-message.js';
|
|
7
8
|
export interface SessionSyncOptions {
|
|
8
9
|
/** Channel name for BroadcastChannel (default: 'afd-auth-session') */
|
|
9
10
|
channelName?: string;
|
|
@@ -13,9 +14,15 @@ export interface SessionSyncOptions {
|
|
|
13
14
|
lockKey?: string;
|
|
14
15
|
/** Lock timeout in ms (default: 10_000) */
|
|
15
16
|
lockTimeoutMs?: number;
|
|
16
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* How long `acquireRefreshLockAsync()` waits after writing the lock before
|
|
19
|
+
* reading it back to confirm ownership, in ms (default: 50)
|
|
20
|
+
*/
|
|
17
21
|
lockCheckDelayMs?: number;
|
|
18
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Debounce interval for received messages of the same type, in ms
|
|
24
|
+
* (default: 100). `signed-out` is never debounced.
|
|
25
|
+
*/
|
|
19
26
|
debounceMs?: number;
|
|
20
27
|
/** Re-check session after tab hidden for this long in ms (default: 300_000 = 5 min) */
|
|
21
28
|
visibilityRefreshMs?: number;
|
|
@@ -23,21 +30,28 @@ export interface SessionSyncOptions {
|
|
|
23
30
|
export declare class SessionSync {
|
|
24
31
|
private readonly options;
|
|
25
32
|
private channel;
|
|
26
|
-
private listeners;
|
|
27
|
-
private
|
|
33
|
+
private readonly listeners;
|
|
34
|
+
private readonly pending;
|
|
28
35
|
private hiddenAt;
|
|
29
36
|
private visibilityHandler;
|
|
30
37
|
private storageHandler;
|
|
31
38
|
private disposed;
|
|
39
|
+
private readonly ownerId;
|
|
40
|
+
private heldLockId;
|
|
32
41
|
constructor(options?: SessionSyncOptions);
|
|
33
42
|
/**
|
|
34
43
|
* Broadcast a session change to other tabs.
|
|
44
|
+
*
|
|
45
|
+
* @throws TypeError when `message` is not a valid {@link SessionSyncMessage}
|
|
35
46
|
*/
|
|
36
|
-
notifySessionChanged(
|
|
47
|
+
notifySessionChanged(message: SessionSyncMessage): void;
|
|
37
48
|
/**
|
|
38
|
-
* Subscribe to session changes from other tabs.
|
|
49
|
+
* Subscribe to session changes from other tabs. Invalid payloads are
|
|
50
|
+
* dropped. Messages of one type are debounced by `debounceMs`; a
|
|
51
|
+
* `signed-out` message is delivered at once and cancels messages still
|
|
52
|
+
* waiting in the debounce, since it supersedes them.
|
|
39
53
|
*/
|
|
40
|
-
onSessionChanged(callback: (
|
|
54
|
+
onSessionChanged(callback: (message: SessionSyncMessage) => void): {
|
|
41
55
|
unsubscribe: () => void;
|
|
42
56
|
};
|
|
43
57
|
/**
|
|
@@ -45,6 +59,13 @@ export declare class SessionSync {
|
|
|
45
59
|
* Returns true if lock was acquired, false if another tab holds it.
|
|
46
60
|
*/
|
|
47
61
|
acquireRefreshLock(): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Like {@link acquireRefreshLock}, but waits `lockCheckDelayMs` after
|
|
64
|
+
* writing the lock and reads it back before claiming it. A tab whose write
|
|
65
|
+
* raced another tab's then sees the other owner and declines. This narrows
|
|
66
|
+
* the race the synchronous check cannot see, but is still not atomic.
|
|
67
|
+
*/
|
|
68
|
+
acquireRefreshLockAsync(): Promise<boolean>;
|
|
48
69
|
/**
|
|
49
70
|
* Release the refresh lock.
|
|
50
71
|
*/
|
|
@@ -54,8 +75,14 @@ export declare class SessionSync {
|
|
|
54
75
|
*/
|
|
55
76
|
dispose(): void;
|
|
56
77
|
private init;
|
|
78
|
+
private receive;
|
|
79
|
+
private cancelPending;
|
|
57
80
|
private notifyViaStorage;
|
|
58
|
-
private
|
|
81
|
+
private writeRefreshLock;
|
|
82
|
+
private isLockLive;
|
|
83
|
+
private confirmRefreshLock;
|
|
84
|
+
private removeRefreshLock;
|
|
59
85
|
private hasLocalStorage;
|
|
86
|
+
private readRefreshLock;
|
|
60
87
|
}
|
|
61
88
|
//# sourceMappingURL=session-sync.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-sync.d.ts","sourceRoot":"","sources":["../src/session-sync.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"session-sync.d.ts","sourceRoot":"","sources":["../src/session-sync.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAEN,KAAK,kBAAkB,EAEvB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,kBAAkB;IAClC,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uFAAuF;IACvF,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAqCD,qBAAa,WAAW;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyC;IACnE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoE;IAC5F,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,UAAU,CAAuB;gBAE7B,OAAO,GAAE,kBAAuB;IAK5C;;;;OAIG;IACH,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAmBvD;;;;;OAKG;IACH,gBAAgB,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,GAAG;QAAE,WAAW,EAAE,MAAM,IAAI,CAAA;KAAE;IAI9F;;;OAGG;IACH,kBAAkB,IAAI,OAAO;IAU7B;;;;;OAKG;IACG,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;IAYjD;;OAEG;IACH,kBAAkB,IAAI,IAAI;IAQ1B;;OAEG;IACH,OAAO,IAAI,IAAI;IAuBf,OAAO,CAAC,IAAI;IA4CZ,OAAO,CAAC,OAAO;IAsBf,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,gBAAgB;IAyBxB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,kBAAkB;IAe1B,OAAO,CAAC,iBAAiB;IAgBzB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,eAAe;CAqBvB"}
|
package/dist/session-sync.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Uses BroadcastChannel (primary) with localStorage fallback.
|
|
5
5
|
* All browser APIs are guarded with typeof checks for SSR safety.
|
|
6
6
|
*/
|
|
7
|
+
import { ListenerSet } from './listeners.js';
|
|
8
|
+
import { parseSessionSyncMessage, } from './session-sync-message.js';
|
|
7
9
|
const DEFAULTS = {
|
|
8
10
|
channelName: 'afd-auth-session',
|
|
9
11
|
storageKey: 'afd-auth-sync',
|
|
@@ -13,82 +15,108 @@ const DEFAULTS = {
|
|
|
13
15
|
debounceMs: 100,
|
|
14
16
|
visibilityRefreshMs: 300_000,
|
|
15
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* How far in the future a lock timestamp may be and still count as live.
|
|
20
|
+
* Tabs share one clock, so a later timestamp comes from a clock change or a
|
|
21
|
+
* corrupted value and must not block refresh until the clock catches up.
|
|
22
|
+
*/
|
|
23
|
+
const LOCK_CLOCK_SKEW_MS = 1_000;
|
|
24
|
+
let ownerSequence = 0;
|
|
16
25
|
export class SessionSync {
|
|
17
26
|
options;
|
|
18
27
|
channel = null;
|
|
19
|
-
listeners = new
|
|
20
|
-
|
|
28
|
+
listeners = new ListenerSet();
|
|
29
|
+
pending = new Map();
|
|
21
30
|
hiddenAt = null;
|
|
22
31
|
visibilityHandler = null;
|
|
23
32
|
storageHandler = null;
|
|
24
33
|
disposed = false;
|
|
34
|
+
ownerId = createOwnerId();
|
|
35
|
+
heldLockId = null;
|
|
25
36
|
constructor(options = {}) {
|
|
26
37
|
this.options = { ...DEFAULTS, ...options };
|
|
27
38
|
this.init();
|
|
28
39
|
}
|
|
29
40
|
/**
|
|
30
41
|
* Broadcast a session change to other tabs.
|
|
42
|
+
*
|
|
43
|
+
* @throws TypeError when `message` is not a valid {@link SessionSyncMessage}
|
|
31
44
|
*/
|
|
32
|
-
notifySessionChanged(
|
|
45
|
+
notifySessionChanged(message) {
|
|
46
|
+
const valid = parseSessionSyncMessage(message);
|
|
47
|
+
if (!valid) {
|
|
48
|
+
throw new TypeError(`Invalid session sync message: ${JSON.stringify(message)}`);
|
|
49
|
+
}
|
|
33
50
|
if (this.disposed)
|
|
34
51
|
return;
|
|
35
52
|
if (this.channel) {
|
|
36
53
|
try {
|
|
37
|
-
this.channel.postMessage(
|
|
54
|
+
this.channel.postMessage(valid);
|
|
38
55
|
return;
|
|
39
56
|
}
|
|
40
57
|
catch {
|
|
41
58
|
// Fall through to localStorage
|
|
42
59
|
}
|
|
43
60
|
}
|
|
44
|
-
this.notifyViaStorage(
|
|
61
|
+
this.notifyViaStorage(valid);
|
|
45
62
|
}
|
|
46
63
|
/**
|
|
47
|
-
* Subscribe to session changes from other tabs.
|
|
64
|
+
* Subscribe to session changes from other tabs. Invalid payloads are
|
|
65
|
+
* dropped. Messages of one type are debounced by `debounceMs`; a
|
|
66
|
+
* `signed-out` message is delivered at once and cancels messages still
|
|
67
|
+
* waiting in the debounce, since it supersedes them.
|
|
48
68
|
*/
|
|
49
69
|
onSessionChanged(callback) {
|
|
50
|
-
this.listeners.add(callback);
|
|
51
|
-
return {
|
|
52
|
-
unsubscribe: () => {
|
|
53
|
-
this.listeners.delete(callback);
|
|
54
|
-
},
|
|
55
|
-
};
|
|
70
|
+
return this.listeners.add(callback);
|
|
56
71
|
}
|
|
57
72
|
/**
|
|
58
73
|
* Attempt to acquire a refresh lock to coordinate token refresh across tabs.
|
|
59
74
|
* Returns true if lock was acquired, false if another tab holds it.
|
|
60
75
|
*/
|
|
61
76
|
acquireRefreshLock() {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
const attempt = this.writeRefreshLock();
|
|
78
|
+
if (attempt.kind !== 'written')
|
|
79
|
+
return attempt.kind === 'uncoordinated';
|
|
80
|
+
// localStorage has no compare-and-swap. The immediate read makes the
|
|
81
|
+
// synchronous fallback best-effort: if another tab won the write race,
|
|
82
|
+
// this instance declines the lock. It cannot make the operation atomic.
|
|
83
|
+
return this.confirmRefreshLock(attempt.lockId);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Like {@link acquireRefreshLock}, but waits `lockCheckDelayMs` after
|
|
87
|
+
* writing the lock and reads it back before claiming it. A tab whose write
|
|
88
|
+
* raced another tab's then sees the other owner and declines. This narrows
|
|
89
|
+
* the race the synchronous check cannot see, but is still not atomic.
|
|
90
|
+
*/
|
|
91
|
+
async acquireRefreshLockAsync() {
|
|
92
|
+
const attempt = this.writeRefreshLock();
|
|
93
|
+
if (attempt.kind !== 'written')
|
|
94
|
+
return attempt.kind === 'uncoordinated';
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, this.options.lockCheckDelayMs));
|
|
96
|
+
if (this.disposed) {
|
|
97
|
+
this.removeRefreshLock(attempt.lockId);
|
|
98
|
+
return false;
|
|
71
99
|
}
|
|
72
|
-
|
|
73
|
-
return true;
|
|
100
|
+
return this.confirmRefreshLock(attempt.lockId);
|
|
74
101
|
}
|
|
75
102
|
/**
|
|
76
103
|
* Release the refresh lock.
|
|
77
104
|
*/
|
|
78
105
|
releaseRefreshLock() {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
106
|
+
const heldLockId = this.heldLockId;
|
|
107
|
+
this.heldLockId = null;
|
|
108
|
+
// A stale owner must never retain permission to release a later owner's
|
|
109
|
+
// lock through this instance.
|
|
110
|
+
if (heldLockId !== null)
|
|
111
|
+
this.removeRefreshLock(heldLockId);
|
|
82
112
|
}
|
|
83
113
|
/**
|
|
84
114
|
* Clean up all resources.
|
|
85
115
|
*/
|
|
86
116
|
dispose() {
|
|
117
|
+
this.releaseRefreshLock();
|
|
87
118
|
this.disposed = true;
|
|
88
|
-
|
|
89
|
-
clearTimeout(this.debounceTimer);
|
|
90
|
-
this.debounceTimer = null;
|
|
91
|
-
}
|
|
119
|
+
this.cancelPending();
|
|
92
120
|
if (this.channel) {
|
|
93
121
|
this.channel.close();
|
|
94
122
|
this.channel = null;
|
|
@@ -109,7 +137,7 @@ export class SessionSync {
|
|
|
109
137
|
try {
|
|
110
138
|
this.channel = new BroadcastChannel(this.options.channelName);
|
|
111
139
|
this.channel.onmessage = (event) => {
|
|
112
|
-
this.
|
|
140
|
+
this.receive(event.data);
|
|
113
141
|
};
|
|
114
142
|
}
|
|
115
143
|
catch {
|
|
@@ -121,8 +149,7 @@ export class SessionSync {
|
|
|
121
149
|
this.storageHandler = (e) => {
|
|
122
150
|
if (e.key === this.options.storageKey && e.newValue) {
|
|
123
151
|
try {
|
|
124
|
-
|
|
125
|
-
this.debouncedNotify(data);
|
|
152
|
+
this.receive(JSON.parse(e.newValue));
|
|
126
153
|
}
|
|
127
154
|
catch {
|
|
128
155
|
// Ignore malformed data
|
|
@@ -141,18 +168,42 @@ export class SessionSync {
|
|
|
141
168
|
const elapsed = Date.now() - this.hiddenAt;
|
|
142
169
|
this.hiddenAt = null;
|
|
143
170
|
if (elapsed >= this.options.visibilityRefreshMs) {
|
|
144
|
-
this.
|
|
171
|
+
this.receive({ type: 'visibility-refresh' });
|
|
145
172
|
}
|
|
146
173
|
}
|
|
147
174
|
};
|
|
148
175
|
document.addEventListener('visibilitychange', this.visibilityHandler);
|
|
149
176
|
}
|
|
150
177
|
}
|
|
151
|
-
|
|
178
|
+
receive(payload) {
|
|
179
|
+
if (this.disposed)
|
|
180
|
+
return;
|
|
181
|
+
const message = parseSessionSyncMessage(payload);
|
|
182
|
+
if (!message)
|
|
183
|
+
return;
|
|
184
|
+
if (message.type === 'signed-out') {
|
|
185
|
+
this.cancelPending();
|
|
186
|
+
this.listeners.emit(message);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const existing = this.pending.get(message.type);
|
|
190
|
+
if (existing !== undefined)
|
|
191
|
+
clearTimeout(existing);
|
|
192
|
+
this.pending.set(message.type, setTimeout(() => {
|
|
193
|
+
this.pending.delete(message.type);
|
|
194
|
+
this.listeners.emit(message);
|
|
195
|
+
}, this.options.debounceMs));
|
|
196
|
+
}
|
|
197
|
+
cancelPending() {
|
|
198
|
+
for (const timer of this.pending.values())
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
this.pending.clear();
|
|
201
|
+
}
|
|
202
|
+
notifyViaStorage(message) {
|
|
152
203
|
if (!this.hasLocalStorage())
|
|
153
204
|
return;
|
|
154
205
|
try {
|
|
155
|
-
localStorage.setItem(this.options.storageKey, JSON.stringify(
|
|
206
|
+
localStorage.setItem(this.options.storageKey, JSON.stringify(message));
|
|
156
207
|
// Clean up immediately — the storage event fires in other tabs
|
|
157
208
|
localStorage.removeItem(this.options.storageKey);
|
|
158
209
|
}
|
|
@@ -160,16 +211,59 @@ export class SessionSync {
|
|
|
160
211
|
// localStorage full or unavailable
|
|
161
212
|
}
|
|
162
213
|
}
|
|
163
|
-
|
|
164
|
-
if (this.
|
|
165
|
-
|
|
214
|
+
writeRefreshLock() {
|
|
215
|
+
if (this.disposed)
|
|
216
|
+
return { kind: 'declined' };
|
|
217
|
+
if (!this.hasLocalStorage())
|
|
218
|
+
return { kind: 'uncoordinated' };
|
|
219
|
+
const now = Date.now();
|
|
220
|
+
const existing = this.readRefreshLock();
|
|
221
|
+
if (!existing.available)
|
|
222
|
+
return { kind: 'uncoordinated' };
|
|
223
|
+
if (existing.value && this.isLockLive(existing.value.timestamp, now)) {
|
|
224
|
+
return { kind: 'declined' }; // Another tab holds a valid lock
|
|
225
|
+
}
|
|
226
|
+
const lockId = createLockId();
|
|
227
|
+
const lock = { ownerId: this.ownerId, lockId, timestamp: now };
|
|
228
|
+
try {
|
|
229
|
+
localStorage.setItem(this.options.lockKey, JSON.stringify(lock));
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// Storage can be present but denied (private browsing, blocked cookies,
|
|
233
|
+
// quota). Proceed without coordination rather than failing auth refresh.
|
|
234
|
+
this.heldLockId = null;
|
|
235
|
+
return { kind: 'uncoordinated' };
|
|
236
|
+
}
|
|
237
|
+
return { kind: 'written', lockId };
|
|
238
|
+
}
|
|
239
|
+
isLockLive(timestamp, now) {
|
|
240
|
+
return timestamp <= now + LOCK_CLOCK_SKEW_MS && now - timestamp < this.options.lockTimeoutMs;
|
|
241
|
+
}
|
|
242
|
+
confirmRefreshLock(lockId) {
|
|
243
|
+
const observed = this.readRefreshLock();
|
|
244
|
+
if (observed.available &&
|
|
245
|
+
observed.value?.ownerId === this.ownerId &&
|
|
246
|
+
observed.value.lockId === lockId) {
|
|
247
|
+
this.heldLockId = lockId;
|
|
248
|
+
return true;
|
|
166
249
|
}
|
|
167
|
-
this.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
250
|
+
this.heldLockId = null;
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
removeRefreshLock(lockId) {
|
|
254
|
+
if (!this.hasLocalStorage())
|
|
255
|
+
return;
|
|
256
|
+
const current = this.readRefreshLock();
|
|
257
|
+
if (current.available &&
|
|
258
|
+
current.value?.ownerId === this.ownerId &&
|
|
259
|
+
current.value.lockId === lockId) {
|
|
260
|
+
try {
|
|
261
|
+
localStorage.removeItem(this.options.lockKey);
|
|
171
262
|
}
|
|
172
|
-
|
|
263
|
+
catch {
|
|
264
|
+
// Storage became unavailable; there is nothing safe to release.
|
|
265
|
+
}
|
|
266
|
+
}
|
|
173
267
|
}
|
|
174
268
|
hasLocalStorage() {
|
|
175
269
|
try {
|
|
@@ -179,5 +273,45 @@ export class SessionSync {
|
|
|
179
273
|
return false;
|
|
180
274
|
}
|
|
181
275
|
}
|
|
276
|
+
readRefreshLock() {
|
|
277
|
+
try {
|
|
278
|
+
const raw = localStorage.getItem(this.options.lockKey);
|
|
279
|
+
if (!raw)
|
|
280
|
+
return { available: true, value: null };
|
|
281
|
+
const parsed = JSON.parse(raw);
|
|
282
|
+
if (isRefreshLockRecord(parsed))
|
|
283
|
+
return { available: true, value: parsed };
|
|
284
|
+
// Read locks written by older versions so they expire normally. A
|
|
285
|
+
// legacy lock has no owner and therefore cannot be released by us.
|
|
286
|
+
const timestamp = Number.parseInt(raw, 10);
|
|
287
|
+
return Number.isFinite(timestamp)
|
|
288
|
+
? {
|
|
289
|
+
available: true,
|
|
290
|
+
value: { ownerId: '', lockId: '', timestamp },
|
|
291
|
+
}
|
|
292
|
+
: { available: true, value: null };
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return { available: false, value: null };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function createOwnerId() {
|
|
300
|
+
ownerSequence += 1;
|
|
301
|
+
return `afd-auth-${Date.now().toString(36)}-${ownerSequence.toString(36)}-${Math.random()
|
|
302
|
+
.toString(36)
|
|
303
|
+
.slice(2)}`;
|
|
304
|
+
}
|
|
305
|
+
function createLockId() {
|
|
306
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
307
|
+
}
|
|
308
|
+
function isRefreshLockRecord(value) {
|
|
309
|
+
if (typeof value !== 'object' || value === null)
|
|
310
|
+
return false;
|
|
311
|
+
const record = value;
|
|
312
|
+
return (typeof record.ownerId === 'string' &&
|
|
313
|
+
typeof record.lockId === 'string' &&
|
|
314
|
+
typeof record.timestamp === 'number' &&
|
|
315
|
+
Number.isFinite(record.timestamp));
|
|
182
316
|
}
|
|
183
317
|
//# sourceMappingURL=session-sync.js.map
|
package/dist/session-sync.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-sync.js","sourceRoot":"","sources":["../src/session-sync.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"session-sync.js","sourceRoot":"","sources":["../src/session-sync.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EACN,uBAAuB,GAGvB,MAAM,2BAA2B,CAAC;AAyBnC,MAAM,QAAQ,GAAiC;IAC9C,WAAW,EAAE,kBAAkB;IAC/B,UAAU,EAAE,eAAe;IAC3B,OAAO,EAAE,uBAAuB;IAChC,aAAa,EAAE,MAAM;IACrB,gBAAgB,EAAE,EAAE;IACpB,UAAU,EAAE,GAAG;IACf,mBAAmB,EAAE,OAAO;CAC5B,CAAC;AAEF;;;;GAIG;AACH,MAAM,kBAAkB,GAAG,KAAK,CAAC;AAkBjC,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB,MAAM,OAAO,WAAW;IACN,OAAO,CAA+B;IAC/C,OAAO,GAA4B,IAAI,CAAC;IAC/B,SAAS,GAAG,IAAI,WAAW,EAAsB,CAAC;IAClD,OAAO,GAAG,IAAI,GAAG,EAAyD,CAAC;IACpF,QAAQ,GAAkB,IAAI,CAAC;IAC/B,iBAAiB,GAAwB,IAAI,CAAC;IAC9C,cAAc,GAAuC,IAAI,CAAC;IAC1D,QAAQ,GAAG,KAAK,CAAC;IACR,OAAO,GAAG,aAAa,EAAE,CAAC;IACnC,UAAU,GAAkB,IAAI,CAAC;IAEzC,YAAY,UAA8B,EAAE;QAC3C,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;QAC3C,IAAI,CAAC,IAAI,EAAE,CAAC;IACb,CAAC;IAED;;;;OAIG;IACH,oBAAoB,CAAC,OAA2B;QAC/C,MAAM,KAAK,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,IAAI,SAAS,CAAC,iCAAiC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjF,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAE1B,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC;gBACJ,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;gBAChC,OAAO;YACR,CAAC;YAAC,MAAM,CAAC;gBACR,+BAA+B;YAChC,CAAC;QACF,CAAC;QAED,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED;;;;;OAKG;IACH,gBAAgB,CAAC,QAA+C;QAC/D,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED;;;OAGG;IACH,kBAAkB;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,IAAI,KAAK,eAAe,CAAC;QAExE,qEAAqE;QACrE,uEAAuE;QACvE,wEAAwE;QACxE,OAAO,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,uBAAuB;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,IAAI,KAAK,eAAe,CAAC;QAExE,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACnF,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACvC,OAAO,KAAK,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,CAAC;IAED;;OAEG;IACH,kBAAkB;QACjB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QACnC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,wEAAwE;QACxE,8BAA8B;QAC9B,IAAI,UAAU,KAAK,IAAI;YAAE,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAC7D,CAAC;IAED;;OAEG;IACH,OAAO;QACN,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,aAAa,EAAE,CAAC;QAErB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACrB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,CAAC;QAED,IAAI,IAAI,CAAC,cAAc,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAC1D,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;YAC3D,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC5B,CAAC;QAED,IAAI,IAAI,CAAC,iBAAiB,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YAC/D,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;YACzE,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAC/B,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAEO,IAAI;QACX,6BAA6B;QAC7B,IAAI,OAAO,gBAAgB,KAAK,WAAW,EAAE,CAAC;YAC7C,IAAI,CAAC;gBACJ,IAAI,CAAC,OAAO,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;gBAC9D,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,KAAmB,EAAE,EAAE;oBAChD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC1B,CAAC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACR,6DAA6D;YAC9D,CAAC;QACF,CAAC;QAED,2CAA2C;QAC3C,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,eAAe,EAAE,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAC9E,IAAI,CAAC,cAAc,GAAG,CAAC,CAAe,EAAE,EAAE;gBACzC,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;oBACrD,IAAI,CAAC;wBACJ,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;oBACtC,CAAC;oBAAC,MAAM,CAAC;wBACR,wBAAwB;oBACzB,CAAC;gBACF,CAAC;YACF,CAAC,CAAC;YACF,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QACzD,CAAC;QAED,4BAA4B;QAC5B,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YACrC,IAAI,CAAC,iBAAiB,GAAG,GAAG,EAAE;gBAC7B,IAAI,QAAQ,CAAC,eAAe,KAAK,QAAQ,EAAE,CAAC;oBAC3C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC5B,CAAC;qBAAM,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;oBAC7E,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;oBAC3C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;oBACrB,IAAI,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC;wBACjD,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC,CAAC;oBAC9C,CAAC;gBACF,CAAC;YACF,CAAC,CAAC;YACF,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACvE,CAAC;IACF,CAAC;IAEO,OAAO,CAAC,OAAgB;QAC/B,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,MAAM,OAAO,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;QACjD,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,IAAI,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACnC,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7B,OAAO;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChD,IAAI,QAAQ,KAAK,SAAS;YAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO,CAAC,GAAG,CACf,OAAO,CAAC,IAAI,EACZ,UAAU,CAAC,GAAG,EAAE;YACf,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAClC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAC3B,CAAC;IACH,CAAC;IAEO,aAAa;QACpB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/D,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAEO,gBAAgB,CAAC,OAA2B;QACnD,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE;YAAE,OAAO;QACpC,IAAI,CAAC;YACJ,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;YACvE,+DAA+D;YAC/D,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACR,mCAAmC;QACpC,CAAC;IACF,CAAC;IAEO,gBAAgB;QACvB,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAC/C,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE;YAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;QAE9D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACxC,IAAI,CAAC,QAAQ,CAAC,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;QAE1D,IAAI,QAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC;YACtE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,iCAAiC;QAC/D,CAAC;QAED,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAsB,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;QAClF,IAAI,CAAC;YACJ,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAClE,CAAC;QAAC,MAAM,CAAC;YACR,wEAAwE;YACxE,yEAAyE;YACzE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;QAClC,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpC,CAAC;IAEO,UAAU,CAAC,SAAiB,EAAE,GAAW;QAChD,OAAO,SAAS,IAAI,GAAG,GAAG,kBAAkB,IAAI,GAAG,GAAG,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;IAC9F,CAAC;IAEO,kBAAkB,CAAC,MAAc;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACxC,IACC,QAAQ,CAAC,SAAS;YAClB,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,OAAO;YACxC,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,EAC/B,CAAC;YACF,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC;YACzB,OAAO,IAAI,CAAC;QACb,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,OAAO,KAAK,CAAC;IACd,CAAC;IAEO,iBAAiB,CAAC,MAAc;QACvC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE;YAAE,OAAO;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACvC,IACC,OAAO,CAAC,SAAS;YACjB,OAAO,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,OAAO;YACvC,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,EAC9B,CAAC;YACF,IAAI,CAAC;gBACJ,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC/C,CAAC;YAAC,MAAM,CAAC;gBACR,gEAAgE;YACjE,CAAC;QACF,CAAC;IACF,CAAC;IAEO,eAAe;QACtB,IAAI,CAAC;YACJ,OAAO,OAAO,YAAY,KAAK,WAAW,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IAEO,eAAe;QACtB,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACvD,IAAI,CAAC,GAAG;gBAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YAElD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,mBAAmB,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;YAE3E,kEAAkE;YAClE,mEAAmE;YACnE,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC3C,OAAO,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAChC,CAAC,CAAC;oBACA,SAAS,EAAE,IAAI;oBACf,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE;iBAC7C;gBACF,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC1C,CAAC;IACF,CAAC;CACD;AAED,SAAS,aAAa;IACrB,aAAa,IAAI,CAAC,CAAC;IACnB,OAAO,YAAY,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE;SACvF,QAAQ,CAAC,EAAE,CAAC;SACZ,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACd,CAAC;AAED,SAAS,YAAY;IACpB,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAc;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,OAAO,CACN,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;QAClC,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ;QACjC,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;QACpC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CACjC,CAAC;AACH,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,7 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export interface Session {
|
|
8
8
|
id: string;
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* When the session stops being valid. Omitted when the provider manages the
|
|
11
|
+
* token lifetime itself and does not report an expiry (Convex).
|
|
12
|
+
* `createAuthMiddleware` rejects a session whose `expiresAt` is at or before
|
|
13
|
+
* the current time.
|
|
14
|
+
*/
|
|
15
|
+
expiresAt?: Date;
|
|
10
16
|
}
|
|
11
17
|
export interface User {
|
|
12
18
|
id: string;
|
|
@@ -27,6 +33,24 @@ export interface OAuthSignInOptions {
|
|
|
27
33
|
redirectTo?: string;
|
|
28
34
|
}
|
|
29
35
|
export type SignInOptions = CredentialsSignInOptions | OAuthSignInOptions;
|
|
36
|
+
/**
|
|
37
|
+
* What an adapter knows about a sign-in when `signIn()` resolves.
|
|
38
|
+
*
|
|
39
|
+
* - `signed-in`: the provider accepted the sign-in. The new session can still
|
|
40
|
+
* reach `getSession()` later, through `onAuthStateChange`.
|
|
41
|
+
* - `redirect`: sign-in continues at the provider (OAuth). No session exists
|
|
42
|
+
* until the provider redirects back to the app.
|
|
43
|
+
* - `pending`: the provider needs another step first, such as email
|
|
44
|
+
* verification.
|
|
45
|
+
*/
|
|
46
|
+
export type SignInOutcome = {
|
|
47
|
+
kind: 'signed-in';
|
|
48
|
+
} | {
|
|
49
|
+
kind: 'redirect';
|
|
50
|
+
url?: string;
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'pending';
|
|
53
|
+
};
|
|
30
54
|
export type AuthSessionState = {
|
|
31
55
|
status: 'unauthenticated';
|
|
32
56
|
session: null;
|
|
@@ -40,6 +64,10 @@ export type AuthSessionState = {
|
|
|
40
64
|
session: Session;
|
|
41
65
|
user: User;
|
|
42
66
|
};
|
|
67
|
+
/** The `authenticated` member of {@link AuthSessionState}. */
|
|
68
|
+
export type AuthenticatedSessionState = Extract<AuthSessionState, {
|
|
69
|
+
status: 'authenticated';
|
|
70
|
+
}>;
|
|
43
71
|
/**
|
|
44
72
|
* Constant for the unauthenticated state.
|
|
45
73
|
* Reuse this to avoid object allocation on every check.
|
|
@@ -51,7 +79,12 @@ export declare const UNAUTHENTICATED: AuthSessionState;
|
|
|
51
79
|
*/
|
|
52
80
|
export declare const LOADING: AuthSessionState;
|
|
53
81
|
export interface AuthAdapter {
|
|
54
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Start a sign-in. Resolve with a {@link SignInOutcome} when the provider
|
|
84
|
+
* says how the sign-in continues. Resolving with nothing is still allowed
|
|
85
|
+
* and is treated like `signed-in`.
|
|
86
|
+
*/
|
|
87
|
+
signIn(options: SignInOptions): Promise<SignInOutcome | void>;
|
|
55
88
|
signOut(): Promise<void>;
|
|
56
89
|
getSession(): AuthSessionState;
|
|
57
90
|
onAuthStateChange(callback: (state: AuthSessionState) => void): {
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,MAAM,CAAC;IACX;;;;;OAKG;IACH,SAAS,CAAC,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,IAAI;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAMD,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAErE,MAAM,WAAW,wBAAwB;IACxC,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,wBAAwB,GAAG,kBAAkB,CAAC;AAE1E;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GACtB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAMvB,MAAM,MAAM,gBAAgB,GACzB;IAAE,MAAM,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GACxD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GAChD;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC;AAE7D,8DAA8D;AAC9D,MAAM,MAAM,yBAAyB,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,CAAC,CAAC;AAE/F;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,gBAI5B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,gBAIpB,CAAC;AAMH,MAAM,WAAW,WAAW;IAC3B;;;;OAIG;IAEH,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC9D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,IAAI,gBAAgB,CAAC;IAC/B,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG;QAAE,WAAW,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC;CAC5F"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwEH;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAqB,MAAM,CAAC,MAAM,CAAC;IAC9D,MAAM,EAAE,iBAA0B;IAClC,OAAO,EAAE,IAAI;IACb,IAAI,EAAE,IAAI;CACV,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAqB,MAAM,CAAC,MAAM,CAAC;IACtD,MAAM,EAAE,SAAkB;IAC1B,OAAO,EAAE,IAAI;IACb,IAAI,EAAE,IAAI;CACV,CAAC,CAAC"}
|