@livestore/sync-cf 0.4.0-dev.21 → 0.4.0-dev.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/cf-worker/do/durable-object.d.ts.map +1 -1
- package/dist/cf-worker/do/durable-object.js +8 -4
- package/dist/cf-worker/do/durable-object.js.map +1 -1
- package/dist/cf-worker/do/pull.d.ts +6 -1
- package/dist/cf-worker/do/pull.d.ts.map +1 -1
- package/dist/cf-worker/do/pull.js +2 -2
- package/dist/cf-worker/do/pull.js.map +1 -1
- package/dist/cf-worker/do/push.d.ts +3 -2
- package/dist/cf-worker/do/push.d.ts.map +1 -1
- package/dist/cf-worker/do/push.js +3 -3
- package/dist/cf-worker/do/push.js.map +1 -1
- package/dist/cf-worker/do/transport/do-rpc-server.d.ts.map +1 -1
- package/dist/cf-worker/do/transport/do-rpc-server.js +4 -2
- package/dist/cf-worker/do/transport/do-rpc-server.js.map +1 -1
- package/dist/cf-worker/do/transport/http-rpc-server.d.ts +2 -1
- package/dist/cf-worker/do/transport/http-rpc-server.d.ts.map +1 -1
- package/dist/cf-worker/do/transport/http-rpc-server.js +16 -13
- package/dist/cf-worker/do/transport/http-rpc-server.js.map +1 -1
- package/dist/cf-worker/do/transport/ws-rpc-server.d.ts +2 -1
- package/dist/cf-worker/do/transport/ws-rpc-server.d.ts.map +1 -1
- package/dist/cf-worker/do/transport/ws-rpc-server.js +24 -6
- package/dist/cf-worker/do/transport/ws-rpc-server.js.map +1 -1
- package/dist/cf-worker/shared.d.ts +53 -8
- package/dist/cf-worker/shared.d.ts.map +1 -1
- package/dist/cf-worker/shared.js +27 -0
- package/dist/cf-worker/shared.js.map +1 -1
- package/dist/cf-worker/worker.d.ts +31 -31
- package/dist/cf-worker/worker.d.ts.map +1 -1
- package/dist/cf-worker/worker.js +20 -23
- package/dist/cf-worker/worker.js.map +1 -1
- package/package.json +4 -4
- package/src/cf-worker/do/durable-object.ts +9 -3
- package/src/cf-worker/do/pull.ts +13 -5
- package/src/cf-worker/do/push.ts +10 -2
- package/src/cf-worker/do/transport/do-rpc-server.ts +4 -2
- package/src/cf-worker/do/transport/http-rpc-server.ts +11 -5
- package/src/cf-worker/do/transport/ws-rpc-server.ts +29 -10
- package/src/cf-worker/shared.ts +86 -8
- package/src/cf-worker/worker.ts +48 -34
package/src/cf-worker/worker.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { HelperTypes } from '@livestore/common-cf'
|
|
|
4
4
|
import { Effect, Schema } from '@livestore/utils/effect'
|
|
5
5
|
import type { CfTypes, SearchParams } from '../common/mod.ts'
|
|
6
6
|
import type { CfDeclare } from './mod.ts'
|
|
7
|
-
import { type Env, matchSyncRequest } from './shared.ts'
|
|
7
|
+
import { type Env, type ForwardedHeaders, matchSyncRequest } from './shared.ts'
|
|
8
8
|
|
|
9
9
|
// NOTE We need to redeclare runtime types here to avoid type conflicts with the lib.dom Response type.
|
|
10
10
|
declare class Response extends CfDeclare.Response {}
|
|
@@ -18,6 +18,13 @@ export type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjec
|
|
|
18
18
|
) => Promise<CfTypes.Response>
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** Context passed to validatePayload callback */
|
|
22
|
+
export type ValidatePayloadContext = {
|
|
23
|
+
storeId: string
|
|
24
|
+
/** Request headers (raw, not filtered by forwardHeaders) */
|
|
25
|
+
headers: ForwardedHeaders
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
/**
|
|
22
29
|
* Options accepted by {@link makeWorker}. The Durable Object binding has to be
|
|
23
30
|
* supplied explicitly so we never fall back to deprecated defaults when Cloudflare config changes.
|
|
@@ -27,11 +34,6 @@ export type MakeWorkerOptions<TEnv extends Env = Env, TSyncPayload = Schema.Json
|
|
|
27
34
|
* Binding name of the sync Durable Object declared in wrangler config.
|
|
28
35
|
*/
|
|
29
36
|
syncBackendBinding: HelperTypes.ExtractDurableObjectKeys<TEnv>
|
|
30
|
-
/**
|
|
31
|
-
* Validates the payload during WebSocket connection establishment.
|
|
32
|
-
* Note: This runs only at connection time, not for individual push events.
|
|
33
|
-
* For push event validation, use the `onPush` callback in the Durable Object.
|
|
34
|
-
*/
|
|
35
37
|
/**
|
|
36
38
|
* Optionally pass a schema to decode the client-provided payload into a typed object
|
|
37
39
|
* before calling {@link validatePayload}. If omitted, the raw JSON value is forwarded.
|
|
@@ -39,9 +41,23 @@ export type MakeWorkerOptions<TEnv extends Env = Env, TSyncPayload = Schema.Json
|
|
|
39
41
|
syncPayloadSchema?: Schema.Schema<TSyncPayload>
|
|
40
42
|
/**
|
|
41
43
|
* Validates the (optionally decoded) payload during WebSocket connection establishment.
|
|
42
|
-
* If {@link syncPayloadSchema} is provided, `payload` will be of the schema
|
|
44
|
+
* If {@link syncPayloadSchema} is provided, `payload` will be of the schema's inferred type.
|
|
45
|
+
*
|
|
46
|
+
* The context includes request headers for cookie-based or header-based authentication.
|
|
47
|
+
*
|
|
48
|
+
* @example Cookie-based authentication
|
|
49
|
+
* ```ts
|
|
50
|
+
* validatePayload: async (payload, { storeId, headers }) => {
|
|
51
|
+
* const cookie = headers.get('cookie')
|
|
52
|
+
* const session = await validateSessionFromCookie(cookie)
|
|
53
|
+
* if (!session) throw new Error('Unauthorized')
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* Note: This runs only at connection time, not for individual push events.
|
|
58
|
+
* For push event validation, use the `onPush` callback in the Durable Object.
|
|
43
59
|
*/
|
|
44
|
-
validatePayload?: (payload: TSyncPayload, context:
|
|
60
|
+
validatePayload?: (payload: TSyncPayload, context: ValidatePayloadContext) => void | Promise<void>
|
|
45
61
|
/** @default false */
|
|
46
62
|
enableCORS?: boolean
|
|
47
63
|
}
|
|
@@ -117,37 +133,33 @@ export const makeWorker = <
|
|
|
117
133
|
}
|
|
118
134
|
}
|
|
119
135
|
|
|
136
|
+
/** Convert CF Request headers to a ForwardedHeaders map */
|
|
137
|
+
const requestHeadersToMap = (request: CfTypes.Request): ForwardedHeaders => {
|
|
138
|
+
const result = new Map<string, string>()
|
|
139
|
+
request.headers.forEach((value, key) => {
|
|
140
|
+
result.set(key.toLowerCase(), value)
|
|
141
|
+
})
|
|
142
|
+
return result
|
|
143
|
+
}
|
|
144
|
+
|
|
120
145
|
/**
|
|
121
146
|
* Handles LiveStore sync requests (e.g. with search params `?storeId=...&transport=...`).
|
|
122
147
|
*
|
|
123
|
-
* @example
|
|
148
|
+
* @example Token-based authentication
|
|
124
149
|
* ```ts
|
|
125
150
|
* const validatePayload = (payload: Schema.JsonValue | undefined, context: { storeId: string }) => {
|
|
126
|
-
* console.log(`Validating connection for store: ${context.storeId}`)
|
|
127
151
|
* if (payload?.authToken !== 'insecure-token-change-me') {
|
|
128
152
|
* throw new Error('Invalid auth token')
|
|
129
153
|
* }
|
|
130
154
|
* }
|
|
155
|
+
* ```
|
|
131
156
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* return handleSyncRequest({
|
|
139
|
-
* request,
|
|
140
|
-
* searchParams,
|
|
141
|
-
* env,
|
|
142
|
-
* ctx,
|
|
143
|
-
* syncBackendBinding: 'SYNC_BACKEND_DO',
|
|
144
|
-
* headers: {},
|
|
145
|
-
* validatePayload,
|
|
146
|
-
* })
|
|
147
|
-
* }
|
|
148
|
-
*
|
|
149
|
-
* return new Response('Invalid path', { status: 400 })
|
|
150
|
-
* }
|
|
157
|
+
* @example Cookie-based authentication
|
|
158
|
+
* ```ts
|
|
159
|
+
* const validatePayload = async (payload: Schema.JsonValue | undefined, { storeId, headers }) => {
|
|
160
|
+
* const cookie = headers.get('cookie')
|
|
161
|
+
* const session = await validateSessionFromCookie(cookie)
|
|
162
|
+
* if (!session) throw new Error('Unauthorized')
|
|
151
163
|
* }
|
|
152
164
|
* ```
|
|
153
165
|
*
|
|
@@ -180,6 +192,9 @@ export const handleSyncRequest = <
|
|
|
180
192
|
}): Promise<CfTypes.Response> =>
|
|
181
193
|
Effect.gen(function* () {
|
|
182
194
|
if (validatePayload !== undefined) {
|
|
195
|
+
// Convert request headers to a Map for the validation context
|
|
196
|
+
const requestHeaders = requestHeadersToMap(request)
|
|
197
|
+
|
|
183
198
|
// Always decode with the supplied schema when present, even if payload is undefined.
|
|
184
199
|
// This ensures required payloads are enforced by the schema.
|
|
185
200
|
if (syncPayloadSchema !== undefined) {
|
|
@@ -191,7 +206,7 @@ export const handleSyncRequest = <
|
|
|
191
206
|
}
|
|
192
207
|
|
|
193
208
|
const result = yield* Effect.promise(async () =>
|
|
194
|
-
validatePayload(decodedEither.right as TSyncPayload, { storeId }),
|
|
209
|
+
validatePayload(decodedEither.right as TSyncPayload, { storeId, headers: requestHeaders }),
|
|
195
210
|
).pipe(UnknownError.mapToUnknownError, Effect.either)
|
|
196
211
|
|
|
197
212
|
if (result._tag === 'Left') {
|
|
@@ -199,10 +214,9 @@ export const handleSyncRequest = <
|
|
|
199
214
|
return new Response(result.left.toString(), { status: 400, headers })
|
|
200
215
|
}
|
|
201
216
|
} else {
|
|
202
|
-
const result = yield* Effect.promise(async () =>
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
)
|
|
217
|
+
const result = yield* Effect.promise(async () =>
|
|
218
|
+
validatePayload(payload as TSyncPayload, { storeId, headers: requestHeaders }),
|
|
219
|
+
).pipe(UnknownError.mapToUnknownError, Effect.either)
|
|
206
220
|
|
|
207
221
|
if (result._tag === 'Left') {
|
|
208
222
|
console.error('Invalid payload (validation failed)', result.left)
|