@sveltebase/sync 1.1.4 → 1.3.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 +90 -30
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/live-query.svelte.d.ts +7 -0
- package/dist/client/live-query.svelte.d.ts.map +1 -0
- package/dist/client/live-query.svelte.js +25 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/server/broker.d.ts +3 -2
- package/dist/server/broker.d.ts.map +1 -1
- package/dist/server/broker.js +2 -5
- package/dist/server/dev-engine.d.ts +2 -1
- package/dist/server/dev-engine.d.ts.map +1 -1
- package/dist/server/dev-engine.js +47 -8
- package/dist/server/engine.d.ts.map +1 -1
- package/dist/server/engine.js +25 -1
- package/dist/server/handler.d.ts +21 -1
- package/dist/server/handler.d.ts.map +1 -1
- package/dist/server/handler.js +34 -2
- package/dist/server/index.d.ts +17 -14
- package/dist/server/index.d.ts.map +1 -1
- package/dist/vite.d.ts +4 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,15 +92,17 @@ export const sync = new SyncClient<AppDatabaseSchema>({
|
|
|
92
92
|
});
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
Use it in your Svelte 5 components
|
|
95
|
+
Use it in your Svelte 5 components with `createLiveQuery`. It wraps Dexie's `liveQuery` in Svelte 5 rune-based reactive state and exposes `data`, `isLoading`, and `error`.
|
|
96
|
+
|
|
96
97
|
```svelte
|
|
97
98
|
<script lang="ts">
|
|
98
99
|
import { sync } from "$lib/sync-client";
|
|
99
|
-
import {
|
|
100
|
+
import { createLiveQuery } from "@sveltebase/sync/client";
|
|
100
101
|
import { Check, Trash } from "lucide-svelte";
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
const todosQuery = createLiveQuery(() =>
|
|
104
|
+
sync.todos.orderBy("createdAt").reverse().toArray()
|
|
105
|
+
);
|
|
104
106
|
|
|
105
107
|
let title = "";
|
|
106
108
|
|
|
@@ -119,7 +121,12 @@ Use it in your Svelte 5 components (using your preferred Dexie reactivity hook,
|
|
|
119
121
|
|
|
120
122
|
<input bind:value={title} onkeydown={(e) => e.key === 'Enter' && addTodo()} />
|
|
121
123
|
|
|
122
|
-
{#
|
|
124
|
+
{#if todosQuery.isLoading}
|
|
125
|
+
<p>Loading...</p>
|
|
126
|
+
{:else if todosQuery.error}
|
|
127
|
+
<p>Failed to load todos.</p>
|
|
128
|
+
{:else}
|
|
129
|
+
{#each (todosQuery.data || []) as todo (todo.id)}
|
|
123
130
|
<div>
|
|
124
131
|
<button onclick={() => sync.todos.update(todo.id, { completed: !todo.completed })}>
|
|
125
132
|
<Check class={todo.completed ? "text-emerald-500" : ""} />
|
|
@@ -127,9 +134,25 @@ Use it in your Svelte 5 components (using your preferred Dexie reactivity hook,
|
|
|
127
134
|
<span>{todo.title}</span>
|
|
128
135
|
<button onclick={() => sync.todos.delete(todo.id)}><Trash /></button>
|
|
129
136
|
</div>
|
|
130
|
-
{/each}
|
|
137
|
+
{/each}
|
|
138
|
+
{/if}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`createLiveQuery` accepts a Dexie query function and an optional dependency getter. When any dependency changes, the live query is recreated with the latest reactive values:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const query = createLiveQuery(
|
|
145
|
+
() => sync.todos.where("completed").equals(false).toArray(),
|
|
146
|
+
() => [filterValue]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
query.data;
|
|
150
|
+
query.isLoading;
|
|
151
|
+
query.error;
|
|
131
152
|
```
|
|
132
153
|
|
|
154
|
+
You can import it from either `@sveltebase/sync/client` or the root `@sveltebase/sync` entrypoint.
|
|
155
|
+
|
|
133
156
|
### Synced Database Operations
|
|
134
157
|
|
|
135
158
|
Under the hood, `@sveltebase/sync` intercepts native Dexie table writes to capture and propagate mutations to the backend. The following methods automatically sync with the server:
|
|
@@ -324,6 +347,41 @@ async function getSession(ctx: SyncContext) {
|
|
|
324
347
|
}
|
|
325
348
|
```
|
|
326
349
|
|
|
350
|
+
### Connection Auth (`ctx.auth`)
|
|
351
|
+
Sveltebase Sync can resolve and store an authenticated app payload during the WebSocket handshake. The resolved payload is passed to every sync handler as `ctx.auth`.
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
// src/routes/api/sync/+server.ts
|
|
355
|
+
import { JWT_SECRET } from "$env/static/private";
|
|
356
|
+
import { getVerifiedUserFromRequest } from "@sveltebase/auth";
|
|
357
|
+
import { handleUpgrade } from "@sveltebase/sync";
|
|
358
|
+
import type { User } from "$lib/server/db/schema";
|
|
359
|
+
import type { RequestHandler } from "@sveltejs/kit";
|
|
360
|
+
|
|
361
|
+
export const GET: RequestHandler = (event) => {
|
|
362
|
+
return handleUpgrade(event.request, event.platform, {
|
|
363
|
+
auth: async (request) => {
|
|
364
|
+
const user = await getVerifiedUserFromRequest<User>(
|
|
365
|
+
request,
|
|
366
|
+
JWT_SECRET
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
return user ? { user } : null;
|
|
370
|
+
},
|
|
371
|
+
identity: (auth) => auth.user.id,
|
|
372
|
+
allowUnauthenticated: false
|
|
373
|
+
});
|
|
374
|
+
};
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
After this, every handler can access the user object:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
ctx.auth?.user;
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
The `identity` function returns the stable string/number key used by scoped broadcasts. If omitted, Sync defaults to `auth.user.id` when present. Existing `userId` query parameter and `x-user-id` header identity are still supported as legacy fallback, but should not be used as the primary auth mechanism for private data.
|
|
384
|
+
|
|
327
385
|
---
|
|
328
386
|
|
|
329
387
|
### The `authorize` Hook
|
|
@@ -350,7 +408,8 @@ Use the handshake HTTP request (`ctx.request`) to dynamically filter the records
|
|
|
350
408
|
```typescript
|
|
351
409
|
fetch: async (ctx, since) => {
|
|
352
410
|
const db = getDB(ctx.platform);
|
|
353
|
-
const user =
|
|
411
|
+
const user = ctx.auth?.user;
|
|
412
|
+
if (!user) return [];
|
|
354
413
|
|
|
355
414
|
let query = db.select().from(todos);
|
|
356
415
|
const conditions = [];
|
|
@@ -393,12 +452,16 @@ create: async (ctx, data) => {
|
|
|
393
452
|
},
|
|
394
453
|
|
|
395
454
|
update: async (ctx, key, changes) => {
|
|
396
|
-
const user =
|
|
455
|
+
const user = ctx.auth?.user;
|
|
456
|
+
if (!user) {
|
|
457
|
+
throw new Error("Unauthorized");
|
|
458
|
+
}
|
|
459
|
+
|
|
397
460
|
const db = getDB(ctx.platform);
|
|
398
461
|
|
|
399
462
|
// Fetch target record to verify ownership
|
|
400
463
|
const [record] = await db.select().from(todos).where(eq(todos.id, key));
|
|
401
|
-
if (record.ownerId !== user.
|
|
464
|
+
if (record.ownerId !== user.id && user.role !== "admin") {
|
|
402
465
|
throw new Error("You cannot update a record owned by someone else.");
|
|
403
466
|
}
|
|
404
467
|
|
|
@@ -456,35 +519,35 @@ export const todoSync = defineSync<Todo>({
|
|
|
456
519
|
```
|
|
457
520
|
|
|
458
521
|
#### How Connection Identities are Registered
|
|
459
|
-
The broker matches the
|
|
460
|
-
```typescript
|
|
461
|
-
const userId = url.searchParams.get("userId") || request.headers.get("x-user-id");
|
|
462
|
-
```
|
|
522
|
+
The broker matches the IDs returned by `scope` to each active connection's registered identity. That identity is resolved during the WebSocket handshake with the `identity` option on `handleUpgrade`.
|
|
463
523
|
|
|
464
524
|
##### Authenticating using SvelteKit Sessions & Cookies (Recommended)
|
|
465
|
-
|
|
525
|
+
Resolve the user session on the server inside your SvelteKit route (`+server.ts`) and return the full user object as connection auth:
|
|
466
526
|
|
|
467
527
|
```typescript
|
|
468
528
|
// src/routes/api/sync/+server.ts
|
|
529
|
+
import { JWT_SECRET } from "$env/static/private";
|
|
530
|
+
import { getVerifiedUserFromRequest } from "@sveltebase/auth";
|
|
469
531
|
import { handleUpgrade } from "@sveltebase/sync";
|
|
532
|
+
import type { User } from "$lib/server/db/schema";
|
|
470
533
|
import type { RequestEvent, RequestHandler } from "@sveltejs/kit";
|
|
471
534
|
|
|
472
|
-
export const GET: RequestHandler = (event: RequestEvent) => {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
// 2. Clone the request and inject the verified user ID header
|
|
480
|
-
const request = new Request(event.request);
|
|
481
|
-
request.headers.set("x-user-id", user.id);
|
|
535
|
+
export const GET: RequestHandler = async (event: RequestEvent) => {
|
|
536
|
+
return handleUpgrade(event.request, event.platform, {
|
|
537
|
+
auth: async (request) => {
|
|
538
|
+
const user = await getVerifiedUserFromRequest<User>(
|
|
539
|
+
request,
|
|
540
|
+
JWT_SECRET
|
|
541
|
+
);
|
|
482
542
|
|
|
483
|
-
|
|
484
|
-
|
|
543
|
+
return user ? { user } : null;
|
|
544
|
+
},
|
|
545
|
+
identity: (auth) => auth.user.id,
|
|
546
|
+
allowUnauthenticated: false
|
|
547
|
+
});
|
|
485
548
|
};
|
|
486
549
|
```
|
|
487
|
-
This approach keeps WebSocket URLs clean of private IDs
|
|
550
|
+
This approach keeps WebSocket URLs clean of private IDs, makes `ctx.auth.user` available to your sync handlers, and gives the broker a stable identity for `scope` filtering.
|
|
488
551
|
|
|
489
552
|
---
|
|
490
553
|
|
|
@@ -520,6 +583,3 @@ await publish("todos", "delete", todo.id, undefined);
|
|
|
520
583
|
// 4. Supports scoped/dynamic channels (e.g. "channelName:scopeId")
|
|
521
584
|
await publish("todos:user_123", "update", todo.id, { title: "New Title" });
|
|
522
585
|
```
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import Dexie, { type Table } from "dexie";
|
|
2
|
+
export { createLiveQuery } from "./live-query.svelte.js";
|
|
3
|
+
export type { LiveQueryState } from "./live-query.svelte.js";
|
|
2
4
|
export type TableConfig = {
|
|
3
5
|
indexes: string;
|
|
4
6
|
channel: string;
|
|
@@ -49,5 +51,4 @@ export declare const SyncClient: new <TSchema extends Record<string, any> = Reco
|
|
|
49
51
|
url: string | (() => string | Promise<string>);
|
|
50
52
|
tables: Record<keyof TSchema & string, TableConfig>;
|
|
51
53
|
}) => SyncClient<TSchema>;
|
|
52
|
-
export {};
|
|
53
54
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC;AAI1C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACrC,CAAC;AAaF,cAAM,eAAe,CACnB,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACzD,SAAQ,KAAK;IACb,OAAO,CAAC,KAAK,CAA4C;IACzD,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,YAAY,CAA6C;IACjE,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,cAAc,CAAqB;IAG3C,OAAO,CAAC,YAAY,CAA0B;IAG9C,OAAO,CAAC,gBAAgB,CAAsC;IAE9D,OAAO,CAAC,aAAa,CAMb;gBAEI,OAAO,EAAE;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/C,MAAM,EAAE,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,EAAE,WAAW,CAAC,CAAC;KACrD;IAoBD,IAAW,MAAM,gDAEhB;IAED,OAAO,CAAC,cAAc;YAiJR,OAAO;IAuFd,SAAS;IAgBhB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,aAAa;YAOP,kBAAkB;IAoBhC,OAAO,CAAC,kBAAkB;YAkBZ,UAAU;YAiBV,aAAa;YAqBb,mBAAmB;IAyFjC,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,eAAe;IAqChB,UAAU;CAelB;AAED,MAAM,MAAM,UAAU,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,GAAG;KAC5G,CAAC,IAAI,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;CACxC,GAAG;IACF,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;CACjC,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KACvB,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzD,OAAO,EAAE;IACT,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/C,MAAM,EAAE,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,EAAE,WAAW,CAAC,CAAC;CACrD,KAAK,UAAU,CAAC,OAAO,CAA0B,CAAC"}
|
package/dist/client/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-query.svelte.d.ts","sourceRoot":"","sources":["../../src/client/live-query.svelte.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,GAAG,CAAC;CACb,CAAC;AAEF,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC7B,YAAY,CAAC,EAAE,MAAM,OAAO,EAAE,GAC7B,cAAc,CAAC,CAAC,CAAC,CA6BnB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { liveQuery } from "dexie";
|
|
2
|
+
export function createLiveQuery(querier, dependencies) {
|
|
3
|
+
const query = $state({
|
|
4
|
+
data: undefined,
|
|
5
|
+
isLoading: true,
|
|
6
|
+
error: undefined,
|
|
7
|
+
});
|
|
8
|
+
$effect(() => {
|
|
9
|
+
dependencies?.();
|
|
10
|
+
return liveQuery(querier).subscribe((value) => {
|
|
11
|
+
query.error = undefined;
|
|
12
|
+
if (value !== undefined) {
|
|
13
|
+
query.data = value;
|
|
14
|
+
query.isLoading = false;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
query.isLoading = true;
|
|
18
|
+
}
|
|
19
|
+
}, (error) => {
|
|
20
|
+
query.error = error;
|
|
21
|
+
query.isLoading = false;
|
|
22
|
+
}).unsubscribe;
|
|
23
|
+
});
|
|
24
|
+
return query;
|
|
25
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { SyncClient } from "./client/index.js";
|
|
1
|
+
export { SyncClient, createLiveQuery } from "./client/index.js";
|
|
2
|
+
export type { LiveQueryState } from "./client/index.js";
|
|
2
3
|
export { defineSync } from "./server/index.js";
|
|
3
4
|
export { handleUpgrade, publishEvent, publishBulkEvent, createPublisher, createBulkPublisher } from "./server/handler.js";
|
|
4
|
-
export type { PublishEventData, InferSchemaFromHandlers } from "./server/handler.js";
|
|
5
|
-
export type { SyncContext, SyncHandler } from "./server/index.js";
|
|
5
|
+
export type { PublishEventData, InferSchemaFromHandlers, SyncAuthResult, SyncUpgradeOptions } from "./server/handler.js";
|
|
6
|
+
export type { SyncConnectionAuth, SyncContext, SyncHandler } from "./server/index.js";
|
|
6
7
|
export type { SyncMessage } from "./protocol.js";
|
|
7
8
|
//# 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,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1H,YAAY,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzH,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACtF,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { SyncClient } from "./client/index.js";
|
|
1
|
+
export { SyncClient, createLiveQuery } from "./client/index.js";
|
|
2
2
|
export { defineSync } from "./server/index.js";
|
|
3
3
|
export { handleUpgrade, publishEvent, publishBulkEvent, createPublisher, createBulkPublisher } from "./server/handler.js";
|
package/dist/server/broker.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export interface ISyncConnection {
|
|
|
4
4
|
close(code?: number, reason?: string): void;
|
|
5
5
|
getAuth(): any;
|
|
6
6
|
setAuth(auth: any): void;
|
|
7
|
+
getIdentity(): string | null;
|
|
8
|
+
setIdentity(identity: string | null): void;
|
|
7
9
|
getSubscribedChannels(): Set<string>;
|
|
8
10
|
readonly headers: Headers;
|
|
9
11
|
readonly url: string;
|
|
@@ -11,8 +13,7 @@ export interface ISyncConnection {
|
|
|
11
13
|
export declare class SyncBroker {
|
|
12
14
|
private handlers;
|
|
13
15
|
private connections;
|
|
14
|
-
|
|
15
|
-
constructor(handlers: SyncHandler[], authorizeConnection?: (request: Request, platform: App.Platform | undefined) => Promise<any>);
|
|
16
|
+
constructor(handlers: SyncHandler[]);
|
|
16
17
|
setHandlers(handlers: SyncHandler[]): void;
|
|
17
18
|
registerConnection(conn: ISyncConnection): void;
|
|
18
19
|
removeConnection(conn: ISyncConnection): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker.d.ts","sourceRoot":"","sources":["../../src/server/broker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,YAAY,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,OAAO,IAAI,GAAG,CAAC;IACf,OAAO,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC;IACzB,qBAAqB,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,WAAW,CAAmC;
|
|
1
|
+
{"version":3,"file":"broker.d.ts","sourceRoot":"","sources":["../../src/server/broker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,YAAY,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,OAAO,IAAI,GAAG,CAAC;IACf,OAAO,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC;IACzB,WAAW,IAAI,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAC3C,qBAAqB,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,WAAW,CAAmC;gBAE1C,QAAQ,EAAE,WAAW,EAAE;IAK5B,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE;IAanC,kBAAkB,CAAC,IAAI,EAAE,eAAe;IAIxC,gBAAgB,CAAC,IAAI,EAAE,eAAe;IAI7C;;OAEG;IACH,OAAO,CAAC,WAAW;IAqCN,aAAa,CACxB,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,SAAS,EAClC,OAAO,EAAE,OAAO;YA0IJ,eAAe;IAmDhB,oBAAoB,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EACtC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,GAAG;IAqBE,yBAAyB,CACpC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;CAkBvF"}
|
package/dist/server/broker.js
CHANGED
|
@@ -2,11 +2,9 @@ import { parseSyncMessage } from "../protocol.js";
|
|
|
2
2
|
export class SyncBroker {
|
|
3
3
|
handlers;
|
|
4
4
|
connections = new Set();
|
|
5
|
-
|
|
6
|
-
constructor(handlers, authorizeConnection) {
|
|
5
|
+
constructor(handlers) {
|
|
7
6
|
this.handlers = new Map();
|
|
8
7
|
this.setHandlers(handlers);
|
|
9
|
-
this.authorizeConnection = authorizeConnection;
|
|
10
8
|
}
|
|
11
9
|
setHandlers(handlers) {
|
|
12
10
|
this.handlers.clear();
|
|
@@ -194,8 +192,7 @@ export class SyncBroker {
|
|
|
194
192
|
}
|
|
195
193
|
// Filter based on scope
|
|
196
194
|
if (allowedUserIds !== "all") {
|
|
197
|
-
const
|
|
198
|
-
const userId = connAuth?.userId;
|
|
195
|
+
const userId = conn.getIdentity();
|
|
199
196
|
if (!userId || !allowedUserIds.includes(userId)) {
|
|
200
197
|
continue;
|
|
201
198
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { SyncHandler } from "./index.js";
|
|
2
|
+
import type { SyncUpgradeOptions } from "./handler.js";
|
|
2
3
|
import type { IncomingMessage } from "node:http";
|
|
3
4
|
export declare function setHandlers(handlers: SyncHandler[]): void;
|
|
4
5
|
export declare function addClient(ws: {
|
|
5
6
|
send: (data: string) => void;
|
|
6
7
|
close: (code?: number, reason?: string) => void;
|
|
7
8
|
on: (event: string, listener: (...args: any[]) => void) => void;
|
|
8
|
-
}, req: IncomingMessage):
|
|
9
|
+
}, req: IncomingMessage, options?: SyncUpgradeOptions): Promise<boolean>;
|
|
9
10
|
export declare function broadcastExternalChange(channel: string, action: "create" | "update" | "delete", key: string | undefined, data: any): Promise<void>;
|
|
10
11
|
export declare function broadcastExternalBatchChange(channel: string, changes: Array<{
|
|
11
12
|
action: "create" | "update" | "delete";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-engine.d.ts","sourceRoot":"","sources":["../../src/server/dev-engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAUjD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,QASlD;AAcD,
|
|
1
|
+
{"version":3,"file":"dev-engine.d.ts","sourceRoot":"","sources":["../../src/server/dev-engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAUjD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,QASlD;AAcD,wBAAsB,SAAS,CAC7B,EAAE,EAAE;IACF,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,IAAI,CAAC;CACjE,EACD,GAAG,EAAE,eAAe,EACpB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,OAAO,CAAC,CAwHlB;AAyBD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EACtC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,GAAG,iBAIV;AAED,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ,CAAC,iBAIH"}
|
|
@@ -22,10 +22,11 @@ function getDevBroker() {
|
|
|
22
22
|
}
|
|
23
23
|
throw new Error("Sync dev broker not initialized. Call setHandlers first.");
|
|
24
24
|
}
|
|
25
|
-
export function addClient(ws, req) {
|
|
25
|
+
export async function addClient(ws, req, options) {
|
|
26
26
|
const broker = getDevBroker();
|
|
27
27
|
const subscribedChannels = new Set();
|
|
28
28
|
let auth = null;
|
|
29
|
+
let identity = null;
|
|
29
30
|
// Convert Node IncomingMessage headers to web-standard Headers
|
|
30
31
|
const headers = new Headers();
|
|
31
32
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
@@ -41,11 +42,29 @@ export function addClient(ws, req) {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
const urlObj = new URL(req.url ?? "", `http://${req.headers.host || "localhost"}`);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
const initConnection = async () => {
|
|
46
|
+
const request = new Request(urlObj.toString(), { headers });
|
|
47
|
+
if (options?.auth) {
|
|
48
|
+
auth = (await options.auth(request, undefined)) ?? null;
|
|
49
|
+
if (!auth && options.allowUnauthenticated === false) {
|
|
50
|
+
ws.close(1008, "Unauthorized");
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (auth) {
|
|
54
|
+
const identityValue = options.identity
|
|
55
|
+
? options.identity(auth)
|
|
56
|
+
: (auth?.user?.id ?? auth?.userId);
|
|
57
|
+
identity = identityValue == null ? null : String(identityValue);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Legacy fallback for existing demos/apps that pass identity in URL/header.
|
|
61
|
+
const userId = urlObj.searchParams.get("userId") || headers.get("x-user-id");
|
|
62
|
+
if (userId && !auth) {
|
|
63
|
+
auth = { userId };
|
|
64
|
+
identity = userId;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
};
|
|
49
68
|
const conn = {
|
|
50
69
|
send(data) {
|
|
51
70
|
ws.send(data);
|
|
@@ -59,14 +78,33 @@ export function addClient(ws, req) {
|
|
|
59
78
|
setAuth(newAuth) {
|
|
60
79
|
auth = newAuth;
|
|
61
80
|
},
|
|
81
|
+
getIdentity() {
|
|
82
|
+
return identity;
|
|
83
|
+
},
|
|
84
|
+
setIdentity(newIdentity) {
|
|
85
|
+
identity = newIdentity;
|
|
86
|
+
},
|
|
62
87
|
getSubscribedChannels() {
|
|
63
88
|
return subscribedChannels;
|
|
64
89
|
},
|
|
65
90
|
headers,
|
|
66
91
|
url: urlObj.toString(),
|
|
67
92
|
};
|
|
68
|
-
|
|
69
|
-
|
|
93
|
+
try {
|
|
94
|
+
const ok = await initConnection();
|
|
95
|
+
if (!ok)
|
|
96
|
+
return false;
|
|
97
|
+
broker.registerConnection(conn);
|
|
98
|
+
console.log("dev-engine: addClient registered connection");
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
console.error("dev-engine: Error initializing connection:", err);
|
|
102
|
+
try {
|
|
103
|
+
ws.close(1011, "Internal server error");
|
|
104
|
+
}
|
|
105
|
+
catch { }
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
70
108
|
ws.on("message", async (data) => {
|
|
71
109
|
const messageString = String(data);
|
|
72
110
|
console.log("dev-engine: WebSocket message received:", messageString.slice(0, 100));
|
|
@@ -92,6 +130,7 @@ export function addClient(ws, req) {
|
|
|
92
130
|
console.error("dev-engine: WebSocket connection error:", err);
|
|
93
131
|
broker.removeConnection(conn);
|
|
94
132
|
});
|
|
133
|
+
return true;
|
|
95
134
|
}
|
|
96
135
|
const GLOBAL_PLATFORM_KEY = "__sync_dev_platform__";
|
|
97
136
|
async function getPlatform() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/server/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAwB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/server/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAwB,MAAM,aAAa,CAAC;AAqE/D,qBAAa,cAAe,SAAQ,aAAa,CAAC,GAAG,CAAC;IACpD,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC;IAC7B,OAAO,CAAC,OAAO,CAAkD;gBAErD,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE;IAKxD,KAAK,CAAC,OAAO,EAAE,OAAO;IAoC5B,OAAO,CAAC,gBAAgB;IAoClB,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW;IAiBnE,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAa1D,cAAc,CAAC,EAAE,EAAE,SAAS;CAO7B"}
|
package/dist/server/engine.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { DurableObject } from "cloudflare:workers";
|
|
2
2
|
import { SyncBroker } from "./broker.js";
|
|
3
|
+
import { INTERNAL_AUTH_HEADER } from "./handler.js";
|
|
4
|
+
function deserializeConnectionAuth(value) {
|
|
5
|
+
if (!value)
|
|
6
|
+
return null;
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(decodeURIComponent(escape(atob(value))));
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
3
14
|
class CloudflareSyncConnection {
|
|
4
15
|
ws;
|
|
5
16
|
auth = null;
|
|
17
|
+
identity = null;
|
|
6
18
|
subscribedChannels = new Set();
|
|
7
19
|
headers;
|
|
8
20
|
url;
|
|
@@ -33,6 +45,12 @@ class CloudflareSyncConnection {
|
|
|
33
45
|
setAuth(newAuth) {
|
|
34
46
|
this.auth = newAuth;
|
|
35
47
|
}
|
|
48
|
+
getIdentity() {
|
|
49
|
+
return this.identity;
|
|
50
|
+
}
|
|
51
|
+
setIdentity(identity) {
|
|
52
|
+
this.identity = identity;
|
|
53
|
+
}
|
|
36
54
|
getSubscribedChannels() {
|
|
37
55
|
return this.subscribedChannels;
|
|
38
56
|
}
|
|
@@ -84,10 +102,16 @@ export class SyncEngineBase extends DurableObject {
|
|
|
84
102
|
const [client, server] = Object.values(new WebSocketPair());
|
|
85
103
|
this.ctx.acceptWebSocket(server);
|
|
86
104
|
const conn = new CloudflareSyncConnection(server, request);
|
|
105
|
+
const forwardedAuth = deserializeConnectionAuth(request.headers.get(INTERNAL_AUTH_HEADER));
|
|
106
|
+
if (forwardedAuth) {
|
|
107
|
+
conn.setAuth(forwardedAuth.auth);
|
|
108
|
+
conn.setIdentity(forwardedAuth.identity);
|
|
109
|
+
}
|
|
87
110
|
const url = new URL(request.url);
|
|
88
111
|
const userId = url.searchParams.get("userId") || request.headers.get("x-user-id");
|
|
89
|
-
if (userId) {
|
|
112
|
+
if (userId && !conn.getAuth()) {
|
|
90
113
|
conn.setAuth({ userId });
|
|
114
|
+
conn.setIdentity(userId);
|
|
91
115
|
}
|
|
92
116
|
this.connMap.set(server, conn);
|
|
93
117
|
this.broker.registerConnection(conn);
|
package/dist/server/handler.d.ts
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
import type { SyncHandler } from "./index.js";
|
|
2
|
+
export type SyncAuthResult<TAuth> = TAuth | null | undefined;
|
|
3
|
+
export type SyncUpgradeOptions<TAuth = any> = {
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the authenticated app payload for this WebSocket connection.
|
|
6
|
+
* Return null/undefined when no session is present.
|
|
7
|
+
*/
|
|
8
|
+
auth?: (request: Request, platform: App.Platform | undefined) => Promise<SyncAuthResult<TAuth>> | SyncAuthResult<TAuth>;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the stable identity key used by scope filtering.
|
|
11
|
+
* Defaults to auth.user.id when available, then auth.userId for legacy payloads.
|
|
12
|
+
*/
|
|
13
|
+
identity?: (auth: TAuth) => string | number | bigint | null | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Defaults to true so existing public channels continue to work.
|
|
16
|
+
* Set false to reject WebSocket upgrades without a resolved auth payload.
|
|
17
|
+
*/
|
|
18
|
+
allowUnauthenticated?: boolean;
|
|
19
|
+
};
|
|
20
|
+
declare const INTERNAL_AUTH_HEADER = "x-sveltebase-sync-auth";
|
|
2
21
|
export type PublishEventData<TRecord, TAction extends "create" | "update" | "delete"> = TAction extends "create" ? TRecord : TAction extends "update" ? Partial<TRecord> : {
|
|
3
22
|
updatedAt?: string;
|
|
4
23
|
} | undefined;
|
|
@@ -23,5 +42,6 @@ export declare function publishBulkEvent(channel: string, changes: Array<{
|
|
|
23
42
|
key?: string;
|
|
24
43
|
data?: any;
|
|
25
44
|
}>): Promise<void>;
|
|
26
|
-
export declare function handleUpgrade(request: Request, platform: App.Platform | undefined): Promise<Response>;
|
|
45
|
+
export declare function handleUpgrade<TAuth = any>(request: Request, platform: App.Platform | undefined, options?: SyncUpgradeOptions<TAuth>): Promise<Response>;
|
|
46
|
+
export { INTERNAL_AUTH_HEADER };
|
|
27
47
|
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/server/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,MAAM,gBAAgB,CAAC,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,QAAQ,GAAG,QAAQ,IAClF,OAAO,SAAS,QAAQ,GACpB,OAAO,GACP,OAAO,SAAS,QAAQ,GACtB,OAAO,CAAC,OAAO,CAAC,GAChB;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC;AAE3C,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI;KAC5D,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,SAAS,MAAM,GACpD,CAAC,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,GACtB,CAAC,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC,GACxD,CAAC,SAAS,MAAM,GACd,CAAC,GACD,MAAM,GACR,MAAM,GAAG,CAAC,SAAS,WAAW,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK;CAChE,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CACtE,QAAQ,SAAS,MAAM,OAAO,GAAG,MAAM,EACvC,OAAO,SAAS,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EAE9C,OAAO,EAAE,QAAQ,GAAG,GAAG,QAAQ,IAAI,MAAM,EAAE,EAC3C,MAAM,EAAE,OAAO,EACf,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,KAC/C,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,wBAAgB,eAAe,CAAC,SAAS,SAAS,WAAW,EAAE,EAC7D,QAAQ,EAAE,SAAS,GAClB,CACD,QAAQ,SAAS,MAAM,uBAAuB,CAAC,SAAS,CAAC,GAAG,MAAM,EAClE,OAAO,SAAS,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EAE9C,OAAO,EAAE,QAAQ,GAAG,GAAG,QAAQ,IAAI,MAAM,EAAE,EAC3C,MAAM,EAAE,OAAO,EACf,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,gBAAgB,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,KAC1E,OAAO,CAAC,IAAI,CAAC,CAAC;AAcnB,wBAAgB,mBAAmB,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CAC1E,QAAQ,SAAS,MAAM,OAAO,GAAG,MAAM,EAEvC,OAAO,EAAE,QAAQ,GAAG,GAAG,QAAQ,IAAI,MAAM,EAAE,EAC3C,OAAO,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ,CAAC,KACC,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,wBAAgB,mBAAmB,CAAC,SAAS,SAAS,WAAW,EAAE,EACjE,QAAQ,EAAE,SAAS,GAClB,CACD,QAAQ,SAAS,MAAM,uBAAuB,CAAC,SAAS,CAAC,GAAG,MAAM,EAElE,OAAO,EAAE,QAAQ,GAAG,GAAG,QAAQ,IAAI,MAAM,EAAE,EAC3C,OAAO,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ,CAAC,KACC,OAAO,CAAC,IAAI,CAAC,CAAC;AAgBnB,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EACtC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,GAAG,iBA4CV;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ,CAAC,iBA4CH;AAED,wBAAsB,aAAa,
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/server/handler.ts"],"names":[],"mappings":"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"}
|
package/dist/server/handler.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
const INTERNAL_AUTH_HEADER = "x-sveltebase-sync-auth";
|
|
2
|
+
function defaultIdentity(auth) {
|
|
3
|
+
const value = auth?.user?.id ?? auth?.userId;
|
|
4
|
+
return value == null ? null : String(value);
|
|
5
|
+
}
|
|
6
|
+
function serializeConnectionAuth(auth, identity) {
|
|
7
|
+
const payload = { auth, identity };
|
|
8
|
+
return btoa(unescape(encodeURIComponent(JSON.stringify(payload))));
|
|
9
|
+
}
|
|
1
10
|
export function createPublisher(handlers) {
|
|
2
11
|
return async (channel, action, key, data) => {
|
|
3
12
|
const resolvedChannel = String(channel);
|
|
@@ -92,7 +101,7 @@ export async function publishBulkEvent(channel, changes) {
|
|
|
92
101
|
console.error("Failed to publish bulk sync event to Durable Object:", err);
|
|
93
102
|
}
|
|
94
103
|
}
|
|
95
|
-
export async function handleUpgrade(request, platform) {
|
|
104
|
+
export async function handleUpgrade(request, platform, options) {
|
|
96
105
|
if (request.headers.get("Upgrade") !== "websocket") {
|
|
97
106
|
return new Response("Expected Upgrade: websocket", { status: 426 });
|
|
98
107
|
}
|
|
@@ -101,9 +110,31 @@ export async function handleUpgrade(request, platform) {
|
|
|
101
110
|
return new Response("SyncEngine binding is not available", { status: 500 });
|
|
102
111
|
}
|
|
103
112
|
try {
|
|
113
|
+
let resolvedAuth = null;
|
|
114
|
+
let identity = null;
|
|
115
|
+
if (options?.auth) {
|
|
116
|
+
resolvedAuth = (await options.auth(request, platform)) ?? null;
|
|
117
|
+
if (!resolvedAuth && options.allowUnauthenticated === false) {
|
|
118
|
+
return new Response("Unauthorized", { status: 401 });
|
|
119
|
+
}
|
|
120
|
+
if (resolvedAuth) {
|
|
121
|
+
const identityValue = options.identity
|
|
122
|
+
? options.identity(resolvedAuth)
|
|
123
|
+
: defaultIdentity(resolvedAuth);
|
|
124
|
+
identity = identityValue == null ? null : String(identityValue);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const forwardedRequest = new Request("https://realtime.internal/websocket", request);
|
|
128
|
+
forwardedRequest.headers.delete(INTERNAL_AUTH_HEADER);
|
|
129
|
+
if (resolvedAuth) {
|
|
130
|
+
forwardedRequest.headers.set(INTERNAL_AUTH_HEADER, serializeConnectionAuth(resolvedAuth, identity));
|
|
131
|
+
}
|
|
132
|
+
else if (options?.auth) {
|
|
133
|
+
forwardedRequest.headers.delete(INTERNAL_AUTH_HEADER);
|
|
134
|
+
}
|
|
104
135
|
const id = namespace.idFromName("global");
|
|
105
136
|
const stub = namespace.get(id);
|
|
106
|
-
return await stub.fetch(
|
|
137
|
+
return await stub.fetch(forwardedRequest);
|
|
107
138
|
}
|
|
108
139
|
catch (err) {
|
|
109
140
|
return new Response(err.message || "SyncEngine binding is not available", {
|
|
@@ -111,3 +142,4 @@ export async function handleUpgrade(request, platform) {
|
|
|
111
142
|
});
|
|
112
143
|
}
|
|
113
144
|
}
|
|
145
|
+
export { INTERNAL_AUTH_HEADER };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import type { ZodSchema } from "zod";
|
|
2
|
-
export type
|
|
2
|
+
export type SyncConnectionAuth<TUser = unknown> = {
|
|
3
|
+
user: TUser;
|
|
4
|
+
};
|
|
5
|
+
export type SyncContext<TAuth = any> = {
|
|
3
6
|
platform: App.Platform | undefined;
|
|
4
7
|
request: Request;
|
|
5
|
-
auth
|
|
8
|
+
auth: TAuth | null;
|
|
6
9
|
};
|
|
7
|
-
export type SyncHandlerConfig<TRow = any> = {
|
|
8
|
-
channel: string | ((ctx: SyncContext) => string);
|
|
9
|
-
fetch: (ctx: SyncContext
|
|
10
|
-
create?: (ctx: SyncContext
|
|
11
|
-
update?: (ctx: SyncContext
|
|
12
|
-
delete?: (ctx: SyncContext
|
|
13
|
-
authorize?: (ctx: SyncContext) => Promise<void>;
|
|
10
|
+
export type SyncHandlerConfig<TRow = any, TAuth = any> = {
|
|
11
|
+
channel: string | ((ctx: SyncContext<TAuth>) => string);
|
|
12
|
+
fetch: (ctx: SyncContext<TAuth>, since?: string) => Promise<TRow[]>;
|
|
13
|
+
create?: (ctx: SyncContext<TAuth>, data: TRow) => Promise<TRow>;
|
|
14
|
+
update?: (ctx: SyncContext<TAuth>, key: string, changes: Partial<TRow>) => Promise<TRow>;
|
|
15
|
+
delete?: (ctx: SyncContext<TAuth>, key: string) => Promise<void>;
|
|
16
|
+
authorize?: (ctx: SyncContext<TAuth>) => Promise<void>;
|
|
14
17
|
validate?: {
|
|
15
18
|
create?: ZodSchema<any>;
|
|
16
19
|
update?: ZodSchema<any>;
|
|
17
20
|
};
|
|
18
|
-
scope?: (ctx: SyncContext
|
|
21
|
+
scope?: (ctx: SyncContext<TAuth>, action: "create" | "update" | "delete", data: TRow) => Promise<string[] | "all"> | string[] | "all";
|
|
19
22
|
};
|
|
20
|
-
export interface SyncHandler<TRow = any> {
|
|
21
|
-
config: SyncHandlerConfig<TRow>;
|
|
22
|
-
resolveChannel(ctx: SyncContext): string;
|
|
23
|
+
export interface SyncHandler<TRow = any, TAuth = any> {
|
|
24
|
+
config: SyncHandlerConfig<TRow, TAuth>;
|
|
25
|
+
resolveChannel(ctx: SyncContext<TAuth>): string;
|
|
23
26
|
}
|
|
24
|
-
export declare function defineSync<TRow = any>(config: SyncHandlerConfig<TRow>): SyncHandler<TRow>;
|
|
27
|
+
export declare function defineSync<TRow = any, TAuth = any>(config: SyncHandlerConfig<TRow, TAuth>): SyncHandler<TRow, TAuth>;
|
|
25
28
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;AAErC,MAAM,MAAM,WAAW,GAAG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;AAErC,MAAM,MAAM,kBAAkB,CAAC,KAAK,GAAG,OAAO,IAAI;IAChD,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,KAAK,GAAG,GAAG,IAAI;IACrC,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,SAAS,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,CAAC,IAAI,GAAG,GAAG,EAAE,KAAK,GAAG,GAAG,IAAI;IACvD,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,CAAC;IACxD,KAAK,EAAE,CAAC,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACpE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,MAAM,CAAC,EAAE,CACP,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,EACvB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;QACxB,MAAM,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;KACzB,CAAC;IACF,KAAK,CAAC,EAAE,CACN,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,EACvB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EACtC,IAAI,EAAE,IAAI,KACP,OAAO,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,MAAM,EAAE,GAAG,KAAK,CAAC;CACnD,CAAC;AAEF,MAAM,WAAW,WAAW,CAAC,IAAI,GAAG,GAAG,EAAE,KAAK,GAAG,GAAG;IAClD,MAAM,EAAE,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACvC,cAAc,CAAC,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;CACjD;AAED,wBAAgB,UAAU,CAAC,IAAI,GAAG,GAAG,EAAE,KAAK,GAAG,GAAG,EAChD,MAAM,EAAE,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,GACrC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAS1B"}
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
|
+
import type { SyncUpgradeOptions } from "./server/handler.js";
|
|
2
3
|
export type SyncDevPluginOptions = {
|
|
3
4
|
handlersPath?: string;
|
|
5
|
+
auth?: SyncUpgradeOptions["auth"];
|
|
6
|
+
identity?: SyncUpgradeOptions["identity"];
|
|
7
|
+
allowUnauthenticated?: SyncUpgradeOptions["allowUnauthenticated"];
|
|
4
8
|
};
|
|
5
9
|
export declare function syncDevPlugin(options?: SyncDevPluginOptions): Plugin;
|
|
6
10
|
//# sourceMappingURL=vite.d.ts.map
|
package/dist/vite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAa9D,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC1C,oBAAoB,CAAC,EAAE,kBAAkB,CAAC,sBAAsB,CAAC,CAAC;CACnE,CAAC;AAEF,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAwFpE"}
|
package/dist/vite.js
CHANGED
|
@@ -32,7 +32,14 @@ export function syncDevPlugin(options) {
|
|
|
32
32
|
// Remove temporary buffering listener
|
|
33
33
|
client.off("message", onMessage);
|
|
34
34
|
// Register client in the dev engine
|
|
35
|
-
devEngine.addClient(client, request
|
|
35
|
+
const connected = await devEngine.addClient(client, request, {
|
|
36
|
+
auth: options?.auth,
|
|
37
|
+
identity: options?.identity,
|
|
38
|
+
allowUnauthenticated: options?.allowUnauthenticated,
|
|
39
|
+
});
|
|
40
|
+
if (!connected) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
36
43
|
console.log("sync-dev-plugin: WebSocket upgrade handler completed, replaying buffered messages:", messageQueue.length);
|
|
37
44
|
// Replay any buffered messages
|
|
38
45
|
for (const msg of messageQueue) {
|