@playfast/reform-remote-web 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +16 -7
- package/dist/transport.js.map +1 -1
- package/package.json +1 -1
- package/src/transport-malformed-frame.test.ts +130 -0
- package/src/transport.ts +20 -8
package/dist/transport.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,QAAQ,EAAU,MAAM,QAAQ,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAG5F,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,QAAQ,EAAU,MAAM,QAAQ,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAG5F,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAA;AA0D5E,MAAM,WAAW,0CAA0C;IACzD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IAEnD,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,SAAS,CAAA;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;CACnD;AAED,MAAM,MAAM,+BAA+B,GAAG,0CAA0C,CAAA;AAExF,MAAM,MAAM,wBAAwB,GAAG,eAAe,CAAC,aAAa,EAAE,aAAa,CAAC,GAAG;IACrF,QAAQ,CAAC,MAAM,EAAE,MAAM,YAAY,CAAA;IACnC,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAA;CAC3B,CAAA;AAmBD,eAAO,MAAM,8BAA8B,GACzC,SAAS,+BAA+B,KACvC,wBA4IF,CAAA"}
|
package/dist/transport.js
CHANGED
|
@@ -34,7 +34,12 @@ const ServerMessageJson = Schema.parseJson(Schema.Union(Schema.Struct({
|
|
|
34
34
|
patches: Schema.Array(WirePatchSchema),
|
|
35
35
|
})));
|
|
36
36
|
const encodeInvokeMessage = Schema.encodeSync(InvokeMessageJson);
|
|
37
|
-
|
|
37
|
+
// A frame off the network is untrusted: version skew against a newer server, a
|
|
38
|
+
// truncated write or a hostile endpoint must cost that frame and nothing else. A
|
|
39
|
+
// throw here would escape the socket's own event dispatch — an uncaught exception
|
|
40
|
+
// under `ws`, a lost frame plus a global error in a browser — and the socket never
|
|
41
|
+
// closes, so the reconnect ladder is never armed. Same answer the servers give.
|
|
42
|
+
const decodeServerMessage = Schema.decodeUnknownOption(ServerMessageJson);
|
|
38
43
|
const resolveWebSocket = (injected) => {
|
|
39
44
|
const globalScope = globalThis;
|
|
40
45
|
const impl = injected ?? globalScope.WebSocket;
|
|
@@ -113,12 +118,16 @@ export const createWebSocketClientTransport = (options) => {
|
|
|
113
118
|
flush();
|
|
114
119
|
});
|
|
115
120
|
socket.addEventListener('message', (event) => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
Option.match(decodeServerMessage(event.data), {
|
|
122
|
+
onNone: () => Effect.runSync(Effect.logWarning('reform: dropped a server frame this client cannot parse')),
|
|
123
|
+
onSome: (message) => {
|
|
124
|
+
if (handlers.size === 0) {
|
|
125
|
+
state.inbox = [...state.inbox, message];
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
handlers.forEach((handler) => handler(message));
|
|
129
|
+
},
|
|
130
|
+
});
|
|
122
131
|
});
|
|
123
132
|
socket.addEventListener('close', () => handleDrop(socket));
|
|
124
133
|
socket.addEventListener('error', () => handleDrop(socket));
|
package/dist/transport.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEhE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAI9C,MAAM,qBAAqB,GAAG,GAAG,CAAA;AACjC,MAAM,oBAAoB,GAAG,IAAI,CAAA;AAEjC,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CACjC,MAAM,CAAC,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,KAAK,EAAE,MAAM,CAAC,OAAO;CACtB,CAAC,EACF,MAAM,CAAC,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM;CACtB,CAAC,CACH,CAAA;AACD,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC;IACnC,EAAE,EAAE,MAAM,CAAC,MAAM;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;IAClC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC;CACpC,CAAC,CAAA;AACF,MAAM,eAAe,GAAG,MAAM,CAAC,KAAK,CAClC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,EACvE,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CACrE,CAAA;AACD,MAAM,iBAAiB,GAAgD,MAAM,CAAC,SAAS,CACrF,MAAM,CAAC,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM;IACrB,OAAO,EAAE,MAAM,CAAC,OAAO;CACxB,CAAC,CACH,CAAA;AACD,MAAM,iBAAiB,GAAgD,MAAM,CAAC,SAAS,CACrF,MAAM,CAAC,KAAK,CACV,MAAM,CAAC,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC;CACnC,CAAC,EACF,MAAM,CAAC,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC;CACvC,CAAC,CACH,CACF,CAAA;AACD,MAAM,mBAAmB,GAAuC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAA;AACpG,MAAM,mBAAmB,GACvB,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { expect, test } from 'vitest'
|
|
2
|
+
import { Data, Option } from 'effect'
|
|
3
|
+
import type { ServerMessage } from '@playfast/reform-remote'
|
|
4
|
+
import { createWebSocketClientTransport } from './transport'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Round-3 adversarial audit — claim 4.
|
|
8
|
+
*
|
|
9
|
+
* `reform-remote-node` and `reform-remote-bun` swapped `Schema.decodeUnknownSync`
|
|
10
|
+
* for `Schema.decodeUnknownOption` on the invoke frame, with the reason: "the frame
|
|
11
|
+
* comes from the network... A throw here lands inside a `socket.on('message')`
|
|
12
|
+
* listener, where it escapes as a stream error and takes the shared serving path
|
|
13
|
+
* down with it."
|
|
14
|
+
*
|
|
15
|
+
* The identical construct is still in the client transport. `decodeServerMessage`
|
|
16
|
+
* is `Schema.decodeUnknownSync(ServerMessageJson)` (transport.ts:56) and it is
|
|
17
|
+
* called straight inside `socket.addEventListener('message', ...)` (transport.ts:184),
|
|
18
|
+
* so one frame this client cannot parse — version skew against a newer server, a
|
|
19
|
+
* truncated write, a hostile endpoint — throws out of the socket's own event
|
|
20
|
+
* dispatch. Under the `ws` package (which this transport documents for Node < 22)
|
|
21
|
+
* that is an uncaught exception and the host process exits; in a browser it is a
|
|
22
|
+
* global error and the frame is lost, and if the lost frame was the opening
|
|
23
|
+
* Snapshot the app renders null forever because the transport's own reconnect
|
|
24
|
+
* ladder is never armed (the socket did not close).
|
|
25
|
+
*
|
|
26
|
+
* Dropping the frame is the answer the servers already give.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
class NoSocket extends Data.TaggedError('NoSocket')<{ readonly message: string }> {}
|
|
30
|
+
|
|
31
|
+
type Listener = (event: unknown) => void
|
|
32
|
+
|
|
33
|
+
// Mirrors the fake in `transport.test.ts`: no real I/O, events fired by hand.
|
|
34
|
+
class ProbeSocket {
|
|
35
|
+
static readonly CONNECTING = 0
|
|
36
|
+
static readonly OPEN = 1
|
|
37
|
+
static readonly CLOSING = 2
|
|
38
|
+
static readonly CLOSED = 3
|
|
39
|
+
static instances: ProbeSocket[] = []
|
|
40
|
+
|
|
41
|
+
readyState: number = ProbeSocket.CONNECTING
|
|
42
|
+
readonly sent: string[] = []
|
|
43
|
+
readonly url: string
|
|
44
|
+
private readonly listeners = new Map<string, Set<Listener>>()
|
|
45
|
+
|
|
46
|
+
constructor(url: string) {
|
|
47
|
+
this.url = url
|
|
48
|
+
ProbeSocket.instances.push(this)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
addEventListener(type: string, cb: Listener): void {
|
|
52
|
+
const set = this.listeners.get(type) ?? new Set<Listener>()
|
|
53
|
+
set.add(cb)
|
|
54
|
+
this.listeners.set(type, set)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
removeEventListener(type: string, cb: Listener): void {
|
|
58
|
+
this.listeners.get(type)?.delete(cb)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
send(data: string): void {
|
|
62
|
+
this.sent.push(data)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
close(): void {
|
|
66
|
+
this.readyState = ProbeSocket.CLOSED
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
emitOpen(): void {
|
|
70
|
+
this.readyState = ProbeSocket.OPEN
|
|
71
|
+
this.fire('open', {})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Whatever the endpoint actually wrote, verbatim — the point of the test.
|
|
75
|
+
emitRaw(data: string): void {
|
|
76
|
+
this.fire('message', { data })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private fire(type: string, event: unknown): void {
|
|
80
|
+
this.listeners.get(type)?.forEach((cb) => cb(event))
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const makeProbe = () => {
|
|
85
|
+
ProbeSocket.instances = []
|
|
86
|
+
const transport = createWebSocketClientTransport({
|
|
87
|
+
url: 'ws://audit3',
|
|
88
|
+
// Same single cast as `transport.test.ts`: `typeof WebSocket` is a DOM
|
|
89
|
+
// constructor type the fake cannot structurally satisfy.
|
|
90
|
+
WebSocket: ProbeSocket as unknown as typeof WebSocket,
|
|
91
|
+
})
|
|
92
|
+
const socket = Option.getOrThrowWith(
|
|
93
|
+
Option.fromNullable(ProbeSocket.instances[0]),
|
|
94
|
+
() => new NoSocket({ message: 'transport did not construct a socket' }),
|
|
95
|
+
)
|
|
96
|
+
return { transport, socket }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const SNAPSHOT = JSON.stringify({ _tag: 'Snapshot', tree: [] })
|
|
100
|
+
|
|
101
|
+
test('a server frame this client cannot parse is dropped, not thrown at the socket', () => {
|
|
102
|
+
const { transport, socket } = makeProbe()
|
|
103
|
+
socket.emitOpen()
|
|
104
|
+
const received: ServerMessage[] = []
|
|
105
|
+
transport.onMessage((message) => received.push(message))
|
|
106
|
+
|
|
107
|
+
// Control — a well-formed frame is delivered.
|
|
108
|
+
socket.emitRaw(SNAPSHOT)
|
|
109
|
+
expect(received.length).toBe(1)
|
|
110
|
+
|
|
111
|
+
const thrown: string[] = []
|
|
112
|
+
const deliver = (label: string, data: string): void => {
|
|
113
|
+
try {
|
|
114
|
+
socket.emitRaw(data)
|
|
115
|
+
} catch {
|
|
116
|
+
thrown.push(label)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
deliver('not json', 'this-is-not-json')
|
|
120
|
+
deliver('unknown message tag', JSON.stringify({ _tag: 'Nope' }))
|
|
121
|
+
deliver('unknown patch tag', JSON.stringify({ _tag: 'Patches', patches: [{ _tag: 'Weird' }] }))
|
|
122
|
+
deliver('node missing fields', JSON.stringify({ _tag: 'Snapshot', tree: [{ id: 'x' }] }))
|
|
123
|
+
expect(thrown).toEqual([])
|
|
124
|
+
|
|
125
|
+
// …and the connection is still usable afterwards: an unparseable frame costs
|
|
126
|
+
// that frame, nothing else.
|
|
127
|
+
socket.emitRaw(SNAPSHOT)
|
|
128
|
+
expect(received.length).toBe(2)
|
|
129
|
+
transport.close()
|
|
130
|
+
})
|
package/src/transport.ts
CHANGED
|
@@ -52,8 +52,13 @@ const ServerMessageJson: Schema.Schema<ServerMessage, string, never> = Schema.pa
|
|
|
52
52
|
),
|
|
53
53
|
)
|
|
54
54
|
const encodeInvokeMessage: (message: InvokeMessage) => string = Schema.encodeSync(InvokeMessageJson)
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// A frame off the network is untrusted: version skew against a newer server, a
|
|
56
|
+
// truncated write or a hostile endpoint must cost that frame and nothing else. A
|
|
57
|
+
// throw here would escape the socket's own event dispatch — an uncaught exception
|
|
58
|
+
// under `ws`, a lost frame plus a global error in a browser — and the socket never
|
|
59
|
+
// closes, so the reconnect ladder is never armed. Same answer the servers give.
|
|
60
|
+
const decodeServerMessage: (input: unknown) => Option.Option<ServerMessage> =
|
|
61
|
+
Schema.decodeUnknownOption(ServerMessageJson)
|
|
57
62
|
|
|
58
63
|
export interface WebSocketClientTransportOptionsExternalApi {
|
|
59
64
|
readonly url: string
|
|
@@ -184,12 +189,19 @@ export const createWebSocketClientTransport = (
|
|
|
184
189
|
flush()
|
|
185
190
|
})
|
|
186
191
|
socket.addEventListener('message', (event: MessageEvent<unknown>) => {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
Option.match(decodeServerMessage(event.data), {
|
|
193
|
+
onNone: () =>
|
|
194
|
+
Effect.runSync(
|
|
195
|
+
Effect.logWarning('reform: dropped a server frame this client cannot parse'),
|
|
196
|
+
),
|
|
197
|
+
onSome: (message) => {
|
|
198
|
+
if (handlers.size === 0) {
|
|
199
|
+
state.inbox = [...state.inbox, message]
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
handlers.forEach((handler) => handler(message))
|
|
203
|
+
},
|
|
204
|
+
})
|
|
193
205
|
})
|
|
194
206
|
socket.addEventListener('close', () => handleDrop(socket))
|
|
195
207
|
socket.addEventListener('error', () => handleDrop(socket))
|