svelte-adapter-uws 0.5.0-next.21 → 0.5.0-next.23
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/MIGRATION.md +139 -84
- package/README.md +12 -1
- package/client.js +17 -4
- package/files/handler.js +15 -8
- package/files/index.js +3 -2
- package/index.d.ts +3 -3
- package/index.js +7 -6
- package/package.json +2 -2
- package/testing.js +2 -1
- package/vite.js +6 -4
package/MIGRATION.md
CHANGED
|
@@ -1,107 +1,138 @@
|
|
|
1
1
|
# Migration guide: svelte-adapter-uws 0.4.x to 0.5.x
|
|
2
2
|
|
|
3
|
-
This guide
|
|
3
|
+
This guide is organized by **tier**. Most apps only need to read the first two sections.
|
|
4
|
+
|
|
5
|
+
- **[Critical](#critical-read-first)** - security-class behavior changes; audit required.
|
|
6
|
+
- **[Required source changes](#required-source-changes)** - won't run cleanly without these.
|
|
7
|
+
- **[Notable defaults and behaviors](#notable-defaults-and-behaviors)** - probably fine, but you may notice.
|
|
8
|
+
- **[Recommended new patterns](#recommended-new-patterns)** - not required, but better.
|
|
9
|
+
- **[Cosmetic](#cosmetic)** - type-only, internal refactors, niche edge cases.
|
|
4
10
|
|
|
5
11
|
If you are upgrading via `npm install svelte-adapter-uws@latest`, the registry will pull whatever the current `latest` dist-tag points to. To pin a specific 0.5 prerelease, use `svelte-adapter-uws@next`.
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
If you have a small app and want the 5-minute version, see the [docs site upgrade quickstart](https://svelte-realtime.dev/docs/upgrade-quickstart).
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
---
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
## Critical (read first)
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
These close real security bugs that affected idiomatic 0.4 code paths. Audit your code; production deploys may surface new denials or new startup errors that previously slipped through silently.
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
- Bump any `node:20-*` Docker base image to `node:22-*` or later.
|
|
17
|
-
- Bump CI matrix entries from `node-version: '20'` to `'22'` (and optionally add `'24'`).
|
|
18
|
-
- Apps that hand-roll `engines` checks against Node 20 should update accordingly.
|
|
21
|
+
### Async `subscribe` / `subscribeBatch` hooks now fail closed
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
**What changed.** Previously, an `async (ws, topic) => false` subscribe hook returned a `Promise<false>` to the runtime; the framework compared the Promise object against `false`, both of which were always falsy-against-truthy mismatches, so the framework treated EVERY async hook return as ALLOWED. Every app using async subscribe hooks (the idiomatic style for hooks that read a session store or DB) silently let every subscribe through, bypassing the developer's intended access control. The runtime now awaits the hook before inspecting its return value.
|
|
21
24
|
|
|
22
|
-
**
|
|
25
|
+
**How to migrate.** No source change is required if your hook was already correct in intent. AUDIT every async `subscribe` / `subscribeBatch` export to confirm it returns the values you expect (`false`, `'FORBIDDEN'`, etc) on the deny path; tests that previously passed because every subscribe was allowed will now exercise the real gate. `platform.subscribe(ws, topic)` and `platform.checkSubscribe(ws, topic)` are now async and must be awaited at every call site. If you wrote a `platform.sendTo(filter, ...)` filter as an async function, rewrite it to be sync (resolve any input data into `userData` from the upgrade hook); async filters are now treated as not-matching (fail closed) and log a one-time `console.error`.
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
### Wire-level subscribes to `__`-prefixed system topics blocked by default
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
**What changed.** Previously, any authenticated client could send `{"type":"subscribe","topic":"__signal:victim-userId"}` and intercept every `live.signal()` to that user, plus plugin presence / group / replay broadcasts on `__presence:*`, `__group:*`, `__replay:*`. The wire-level subscribe and subscribe-batch handlers now reject any topic whose first two bytes are `__` with `INVALID_TOPIC`. Server-side `platform.subscribe(ws, '__signal:userId')` (the legitimate framework pattern) still works.
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
As of `0.5.0-next.23`, the bundled client's `on(topic)` complements the server-side block: `__`-prefixed topics are treated as local broadcast taps, registering the inbound dispatch entry without sending a wire `subscribe` frame. The plugin or framework that publishes on the topic owns server-side subscriber-set membership (via `ws.subscribe` or `platform.subscribe`); the wire subscribe was always redundant and would now be denied. This eliminates the `[ws] subscribe denied topic=__presence:<room> reason=INVALID_TOPIC` console.warn that bundled plugin clients (`presence`, `replay`, `cursor`, `groups`) and `svelte-realtime`'s `health` store emitted on every page mount and reconnect under `next.21` / `next.22`.
|
|
29
32
|
|
|
30
|
-
**How to migrate.** No
|
|
33
|
+
**How to migrate.** No action needed if you only subscribe to system topics from server code, or if you use the bundled plugin clients or `svelte-realtime`'s `health` store (these are fixed automatically as of `next.23`). If your app legitimately routes public topics through the `__` prefix (rare), the migration is one of:
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
- **Recommended**: rename the topic to a non-`__` prefix. The `__` namespace is reserved for the framework and its bundled plugins.
|
|
36
|
+
- Send the wire frame directly via `connect().send({ type: 'subscribe', topic: '__foo', ref: 1 })` and set `websocket.allowSystemTopicSubscribe: true` in `svelte.config.js` on the server. `on('__foo')` from the bundled client will not send the frame for you.
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
### SSR dedup cache key includes `base_origin` (cross-tenant leak fix)
|
|
35
39
|
|
|
36
|
-
**
|
|
40
|
+
**What changed.** Previously the dedup key was `method + '\0' + url`. In virtual-hosting deployments (one uWS instance behind multiple Host aliases), two concurrent anonymous GETs to `/` from `tenantA.example` and `tenantB.example` shared one SSR call, producing a cross-tenant leak. The key is now `method + '\0' + base_origin + '\0' + url`.
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
**How to migrate.** No action needed; previously-correct apps see a tighter dedup behavior. Multi-tenant deployments should verify the fix is in place.
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
### Replay plugin checks subscribe authorization before reading a topic's buffer
|
|
41
45
|
|
|
42
|
-
**
|
|
46
|
+
**What changed.** Replay backends now consult `platform.checkSubscribe(ws, topic)` before reading any topic's buffer; topics the wire-subscribe gate would deny emit a `denied` event on `__replay:{topic}` (the client treats this similarly to `truncated`). Pre-fix, an attacker could send a crafted `lastSeenSeqs` map and read history for a topic they could not subscribe to live.
|
|
47
|
+
|
|
48
|
+
**How to migrate.** No action needed if you use the bundled replay client, which handles the new event. Hand-rolled replay consumers should add a branch for `denied` events.
|
|
43
49
|
|
|
44
|
-
###
|
|
50
|
+
### `resume` hook now awaited before the `resumed` ack frame
|
|
45
51
|
|
|
46
52
|
**What changed.** The user's `resume` hook previously fired fire-and-forget; the `{type:'resumed'}` ack went out immediately and the client switched to live mode while replay frames were still in flight, producing out-of-order events. The runtime now awaits the resume hook before sending the ack.
|
|
47
53
|
|
|
48
54
|
**How to migrate.** Confirm any async work you do in the `resume` hook (replay enqueue, DB lookup, etc.) is `await`ed inside the hook body. Long-running synchronous work in the hook will now delay the resume ack; refactor expensive work to fire after the ack via `setImmediate` if needed.
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
---
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
## Required source changes
|
|
53
59
|
|
|
54
|
-
|
|
60
|
+
These won't run cleanly until you make the change. The first one is loud (startup error); the others surface as compile-time type errors or runtime throws on bad input.
|
|
61
|
+
|
|
62
|
+
### Runtime: Node.js 22+ required (was Node 20+)
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
**What changed.** `package.json#engines.node` moved from `>=20.0.0` to `>=22.0.0`. Tracks `uWebSockets.js` v20.67.0, which dropped Node 20 support upstream. Node 22 LTS, Node 24 current, and Node 26 are supported. Picks up real upstream wins from v20.60 to v20.67: backpressure fix (v20.64), Latin-1 string handling (v20.65), faster String args via V8 ValueView (v20.63), zero-cost `getRemoteAddress` / `getRemoteAddressAsText` (v20.66), `getRemotePort` / `getProxiedRemotePort` (v20.61), DeclarativeResponse improvements, and symbol-keyed userData support.
|
|
57
65
|
|
|
58
|
-
**
|
|
66
|
+
**How to migrate.**
|
|
59
67
|
|
|
60
|
-
|
|
68
|
+
- Confirm your runtime is Node 22+ in CI and prod. Node 22 LTS is the minimum.
|
|
69
|
+
- Bump any `node:20-*` Docker base image to `node:22-*` or later.
|
|
70
|
+
- Bump CI matrix entries from `node-version: '20'` to `'22'` (and optionally add `'24'`).
|
|
71
|
+
- Apps that hand-roll `engines` checks against Node 20 should update accordingly.
|
|
61
72
|
|
|
62
|
-
###
|
|
73
|
+
### Refuse to start on `same-origin` policy without host pin
|
|
74
|
+
|
|
75
|
+
**Your app will throw at startup until you fix this.**
|
|
63
76
|
|
|
64
77
|
**What changed.** A bare `allowedOrigins: 'same-origin'` config running without `ORIGIN` env, `HOST_HEADER` env, native TLS (`SSL_CERT`/`SSL_KEY`), or an `upgrade()` hook previously silently accepted any non-browser scripted client (the same-origin check compares two attacker-controlled headers). The runtime now throws at startup with a human-readable resolution list.
|
|
65
78
|
|
|
66
79
|
**How to migrate.** Pick one of: set the `ORIGIN` env var to your canonical origin, set `HOST_HEADER` env, configure native TLS, export an `upgrade()` hook, or switch `allowedOrigins` to an explicit string-array allowlist. Apps that have audited the deployment can opt out via `websocket.unsafeSameOriginWithoutHostPin: true`.
|
|
67
80
|
|
|
68
|
-
###
|
|
81
|
+
### `platform.subscribe` and `platform.checkSubscribe` are now async
|
|
69
82
|
|
|
70
|
-
**What changed.**
|
|
83
|
+
**What changed.** Both methods previously returned `string | null` synchronously. Returns are now `Promise<string | null>`. Direct `ws.subscribe()` calls intentionally bypass the hook (the bypass we are guarding against).
|
|
71
84
|
|
|
72
|
-
**How to migrate.**
|
|
85
|
+
**How to migrate.** Downstream library / framework code that previously called `ws.subscribe()` directly inside an RPC handler (a real authorization-bypass class of bug) should switch to `await platform.subscribe(ws, topic)` and treat a non-null return value as a denial. Apps using `platform.checkSubscribe` similarly must `await` the return.
|
|
73
86
|
|
|
74
|
-
###
|
|
87
|
+
### Cookie `path` / `domain` attribute injection blocked in `serializeCookie`
|
|
75
88
|
|
|
76
|
-
**What changed.**
|
|
89
|
+
**What changed.** Both attributes are now validated against the same character class as cookie values (no CTLs, no `;`, no `,`, no whitespace, no DEL) before concatenation. Non-strings throw the same way as malformed names/values.
|
|
77
90
|
|
|
78
|
-
**How to migrate.**
|
|
91
|
+
**How to migrate.** Confirm every `cookies.set(name, value, { path, domain })` call passes a valid path / domain. Calls passing user-influenced strings will now throw at the call site instead of silently producing a malformed `Set-Cookie`.
|
|
79
92
|
|
|
80
|
-
###
|
|
93
|
+
### `parse_as_bytes` rejects negative and non-finite values
|
|
81
94
|
|
|
82
|
-
**What changed.**
|
|
95
|
+
**What changed.** `BODY_SIZE_LIMIT=-100K` previously resolved to a negative number that read like "no limit" downstream; `BODY_SIZE_LIMIT=Infinity` similarly bypassed every byte-budget check. Both now resolve to NaN, which the existing `if (isNaN(body_size_limit)) throw` guard routes to a clean startup error.
|
|
83
96
|
|
|
84
|
-
**How to migrate.**
|
|
97
|
+
**How to migrate.** Audit any `BODY_SIZE_LIMIT` env value you set; values must be strictly positive finite (`512K`, `2M`) or `0` (which means unlimited).
|
|
85
98
|
|
|
86
|
-
|
|
99
|
+
---
|
|
87
100
|
|
|
88
|
-
|
|
101
|
+
## Notable defaults and behaviors
|
|
89
102
|
|
|
90
|
-
|
|
103
|
+
These change observable runtime behavior. Most apps are unaffected; a few will notice.
|
|
91
104
|
|
|
92
|
-
###
|
|
105
|
+
### Default `maxPayloadLength` raised from 16 KB to 1 MB
|
|
93
106
|
|
|
94
|
-
**What changed.**
|
|
107
|
+
**What changed.** The default cap on a single inbound WebSocket frame moved from 16 KB to 1 MB. uWS itself defaults to 16 MB; 16 KB was excessively conservative and forced chunked-upload frameworks to use ~12 KB chunks (~9000 chunks for a 100 MB file after typical 90% headroom). Apps that were chunking large payloads to fit under 16 KB will now accept them in fewer chunks (or in a single frame).
|
|
95
108
|
|
|
96
|
-
**How to migrate.**
|
|
109
|
+
**How to migrate.** No action needed for most apps. To pin the previous cap, set `websocket.maxPayloadLength: 16 * 1024` in `svelte.config.js`. To pin any other value, set the option to that byte count. DoS protection remains layered: `upgradeAdmission.maxConcurrent` caps connection count, `maxBackpressure` caps per-connection outbound queue size.
|
|
97
110
|
|
|
98
|
-
###
|
|
111
|
+
### `/__ws/auth` POST requires Origin / `x-requested-with` / `Sec-Fetch-Site`
|
|
99
112
|
|
|
100
|
-
**What changed.**
|
|
113
|
+
**What changed.** The authenticate POST endpoint previously accepted any credentialed cross-origin POST. A request must now satisfy at least one of: `x-requested-with: XMLHttpRequest`, `Sec-Fetch-Site: same-origin`, or an `Origin` header matching `allowedOrigins`. The bundled adapter client always stamps `x-requested-with` on its preflight POST, so browser-side flows are unaffected.
|
|
101
114
|
|
|
102
|
-
**How to migrate.**
|
|
115
|
+
**How to migrate.** No action needed for browser apps using the bundled client. For native (non-browser) clients hitting `/__ws/auth` directly, either stamp `x-requested-with: XMLHttpRequest` on the request, or set `websocket.authPathRequireOrigin: false` in `svelte.config.js` to opt out.
|
|
116
|
+
|
|
117
|
+
### Dynamic compression skipped for credentialed responses (BREACH defense)
|
|
118
|
+
|
|
119
|
+
**What changed.** The dynamic brotli/gzip branch previously fired on every response above 1 KB. Combined with attacker-influenced reflected input alongside a secret in the page body (CSRF token, session ID, API key), the compressed length leaked the secret one byte at a time via the BREACH attack. Requests carrying `Cookie` or `Authorization` now skip dynamic compression. Anonymous responses still compress; build-time precompressed static files are unaffected.
|
|
120
|
+
|
|
121
|
+
**How to migrate.** No action needed; uncompressed credentialed SSR is the safe default. If you have audited every page for BREACH defenses (random per-response masking, prefix randomization, no secrets reflected with attacker input), opt back in via `websocket.compressCredentialedResponses: true`.
|
|
122
|
+
|
|
123
|
+
### Wire-topic accept set tightened to printable ASCII
|
|
124
|
+
|
|
125
|
+
**What changed.** The wire accept set moved from "anything except control bytes / quote / backslash" to "printable ASCII (0x20-0x7E) except quote / backslash". Pre-fix, hostile clients could subscribe to topics containing line-separator characters, BiDi overrides, byte-order marks, or arbitrary non-ASCII. Server-side `platform.subscribe` and `platform.checkSubscribe` keep their previous looser accept set, so server-side code using non-ASCII topic names is unaffected.
|
|
126
|
+
|
|
127
|
+
**How to migrate.** No action needed unless your app legitimately accepts non-ASCII topic names FROM CLIENTS. If it does, set `websocket.allowNonAsciiTopics: true`.
|
|
128
|
+
|
|
129
|
+
### `isValidWireTopic` rejects `"` and `\\`
|
|
130
|
+
|
|
131
|
+
**What changed.** The wire-accept set now matches `esc()`'s rejection set. Pre-fix, a client could subscribe to topic `"` (passes wire), and any later `platform.publish('"', ...)` crashed because envelope-build threw on those characters.
|
|
132
|
+
|
|
133
|
+
**How to migrate.** No action needed for healthy apps. If you have client code that sent literal `"` or `\\` topic names, rename those topics.
|
|
103
134
|
|
|
104
|
-
###
|
|
135
|
+
### Client `status` store expanded to a five-state machine
|
|
105
136
|
|
|
106
137
|
**What changed.** The `'closed'` state was split into `'disconnected'` (transient, will retry), `'failed'` (terminal: auth denied, max retries, or `close()` called), and `'suspended'` (open but tab is backgrounded). `ready()` now resolves on either `'open'` or `'suspended'`.
|
|
107
138
|
|
|
@@ -112,89 +143,113 @@ If you are upgrading via `npm install svelte-adapter-uws@latest`, the registry w
|
|
|
112
143
|
- `$status === 'failed' || $status === 'disconnected'` for "anything not connected"
|
|
113
144
|
- Read `_permaClosed` directly if the only relevant case was the terminal one.
|
|
114
145
|
|
|
115
|
-
###
|
|
146
|
+
### Presence plugin wire format switched to a compact diff protocol
|
|
116
147
|
|
|
117
148
|
**What changed.** The five-event format (`list` / `join` / `updated` / `leave` / `heartbeat`) collapses to two diff-shaped events plus the existing heartbeat: `presence_state` (full snapshot) and `presence_diff` (joins/leaves). Diffs are microtask-batched. Server and client ship in one bundle, so a single-package upgrade is seamless.
|
|
118
149
|
|
|
119
150
|
**How to migrate.** No action needed for users of the bundled `presence()` Svelte store on the client. Hand-rolled clients that consume the wire directly need to switch decoders to handle `presence_state` and `presence_diff` events. Stale browser tabs from a previous deploy will see a blank presence list until refresh.
|
|
120
151
|
|
|
121
|
-
###
|
|
122
|
-
|
|
123
|
-
**What changed.** Replay backends now consult `platform.checkSubscribe(ws, topic)` before reading any topic's buffer; topics the wire-subscribe gate would deny emit a `denied` event on `__replay:{topic}` (the client treats this similarly to `truncated`).
|
|
124
|
-
|
|
125
|
-
**How to migrate.** No action needed if you use the bundled replay client, which handles the new event. Hand-rolled replay consumers should add a branch for `denied` events.
|
|
126
|
-
|
|
127
|
-
### 17. `parse_as_bytes` rejects negative and non-finite values
|
|
128
|
-
|
|
129
|
-
**What changed.** `BODY_SIZE_LIMIT=-100K` previously resolved to a negative number that read like "no limit" downstream; `BODY_SIZE_LIMIT=Infinity` similarly bypassed every byte-budget check. Both now resolve to NaN, which the existing `if (isNaN(body_size_limit)) throw` guard routes to a clean startup error.
|
|
130
|
-
|
|
131
|
-
**How to migrate.** Audit any `BODY_SIZE_LIMIT` env value you set; values must be strictly positive finite (`512K`, `2M`) or `0` (which means unlimited).
|
|
132
|
-
|
|
133
|
-
### 18. `parseCookies` returns a null-prototype object
|
|
152
|
+
### Wire single-subscribe frames consult `subscribeBatch` when only `subscribeBatch` is exported
|
|
134
153
|
|
|
135
|
-
**What changed.**
|
|
154
|
+
**What changed.** Previously, an app exporting only `subscribeBatch` for centralized authorization had its hook fire for batch frames but silently bypassed for single subscribes. Single subscribes are now routed through `subscribeBatch` (treated as a 1-element batch) when only `subscribeBatch` is exported.
|
|
136
155
|
|
|
137
|
-
**How to migrate.**
|
|
156
|
+
**How to migrate.** A `subscribeBatch` hook authored before this fix may now receive 1-element `topics` arrays where it previously did not see single frames at all. Confirm your hook handles short arrays correctly (any reasonable hook does).
|
|
138
157
|
|
|
139
|
-
###
|
|
158
|
+
### Initial-mount client subscribes are microtask-batched
|
|
140
159
|
|
|
141
|
-
**What changed.**
|
|
160
|
+
**What changed.** Multiple `subscribe(topic)` calls landing in the same microtask now coalesce into one `{type:'subscribe-batch', topics, ref}` wire frame instead of N individual `{type:'subscribe', topic, ref}` frames. Triggers `subscribeBatch` on the server once instead of `subscribe` N times.
|
|
142
161
|
|
|
143
|
-
**How to migrate.**
|
|
162
|
+
**How to migrate.** Update any test that asserts on the exact wire shape of two same-microtask subscribes seeing two `subscribe` frames; use `.find(m => m.type === 'subscribe-batch' && m.topics.includes(...))` instead. No source change in app code.
|
|
144
163
|
|
|
145
|
-
###
|
|
164
|
+
### Dev plugin enforces `allowedOrigins` on the WSS upgrade
|
|
146
165
|
|
|
147
166
|
**What changed.** The dev plugin previously printed a warning that "Dev mode does not enforce allowedOrigins" and accepted every WS upgrade. Dev now runs the same `isOriginAllowed` check production runs.
|
|
148
167
|
|
|
149
168
|
**How to migrate.** No action needed if your dev `allowedOrigins` matches the dev origin you connect from. To accept dev connections from arbitrary origins (legacy local dev scenarios), pass `devSkipOriginCheck: true` to the Vite plugin.
|
|
150
169
|
|
|
151
|
-
###
|
|
170
|
+
### Bounded-by-default capacity caps across the adapter and bundled plugins
|
|
152
171
|
|
|
153
172
|
**What changed.** Every internal `Map` / `Set` whose growth is driven by client behaviour or topic cardinality now has an explicit upper bound (default 1,000,000) and a documented saturation behaviour. New subscribes past `MAX_SUBSCRIPTIONS_PER_CONNECTION` respond with `subscribe-denied` reason `'RATE_LIMITED'`. New `platform.request()` past the per-connection cap rejects synchronously. Plugins (`ratelimit`, `throttle`, `debounce`, `cursor`, `presence`, `lock`) gain `maxBuckets` / `maxTopics` / `maxConnections` / `maxKeys` options at the same defaults.
|
|
154
173
|
|
|
155
174
|
**How to migrate.** No action needed for healthy apps; defaults are deliberately generous. Apps that approach 1M of any single resource per connection or per topic registry should investigate the leak rather than raise the cap.
|
|
156
175
|
|
|
157
|
-
###
|
|
176
|
+
### `queue` plugin `maxSize` default changed from `Infinity` to `1,000,000`
|
|
158
177
|
|
|
159
178
|
**What changed.** The `queue` plugin now drops tasks via `onDrop` once 1M waiting tasks accumulate per key.
|
|
160
179
|
|
|
161
180
|
**How to migrate.** Pass `{ maxSize: Infinity }` explicitly to opt back into the previous behaviour. Any real workload reaching 1M waiting tasks per key likely has a leak.
|
|
162
181
|
|
|
163
|
-
###
|
|
182
|
+
### `lock.clear()` rejects pending waiters with `LOCK_CLEARED`
|
|
164
183
|
|
|
165
184
|
**What changed.** Pre-fix, `clear()` only cleared the lookup Map and pending callers continued to resolve as the chain unfolded. The new waiter-queue owns the only reference to pending callers, so `clear()` must explicitly reject them.
|
|
166
185
|
|
|
167
186
|
**How to migrate.** If you relied on pending calls completing across a `clear()` in a teardown path, catch `LOCK_CLEARED` and treat it as success.
|
|
168
187
|
|
|
169
|
-
###
|
|
188
|
+
### `lock.withLock` accepts `maxWaitMs` and rejects with `LOCK_TIMEOUT`
|
|
170
189
|
|
|
171
|
-
**What changed.** `
|
|
190
|
+
**What changed.** Third argument to `withLock(key, fn, { maxWaitMs })` now supports bounded-wait. A rejected waiter receives a typed `LOCK_TIMEOUT` error with `.code`, `.key`, and `.maxWaitMs`. The current holder is not interrupted; subsequent waiters are unaffected.
|
|
172
191
|
|
|
173
|
-
**How to migrate.** No action needed for
|
|
192
|
+
**How to migrate.** No action needed for existing two-argument callers (no behavior change for them). If you adopt `maxWaitMs`, handle `LOCK_TIMEOUT` rejections at the call site.
|
|
174
193
|
|
|
175
|
-
###
|
|
194
|
+
### `start()` is now async; init / shutdown lifecycle hooks supported
|
|
176
195
|
|
|
177
|
-
**What changed.**
|
|
196
|
+
**What changed.** `start(host, port)` (production) and `createTestServer()` (test harness) return promises that resolve only after the new `init` hook completes. A throwing `init` rejects the promise. The dev plugin and test harness await `init` before declaring readiness. Two new optional `hooks.ws` exports: `init({ platform })` and `shutdown({ platform })`.
|
|
178
197
|
|
|
179
|
-
**How to migrate.** No action needed for
|
|
198
|
+
**How to migrate.** No action needed for the default code paths (the adapter's `index.js` already awaits). If you have custom code calling `start()` directly, await it. To use `init` to capture `platform` at boot (replacing the lazy "first connect" pattern), export `async init({ platform })`. Each worker fires its own `init`; layer leader election on top if you need cluster-wide singleton semantics.
|
|
180
199
|
|
|
181
|
-
###
|
|
200
|
+
### Per-event `coalesceKey` collapses duplicates in `publishBatched`
|
|
182
201
|
|
|
183
202
|
**What changed.** Per-event `coalesceKey?: string` collapses same-key duplicates before framing in `publishBatched`. Latest value wins at the latest occurrence's position. Capability-gated: clients opt in via a `{type:'hello', caps:['batch']}` frame after open (the bundled client does this automatically). When the fast path does not apply, `publishBatched` falls back to a per-event `publish()` loop.
|
|
184
203
|
|
|
185
204
|
**How to migrate.** Hand-rolled client code consuming the wire directly should send a `hello` frame advertising the `batch` capability if it wants batched frames; otherwise the server falls back to per-event frames as before. Bundled clients work automatically.
|
|
186
205
|
|
|
187
|
-
###
|
|
206
|
+
### `x-no-dedup` header is no longer consulted
|
|
188
207
|
|
|
189
|
-
**What changed.**
|
|
208
|
+
**What changed.** Any anonymous client could previously stamp `x-no-dedup: 1` to defeat SSR shared-leader fan-in and amplify server-side render cost. The header is now ignored.
|
|
190
209
|
|
|
191
|
-
**How to migrate.** If
|
|
210
|
+
**How to migrate.** If you used `x-no-dedup: 1` for per-request tracing during debugging, send a `Cookie` or `Authorization` header instead (legitimate authenticated callers always skip dedup).
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Recommended new patterns
|
|
215
|
+
|
|
216
|
+
Not required. Adopting these gets you the full 0.5 experience.
|
|
217
|
+
|
|
218
|
+
### Use `init({ platform })` / `shutdown({ platform })` for once-per-worker setup
|
|
219
|
+
|
|
220
|
+
`init` fires once per worker after the listen socket is bound, before any upgrade / open / message hook. The deterministic place to capture `platform` for cron / push registry / metrics / leader election:
|
|
192
221
|
|
|
193
|
-
|
|
222
|
+
```js
|
|
223
|
+
export async function init({ platform }) {
|
|
224
|
+
// captures platform for use anywhere
|
|
225
|
+
}
|
|
194
226
|
|
|
195
|
-
|
|
227
|
+
export async function shutdown() {
|
|
228
|
+
// best-effort teardown before the worker exits
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Eliminates the boot-to-first-connect window where state captured "on first open" was unavailable to cron ticks or background work. Per-worker in clustered mode; layer leader election if you need cluster-wide singleton semantics. See the [docs site lifecycle page](https://svelte-realtime.dev/docs/lifecycle).
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Cosmetic
|
|
237
|
+
|
|
238
|
+
Type-only changes, internal refactors, niche edge cases. No action required for most apps.
|
|
239
|
+
|
|
240
|
+
### `parseCookies` returns a null-prototype object
|
|
241
|
+
|
|
242
|
+
**What changed.** The returned bag has no prototype chain; a request with a `__proto__=evil` Cookie cannot leak attacker-controlled values through `cookies.toString` / `cookies.constructor` lookups.
|
|
243
|
+
|
|
244
|
+
**How to migrate.** No action needed unless your code reads inherited prototype methods off a `parseCookies` result (rare). Iterate keys with `for (const k in bag)` or `Object.keys(bag)` as before.
|
|
245
|
+
|
|
246
|
+
### Per-connection adapter scratch state moved to Symbol-keyed slots
|
|
247
|
+
|
|
248
|
+
**What changed.** `__subscriptions` and `__coalesced` previously sat directly on user-visible `getUserData()`. They now live under `WS_SUBSCRIPTIONS` and `WS_COALESCED` symbols exported from `files/utils.js`. The user-facing `CloseContext.subscriptions` shape is unchanged.
|
|
249
|
+
|
|
250
|
+
**How to migrate.** If your `upgrade()` hook returned an object containing a `__subscriptions` or `__coalesced` key, those keys will no longer collide with the adapter; you keep your own values. If your code read the adapter's internals via `getUserData().__subscriptions`, switch to `import { WS_SUBSCRIPTIONS } from 'svelte-adapter-uws/files/utils.js'` and read `getUserData()[WS_SUBSCRIPTIONS]`.
|
|
196
251
|
|
|
197
|
-
|
|
252
|
+
---
|
|
198
253
|
|
|
199
254
|
## After upgrading
|
|
200
255
|
|
package/README.md
CHANGED
|
@@ -75,6 +75,17 @@ I've been loving Svelte and SvelteKit for a long time. I always wanted to expand
|
|
|
75
75
|
|
|
76
76
|
**Getting started**
|
|
77
77
|
|
|
78
|
+
## Version compatibility
|
|
79
|
+
|
|
80
|
+
The three ecosystem packages move together. Bump them as a group:
|
|
81
|
+
|
|
82
|
+
| `svelte-adapter-uws` | `svelte-realtime` | `svelte-adapter-uws-extensions` | Notes |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| `^0.4.x` | `^0.4.x` | `^0.4.x` | Legacy stable |
|
|
85
|
+
| `^0.5.0` | `^0.5.0` | `^0.5.0` | Current. Node 22+ required. See `MIGRATION.md` if upgrading from 0.4. |
|
|
86
|
+
|
|
87
|
+
Mixed-version installs are rejected at install time with a peer-dep warning.
|
|
88
|
+
|
|
78
89
|
## Installation
|
|
79
90
|
|
|
80
91
|
### Starting from scratch
|
|
@@ -415,7 +426,7 @@ adapter({
|
|
|
415
426
|
|
|
416
427
|
These options control how the server handles misbehaving or slow clients at the WebSocket level:
|
|
417
428
|
|
|
418
|
-
**`maxPayloadLength`** (default: 1 MB) - the maximum size of a single incoming WebSocket message. If a client sends a message larger than this, uWS closes the connection immediately (not just the message - the entire connection is dropped). Set this based on the largest message your application expects to receive.
|
|
429
|
+
**`maxPayloadLength`** (default: 1 MB) - the maximum size of a single incoming WebSocket message. If a client sends a message larger than this, uWS closes the connection immediately (not just the message - the entire connection is dropped). Set this based on the largest message your application expects to receive. uWS itself defaults to 16 MB; this adapter sets 1 MB as a balanced default that handles typical app payloads in a single frame without forcing chunked-upload frameworks into ~12 KB chunks (which the previous 16 KB default did). For a stricter cap, pin an explicit value (e.g. `16 * 1024` for 16 KB).
|
|
419
430
|
|
|
420
431
|
**`maxBackpressure`** (default: 1 MB) - the per-connection outbound send buffer, AND the threshold above which `publish` / `send` / `publishBatched` silently skip a subscriber. When a specific subscriber's buffer is over this size, uWS drops that frame *for that subscriber only* while continuing to deliver to every non-backpressured subscriber. This makes `publish` / `send` / `publishBatched` volatile-by-default for slow consumers (the right behavior for cursor positions, typing indicators, presence pings - see "Volatile / fire-and-forget delivery" below). The `drain` hook fires per-connection when the buffer empties again. Lower this if you want subscribers shed sooner; raise it if you prefer to keep the connection queued and absorb temporary slowness. uWS's own default is 64 KB; this adapter sets 1 MB to favor keeping the connection alive under pub/sub spikes.
|
|
421
432
|
|
package/client.js
CHANGED
|
@@ -36,7 +36,8 @@ export function connect(options = {}) {
|
|
|
36
36
|
console.warn(
|
|
37
37
|
'[ws] connect() was called with options, but the connection already exists ' +
|
|
38
38
|
'(created automatically by on(), status, or ready()). ' +
|
|
39
|
-
'Your options are ignored. Call connect() before using other client functions
|
|
39
|
+
'Your options are ignored. Call connect() before using other client functions.\n' +
|
|
40
|
+
' See: https://svti.me/client-connect'
|
|
40
41
|
);
|
|
41
42
|
}
|
|
42
43
|
return ensureConnection(options, true);
|
|
@@ -1088,7 +1089,7 @@ function createConnection(options) {
|
|
|
1088
1089
|
return;
|
|
1089
1090
|
}
|
|
1090
1091
|
if (msg.type === 'subscribe-denied' && typeof msg.topic === 'string' && typeof msg.reason === 'string') {
|
|
1091
|
-
console.warn('[ws] subscribe denied topic=%s reason=%s', msg.topic, msg.reason);
|
|
1092
|
+
console.warn('[ws] subscribe denied topic=%s reason=%s\n See: https://svti.me/subscribe-denied', msg.topic, msg.reason);
|
|
1092
1093
|
denialsStore.set({ topic: msg.topic, reason: msg.reason, ref: msg.ref });
|
|
1093
1094
|
return;
|
|
1094
1095
|
}
|
|
@@ -1201,6 +1202,15 @@ function createConnection(options) {
|
|
|
1201
1202
|
const count = topicRefCounts.get(topic) || 0;
|
|
1202
1203
|
topicRefCounts.set(topic, count + 1);
|
|
1203
1204
|
if (count > 0) return; // Already subscribed at WS level
|
|
1205
|
+
// __-prefixed topics are framework broadcast taps, not wire subscriptions:
|
|
1206
|
+
// the plugin or extension that owns the topic manages server-side subscriber
|
|
1207
|
+
// membership directly (via ws.subscribe / platform.subscribe), and the client
|
|
1208
|
+
// only needs the local topicStores entry that onTopic already registered for
|
|
1209
|
+
// inbound dispatch. Skip the wire frame entirely - it would be rejected by
|
|
1210
|
+
// the default INVALID_TOPIC gate anyway, and the round-trip adds nothing.
|
|
1211
|
+
// Excluded from subscribedTopics for the same reason: the reconnect-resubscribe
|
|
1212
|
+
// path must not re-emit a frame the server will deny.
|
|
1213
|
+
if (topic.charCodeAt(0) === 95 && topic.charCodeAt(1) === 95) return;
|
|
1204
1214
|
subscribedTopics.add(topic);
|
|
1205
1215
|
if (ws?.readyState !== WebSocket.OPEN) return;
|
|
1206
1216
|
if (!pendingSubscribes) {
|
|
@@ -1245,6 +1255,9 @@ function createConnection(options) {
|
|
|
1245
1255
|
if (key.startsWith(topic + '\0')) eventStores.delete(key);
|
|
1246
1256
|
}
|
|
1247
1257
|
if (debug) console.log('[ws] unsubscribe ->', topic);
|
|
1258
|
+
// Symmetric to subscribe(): __-prefixed topics never sent a wire subscribe,
|
|
1259
|
+
// so there is no wire-level subscription state for the server to release.
|
|
1260
|
+
if (topic.charCodeAt(0) === 95 && topic.charCodeAt(1) === 95) return;
|
|
1248
1261
|
if (ws?.readyState === WebSocket.OPEN) {
|
|
1249
1262
|
ws.send(JSON.stringify({ type: 'unsubscribe', topic }));
|
|
1250
1263
|
}
|
|
@@ -1423,7 +1436,7 @@ function createConnection(options) {
|
|
|
1423
1436
|
if (debug) console.log('[ws] send ->', data);
|
|
1424
1437
|
ws.send(serializeForSend(data));
|
|
1425
1438
|
} else if (debug) {
|
|
1426
|
-
console.warn('[ws] send dropped (not connected) - use sendQueued() to queue messages for reconnect:', data);
|
|
1439
|
+
console.warn('[ws] send dropped (not connected) - use sendQueued() to queue messages for reconnect:', data, '\n See: https://svti.me/send-dropped');
|
|
1427
1440
|
}
|
|
1428
1441
|
}
|
|
1429
1442
|
|
|
@@ -1446,7 +1459,7 @@ function createConnection(options) {
|
|
|
1446
1459
|
ws.send(serialized);
|
|
1447
1460
|
} else {
|
|
1448
1461
|
if (sendQueue.length >= MAX_QUEUE_SIZE) {
|
|
1449
|
-
console.warn('[ws] queue full (' + MAX_QUEUE_SIZE + '), dropping oldest message');
|
|
1462
|
+
console.warn('[ws] queue full (' + MAX_QUEUE_SIZE + '), dropping oldest message\n See: https://svti.me/client-queue');
|
|
1450
1463
|
sendQueue.shift();
|
|
1451
1464
|
}
|
|
1452
1465
|
if (debug) console.log('[ws] queued ->', data);
|
package/files/handler.js
CHANGED
|
@@ -327,7 +327,8 @@ if (!origin && !host_header && !protocol_header && !is_tls) {
|
|
|
327
327
|
'For production, either:\n' +
|
|
328
328
|
' SSL_CERT + SSL_KEY for native TLS (no proxy needed)\n' +
|
|
329
329
|
' ORIGIN=https://example.com (behind a TLS proxy)\n' +
|
|
330
|
-
' PROTOCOL_HEADER=x-forwarded-proto + HOST_HEADER=x-forwarded-host (flexible proxy)'
|
|
330
|
+
' PROTOCOL_HEADER=x-forwarded-proto + HOST_HEADER=x-forwarded-host (flexible proxy)\n' +
|
|
331
|
+
' See: https://svti.me/adapter-origin'
|
|
331
332
|
);
|
|
332
333
|
}
|
|
333
334
|
|
|
@@ -527,7 +528,8 @@ function maybeWarnTopicRegistry() {
|
|
|
527
528
|
' distinct topics. Each entry persists for the process lifetime ' +
|
|
528
529
|
'(required by the resume protocol). Reduce topic cardinality or ' +
|
|
529
530
|
'opt out of seq stamping for high-cardinality publishes via ' +
|
|
530
|
-
'{ seq: false }. Top recent publishers: ' + JSON.stringify(top)
|
|
531
|
+
'{ seq: false }. Top recent publishers: ' + JSON.stringify(top) +
|
|
532
|
+
'\n See: https://svti.me/topic-cardinality'
|
|
531
533
|
);
|
|
532
534
|
}
|
|
533
535
|
|
|
@@ -602,7 +604,8 @@ function warnLargeBatchFrame(size) {
|
|
|
602
604
|
lastBatchOversizeWarnAt = now;
|
|
603
605
|
console.warn('[ws] publishBatched frame is ' + size + ' bytes (>' + BATCH_FRAME_WARN_BYTES +
|
|
604
606
|
'). Large frames may trip per-message-deflate and surprise CPU budgets. ' +
|
|
605
|
-
'Consider chunking the batch into multiple publishBatched calls.'
|
|
607
|
+
'Consider chunking the batch into multiple publishBatched calls.' +
|
|
608
|
+
'\n See: https://svti.me/publish-batched');
|
|
606
609
|
}
|
|
607
610
|
|
|
608
611
|
/** @type {ReturnType<typeof setInterval> | null} */
|
|
@@ -706,7 +709,7 @@ function samplePressure(thresholds) {
|
|
|
706
709
|
}
|
|
707
710
|
lastPublishWarnAt.set(e.topic, now);
|
|
708
711
|
console.warn(
|
|
709
|
-
'[ws] runaway publisher topic=%s msg/s=%d bytes/s=%d',
|
|
712
|
+
'[ws] runaway publisher topic=%s msg/s=%d bytes/s=%d\n See: https://svti.me/pressure',
|
|
710
713
|
e.topic, Math.round(e.messagesPerSec), Math.round(e.bytesPerSec)
|
|
711
714
|
);
|
|
712
715
|
}
|
|
@@ -1047,7 +1050,8 @@ const platform = {
|
|
|
1047
1050
|
'[ws] platform.sendTo filter returned a Promise; treating as fail-closed.\n' +
|
|
1048
1051
|
' Async filters cannot be used here because sendTo iterates every active\n' +
|
|
1049
1052
|
' connection synchronously. Resolve the relevant fields into userData from\n' +
|
|
1050
|
-
' your `upgrade` hook so the filter can read them synchronously
|
|
1053
|
+
' your `upgrade` hook so the filter can read them synchronously.\n' +
|
|
1054
|
+
' See: https://svti.me/sendto-async'
|
|
1051
1055
|
);
|
|
1052
1056
|
}
|
|
1053
1057
|
continue;
|
|
@@ -2488,7 +2492,8 @@ if (WS_ENABLED) {
|
|
|
2488
2492
|
if (!knownWsExports.has(name)) {
|
|
2489
2493
|
console.warn(
|
|
2490
2494
|
`Warning: WebSocket handler exports unknown "${name}". ` +
|
|
2491
|
-
`Did you mean one of: ${[...knownWsExports].join(', ')}
|
|
2495
|
+
`Did you mean one of: ${[...knownWsExports].join(', ')}?\n` +
|
|
2496
|
+
' See: https://svti.me/ws-hooks'
|
|
2492
2497
|
);
|
|
2493
2498
|
}
|
|
2494
2499
|
}
|
|
@@ -2510,7 +2515,8 @@ if (WS_ENABLED) {
|
|
|
2510
2515
|
'Cloudflare Tunnel and some other edge proxies (WebSocket opens, then ' +
|
|
2511
2516
|
'closes with 1006 TCP FIN). Migrate to the `authenticate` hook to ' +
|
|
2512
2517
|
'refresh session cookies over a normal HTTP response: ' +
|
|
2513
|
-
'export function authenticate({ cookies }) { cookies.set(...); }'
|
|
2518
|
+
'export function authenticate({ cookies }) { cookies.set(...); }\n' +
|
|
2519
|
+
' See: https://svti.me/cf-cookies'
|
|
2514
2520
|
);
|
|
2515
2521
|
return;
|
|
2516
2522
|
}
|
|
@@ -2961,7 +2967,8 @@ if (WS_ENABLED) {
|
|
|
2961
2967
|
console.warn(
|
|
2962
2968
|
'[ws] userData key "' + key + '" may contain sensitive data. ' +
|
|
2963
2969
|
'userData is accessible to all server-side handlers via ws.getUserData(). ' +
|
|
2964
|
-
'Store sensitive data outside userData and reference it by a non-sensitive ID
|
|
2970
|
+
'Store sensitive data outside userData and reference it by a non-sensitive ID.\n' +
|
|
2971
|
+
' See: https://svti.me/userdata-sensitive'
|
|
2965
2972
|
);
|
|
2966
2973
|
}
|
|
2967
2974
|
}
|
package/files/index.js
CHANGED
|
@@ -45,7 +45,8 @@ if (is_primary) {
|
|
|
45
45
|
if (cluster_mode === 'reuseport' && process.platform !== 'linux') {
|
|
46
46
|
console.error(
|
|
47
47
|
`CLUSTER_MODE=reuseport requires Linux (SO_REUSEPORT is not reliable on ${process.platform}). ` +
|
|
48
|
-
'Remove CLUSTER_MODE to use the default acceptor mode
|
|
48
|
+
'Remove CLUSTER_MODE to use the default acceptor mode.\n' +
|
|
49
|
+
' See: https://svti.me/cluster-mode'
|
|
49
50
|
);
|
|
50
51
|
process.exit(1);
|
|
51
52
|
}
|
|
@@ -204,7 +205,7 @@ if (is_primary) {
|
|
|
204
205
|
}
|
|
205
206
|
restart_attempts++;
|
|
206
207
|
if (restart_attempts > RESTART_MAX_ATTEMPTS) {
|
|
207
|
-
console.error(`Worker restart limit reached (${RESTART_MAX_ATTEMPTS}). Exiting
|
|
208
|
+
console.error(`Worker restart limit reached (${RESTART_MAX_ATTEMPTS}). Exiting.\n See: https://svti.me/worker-restart-limit`);
|
|
208
209
|
process.exit(1);
|
|
209
210
|
}
|
|
210
211
|
restart_delay = restart_delay ? Math.min(restart_delay * 2, RESTART_DELAY_MAX) : 100;
|
package/index.d.ts
CHANGED
|
@@ -143,9 +143,9 @@ export interface WebSocketOptions {
|
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
145
|
* Max message size in bytes. Connections sending larger messages are closed.
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
146
|
+
* Default 1 MB is balanced for typical app payloads in a single frame; uWS
|
|
147
|
+
* itself defaults to 16 MB. Lower this for stricter caps (e.g. `16 * 1024`
|
|
148
|
+
* for 16 KB) when payload-size discipline matters.
|
|
149
149
|
* @default 1048576 (1 MB)
|
|
150
150
|
*/
|
|
151
151
|
maxPayloadLength?: number;
|
package/index.js
CHANGED
|
@@ -282,11 +282,12 @@ export default function (opts = {}) {
|
|
|
282
282
|
);
|
|
283
283
|
}
|
|
284
284
|
const wsOpts = {
|
|
285
|
-
// Default raised from 16 KB to 1 MB in next.19.
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
285
|
+
// Default raised from 16 KB to 1 MB in next.19. uWS itself
|
|
286
|
+
// defaults to 16 MB; 16 KB was excessively conservative and
|
|
287
|
+
// forced chunked-upload frameworks to use ~12 KB chunks
|
|
288
|
+
// (~9000 chunks for a 100 MB file). 1 MB handles typical app
|
|
289
|
+
// payloads in a single frame without per-app tuning. DoS
|
|
290
|
+
// exposure is bounded
|
|
290
291
|
// by `upgradeAdmission.maxConcurrent` (connection count)
|
|
291
292
|
// and `maxBackpressure` (per-conn outbound queue, also
|
|
292
293
|
// 1 MB), so per-frame cost stays predictable. Apps that
|
|
@@ -328,7 +329,7 @@ export default function (opts = {}) {
|
|
|
328
329
|
// Defends against an adjacent process injecting forged
|
|
329
330
|
// messages into the worker_threads relay (typically
|
|
330
331
|
// reachable only post-compromise, hence opt-in).
|
|
331
|
-
workerRelayHmacSecret: websocket?.workerRelayHmacSecret
|
|
332
|
+
workerRelayHmacSecret: websocket?.workerRelayHmacSecret,
|
|
332
333
|
// CSRF defense for the `/__ws/auth` POST endpoint. By
|
|
333
334
|
// default, the request must carry one of:
|
|
334
335
|
// - `x-requested-with: XMLHttpRequest`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-adapter-uws",
|
|
3
|
-
"version": "0.5.0-next.
|
|
3
|
+
"version": "0.5.0-next.23",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"tag": "next"
|
|
6
6
|
},
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"node": ">=22.0.0"
|
|
134
134
|
},
|
|
135
135
|
"peerDependencies": {
|
|
136
|
-
"@sveltejs/kit": "^2.
|
|
136
|
+
"@sveltejs/kit": "^2.0.0",
|
|
137
137
|
"svelte": "^4.0.0 || ^5.0.0",
|
|
138
138
|
"ws": "^8.0.0"
|
|
139
139
|
},
|
package/testing.js
CHANGED
|
@@ -232,7 +232,8 @@ export async function createTestServer(options = {}) {
|
|
|
232
232
|
console.error(
|
|
233
233
|
'[adapter-uws/testing] platform.sendTo filter returned a Promise; treating as fail-closed.\n' +
|
|
234
234
|
' Resolve filter inputs into userData from your `upgrade` hook so the\n' +
|
|
235
|
-
' filter can read them synchronously
|
|
235
|
+
' filter can read them synchronously.\n' +
|
|
236
|
+
' See: https://svti.me/sendto-async'
|
|
236
237
|
);
|
|
237
238
|
}
|
|
238
239
|
continue;
|
package/vite.js
CHANGED
|
@@ -240,7 +240,8 @@ export default function uws(options = {}) {
|
|
|
240
240
|
console.error(
|
|
241
241
|
'[adapter-uws] platform.sendTo filter returned a Promise; treating as fail-closed.\n' +
|
|
242
242
|
' Resolve filter inputs into userData from your `upgrade` hook so the\n' +
|
|
243
|
-
' filter can read them synchronously
|
|
243
|
+
' filter can read them synchronously.\n' +
|
|
244
|
+
' See: https://svti.me/sendto-async'
|
|
244
245
|
);
|
|
245
246
|
}
|
|
246
247
|
continue;
|
|
@@ -692,7 +693,7 @@ export default function uws(options = {}) {
|
|
|
692
693
|
applyHandlers(mod);
|
|
693
694
|
}).catch((err) => {
|
|
694
695
|
handlerFailed = true;
|
|
695
|
-
console.error(`[adapter-uws] Failed to load WebSocket handler '${options.handler}':`, err);
|
|
696
|
+
console.error(`[adapter-uws] Failed to load WebSocket handler '${options.handler}':`, err, '\n See: https://svti.me/ws-handler-load');
|
|
696
697
|
});
|
|
697
698
|
} else {
|
|
698
699
|
// Auto-discover src/hooks.ws.{js,ts,mjs}
|
|
@@ -928,10 +929,11 @@ export default function uws(options = {}) {
|
|
|
928
929
|
'[adapter-uws] upgradeResponse() attaches Set-Cookie to the 101 response. ' +
|
|
929
930
|
'This fails silently behind Cloudflare Tunnel and some other strict edge proxies ' +
|
|
930
931
|
'(WebSocket opens, then closes with 1006). Use the `authenticate` hook to ' +
|
|
931
|
-
'refresh session cookies over a normal HTTP response
|
|
932
|
+
'refresh session cookies over a normal HTTP response.\n' +
|
|
933
|
+
' See: https://svti.me/cf-cookies'
|
|
932
934
|
);
|
|
933
935
|
} else {
|
|
934
|
-
console.warn('[adapter-uws] upgrade() returned response headers. These are only applied in production (uWS); the ws library used in dev does not support custom 101 headers.');
|
|
936
|
+
console.warn('[adapter-uws] upgrade() returned response headers. These are only applied in production (uWS); the ws library used in dev does not support custom 101 headers.\n See: https://svti.me/dev-101-headers');
|
|
935
937
|
}
|
|
936
938
|
}
|
|
937
939
|
} else {
|