@spooky-sync/core 0.0.1-canary.280 → 0.0.1-canary.281
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/dist/index.d.ts +7 -329
- package/dist/index.js +748 -39
- package/dist/live.d.ts +141 -0
- package/dist/live.js +709 -0
- package/dist/pure.d.ts +322 -0
- package/dist/pure.js +24 -0
- package/dist/sw.d.ts +207 -0
- package/dist/sw.js +393 -0
- package/dist/types.d.ts +742 -3
- package/dist/worker-edges.js +415 -0
- package/dist/worker-impersonation.js +60 -0
- package/dist/worker-ref-tables.d.ts +46 -0
- package/dist/worker-types.d.ts +112 -0
- package/dist/worker-types.js +73 -0
- package/package.json +16 -4
- package/scripts/check-worker-entries.mjs +172 -0
- package/src/client/services.ts +10 -38
- package/src/client/sp00ky-client.ts +13 -0
- package/src/index.ts +32 -0
- package/src/live/index.test.ts +478 -0
- package/src/live/index.ts +1013 -0
- package/src/modules/auth/index.ts +52 -0
- package/src/modules/web-push/index.test.ts +797 -0
- package/src/modules/web-push/index.ts +997 -0
- package/src/pure.ts +65 -0
- package/src/push/types.ts +174 -0
- package/src/sw/index.test.ts +582 -0
- package/src/sw/index.ts +607 -0
- package/src/sw/store.ts +96 -0
- package/src/testing/fake-services.ts +1 -1
- package/src/types.ts +11 -0
- package/src/utils/edges.ts +39 -0
- package/src/utils/sha256.ts +15 -0
- package/tsdown.config.ts +31 -2
|
@@ -10,6 +10,7 @@ export * from './events/index';
|
|
|
10
10
|
import { AuthEventTypes, createAuthEventSystem } from './events/index';
|
|
11
11
|
import type { PersistenceClient } from '../../types';
|
|
12
12
|
import { classifySyncError } from '../../utils/error-classification';
|
|
13
|
+
import { encodeRecordId } from '../../utils/index';
|
|
13
14
|
import {
|
|
14
15
|
decodeTokenClaims,
|
|
15
16
|
impersonationFromToken,
|
|
@@ -37,6 +38,20 @@ export interface ActiveImpersonation {
|
|
|
37
38
|
expires_at: unknown;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/** What a sign-out hook sees: the session that is about to end. */
|
|
42
|
+
export interface SignOutContext {
|
|
43
|
+
/** `table:id` of the signed-in record, or null. */
|
|
44
|
+
userId: string | null;
|
|
45
|
+
token: string | null;
|
|
46
|
+
/** The session is an admin impersonation (its token is the target's). */
|
|
47
|
+
impersonating: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type SignOutHook = (ctx: SignOutContext) => Promise<void> | void;
|
|
51
|
+
|
|
52
|
+
/** Upper bound on the before-sign-out hooks, so a dead socket never holds a sign-out. */
|
|
53
|
+
export const SIGN_OUT_HOOK_TIMEOUT_MS = 2_000;
|
|
54
|
+
|
|
40
55
|
// Helper to pretty print types
|
|
41
56
|
type Prettify<T> = {
|
|
42
57
|
[K in keyof T]: T[K];
|
|
@@ -81,6 +96,7 @@ export class AuthService<S extends SchemaStructure> {
|
|
|
81
96
|
public isLoading: boolean = true;
|
|
82
97
|
|
|
83
98
|
private events = createAuthEventSystem();
|
|
99
|
+
private signOutHooks = new Set<SignOutHook>();
|
|
84
100
|
|
|
85
101
|
/** The impersonation last announced, for change detection. */
|
|
86
102
|
private announcedImpersonation: ImpersonationInfo | null = null;
|
|
@@ -140,6 +156,40 @@ export class AuthService<S extends SchemaStructure> {
|
|
|
140
156
|
};
|
|
141
157
|
}
|
|
142
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Run `hook` at the start of every sign-out, while the token and the socket
|
|
161
|
+
* still belong to the leaving user (Web Push removes this device's
|
|
162
|
+
* subscription here). Hooks are best-effort: they run in parallel, errors
|
|
163
|
+
* are swallowed, and the sign-out goes on after
|
|
164
|
+
* {@link SIGN_OUT_HOOK_TIMEOUT_MS} whatever they are doing. Returns a disposer.
|
|
165
|
+
*/
|
|
166
|
+
onBeforeSignOut(hook: SignOutHook): () => void {
|
|
167
|
+
this.signOutHooks.add(hook);
|
|
168
|
+
return () => {
|
|
169
|
+
this.signOutHooks.delete(hook);
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private async runSignOutHooks(): Promise<void> {
|
|
174
|
+
if (this.signOutHooks.size === 0 || !this.isAuthenticated) return;
|
|
175
|
+
const id = this.currentUser?.id;
|
|
176
|
+
const ctx: SignOutContext = {
|
|
177
|
+
userId: id ? (typeof id === 'string' ? id : encodeRecordId(id)) : null,
|
|
178
|
+
token: this.token,
|
|
179
|
+
impersonating: this.impersonation !== null,
|
|
180
|
+
};
|
|
181
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
182
|
+
const deadline = new Promise<void>((resolve) => {
|
|
183
|
+
timer = setTimeout(resolve, SIGN_OUT_HOOK_TIMEOUT_MS);
|
|
184
|
+
});
|
|
185
|
+
const hooks = Promise.allSettled([...this.signOutHooks].map(async (hook) => hook(ctx)));
|
|
186
|
+
try {
|
|
187
|
+
await Promise.race([hooks, deadline]);
|
|
188
|
+
} finally {
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
143
193
|
private notifyListeners() {
|
|
144
194
|
const userId = this.currentUser?.id || null;
|
|
145
195
|
this.events.emit(AuthEventTypes.AuthStateChanged, userId);
|
|
@@ -457,6 +507,8 @@ export class AuthService<S extends SchemaStructure> {
|
|
|
457
507
|
* impersonation and signs the admin out too.
|
|
458
508
|
*/
|
|
459
509
|
async signOut() {
|
|
510
|
+
// Before anything is dropped: hooks still act as the leaving user.
|
|
511
|
+
await this.runSignOutHooks();
|
|
460
512
|
const impersonating = this.impersonation;
|
|
461
513
|
this.impersonationTimers.clear();
|
|
462
514
|
if (impersonating) {
|