@shadow-js/realtime 0.0.0-stage → 1.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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +114 -2
- package/dist/dev/adapter.d.ts +75 -0
- package/dist/dev/client/core.d.ts +24 -0
- package/dist/dev/client/core.js +845 -0
- package/dist/dev/client/core.js.map +1 -0
- package/dist/dev/client/index.d.ts +5 -0
- package/dist/dev/client/index.js +3 -0
- package/dist/dev/client/presence.js +23 -0
- package/dist/dev/client/presence.js.map +1 -0
- package/dist/dev/client/sse.js +76 -0
- package/dist/dev/client/sse.js.map +1 -0
- package/dist/dev/client/types.d.ts +256 -0
- package/dist/dev/client/util.js +30 -0
- package/dist/dev/client/util.js.map +1 -0
- package/dist/dev/client/ws.js +39 -0
- package/dist/dev/client/ws.js.map +1 -0
- package/dist/dev/cloudflare/adapter.d.ts +80 -0
- package/dist/dev/cloudflare/adapter.js +347 -0
- package/dist/dev/cloudflare/adapter.js.map +1 -0
- package/dist/dev/cloudflare/core.js +701 -0
- package/dist/dev/cloudflare/core.js.map +1 -0
- package/dist/dev/cloudflare/link.js +397 -0
- package/dist/dev/cloudflare/link.js.map +1 -0
- package/dist/dev/cloudflare/object.d.ts +82 -0
- package/dist/dev/cloudflare/object.js +214 -0
- package/dist/dev/cloudflare/object.js.map +1 -0
- package/dist/dev/cloudflare/types.d.ts +100 -0
- package/dist/dev/cloudflare/wire.js +284 -0
- package/dist/dev/cloudflare/wire.js.map +1 -0
- package/dist/dev/cloudflare.d.ts +4 -0
- package/dist/dev/cloudflare.js +3 -0
- package/dist/dev/errors.d.ts +25 -0
- package/dist/dev/errors.js +24 -0
- package/dist/dev/errors.js.map +1 -0
- package/dist/dev/hooks.d.ts +110 -0
- package/dist/dev/hooks.js +162 -0
- package/dist/dev/hooks.js.map +1 -0
- package/dist/dev/index.d.ts +4 -0
- package/dist/dev/index.js +3 -0
- package/dist/dev/memory.d.ts +33 -0
- package/dist/dev/memory.js +255 -0
- package/dist/dev/memory.js.map +1 -0
- package/dist/dev/protocol.d.ts +211 -0
- package/dist/dev/protocol.js +400 -0
- package/dist/dev/protocol.js.map +1 -0
- package/dist/dev/redis/client.d.ts +139 -0
- package/dist/dev/redis/client.js +146 -0
- package/dist/dev/redis/client.js.map +1 -0
- package/dist/dev/redis/scripts.js +275 -0
- package/dist/dev/redis/scripts.js.map +1 -0
- package/dist/dev/redis/topics.d.ts +8 -0
- package/dist/dev/redis/topics.js +377 -0
- package/dist/dev/redis/topics.js.map +1 -0
- package/dist/dev/redis.d.ts +68 -0
- package/dist/dev/redis.js +422 -0
- package/dist/dev/redis.js.map +1 -0
- package/dist/dev/server/http.d.ts +48 -0
- package/dist/dev/server/http.js +286 -0
- package/dist/dev/server/http.js.map +1 -0
- package/dist/dev/server/hub.d.ts +114 -0
- package/dist/dev/server/hub.js +827 -0
- package/dist/dev/server/hub.js.map +1 -0
- package/dist/dev/server/index.d.ts +47 -0
- package/dist/dev/server/index.js +41 -0
- package/dist/dev/server/index.js.map +1 -0
- package/dist/dev/server/limits.d.ts +54 -0
- package/dist/dev/server/limits.js +85 -0
- package/dist/dev/server/limits.js.map +1 -0
- package/dist/dev/server/rules.d.ts +43 -0
- package/dist/dev/server/rules.js +72 -0
- package/dist/dev/server/rules.js.map +1 -0
- package/dist/dev/testing.d.ts +115 -0
- package/dist/dev/testing.js +427 -0
- package/dist/dev/testing.js.map +1 -0
- package/dist/prod/client/core.js +2 -0
- package/dist/prod/client/core.js.map +1 -0
- package/dist/prod/client/index.js +1 -0
- package/dist/prod/client/presence.js +2 -0
- package/dist/prod/client/presence.js.map +1 -0
- package/dist/prod/client/sse.js +5 -0
- package/dist/prod/client/sse.js.map +1 -0
- package/dist/prod/client/util.js +2 -0
- package/dist/prod/client/util.js.map +1 -0
- package/dist/prod/client/ws.js +2 -0
- package/dist/prod/client/ws.js.map +1 -0
- package/dist/prod/cloudflare/adapter.js +3 -0
- package/dist/prod/cloudflare/adapter.js.map +1 -0
- package/dist/prod/cloudflare/core.js +2 -0
- package/dist/prod/cloudflare/core.js.map +1 -0
- package/dist/prod/cloudflare/link.js +2 -0
- package/dist/prod/cloudflare/link.js.map +1 -0
- package/dist/prod/cloudflare/object.js +2 -0
- package/dist/prod/cloudflare/object.js.map +1 -0
- package/dist/prod/cloudflare/wire.js +2 -0
- package/dist/prod/cloudflare/wire.js.map +1 -0
- package/dist/prod/cloudflare.js +1 -0
- package/dist/prod/errors.js +2 -0
- package/dist/prod/errors.js.map +1 -0
- package/dist/prod/hooks.js +2 -0
- package/dist/prod/hooks.js.map +1 -0
- package/dist/prod/index.js +1 -0
- package/dist/prod/memory.js +2 -0
- package/dist/prod/memory.js.map +1 -0
- package/dist/prod/protocol.js +2 -0
- package/dist/prod/protocol.js.map +1 -0
- package/dist/prod/redis/client.js +2 -0
- package/dist/prod/redis/client.js.map +1 -0
- package/dist/prod/redis/scripts.js +157 -0
- package/dist/prod/redis/scripts.js.map +1 -0
- package/dist/prod/redis/topics.js +2 -0
- package/dist/prod/redis/topics.js.map +1 -0
- package/dist/prod/redis.js +2 -0
- package/dist/prod/redis.js.map +1 -0
- package/dist/prod/server/http.js +2 -0
- package/dist/prod/server/http.js.map +1 -0
- package/dist/prod/server/hub.js +2 -0
- package/dist/prod/server/hub.js.map +1 -0
- package/dist/prod/server/index.js +2 -0
- package/dist/prod/server/index.js.map +1 -0
- package/dist/prod/server/limits.js +2 -0
- package/dist/prod/server/limits.js.map +1 -0
- package/dist/prod/server/rules.js +2 -0
- package/dist/prod/server/rules.js.map +1 -0
- package/dist/prod/testing.js +2 -0
- package/dist/prod/testing.js.map +1 -0
- package/package.json +105 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","names":[],"sources":["../../../src/client/core.ts"],"sourcesContent":["import { type ErrorCode, RealtimeError } from \"../errors\";\nimport {\n byteLength,\n decodeServerFrame,\n isChannelName,\n isEventName,\n isIdempotencyKey,\n type Json,\n type Member,\n type ServerFrame,\n} from \"../protocol\";\nimport { applyPresence } from \"./presence\";\nimport { type Link, type LinkEvents, openStream } from \"./sse\";\nimport type {\n AnyMessage,\n Channel,\n ChannelDef,\n ChannelOptions,\n ChannelSchema,\n ChannelStatus,\n GapInfo,\n MessageMeta,\n PublishAck,\n Realtime,\n RealtimeOptions,\n Status,\n} from \"./types\";\nimport { backoff, clientId, resolveUrl, sendUrl } from \"./util\";\nimport { openSocket } from \"./ws\";\n\n/** Close codes that mean the client did something the server will not accept again. */\nconst FATAL = new Set([1002, 1008, 1009]);\n/** Refusals that may heal by asking again. */\nconst TRANSIENT = new Set<ErrorCode>([\"unavailable\", \"rate_limited\", \"timeout\"]);\n/** HTTP sl of a stream request that are worth retrying among the 4xx. */\nconst RETRYABLE_STATUS = new Set([408, 425, 429]);\nconst MAX_FRAMES_PER_POST = 64;\nconst MAX_POST_BYTES = 256_000;\nconst MAX_STREAM_CHANNELS = 64;\nconst DEFAULT_REQUEST_MS = 10_000;\n\ntype Handler = (data: Json, meta: MessageMeta) => void;\ntype Timer = ReturnType<typeof setTimeout>;\n\ninterface Wire {\n /** The number the subscription went out with; the server answers with it. */\n readonly n: number;\n readonly join: boolean;\n readonly watch: boolean;\n /** The cursor the subscription asked to resume from. */\n readonly after: number | undefined;\n /** The presence state version the server hd for this connection. */\n ver: number;\n /** False until the server can accept presence changes for this connection (over SSE, until the member list arrived). */\n ready: boolean;\n}\n\ninterface Chan {\n readonly name: string;\n readonly options: ChannelOptions;\n readonly ev: Map<string, Set<Handler>>;\n readonly any: Set<(message: AnyMessage<ChannelDef>) => void>;\n readonly gl: Set<(info: GapInfo) => void>;\n readonly sl: Set<(status: ChannelStatus) => void>;\n readonly presence: Set<(members: readonly Member[]) => void>;\n readonly members: Map<string, Member>;\n list: readonly Member[] | undefined;\n status: ChannelStatus;\n /** Listeners registered; the channel subscribes while this is above zero. */\n refs: number;\n /** Requests in flight; a stateful connection answers a request only on a subscribed channel. */\n hd: number;\n last: number;\n /** True once a `subbed` arrived since the channel last went idle. */\n sy: boolean;\n state: Json | undefined;\n sv: number;\n w: Wire | undefined;\n retry: Timer | undefined;\n fails: number;\n closed: boolean;\n hn: Channel<ChannelDef> | undefined;\n}\n\ninterface Op {\n readonly kind: \"pub\" | \"req\" | \"pres\";\n readonly chan: Chan;\n readonly event: string;\n /** The JSON text of the data (or of the presence state). */\n readonly data: string;\n readonly key: string | undefined;\n readonly ack: boolean;\n readonly ms: number;\n readonly order: number;\n n: number;\n timer: Timer | undefined;\n settle: ((error: RealtimeError | undefined, value?: unknown) => void) | undefined;\n}\n\n/**\n * Creates the realtime client: one transport (WebSocket, falling back to server-sent ev), reconnects with backoff, a\n * bounded offline queue, resume from the last sequence number and typed channels. It connects when the first channel\n * listens or the first message is sent, never earlier.\n *\n * @param options The stream URL and the policy.\n * @returns The client.\n * @example\n * import { createRealtime } from \"@shadow-js/realtime/client\";\n *\n * type Schema = { [name: `chat:${string}`]: { ev: { message: { text: string } } } };\n * const rt = createRealtime<Schema>({ url: \"/rt\" });\n * const lobby = rt.channel(\"chat:lobby\");\n * const stop = lobby.on(\"message\", (data) => console.log(data.text));\n * await lobby.publish(\"message\", { text: \"hello\" });\n * stop();\n * rt.close();\n * @since 1.0.0\n */\nexport function createRealtime<Schema extends ChannelSchema = ChannelSchema>(\n options: RealtimeOptions,\n): Realtime<Schema> {\n const reconnect = options.reconnect ?? {};\n const minDelay = reconnect.minDelayMs ?? 250;\n const maxDelay = reconnect.maxDelayMs ?? 30_000;\n const stableMs = reconnect.stableMs ?? 5000;\n const random = reconnect.random ?? Math.random;\n const maxQueued = options.maxQueued ?? 100;\n const queueTtl = options.queueTtlMs ?? 30_000;\n const ackTtl = options.ackTimeoutMs ?? 10_000;\n const auto = (options.transport ?? \"auto\") === \"auto\";\n const id = clientId();\n const life = new AbortController();\n const chans = new Map<string, Chan>();\n const dirty = new Set<Chan>();\n const queue: Op[] = [];\n const inflight = new Map<number, Op>();\n const statusListeners = new Set<(status: Status) => void>();\n\n let status: Status = \"connecting\";\n let mode: \"ws\" | \"sse\" = options.transport === \"sse\" ? \"sse\" : \"ws\";\n let finished = false;\n let scheduled = false;\n let link: Link | undefined;\n let gen = 0;\n let ready = false;\n let connId: string | undefined;\n let maxBytes = 65_536;\n let streamSig = \"\";\n let byeCode: number | undefined;\n let wsFails = 0;\n let attempt = 0;\n let counter = 0;\n let keys = 0;\n let orders = 0;\n let received = false;\n let silent = 0;\n let connectTimer: Timer | undefined;\n let reconnectTimer: Timer | undefined;\n let stableTimer: Timer | undefined;\n let beatTimer: ReturnType<typeof setInterval> | undefined;\n let posting = false;\n let postTimer: Timer | undefined;\n let postFails = 0;\n let listening = false;\n\n const report = (error: unknown): void => {\n try {\n if (options.onError !== undefined) options.onError(error);\n else console.error(\"[realtime]\", error);\n } catch {\n // A failing reporter must not break the client.\n }\n };\n const guard = (run: () => void): void => {\n try {\n run();\n } catch (error) {\n report(error);\n }\n };\n const doFetch: typeof fetch = (input, init) => (options.fetch ?? globalThis.fetch)(input, init);\n const getHeaders = (): HeadersInit | undefined | Promise<HeadersInit | undefined> =>\n typeof options.headers === \"function\" ? options.headers() : options.headers;\n\n const emit = <T>(listeners: Iterable<(value: T) => void>, value: T): void => {\n for (const listener of [...listeners]) guard(() => listener(value));\n };\n const setStatus = (next: Status): void => {\n if (next === status) return;\n status = next;\n if (options.onStatus !== undefined) guard(() => options.onStatus?.(next));\n emit(statusListeners, next);\n };\n const setChanStatus = (chan: Chan, next: ChannelStatus): void => {\n if (chan.status === next) return;\n chan.status = next;\n emit(chan.sl, next);\n };\n\n // --- operations: publishes and requests, queued while there is no connection --------------------------------------\n\n /** Settles an operation once; an unacknowledged publish that fails is reported instead. */\n const finish = (op: Op, error: RealtimeError | undefined, value?: unknown): void => {\n clearTimeout(op.timer);\n const settle = op.settle;\n op.settle = undefined;\n if (settle !== undefined) settle(error, value);\n else if (error !== undefined && op.kind === \"pub\") report(error);\n };\n const fail = (op: Op, error: RealtimeError): void => finish(op, error);\n const succeed = (op: Op, value: unknown): void => finish(op, undefined, value);\n const arm = (op: Op, ms: number): void => {\n clearTimeout(op.timer);\n op.timer = setTimeout(() => {\n const at = queue.indexOf(op);\n if (at >= 0) queue.splice(at, 1);\n if (inflight.get(op.n) === op) inflight.delete(op.n);\n fail(op, new RealtimeError(\"timeout\"));\n }, ms);\n };\n const take = (n: number): Op | undefined => {\n const op = inflight.get(n);\n if (op !== undefined) inflight.delete(n);\n return op;\n };\n\n const enqueue = (op: Op): RealtimeError | undefined => {\n if (finished) return new RealtimeError(\"closed\");\n const flowing = ready && mode === \"ws\";\n if (op.kind !== \"pres\" && !flowing && queue.length >= maxQueued)\n return new RealtimeError(\"queue_full\");\n if (op.kind === \"pres\") {\n const old = queue.findIndex((item) => item.kind === \"pres\" && item.chan === op.chan);\n if (old >= 0) queue.splice(old, 1);\n } else arm(op, queueTtl);\n queue.push(op);\n if (ready) flush();\n else schedule();\n return undefined;\n };\n\n const newOp = (\n kind: Op[\"kind\"],\n chan: Chan,\n event: string,\n data: string,\n extra: Partial<Pick<Op, \"key\" | \"ack\" | \"ms\" | \"settle\">>,\n ): Op => ({\n kind,\n chan,\n event,\n data,\n key: extra.key,\n ack: extra.ack ?? true,\n ms: extra.ms ?? 0,\n order: ++orders,\n n: 0,\n timer: undefined,\n settle: extra.settle,\n });\n\n /** The frame text of an operation, numbered for an answer. Undefined (and the operation failed) when it is too large. */\n const encode = (op: Op): string | undefined => {\n const c = JSON.stringify(op.chan.name);\n const wantsAnswer = op.kind === \"req\" || (op.kind === \"pub\" && op.ack);\n op.n = wantsAnswer ? ++counter : 0;\n const text =\n op.kind === \"pres\"\n ? `{\"t\":\"pres\",\"c\":${c},\"s\":${op.data}}`\n : op.kind === \"req\"\n ? `{\"t\":\"req\",\"n\":${op.n},\"c\":${c},\"e\":\"${op.event}\",\"d\":${op.data},\"ms\":${op.ms}}`\n : `{\"t\":\"pub\",\"c\":${c},\"e\":\"${op.event}\",\"d\":${op.data}${wantsAnswer ? `,\"n\":${op.n}` : \"\"},\"k\":${JSON.stringify(op.key)}}`;\n if (byteLength(text) > maxBytes) {\n fail(op, new RealtimeError(\"too_large\"));\n return undefined;\n }\n return text;\n };\n\n /** Records that an operation went out: it now waits for an answer, or is done. */\n const sent = (op: Op): void => {\n if (op.kind === \"pres\") return;\n if (op.kind === \"req\" || op.ack) {\n inflight.set(op.n, op);\n arm(op, op.kind === \"req\" ? op.ms + 1000 : ackTtl);\n } else clearTimeout(op.timer);\n };\n\n /** Puts operations that went out on a connection that died back at the head of the queue, in their original order. */\n const requeue = (ops: Op[]): void => {\n ops.sort((a, b) => a.order - b.order);\n const again: Op[] = [];\n for (const op of ops) {\n if (op.kind === \"req\") fail(op, new RealtimeError(\"closed\"));\n else {\n if (op.kind === \"pub\") arm(op, queueTtl);\n again.push(op);\n }\n }\n queue.unshift(...again);\n };\n\n // --- channels -----------------------------------------------------------------------------------------------------\n\n const wants = (chan: Chan): boolean =>\n !chan.closed &&\n (chan.refs > 0 ||\n chan.state !== undefined ||\n chan.options.watch === true ||\n (chan.hd > 0 && mode === \"ws\"));\n const joins = (chan: Chan): boolean => chan.state !== undefined;\n const watches = (chan: Chan): boolean =>\n !joins(chan) && (chan.presence.size > 0 || chan.options.watch === true);\n\n const touch = (chan: Chan): void => {\n if (chan.status === \"idle\" && wants(chan)) setChanStatus(chan, \"subscribing\");\n dirty.add(chan);\n schedule();\n };\n\n /** The fields a subscription goes out with, over a WebSocket as a frame and over SSE as a query entry. */\n const subscription = (chan: Chan, n: number): { fields: Record<string, unknown> } => {\n const join = joins(chan);\n const watch = watches(chan);\n const after = chan.sy ? chan.last : chan.options.replay === true ? 0 : undefined;\n const w: Wire = { n, join, watch, after, ver: chan.sv, ready: !join };\n chan.w = w;\n setChanStatus(chan, chan.sy ? \"resuming\" : \"subscribing\");\n return {\n fields: {\n c: chan.name,\n ...(after === undefined ? {} : { after }),\n ...(chan.options.echo === false ? { echo: false } : {}),\n ...(join ? { presence: chan.state } : {}),\n ...(watch ? { watch: true } : {}),\n },\n };\n };\n\n const sendPresence = (chan: Chan): void => {\n const w = chan.w;\n if (w === undefined || chan.state === undefined) return;\n w.ver = chan.sv;\n const op = newOp(\"pres\", chan, \"\", JSON.stringify(chan.state), {});\n if (mode === \"ws\") {\n const text = encode(op);\n if (text !== undefined) link?.send(text);\n } else enqueue(op);\n };\n\n const goIdle = (chan: Chan): void => {\n chan.sy = false;\n chan.last = 0;\n chan.w = undefined;\n clearTimeout(chan.retry);\n chan.retry = undefined;\n chan.fails = 0;\n if (chan.members.size > 0) {\n chan.members.clear();\n chan.list = undefined;\n }\n setChanStatus(chan, chan.closed ? \"closed\" : \"idle\");\n };\n\n /** Brings the w in line with what a channel wants (WebSocket: subscribe, unsubscribe, presence). */\n const syncChan = (chan: Chan): void => {\n const want = wants(chan);\n const w = chan.w;\n if (mode === \"ws\" && ready) {\n if (w !== undefined && (!want || w.join !== joins(chan) || w.watch !== watches(chan))) {\n link?.send(`{\"t\":\"unsub\",\"c\":${JSON.stringify(chan.name)}}`);\n chan.w = undefined;\n }\n if (want && chan.w === undefined && chan.retry === undefined) {\n const n = ++counter;\n link?.send(JSON.stringify({ t: \"sub\", n, ...subscription(chan, n).fields }));\n }\n }\n if (!want) {\n if (chan.status !== \"idle\" && chan.status !== \"closed\") goIdle(chan);\n return;\n }\n const current = chan.w;\n if (current?.join && current.ready && current.ver !== chan.sv) sendPresence(chan);\n };\n\n const syncAll = (): void => {\n const batch = [...dirty];\n dirty.clear();\n for (const chan of batch) syncChan(chan);\n };\n\n const closeChan = (chan: Chan, error?: RealtimeError): void => {\n if (chan.closed) return;\n chan.closed = true;\n if (chans.get(chan.name) === chan) chans.delete(chan.name);\n if (chan.w !== undefined && mode === \"ws\" && ready)\n link?.send(`{\"t\":\"unsub\",\"c\":${JSON.stringify(chan.name)}}`);\n goIdle(chan);\n chan.ev.clear();\n chan.any.clear();\n chan.gl.clear();\n chan.presence.clear();\n chan.sl.clear();\n chan.refs = 0;\n dirty.delete(chan);\n if (error !== undefined) report(error);\n schedule();\n };\n\n /** Subscribes again after a refusal or a removal the server announced; `now` skips the delay. */\n const resubscribe = (chan: Chan, now: boolean): void => {\n chan.w = undefined;\n setChanStatus(chan, chan.sy ? \"resuming\" : \"subscribing\");\n if (now) {\n touch(chan);\n return;\n }\n chan.retry = setTimeout(\n () => {\n chan.retry = undefined;\n touch(chan);\n },\n backoff(chan.fails, minDelay, maxDelay, random),\n );\n chan.fails += 1;\n };\n\n // --- connection ---------------------------------------------------------------------------------------------------\n\n /** Declares the subscriptions of an SSE stream; returns the query entries. */\n const declare = (): unknown[] => {\n const entries: unknown[] = [];\n for (const chan of [...chans.values()]) {\n if (!wants(chan)) continue;\n if (entries.length >= MAX_STREAM_CHANNELS) {\n closeChan(chan, new RealtimeError(\"limit\"));\n continue;\n }\n entries.push(subscription(chan, entries.length + 1).fields);\n }\n streamSig = signature();\n return entries;\n };\n\n /** What the open stream must be reopened for: the channels it carries and how they join. */\n const signature = (): string => {\n let sig = \"\";\n for (const chan of chans.values())\n if (wants(chan))\n sig += `${chan.name}|${chan.options.echo === false ? 0 : 1}${joins(chan) ? 1 : 0}${watches(chan) ? 1 : 0};`;\n return sig;\n };\n\n const connect = (): void => {\n clearTimeout(reconnectTimer);\n reconnectTimer = undefined;\n if (finished || link !== undefined) return;\n gen += 1;\n const g = gen;\n ready = false;\n received = false;\n silent = 0;\n const ev: LinkEvents = {\n rx: () => {\n if (g === gen) received = true;\n },\n frame: (frame) => {\n if (g === gen) guard(() => onFrame(frame, false));\n },\n closed: (info) => {\n if (g === gen) linkClosed(info);\n },\n };\n try {\n if (!listening && typeof addEventListener === \"function\") {\n listening = true;\n addEventListener(\"online\", onOnline);\n }\n if (mode === \"sse\") {\n const url = resolveUrl(options.url, false);\n url.searchParams.set(\"s\", JSON.stringify(declare()));\n link = openStream(url.toString(), doFetch, getHeaders, ev);\n } else\n link = openSocket(\n resolveUrl(options.url, true).toString(),\n options.WebSocket ?? globalThis.WebSocket,\n ev,\n );\n } catch (error) {\n gen += 1;\n shutdown(new RealtimeError(\"closed\", \"cannot open\", { cause: error }));\n return;\n }\n connectTimer = setTimeout(() => {\n if (g === gen) linkClosed({});\n }, ackTtl);\n };\n\n const onOnline = (): void => {\n if (reconnectTimer !== undefined) connect();\n };\n\n /** Releases the current transport and everything tied to it. */\n const drop = (): void => {\n gen += 1;\n const old = link;\n link = undefined;\n ready = false;\n connId = undefined;\n clearTimeout(connectTimer);\n clearTimeout(stableTimer);\n clearInterval(beatTimer);\n beatTimer = undefined;\n old?.close();\n };\n\n const linkClosed = (info: { readonly code?: number; readonly status?: number }): void => {\n const wasReady = ready;\n const code = byeCode ?? info.code;\n byeCode = undefined;\n drop();\n if (finished) return;\n if (mode === \"ws\" && inflight.size > 0) {\n const lost = [...inflight.values()];\n inflight.clear();\n requeue(lost);\n }\n for (const chan of chans.values()) {\n chan.w = undefined;\n clearTimeout(chan.retry);\n chan.retry = undefined;\n if (wants(chan)) setChanStatus(chan, chan.sy ? \"resuming\" : \"subscribing\");\n }\n const refused =\n info.status !== undefined &&\n info.status >= 400 &&\n info.status < 500 &&\n !RETRYABLE_STATUS.has(info.status);\n if (((code !== undefined && FATAL.has(code)) || refused) && reconnect.retryFatal !== true) {\n const why =\n code !== undefined\n ? `the server closed the connection with ${code}`\n : `the server answered ${info.status}`;\n shutdown(new RealtimeError(\"closed\", why));\n return;\n }\n let delay = backoff(attempt, minDelay, maxDelay, random);\n attempt += 1;\n if (wasReady) wsFails = 0;\n else if (auto && mode === \"ws\") {\n wsFails += 1;\n if (wsFails >= 2) {\n mode = \"sse\";\n delay = 0;\n }\n }\n setStatus(\"reconnecting\");\n reconnectTimer = setTimeout(connect, delay);\n };\n\n /** Heartbeat: every interval sends a ping; two intervals with nothing received declare the connection dead. */\n const beat = (): void => {\n if (received) {\n received = false;\n silent = 0;\n } else {\n silent += 1;\n if (silent >= 2) {\n linkClosed({});\n return;\n }\n }\n if (mode === \"ws\") link?.send('{\"t\":\"ping\"}');\n };\n\n const reopen = (): void => {\n drop();\n for (const chan of chans.values()) chan.w = undefined;\n connect();\n };\n\n const onReady = (frame: Extract<ServerFrame, { t: \"ready\" }>): void => {\n if (ready) return;\n ready = true;\n connId = frame.id;\n maxBytes = frame.max;\n wsFails = 0;\n received = true;\n silent = 0;\n clearTimeout(connectTimer);\n if (frame.hb > 0) beatTimer = setInterval(beat, frame.hb);\n stableTimer = setTimeout(() => {\n attempt = 0;\n }, stableMs);\n setStatus(\"open\");\n if (mode === \"ws\") for (const chan of chans.values()) dirty.add(chan);\n flush();\n };\n\n // --- frames from the server ---------------------------------------------------------------------------------------\n\n const notifyPresence = (chan: Chan): void => {\n const members = [...chan.members.values()];\n chan.list = members;\n emit(chan.presence, members);\n };\n\n const onFrame = (frame: ServerFrame, viaPost: boolean): void => {\n switch (frame.t) {\n case \"ready\":\n onReady(frame);\n return;\n case \"bye\":\n byeCode = frame.code;\n return;\n case \"subbed\": {\n const chan = chans.get(frame.c);\n const w = chan?.w;\n if (chan === undefined || w === undefined || w.n !== frame.n) return;\n if (frame.reset || w.after === undefined) chan.last = frame.head;\n const info = { after: w.after ?? 0, head: frame.head };\n const gap = frame.reset\n ? true\n : frame.gap === true && w.after !== undefined && !(w.after === 0 && !chan.sy);\n chan.sy = true;\n chan.fails = 0;\n setChanStatus(chan, \"live\");\n if (gap) emit(chan.gl, { ...info, reset: frame.reset === true });\n return;\n }\n case \"unsubbed\": {\n const chan = chans.get(frame.c);\n if (chan === undefined) return;\n if (frame.why === \"revoked\") {\n // The hub dropped the subscription: closing must not send an unsub it would answer with not_subscribed.\n chan.w = undefined;\n closeChan(chan, new RealtimeError(\"forbidden\", \"revoked\"));\n } else resubscribe(chan, frame.why === \"gap\" || frame.why === \"reset\");\n return;\n }\n case \"msg\": {\n const chan = chans.get(frame.c);\n if (chan === undefined || frame.s <= chan.last) return;\n chan.last = frame.s;\n const meta: MessageMeta = {\n seq: frame.s,\n replayed: frame.r === 1,\n ...(frame.p === undefined ? {} : { publisher: frame.p }),\n ...(frame.u === undefined ? {} : { user: frame.u }),\n };\n for (const handler of [...(chan.ev.get(frame.e) ?? [])])\n guard(() => handler(frame.d, meta));\n for (const handler of [...chan.any])\n guard(() => handler({ event: frame.e, data: frame.d, meta } as AnyMessage<ChannelDef>));\n return;\n }\n case \"ack\": {\n const op = take(frame.n);\n if (op !== undefined) succeed(op, { seq: frame.s, duplicate: frame.dup === true });\n return;\n }\n case \"rep\": {\n const op = take(frame.n);\n if (op !== undefined) succeed(op, frame.d);\n return;\n }\n case \"err\": {\n const error = new RealtimeError(frame.code, frame.msg);\n const chan = frame.c === undefined ? undefined : chans.get(frame.c);\n if (frame.n !== undefined) {\n if (!viaPost && chan?.w?.n === frame.n) {\n if (TRANSIENT.has(frame.code)) {\n resubscribe(chan, false);\n report(error);\n } else {\n chan.w = undefined;\n closeChan(chan, error);\n }\n return;\n }\n // Over SSE a number on the stream names a subscription; only a POST answer names an operation.\n const op = viaPost || mode === \"ws\" ? take(frame.n) : undefined;\n if (op !== undefined) {\n fail(op, error);\n return;\n }\n }\n report(error);\n return;\n }\n case \"pres\": {\n const chan = chans.get(frame.c);\n if (chan === undefined) return;\n if (applyPresence(chan.members, frame.op, frame.m)) notifyPresence(chan);\n if (frame.op === \"sync\" && chan.w !== undefined) {\n chan.w.ready = true;\n if (chan.w.join && chan.w.ver !== chan.sv) sendPresence(chan);\n }\n return;\n }\n case \"pong\":\n return;\n }\n };\n\n // --- sending ------------------------------------------------------------------------------------------------------\n\n const flush = (): void => {\n if (!ready || finished) return;\n syncAll();\n if (mode === \"sse\") {\n postNext();\n return;\n }\n for (let op = queue[0]; op !== undefined && ready; op = queue[0]) {\n const text = encode(op);\n if (text !== undefined) {\n if (link?.send(text) !== true) return;\n sent(op);\n }\n queue.shift();\n }\n };\n\n const postNext = (): void => {\n if (posting || postTimer !== undefined || connId === undefined) return;\n const ops: Op[] = [];\n const texts: string[] = [];\n let bytes = 0;\n while (queue.length > 0 && ops.length < MAX_FRAMES_PER_POST && bytes < MAX_POST_BYTES) {\n const op = queue.shift();\n if (op === undefined) break;\n const text = encode(op);\n if (text === undefined) continue;\n ops.push(op);\n texts.push(text);\n bytes += text.length;\n }\n if (ops.length === 0) return;\n posting = true;\n postBatch(ops, `{\"cid\":${JSON.stringify(connId)},\"frames\":[${texts.join(\",\")}]}`).then(\n undefined,\n report,\n );\n };\n\n const postBatch = async (ops: Op[], body: string): Promise<void> => {\n for (const op of ops) sent(op);\n let again = false;\n try {\n const headers = new Headers(await getHeaders());\n headers.set(\"content-type\", \"application/json\");\n const url =\n options.postUrl === undefined\n ? sendUrl(resolveUrl(options.url, false))\n : resolveUrl(options.postUrl, false);\n const response = await doFetch(url, { method: \"POST\", headers, body, signal: life.signal });\n if (response.ok) {\n const parsed = (await response.json()) as { frames?: unknown };\n if (Array.isArray(parsed.frames))\n for (const item of parsed.frames as unknown[]) {\n const frame = decodeServerFrame(JSON.stringify(item));\n if (frame !== undefined) guard(() => onFrame(frame, true));\n }\n postFails = 0;\n } else if (response.status >= 500 || RETRYABLE_STATUS.has(response.status)) again = true;\n else {\n const refusal = (await response.json().catch(() => undefined)) as\n | { error?: { code?: ErrorCode; message?: string } }\n | undefined;\n const code: ErrorCode = refusal?.error?.code ?? \"unavailable\";\n for (const op of ops) {\n if (inflight.get(op.n) === op) inflight.delete(op.n);\n fail(op, new RealtimeError(code, refusal?.error?.message));\n }\n }\n } catch {\n if (life.signal.aborted) return;\n again = true;\n }\n posting = false;\n const unanswered = ops.filter(\n (op) => (op.kind === \"req\" || (op.kind === \"pub\" && op.ack)) && inflight.get(op.n) === op,\n );\n for (const op of unanswered) inflight.delete(op.n);\n if (again) {\n for (const op of unanswered)\n if (op.kind === \"req\") fail(op, new RealtimeError(\"unavailable\"));\n requeue(\n ops.filter(\n (op) => op.kind === \"pres\" || (op.kind === \"pub\" && (!op.ack || unanswered.includes(op))),\n ),\n );\n postFails += 1;\n postTimer = setTimeout(\n () => {\n postTimer = undefined;\n flush();\n },\n backoff(postFails - 1, minDelay, maxDelay, random),\n );\n } else for (const op of unanswered) fail(op, new RealtimeError(\"unavailable\"));\n flush();\n };\n\n // --- lifecycle ----------------------------------------------------------------------------------------------------\n\n const needed = (): boolean => queue.length > 0 || [...chans.values()].some(wants);\n\n const run = (): void => {\n scheduled = false;\n if (finished) return;\n syncAll();\n if (link === undefined) {\n if (reconnectTimer === undefined && needed()) connect();\n return;\n }\n if (\n mode === \"sse\" &&\n (signature() !== streamSig ||\n [...chans.values()].some((c) => wants(c) && c.w === undefined && c.retry === undefined))\n )\n reopen();\n else flush();\n };\n\n function schedule(): void {\n if (scheduled || finished) return;\n scheduled = true;\n queueMicrotask(run);\n }\n\n function shutdown(error?: RealtimeError): void {\n if (finished) return;\n finished = true;\n clearTimeout(reconnectTimer);\n clearTimeout(postTimer);\n reconnectTimer = undefined;\n postTimer = undefined;\n drop();\n life.abort();\n if (listening && typeof removeEventListener === \"function\")\n removeEventListener(\"online\", onOnline);\n listening = false;\n const closed = error ?? new RealtimeError(\"closed\");\n const pending = [...queue.splice(0), ...inflight.values()];\n inflight.clear();\n for (const op of pending) {\n if (op.settle === undefined) clearTimeout(op.timer);\n else fail(op, closed);\n }\n for (const chan of [...chans.values()]) closeChan(chan);\n chans.clear();\n dirty.clear();\n setStatus(\"closed\");\n statusListeners.clear();\n if (error !== undefined) report(error);\n }\n\n // --- the public handles -------------------------------------------------------------------------------------------\n\n const makeChannel = (name: string, channelOptions: ChannelOptions): Channel<ChannelDef> => {\n const chan: Chan = {\n name,\n options: channelOptions,\n ev: new Map(),\n any: new Set(),\n gl: new Set(),\n sl: new Set(),\n presence: new Set(),\n members: new Map(),\n list: undefined,\n status: \"idle\",\n refs: 0,\n hd: 0,\n last: 0,\n sy: false,\n state: channelOptions.presence,\n sv: 0,\n w: undefined,\n retry: undefined,\n fails: 0,\n closed: false,\n hn: undefined,\n };\n chans.set(name, chan);\n if (wants(chan)) touch(chan);\n\n /** Registers a listener; unless `passive`, it keeps the channel subscribed until removed. */\n const listen = <T>(\n set: Set<T>,\n item: T,\n passive?: boolean,\n after?: () => void,\n ): (() => void) => {\n if (chan.closed) return () => undefined;\n set.add(item);\n if (!passive) {\n chan.refs += 1;\n touch(chan);\n }\n return () => {\n if (!set.delete(item) || passive) return;\n chan.refs -= 1;\n after?.();\n touch(chan);\n };\n };\n\n /** Validates and queues a publish or a request; the promise settles with its answer. */\n const submit = <T>(\n kind: \"pub\" | \"req\",\n event: string,\n data: unknown,\n key: string | undefined,\n ack: boolean,\n ms: number,\n ): Promise<T> => {\n let text = \"null\";\n let refusal: RealtimeError | undefined;\n if (chan.closed) refusal = new RealtimeError(\"closed\");\n else if (!isEventName(event) || (key !== undefined && !isIdempotencyKey(key)))\n refusal = new RealtimeError(\"bad_frame\");\n else\n try {\n text = JSON.stringify(data ?? null) ?? \"null\";\n } catch (cause) {\n refusal = new RealtimeError(\"bad_frame\", \"not JSON\", { cause });\n }\n if (refusal !== undefined) return Promise.reject(refusal);\n return new Promise<T>((resolve, reject) => {\n const request = kind === \"req\";\n const op = newOp(kind, chan, event, text, {\n key: request ? undefined : (key ?? `${id}:${++keys}`),\n ack,\n ms,\n settle: ack\n ? (error, value) => {\n if (request) {\n chan.hd -= 1;\n touch(chan);\n }\n if (error) reject(error);\n else resolve(value as T);\n }\n : undefined,\n });\n if (request) {\n chan.hd += 1;\n touch(chan);\n }\n const refused = enqueue(op);\n if (refused !== undefined) {\n op.settle = undefined;\n if (request) chan.hd -= 1;\n reject(refused);\n } else if (!ack) resolve({ seq: 0, duplicate: false } as T);\n });\n };\n\n const hn: Channel<ChannelDef> = {\n name,\n get status() {\n return chan.status;\n },\n get lastSeq() {\n return chan.last;\n },\n on(event, handler) {\n let set = chan.ev.get(event);\n if (set === undefined) {\n set = new Set();\n chan.ev.set(event, set);\n }\n const handlers = set;\n return listen(handlers, handler as Handler, false, () => {\n if (handlers.size === 0 && chan.ev.get(event) === handlers) chan.ev.delete(event);\n });\n },\n onAny: (handler) => listen(chan.any, handler as (message: AnyMessage<ChannelDef>) => void),\n onGap: (handler) => listen(chan.gl, handler),\n onStatus: (handler) => listen(chan.sl, handler, true),\n publish: (event, data, publishOptions = {}) =>\n submit<PublishAck>(\"pub\", event, data, publishOptions.key, publishOptions.ack !== false, 0),\n request: (event, data, requestOptions = {}) =>\n submit(\"req\", event, data, undefined, true, requestOptions.timeoutMs ?? DEFAULT_REQUEST_MS),\n presence: {\n members: () => {\n chan.list ??= [...chan.members.values()];\n return chan.list as never;\n },\n set(state) {\n if (chan.closed) return;\n chan.state = state as Json;\n chan.sv += 1;\n touch(chan);\n },\n leave() {\n if (chan.closed || chan.state === undefined) return;\n chan.state = undefined;\n chan.sv += 1;\n touch(chan);\n },\n on: (callback) => listen(chan.presence, callback as (members: readonly Member[]) => void),\n },\n close: () => closeChan(chan),\n };\n chan.hn = hn;\n return hn;\n };\n\n return {\n channel(name, channelOptions = {}) {\n if (finished) throw new RealtimeError(\"closed\");\n if (!isChannelName(name)) throw new RealtimeError(\"bad_channel\", name);\n const live = chans.get(name);\n return (live?.hn ?? makeChannel(name, channelOptions)) as never;\n },\n get status() {\n return status;\n },\n get connectionId() {\n return connId;\n },\n onStatus(handler) {\n if (!finished) statusListeners.add(handler);\n return () => {\n statusListeners.delete(handler);\n };\n },\n close: () => shutdown(),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ErrorCode, RealtimeError } from "../errors.js";
|
|
2
|
+
import { Json, Member } from "../protocol.js";
|
|
3
|
+
import { AnyMessage, Channel, ChannelDef, ChannelOptions, ChannelPresence, ChannelSchema, ChannelStatus, GapInfo, MessageMeta, PresenceOf, PublishAck, PublishOptions, Realtime, RealtimeOptions, RequestData, RequestReply, RequestsOf, Status } from "./types.js";
|
|
4
|
+
import { createRealtime } from "./core.js";
|
|
5
|
+
export { type AnyMessage, type Channel, type ChannelDef, type ChannelOptions, type ChannelPresence, type ChannelSchema, type ChannelStatus, type ErrorCode, type GapInfo, type Json, type Member, type MessageMeta, type PresenceOf, type PublishAck, type PublishOptions, type Realtime, RealtimeError, type RealtimeOptions, type RequestData, type RequestReply, type RequestsOf, type Status, createRealtime };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/client/presence.ts
|
|
2
|
+
/**
|
|
3
|
+
* Applies one presence frame to the member table, ordered by member version so a repeated or reordered event changes
|
|
4
|
+
* nothing. Returns whether the table changed.
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
function applyPresence(table, op, payload) {
|
|
8
|
+
if (op === "sync") {
|
|
9
|
+
table.clear();
|
|
10
|
+
for (const member of payload) table.set(member.id, member);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
const member = payload;
|
|
14
|
+
const known = table.get(member.id);
|
|
15
|
+
if (op === "leave") return known !== void 0 && member.v >= known.v && table.delete(member.id);
|
|
16
|
+
if (known !== void 0 && member.v <= known.v) return false;
|
|
17
|
+
table.set(member.id, member);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { applyPresence };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=presence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.js","names":[],"sources":["../../../src/client/presence.ts"],"sourcesContent":["import type { Member } from \"../protocol\";\n\n/**\n * Applies one presence frame to the member table, ordered by member version so a repeated or reordered event changes\n * nothing. Returns whether the table changed.\n * @internal\n */\nexport function applyPresence(\n table: Map<string, Member>,\n op: \"sync\" | \"join\" | \"set\" | \"leave\",\n payload: Member | readonly Member[],\n): boolean {\n if (op === \"sync\") {\n table.clear();\n for (const member of payload as readonly Member[]) table.set(member.id, member);\n return true;\n }\n const member = payload as Member;\n const known = table.get(member.id);\n if (op === \"leave\") return known !== undefined && member.v >= known.v && table.delete(member.id);\n if (known !== undefined && member.v <= known.v) return false;\n table.set(member.id, member);\n return true;\n}\n"],"mappings":";;;;;;AAOA,SAAgB,cACd,OACA,IACA,SACS;CACT,IAAI,OAAO,QAAQ;EACjB,MAAM,MAAM;EACZ,KAAK,MAAM,UAAU,SAA8B,MAAM,IAAI,OAAO,IAAI,MAAM;EAC9E,OAAO;CACT;CACA,MAAM,SAAS;CACf,MAAM,QAAQ,MAAM,IAAI,OAAO,EAAE;CACjC,IAAI,OAAO,SAAS,OAAO,UAAU,KAAA,KAAa,OAAO,KAAK,MAAM,KAAK,MAAM,OAAO,OAAO,EAAE;CAC/F,IAAI,UAAU,KAAA,KAAa,OAAO,KAAK,MAAM,GAAG,OAAO;CACvD,MAAM,IAAI,OAAO,IAAI,MAAM;CAC3B,OAAO;AACT"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { decodeServerFrame } from "../protocol.js";
|
|
2
|
+
//#region src/client/sse.ts
|
|
3
|
+
/** Splits an event stream into `rt` events; keeps a partial line (and a trailing CR) for the next chunk. @internal */
|
|
4
|
+
function sseParser(onData) {
|
|
5
|
+
let buffer = "";
|
|
6
|
+
let name = "";
|
|
7
|
+
let data = [];
|
|
8
|
+
return (chunk) => {
|
|
9
|
+
buffer += chunk;
|
|
10
|
+
let start = 0;
|
|
11
|
+
for (let at = 0; at < buffer.length; at += 1) {
|
|
12
|
+
const char = buffer[at];
|
|
13
|
+
if (char !== "\n" && char !== "\r") continue;
|
|
14
|
+
if (char === "\r" && at === buffer.length - 1) break;
|
|
15
|
+
const line = buffer.slice(start, at);
|
|
16
|
+
if (char === "\r" && buffer[at + 1] === "\n") at += 1;
|
|
17
|
+
start = at + 1;
|
|
18
|
+
if (line === "") {
|
|
19
|
+
if (name === "rt" && data.length > 0) onData(data.join("\n"));
|
|
20
|
+
name = "";
|
|
21
|
+
data = [];
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (line[0] === ":") continue;
|
|
25
|
+
const colon = line.indexOf(":");
|
|
26
|
+
const key = colon < 0 ? line : line.slice(0, colon);
|
|
27
|
+
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
28
|
+
if (value[0] === " ") value = value.slice(1);
|
|
29
|
+
if (key === "event") name = value;
|
|
30
|
+
else if (key === "data") data.push(value);
|
|
31
|
+
}
|
|
32
|
+
buffer = buffer.slice(start);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Reads a server-sent events stream with `fetch`, so headers work. Frames are events named `rt`. @internal */
|
|
36
|
+
function openStream(url, send, getHeaders, events) {
|
|
37
|
+
const control = new AbortController();
|
|
38
|
+
const run = async () => {
|
|
39
|
+
const headers = new Headers(await getHeaders());
|
|
40
|
+
headers.set("accept", "text/event-stream");
|
|
41
|
+
const response = await send(url, {
|
|
42
|
+
headers,
|
|
43
|
+
signal: control.signal,
|
|
44
|
+
cache: "no-store"
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok || response.body === null) {
|
|
47
|
+
response.body?.cancel().catch(() => void 0);
|
|
48
|
+
return events.closed({ status: response.status });
|
|
49
|
+
}
|
|
50
|
+
const reader = response.body.getReader();
|
|
51
|
+
const decoder = new TextDecoder();
|
|
52
|
+
const feed = sseParser((data) => {
|
|
53
|
+
const frame = decodeServerFrame(data);
|
|
54
|
+
if (frame !== void 0) events.frame(frame);
|
|
55
|
+
});
|
|
56
|
+
for (;;) {
|
|
57
|
+
const { done, value } = await reader.read();
|
|
58
|
+
if (done) break;
|
|
59
|
+
events.rx();
|
|
60
|
+
feed(decoder.decode(value, { stream: true }));
|
|
61
|
+
}
|
|
62
|
+
events.closed({});
|
|
63
|
+
};
|
|
64
|
+
const end = () => {
|
|
65
|
+
if (!control.signal.aborted) events.closed({});
|
|
66
|
+
};
|
|
67
|
+
run().then(void 0, end);
|
|
68
|
+
return {
|
|
69
|
+
send: () => false,
|
|
70
|
+
close: () => control.abort()
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { openStream, sseParser };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","names":[],"sources":["../../../src/client/sse.ts"],"sourcesContent":["import { decodeServerFrame, type ServerFrame } from \"../protocol\";\n\n/** What a transport reports to the client. @internal */\nexport interface LinkEvents {\n /** Any bytes arrived (a comment counts: it proves the stream is alive). */\n rx(): void;\n frame(frame: ServerFrame): void;\n /** The transport ended. `code` is a WebSocket close code, `status` an HTTP status. */\n closed(info: { readonly code?: number; readonly status?: number }): void;\n}\n\n/** One open transport. @internal */\nexport interface Link {\n send(text: string): boolean;\n close(): void;\n}\n\n/** Splits an event stream into `rt` events; keeps a partial line (and a trailing CR) for the next chunk. @internal */\nexport function sseParser(onData: (data: string) => void): (chunk: string) => void {\n let buffer = \"\";\n let name = \"\";\n let data: string[] = [];\n return (chunk) => {\n buffer += chunk;\n let start = 0;\n for (let at = 0; at < buffer.length; at += 1) {\n const char = buffer[at];\n if (char !== \"\\n\" && char !== \"\\r\") continue;\n if (char === \"\\r\" && at === buffer.length - 1) break;\n const line = buffer.slice(start, at);\n if (char === \"\\r\" && buffer[at + 1] === \"\\n\") at += 1;\n start = at + 1;\n if (line === \"\") {\n if (name === \"rt\" && data.length > 0) onData(data.join(\"\\n\"));\n name = \"\";\n data = [];\n continue;\n }\n if (line[0] === \":\") continue;\n const colon = line.indexOf(\":\");\n const key = colon < 0 ? line : line.slice(0, colon);\n let value = colon < 0 ? \"\" : line.slice(colon + 1);\n if (value[0] === \" \") value = value.slice(1);\n if (key === \"event\") name = value;\n else if (key === \"data\") data.push(value);\n }\n buffer = buffer.slice(start);\n };\n}\n\n/** Reads a server-sent events stream with `fetch`, so headers work. Frames are events named `rt`. @internal */\nexport function openStream(\n url: string,\n send: typeof fetch,\n getHeaders: () => HeadersInit | undefined | Promise<HeadersInit | undefined>,\n events: LinkEvents,\n): Link {\n const control = new AbortController();\n const run = async (): Promise<void> => {\n const headers = new Headers(await getHeaders());\n headers.set(\"accept\", \"text/event-stream\");\n const response = await send(url, { headers, signal: control.signal, cache: \"no-store\" });\n if (!response.ok || response.body === null) {\n void response.body?.cancel().catch(() => undefined);\n return events.closed({ status: response.status });\n }\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n const feed = sseParser((data) => {\n const frame = decodeServerFrame(data);\n if (frame !== undefined) events.frame(frame);\n });\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n events.rx();\n feed(decoder.decode(value, { stream: true }));\n }\n events.closed({});\n };\n const end = (): void => {\n if (!control.signal.aborted) events.closed({});\n };\n run().then(undefined, end);\n return { send: () => false, close: () => control.abort() };\n}\n"],"mappings":";;;AAkBA,SAAgB,UAAU,QAAyD;CACjF,IAAI,SAAS;CACb,IAAI,OAAO;CACX,IAAI,OAAiB,CAAC;CACtB,QAAQ,UAAU;EAChB,UAAU;EACV,IAAI,QAAQ;EACZ,KAAK,IAAI,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;GAC5C,MAAM,OAAO,OAAO;GACpB,IAAI,SAAS,QAAQ,SAAS,MAAM;GACpC,IAAI,SAAS,QAAQ,OAAO,OAAO,SAAS,GAAG;GAC/C,MAAM,OAAO,OAAO,MAAM,OAAO,EAAE;GACnC,IAAI,SAAS,QAAQ,OAAO,KAAK,OAAO,MAAM,MAAM;GACpD,QAAQ,KAAK;GACb,IAAI,SAAS,IAAI;IACf,IAAI,SAAS,QAAQ,KAAK,SAAS,GAAG,OAAO,KAAK,KAAK,IAAI,CAAC;IAC5D,OAAO;IACP,OAAO,CAAC;IACR;GACF;GACA,IAAI,KAAK,OAAO,KAAK;GACrB,MAAM,QAAQ,KAAK,QAAQ,GAAG;GAC9B,MAAM,MAAM,QAAQ,IAAI,OAAO,KAAK,MAAM,GAAG,KAAK;GAClD,IAAI,QAAQ,QAAQ,IAAI,KAAK,KAAK,MAAM,QAAQ,CAAC;GACjD,IAAI,MAAM,OAAO,KAAK,QAAQ,MAAM,MAAM,CAAC;GAC3C,IAAI,QAAQ,SAAS,OAAO;QACvB,IAAI,QAAQ,QAAQ,KAAK,KAAK,KAAK;EAC1C;EACA,SAAS,OAAO,MAAM,KAAK;CAC7B;AACF;;AAGA,SAAgB,WACd,KACA,MACA,YACA,QACM;CACN,MAAM,UAAU,IAAI,gBAAgB;CACpC,MAAM,MAAM,YAA2B;EACrC,MAAM,UAAU,IAAI,QAAQ,MAAM,WAAW,CAAC;EAC9C,QAAQ,IAAI,UAAU,mBAAmB;EACzC,MAAM,WAAW,MAAM,KAAK,KAAK;GAAE;GAAS,QAAQ,QAAQ;GAAQ,OAAO;EAAW,CAAC;EACvF,IAAI,CAAC,SAAS,MAAM,SAAS,SAAS,MAAM;GAC1C,SAAc,MAAM,OAAO,CAAC,CAAC,YAAY,KAAA,CAAS;GAClD,OAAO,OAAO,OAAO,EAAE,QAAQ,SAAS,OAAO,CAAC;EAClD;EACA,MAAM,SAAS,SAAS,KAAK,UAAU;EACvC,MAAM,UAAU,IAAI,YAAY;EAChC,MAAM,OAAO,WAAW,SAAS;GAC/B,MAAM,QAAQ,kBAAkB,IAAI;GACpC,IAAI,UAAU,KAAA,GAAW,OAAO,MAAM,KAAK;EAC7C,CAAC;EACD,SAAS;GACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM;GACV,OAAO,GAAG;GACV,KAAK,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC,CAAC;EAC9C;EACA,OAAO,OAAO,CAAC,CAAC;CAClB;CACA,MAAM,YAAkB;EACtB,IAAI,CAAC,QAAQ,OAAO,SAAS,OAAO,OAAO,CAAC,CAAC;CAC/C;CACA,IAAI,CAAC,CAAC,KAAK,KAAA,GAAW,GAAG;CACzB,OAAO;EAAE,YAAY;EAAO,aAAa,QAAQ,MAAM;CAAE;AAC3D"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { Json, Member } from "../protocol.js";
|
|
2
|
+
//#region src/client/types.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The shape of one channel in a schema: the events it carries, the presence state its members publish and the requests its
|
|
5
|
+
* server handlers answer.
|
|
6
|
+
* @example
|
|
7
|
+
* import type { ChannelDef } from "@shadow-js/realtime/client";
|
|
8
|
+
*
|
|
9
|
+
* type Lobby = {
|
|
10
|
+
* events: { message: { text: string } };
|
|
11
|
+
* presence: { typing: boolean };
|
|
12
|
+
* requests: { shout: { data: string; reply: string } };
|
|
13
|
+
* };
|
|
14
|
+
* const check: ChannelDef = {} as Lobby;
|
|
15
|
+
* @since 1.0.0
|
|
16
|
+
*/
|
|
17
|
+
export interface ChannelDef {
|
|
18
|
+
readonly events: Record<string, Json>;
|
|
19
|
+
readonly presence?: Json;
|
|
20
|
+
readonly requests?: Record<string, {
|
|
21
|
+
readonly data: Json;
|
|
22
|
+
readonly reply: Json;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A schema maps channel names to their definitions. A template-literal key covers every channel of a family:
|
|
27
|
+
* `{ [name: `chat:${string}`]: { events: { message: { text: string } } } }`.
|
|
28
|
+
* @example
|
|
29
|
+
* import { createRealtime, type ChannelSchema } from "@shadow-js/realtime/client";
|
|
30
|
+
*
|
|
31
|
+
* type Schema = {
|
|
32
|
+
* [name: `chat:${string}`]: { events: { message: { text: string } } };
|
|
33
|
+
* };
|
|
34
|
+
* const rt = createRealtime<Schema>({ url: "/rt" });
|
|
35
|
+
* rt.channel("chat:lobby").publish("message", { text: "hi" });
|
|
36
|
+
* const loose: ChannelSchema = {};
|
|
37
|
+
* @since 1.0.0
|
|
38
|
+
*/
|
|
39
|
+
export type ChannelSchema = {
|
|
40
|
+
[name: string]: ChannelDef;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The state of the connection: `connecting` before the first open, `open` while a transport is up, `reconnecting` between
|
|
44
|
+
* attempts, `closed` after `close()` or a fatal close.
|
|
45
|
+
* @since 1.0.0
|
|
46
|
+
*/
|
|
47
|
+
export type Status = "connecting" | "open" | "reconnecting" | "closed";
|
|
48
|
+
/**
|
|
49
|
+
* The state of one channel: `idle` (nothing listens), `subscribing` (first subscription in progress), `live`, `resuming`
|
|
50
|
+
* (subscribed before, subscribing again after a reconnect) or `closed`.
|
|
51
|
+
* @since 1.0.0
|
|
52
|
+
*/
|
|
53
|
+
export type ChannelStatus = "idle" | "subscribing" | "live" | "resuming" | "closed";
|
|
54
|
+
/**
|
|
55
|
+
* What a handler learns about a message besides its data.
|
|
56
|
+
* @since 1.0.0
|
|
57
|
+
*/
|
|
58
|
+
export interface MessageMeta {
|
|
59
|
+
/** The channel's sequence number of the message. */
|
|
60
|
+
readonly seq: number;
|
|
61
|
+
/** The connection id of the publisher, when a connection published it. */
|
|
62
|
+
readonly publisher?: string;
|
|
63
|
+
/** The user of the publisher, when it has one. */
|
|
64
|
+
readonly user?: string;
|
|
65
|
+
/** True when the message came from the replay after a resume, not live. */
|
|
66
|
+
readonly replayed: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* What an `onAny` handler receives: one of the channel's events with its data.
|
|
70
|
+
* @since 1.0.0
|
|
71
|
+
*/
|
|
72
|
+
export type AnyMessage<Def extends ChannelDef> = { [E in keyof Def["events"] & string]: {
|
|
73
|
+
readonly event: E;
|
|
74
|
+
readonly data: Def["events"][E];
|
|
75
|
+
readonly meta: MessageMeta;
|
|
76
|
+
}; }[keyof Def["events"] & string];
|
|
77
|
+
/**
|
|
78
|
+
* What an `onGap` handler learns: messages after `after` may be missing (`reset` is true when the server's log restarted).
|
|
79
|
+
* @since 1.0.0
|
|
80
|
+
*/
|
|
81
|
+
export interface GapInfo {
|
|
82
|
+
/** The last sequence number this client had seen. */
|
|
83
|
+
readonly after: number;
|
|
84
|
+
/** The channel's head sequence number at the resubscribe. */
|
|
85
|
+
readonly head: number;
|
|
86
|
+
/** True when the log restarted below `after`. */
|
|
87
|
+
readonly reset: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The names of a definition's requests.
|
|
91
|
+
* @since 1.0.0
|
|
92
|
+
*/
|
|
93
|
+
export type RequestsOf<Def extends ChannelDef> = Def extends {
|
|
94
|
+
readonly requests: infer R extends Record<string, {
|
|
95
|
+
readonly data: Json;
|
|
96
|
+
readonly reply: Json;
|
|
97
|
+
}>;
|
|
98
|
+
} ? R : string extends keyof Def["events"] ? Record<string, {
|
|
99
|
+
readonly data: Json;
|
|
100
|
+
readonly reply: Json;
|
|
101
|
+
}> : Record<never, never>;
|
|
102
|
+
/**
|
|
103
|
+
* The data type of request `E` of a definition.
|
|
104
|
+
* @since 1.0.0
|
|
105
|
+
*/
|
|
106
|
+
export type RequestData<Def extends ChannelDef, E extends string> = RequestsOf<Def>[E & keyof RequestsOf<Def>] extends {
|
|
107
|
+
readonly data: infer D;
|
|
108
|
+
} ? D : never;
|
|
109
|
+
/**
|
|
110
|
+
* The reply type of request `E` of a definition.
|
|
111
|
+
* @since 1.0.0
|
|
112
|
+
*/
|
|
113
|
+
export type RequestReply<Def extends ChannelDef, E extends string> = RequestsOf<Def>[E & keyof RequestsOf<Def>] extends {
|
|
114
|
+
readonly reply: infer R;
|
|
115
|
+
} ? R : never;
|
|
116
|
+
/**
|
|
117
|
+
* The presence state type of a definition (`Json` when it declares none).
|
|
118
|
+
* @since 1.0.0
|
|
119
|
+
*/
|
|
120
|
+
export type PresenceOf<Def extends ChannelDef> = Def extends {
|
|
121
|
+
readonly presence: infer P extends Json;
|
|
122
|
+
} ? P : Json;
|
|
123
|
+
/**
|
|
124
|
+
* Options of one channel.
|
|
125
|
+
* @since 1.0.0
|
|
126
|
+
*/
|
|
127
|
+
export interface ChannelOptions<State extends Json = Json> {
|
|
128
|
+
/** False to not receive this connection's own publishes (default true). */
|
|
129
|
+
readonly echo?: boolean;
|
|
130
|
+
/** Join the presence set with this state as soon as the channel subscribes. */
|
|
131
|
+
readonly presence?: State;
|
|
132
|
+
/** Receive presence events without joining. */
|
|
133
|
+
readonly watch?: boolean;
|
|
134
|
+
/** Replay the retained history on the first subscribe instead of starting at the head. */
|
|
135
|
+
readonly replay?: boolean;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Options of one publish.
|
|
139
|
+
* @since 1.0.0
|
|
140
|
+
*/
|
|
141
|
+
export interface PublishOptions {
|
|
142
|
+
/** Wait for the server's acknowledgment (default true). False resolves as soon as the message is queued or written. */
|
|
143
|
+
readonly ack?: boolean;
|
|
144
|
+
/** An idempotency key (1..64 characters); a resend with the same key is applied once. Default: generated. */
|
|
145
|
+
readonly key?: string;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* What an acknowledged publish resolves with.
|
|
149
|
+
* @since 1.0.0
|
|
150
|
+
*/
|
|
151
|
+
export interface PublishAck {
|
|
152
|
+
/** The sequence number the message got (0 when `ack` was false). */
|
|
153
|
+
readonly seq: number;
|
|
154
|
+
/** True when the key was already accepted and nothing new was stored. */
|
|
155
|
+
readonly duplicate: boolean;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The presence of one channel: its members and the state this connection publishes.
|
|
159
|
+
* @since 1.0.0
|
|
160
|
+
*/
|
|
161
|
+
export interface ChannelPresence<State extends Json> {
|
|
162
|
+
/** The members, in the order they appeared. The same array until something changes. */
|
|
163
|
+
members(): readonly Member<State>[];
|
|
164
|
+
/** Joins the presence set, or replaces this connection's state. */
|
|
165
|
+
set(state: State): void;
|
|
166
|
+
/** Leaves the presence set and keeps the channel subscribed. */
|
|
167
|
+
leave(): void;
|
|
168
|
+
/** Calls `callback` with the member list after every change. Returns the function that stops it. */
|
|
169
|
+
on(callback: (members: readonly Member<State>[]) => void): () => void;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* One channel of a {@link Realtime}.
|
|
173
|
+
* @since 1.0.0
|
|
174
|
+
*/
|
|
175
|
+
export interface Channel<Def extends ChannelDef = ChannelDef> {
|
|
176
|
+
/** The channel name. */
|
|
177
|
+
readonly name: string;
|
|
178
|
+
/** Where the channel is. */
|
|
179
|
+
readonly status: ChannelStatus;
|
|
180
|
+
/** The last sequence number delivered (0 before the first subscribe). */
|
|
181
|
+
readonly lastSeq: number;
|
|
182
|
+
/** Calls `handler` for each message of `event`. The channel subscribes while something listens. */
|
|
183
|
+
on<E extends keyof Def["events"] & string>(event: E, handler: (data: Def["events"][E], meta: MessageMeta) => void): () => void;
|
|
184
|
+
/** Calls `handler` for each message of any event. */
|
|
185
|
+
onAny(handler: (message: AnyMessage<Def>) => void): () => void;
|
|
186
|
+
/** Calls `handler` when messages may be missing after a resubscribe. */
|
|
187
|
+
onGap(handler: (info: GapInfo) => void): () => void;
|
|
188
|
+
/** Calls `handler` when the channel's status changes. */
|
|
189
|
+
onStatus(handler: (status: ChannelStatus) => void): () => void;
|
|
190
|
+
/** Publishes a message. Resolves with the sequence number once the server acknowledged it. */
|
|
191
|
+
publish<E extends keyof Def["events"] & string>(event: E, data: Def["events"][E], options?: PublishOptions): Promise<PublishAck>;
|
|
192
|
+
/** Sends a request to the server handler of `event` and resolves with its reply. */
|
|
193
|
+
request<E extends keyof RequestsOf<Def> & string>(event: E, data: RequestData<Def, E>, options?: {
|
|
194
|
+
readonly timeoutMs?: number;
|
|
195
|
+
}): Promise<RequestReply<Def, E>>;
|
|
196
|
+
/** The presence set of the channel. */
|
|
197
|
+
readonly presence: ChannelPresence<PresenceOf<Def>>;
|
|
198
|
+
/** Unsubscribes and releases the channel; the handle is not usable afterward. */
|
|
199
|
+
close(): void;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Options of {@link createRealtime}.
|
|
203
|
+
* @since 1.0.0
|
|
204
|
+
*/
|
|
205
|
+
export interface RealtimeOptions {
|
|
206
|
+
/** The stream path or URL, for example `"/rt"`. Relative values resolve against the page. */
|
|
207
|
+
readonly url: string;
|
|
208
|
+
/** `"auto"` (default) tries WebSocket and falls back to server-sent events. */
|
|
209
|
+
readonly transport?: "auto" | "ws" | "sse";
|
|
210
|
+
/** Where server-sent-events clients POST (default: `url` plus `/send`). */
|
|
211
|
+
readonly postUrl?: string;
|
|
212
|
+
/** Reconnection policy. */
|
|
213
|
+
readonly reconnect?: {
|
|
214
|
+
readonly minDelayMs?: number;
|
|
215
|
+
readonly maxDelayMs?: number;
|
|
216
|
+
readonly stableMs?: number;
|
|
217
|
+
/** Retry after a close the server marked as a violation (1002, 1008, 1009). Default false. */
|
|
218
|
+
readonly retryFatal?: boolean;
|
|
219
|
+
/** The random source for jitter, in [0, 1). Default `Math.random`. */
|
|
220
|
+
readonly random?: () => number;
|
|
221
|
+
};
|
|
222
|
+
/** Publishes kept while disconnected (default 100). */
|
|
223
|
+
readonly maxQueued?: number;
|
|
224
|
+
/** Milliseconds a queued publish waits for a connection (default 30,000). */
|
|
225
|
+
readonly queueTtlMs?: number;
|
|
226
|
+
/** Milliseconds to wait for an acknowledgment, and for a connection to become ready (default 10,000). */
|
|
227
|
+
readonly ackTimeoutMs?: number;
|
|
228
|
+
/** Headers for the stream and POST requests of the server-sent-events transport (a browser WebSocket cannot send any). */
|
|
229
|
+
readonly headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
230
|
+
/** The `fetch` to use. Default: the global one. */
|
|
231
|
+
readonly fetch?: typeof fetch;
|
|
232
|
+
/** The WebSocket constructor to use. Default: the global one. */
|
|
233
|
+
readonly WebSocket?: typeof WebSocket;
|
|
234
|
+
/** Receives failures nobody awaits: a refused subscription, a dropped unacknowledged publish, a handler that threw. */
|
|
235
|
+
readonly onError?: (error: unknown) => void;
|
|
236
|
+
/** Receives each change of the connection status. */
|
|
237
|
+
readonly onStatus?: (status: Status) => void;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* The client: one transport, many channels.
|
|
241
|
+
* @since 1.0.0
|
|
242
|
+
*/
|
|
243
|
+
export interface Realtime<Schema extends ChannelSchema = ChannelSchema> {
|
|
244
|
+
/** The channel `name`; the same handle again while it is open. Options apply when the channel is first created. */
|
|
245
|
+
channel<Name extends keyof Schema & string>(name: Name, options?: ChannelOptions<PresenceOf<Schema[Name]>>): Channel<Schema[Name]>;
|
|
246
|
+
/** The connection status. */
|
|
247
|
+
readonly status: Status;
|
|
248
|
+
/** The id of the current connection, once the server accepted it. */
|
|
249
|
+
readonly connectionId: string | undefined;
|
|
250
|
+
/** Calls `handler` when the connection status changes. */
|
|
251
|
+
onStatus(handler: (status: Status) => void): () => void;
|
|
252
|
+
/** Closes the connection and every channel. Pending publishes reject with `closed`. */
|
|
253
|
+
close(): void;
|
|
254
|
+
}
|
|
255
|
+
//#endregion
|
|
256
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/client/util.ts
|
|
2
|
+
/** The delay before reconnect attempt `attempt` (0-based): full jitter below an exponential cap. @internal */
|
|
3
|
+
function backoff(attempt, min, max, random) {
|
|
4
|
+
return random() * Math.min(max, min * 2 ** Math.min(attempt, 30));
|
|
5
|
+
}
|
|
6
|
+
/** A random identifier of 12 base-36 characters, used to build idempotency keys. @internal */
|
|
7
|
+
function clientId() {
|
|
8
|
+
const bytes = /* @__PURE__ */ new Uint8Array(8);
|
|
9
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
10
|
+
let id = "";
|
|
11
|
+
for (const byte of bytes) id += byte.toString(36).padStart(2, "0");
|
|
12
|
+
return id.slice(0, 12);
|
|
13
|
+
}
|
|
14
|
+
/** Resolves a stream or POST path against the page; `ws` swaps `http(s):` for `ws(s):`. @internal */
|
|
15
|
+
function resolveUrl(url, ws) {
|
|
16
|
+
const href = globalThis.location?.href;
|
|
17
|
+
const resolved = new URL(url, href !== void 0 && URL.canParse(url, href) ? href : "http://localhost/");
|
|
18
|
+
if (ws) resolved.protocol = resolved.protocol === "https:" ? "wss:" : "ws:";
|
|
19
|
+
return resolved;
|
|
20
|
+
}
|
|
21
|
+
/** The default POST URL: the stream path plus `/send`, keeping the query. @internal */
|
|
22
|
+
function sendUrl(url) {
|
|
23
|
+
const next = new URL(url);
|
|
24
|
+
next.pathname = `${next.pathname.replace(/\/$/, "")}/send`;
|
|
25
|
+
return next;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { backoff, clientId, resolveUrl, sendUrl };
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","names":[],"sources":["../../../src/client/util.ts"],"sourcesContent":["/** The delay before reconnect attempt `attempt` (0-based): full jitter below an exponential cap. @internal */\nexport function backoff(attempt: number, min: number, max: number, random: () => number): number {\n return random() * Math.min(max, min * 2 ** Math.min(attempt, 30));\n}\n\n/** A random identifier of 12 base-36 characters, used to build idempotency keys. @internal */\nexport function clientId(): string {\n const bytes = new Uint8Array(8);\n globalThis.crypto.getRandomValues(bytes);\n let id = \"\";\n for (const byte of bytes) id += byte.toString(36).padStart(2, \"0\");\n return id.slice(0, 12);\n}\n\n/** Resolves a stream or POST path against the page; `ws` swaps `http(s):` for `ws(s):`. @internal */\nexport function resolveUrl(url: string, ws: boolean): URL {\n const href = globalThis.location?.href;\n const base = href !== undefined && URL.canParse(url, href) ? href : \"http://localhost/\";\n const resolved = new URL(url, base);\n if (ws) resolved.protocol = resolved.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return resolved;\n}\n\n/** The default POST URL: the stream path plus `/send`, keeping the query. @internal */\nexport function sendUrl(url: URL): URL {\n const next = new URL(url);\n next.pathname = `${next.pathname.replace(/\\/$/, \"\")}/send`;\n return next;\n}\n"],"mappings":";;AACA,SAAgB,QAAQ,SAAiB,KAAa,KAAa,QAA8B;CAC/F,OAAO,OAAO,IAAI,KAAK,IAAI,KAAK,MAAM,KAAK,KAAK,IAAI,SAAS,EAAE,CAAC;AAClE;;AAGA,SAAgB,WAAmB;CACjC,MAAM,wBAAQ,IAAI,WAAW,CAAC;CAC9B,WAAW,OAAO,gBAAgB,KAAK;CACvC,IAAI,KAAK;CACT,KAAK,MAAM,QAAQ,OAAO,MAAM,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;CACjE,OAAO,GAAG,MAAM,GAAG,EAAE;AACvB;;AAGA,SAAgB,WAAW,KAAa,IAAkB;CACxD,MAAM,OAAO,WAAW,UAAU;CAElC,MAAM,WAAW,IAAI,IAAI,KADZ,SAAS,KAAA,KAAa,IAAI,SAAS,KAAK,IAAI,IAAI,OAAO,mBAClC;CAClC,IAAI,IAAI,SAAS,WAAW,SAAS,aAAa,WAAW,SAAS;CACtE,OAAO;AACT;;AAGA,SAAgB,QAAQ,KAAe;CACrC,MAAM,OAAO,IAAI,IAAI,GAAG;CACxB,KAAK,WAAW,GAAG,KAAK,SAAS,QAAQ,OAAO,EAAE,EAAE;CACpD,OAAO;AACT"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { decodeServerFrame } from "../protocol.js";
|
|
2
|
+
//#region src/client/ws.ts
|
|
3
|
+
/** Opens a WebSocket whose text messages are protocol frames. Throws for a URL the constructor refuses. @internal */
|
|
4
|
+
function openSocket(url, Socket, events) {
|
|
5
|
+
const socket = new Socket(url);
|
|
6
|
+
socket.onmessage = (event) => {
|
|
7
|
+
if (typeof event.data !== "string") return;
|
|
8
|
+
events.rx();
|
|
9
|
+
const frame = decodeServerFrame(event.data);
|
|
10
|
+
if (frame !== void 0) events.frame(frame);
|
|
11
|
+
};
|
|
12
|
+
let ended = false;
|
|
13
|
+
const end = (code) => {
|
|
14
|
+
if (ended) return;
|
|
15
|
+
ended = true;
|
|
16
|
+
events.closed({ code });
|
|
17
|
+
};
|
|
18
|
+
socket.onclose = (event) => end(event.code);
|
|
19
|
+
socket.onerror = () => {
|
|
20
|
+
if (socket.readyState !== 1) end(1006);
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
send(text) {
|
|
24
|
+
if (socket.readyState !== 1) return false;
|
|
25
|
+
socket.send(text);
|
|
26
|
+
return true;
|
|
27
|
+
},
|
|
28
|
+
close() {
|
|
29
|
+
socket.onmessage = null;
|
|
30
|
+
socket.onclose = null;
|
|
31
|
+
socket.onerror = null;
|
|
32
|
+
socket.close(1e3);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { openSocket };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=ws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.js","names":[],"sources":["../../../src/client/ws.ts"],"sourcesContent":["import { decodeServerFrame } from \"../protocol\";\nimport type { Link, LinkEvents } from \"./sse\";\n\n/** Opens a WebSocket whose text messages are protocol frames. Throws for a URL the constructor refuses. @internal */\nexport function openSocket(url: string, Socket: typeof WebSocket, events: LinkEvents): Link {\n const socket = new Socket(url);\n socket.onmessage = (event: MessageEvent<unknown>) => {\n if (typeof event.data !== \"string\") return;\n events.rx();\n const frame = decodeServerFrame(event.data);\n if (frame !== undefined) events.frame(frame);\n };\n // A failed upgrade is `error` then `close` (code 1006) in browsers; Node 22's WebSocket fires only `error` and stays connecting, so\n // an error before the socket opened counts as the close, once.\n let ended = false;\n const end = (code: number): void => {\n if (ended) return;\n ended = true;\n events.closed({ code });\n };\n socket.onclose = (event: CloseEvent) => end(event.code);\n socket.onerror = () => {\n if (socket.readyState !== 1) end(1006);\n };\n return {\n send(text) {\n if (socket.readyState !== 1) return false;\n socket.send(text);\n return true;\n },\n close() {\n socket.onmessage = null;\n socket.onclose = null;\n socket.onerror = null;\n socket.close(1000);\n },\n };\n}\n"],"mappings":";;;AAIA,SAAgB,WAAW,KAAa,QAA0B,QAA0B;CAC1F,MAAM,SAAS,IAAI,OAAO,GAAG;CAC7B,OAAO,aAAa,UAAiC;EACnD,IAAI,OAAO,MAAM,SAAS,UAAU;EACpC,OAAO,GAAG;EACV,MAAM,QAAQ,kBAAkB,MAAM,IAAI;EAC1C,IAAI,UAAU,KAAA,GAAW,OAAO,MAAM,KAAK;CAC7C;CAGA,IAAI,QAAQ;CACZ,MAAM,OAAO,SAAuB;EAClC,IAAI,OAAO;EACX,QAAQ;EACR,OAAO,OAAO,EAAE,KAAK,CAAC;CACxB;CACA,OAAO,WAAW,UAAsB,IAAI,MAAM,IAAI;CACtD,OAAO,gBAAgB;EACrB,IAAI,OAAO,eAAe,GAAG,IAAI,IAAI;CACvC;CACA,OAAO;EACL,KAAK,MAAM;GACT,IAAI,OAAO,eAAe,GAAG,OAAO;GACpC,OAAO,KAAK,IAAI;GAChB,OAAO;EACT;EACA,QAAQ;GACN,OAAO,YAAY;GACnB,OAAO,UAAU;GACjB,OAAO,UAAU;GACjB,OAAO,MAAM,GAAI;EACnB;CACF;AACF"}
|