@playfast/reform-remote-node 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/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +12 -5
- package/src/server.test.ts +47 -0
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAEvC,OAAO,EAAgB,eAAe,EAAE,MAAM,IAAI,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAQzD,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAA;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACnC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAEvC,OAAO,EAAgB,eAAe,EAAE,MAAM,IAAI,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAQzD,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAA;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACnC;AAyDD,eAAO,MAAM,kBAAkB,GAC7B,SAAS,+BAA+B,KACvC,mBA8CF,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,11 @@ const ServerMessageJson = Schema.parseJson(Schema.Union(Schema.Struct({
|
|
|
32
32
|
_tag: Schema.Literal('Patches'),
|
|
33
33
|
patches: Schema.Array(WirePatchSchema),
|
|
34
34
|
})));
|
|
35
|
-
|
|
35
|
+
// Optional, not `Sync`: the frame comes from the network. A throw here lands inside
|
|
36
|
+
// a `socket.on('message')` listener, where it escapes as a stream error and takes the
|
|
37
|
+
// shared serving path down with it — one unauthenticated client's malformed frame
|
|
38
|
+
// stops every other client being served.
|
|
39
|
+
const decodeInvokeMessage = Schema.decodeUnknownOption(InvokeMessageJson);
|
|
36
40
|
const encodeServerMessage = Schema.encodeSync(ServerMessageJson);
|
|
37
41
|
const parseInvoke = (frame) => decodeInvokeMessage(frame.toString());
|
|
38
42
|
export const serveNodeWebSocket = (options) => {
|
|
@@ -51,8 +55,10 @@ export const serveNodeWebSocket = (options) => {
|
|
|
51
55
|
},
|
|
52
56
|
};
|
|
53
57
|
socket.on('message', (frame) => {
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
Option.match(parseInvoke(frame), {
|
|
59
|
+
onNone: () => undefined,
|
|
60
|
+
onSome: (message) => handlers.forEach((handler) => handler(message)),
|
|
61
|
+
});
|
|
56
62
|
});
|
|
57
63
|
const handle = shared.addClient(transport);
|
|
58
64
|
socket.on('close', () => handle.remove());
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,EAAgB,eAAe,EAAE,MAAM,IAAI,CAAA;AAElD,OAAO,EAIL,WAAW,GACZ,MAAM,yBAAyB,CAAA;AAchC,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,GACvB,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playfast/reform-remote-node",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Node WebSocket server adapter for reform-remote — serves a reform scene's UI over the `ws` package, one isolated runtime per connection.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect",
|
package/src/index.ts
CHANGED
|
@@ -65,11 +65,16 @@ const ServerMessageJson: Schema.Schema<ServerMessage, string, never> = Schema.pa
|
|
|
65
65
|
}),
|
|
66
66
|
),
|
|
67
67
|
)
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
// Optional, not `Sync`: the frame comes from the network. A throw here lands inside
|
|
69
|
+
// a `socket.on('message')` listener, where it escapes as a stream error and takes the
|
|
70
|
+
// shared serving path down with it — one unauthenticated client's malformed frame
|
|
71
|
+
// stops every other client being served.
|
|
72
|
+
const decodeInvokeMessage: (input: unknown) => Option.Option<InvokeMessage> =
|
|
73
|
+
Schema.decodeUnknownOption(InvokeMessageJson)
|
|
70
74
|
const encodeServerMessage: (message: ServerMessage) => string = Schema.encodeSync(ServerMessageJson)
|
|
71
75
|
|
|
72
|
-
const parseInvoke = (frame: RawData): InvokeMessage =>
|
|
76
|
+
const parseInvoke = (frame: RawData): Option.Option<InvokeMessage> =>
|
|
77
|
+
decodeInvokeMessage(frame.toString())
|
|
73
78
|
|
|
74
79
|
export const serveNodeWebSocket = (
|
|
75
80
|
options: NodeWebSocketOptionsExternalApi,
|
|
@@ -92,8 +97,10 @@ export const serveNodeWebSocket = (
|
|
|
92
97
|
},
|
|
93
98
|
}
|
|
94
99
|
socket.on('message', (frame) => {
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
Option.match(parseInvoke(frame), {
|
|
101
|
+
onNone: () => undefined,
|
|
102
|
+
onSome: (message) => handlers.forEach((handler) => handler(message)),
|
|
103
|
+
})
|
|
97
104
|
})
|
|
98
105
|
|
|
99
106
|
const handle = shared.addClient(transport)
|
package/src/server.test.ts
CHANGED
|
@@ -150,3 +150,50 @@ test('every connection shares ONE runtime — a bump on one socket reaches the o
|
|
|
150
150
|
b.close()
|
|
151
151
|
}
|
|
152
152
|
})
|
|
153
|
+
|
|
154
|
+
test('a malformed frame from one client does not take the server down for the others', async () => {
|
|
155
|
+
const host = serveNodeWebSocket({ scene: counterScene() })
|
|
156
|
+
open.add(host)
|
|
157
|
+
const port = await listening(host)
|
|
158
|
+
|
|
159
|
+
const crashes: Array<unknown> = []
|
|
160
|
+
const onUncaught = (error: unknown): void => {
|
|
161
|
+
crashes.push(error)
|
|
162
|
+
}
|
|
163
|
+
process.on('uncaughtException', onUncaught)
|
|
164
|
+
|
|
165
|
+
const attacker = new WebSocket(`ws://localhost:${port}`)
|
|
166
|
+
const victim = new WebSocket(`ws://localhost:${port}`)
|
|
167
|
+
// Attach before awaiting: the shared server snapshots immediately.
|
|
168
|
+
const attackerSnapshot = nextMessage(attacker, (message) => message._tag === 'Snapshot')
|
|
169
|
+
const victimSnapshot = nextMessage(victim, (message) => message._tag === 'Snapshot')
|
|
170
|
+
const victimPatches = nextMessage(victim, (message) => message._tag === 'Patches')
|
|
171
|
+
const victimClosed = { closed: false }
|
|
172
|
+
victim.addEventListener('close', () => {
|
|
173
|
+
victimClosed.closed = true
|
|
174
|
+
})
|
|
175
|
+
try {
|
|
176
|
+
const snapshot = await victimSnapshot
|
|
177
|
+
await attackerSnapshot
|
|
178
|
+
const tree = snapshot._tag === 'Snapshot' ? snapshot.tree : []
|
|
179
|
+
|
|
180
|
+
// Frames straight off the network: not JSON, JSON of the wrong shape, and an
|
|
181
|
+
// unknown tag. Decoding any of them throws, and a throw inside the socket's
|
|
182
|
+
// `message` listener is not this client's problem alone.
|
|
183
|
+
attacker.send('not json at all')
|
|
184
|
+
attacker.send(JSON.stringify({ _tag: 'Invoke', handle: 42 }))
|
|
185
|
+
attacker.send(JSON.stringify({ _tag: 'Nonsense' }))
|
|
186
|
+
|
|
187
|
+
// Control: the victim's session is untouched and still drives the shared runtime.
|
|
188
|
+
victim.send(JSON.stringify({ _tag: 'Invoke', handle: '0:bump', payload: { by: 3 } }))
|
|
189
|
+
const frame = await victimPatches
|
|
190
|
+
const patches = frame._tag === 'Patches' ? frame.patches : []
|
|
191
|
+
expect(countOf(Wire.apply(tree, patches)[0]!)).toBe(3)
|
|
192
|
+
expect(victimClosed.closed).toBe(false)
|
|
193
|
+
expect(crashes).toEqual([])
|
|
194
|
+
} finally {
|
|
195
|
+
process.off('uncaughtException', onUncaught)
|
|
196
|
+
attacker.close()
|
|
197
|
+
victim.close()
|
|
198
|
+
}
|
|
199
|
+
})
|