@playfast/reform-remote-node 1.2.0 → 1.2.2

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.
@@ -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;AAoDD,eAAO,MAAM,kBAAkB,GAC7B,SAAS,+BAA+B,KACvC,mBA4CF,CAAA"}
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
- const decodeInvokeMessage = Schema.decodeUnknownSync(InvokeMessageJson);
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
- const message = parseInvoke(frame);
55
- handlers.forEach((handler) => handler(message));
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":"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"}
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.0",
3
+ "version": "1.2.2",
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
- const decodeInvokeMessage: (input: unknown) => InvokeMessage =
69
- Schema.decodeUnknownSync(InvokeMessageJson)
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 => decodeInvokeMessage(frame.toString())
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
- const message = parseInvoke(frame)
96
- handlers.forEach((handler) => handler(message))
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)
@@ -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
+ })