@livestore/webmesh 0.3.0-dev.8 → 0.3.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.
Files changed (69) hide show
  1. package/README.md +43 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/channel/direct-channel-internal.d.ts +26 -0
  4. package/dist/channel/direct-channel-internal.d.ts.map +1 -0
  5. package/dist/channel/direct-channel-internal.js +217 -0
  6. package/dist/channel/direct-channel-internal.js.map +1 -0
  7. package/dist/channel/direct-channel.d.ts +22 -0
  8. package/dist/channel/direct-channel.d.ts.map +1 -0
  9. package/dist/channel/direct-channel.js +153 -0
  10. package/dist/channel/direct-channel.js.map +1 -0
  11. package/dist/channel/proxy-channel.d.ts +3 -3
  12. package/dist/channel/proxy-channel.d.ts.map +1 -1
  13. package/dist/channel/proxy-channel.js +119 -37
  14. package/dist/channel/proxy-channel.js.map +1 -1
  15. package/dist/common.d.ts +47 -19
  16. package/dist/common.d.ts.map +1 -1
  17. package/dist/common.js +13 -5
  18. package/dist/common.js.map +1 -1
  19. package/dist/mesh-schema.d.ts +79 -13
  20. package/dist/mesh-schema.d.ts.map +1 -1
  21. package/dist/mesh-schema.js +59 -10
  22. package/dist/mesh-schema.js.map +1 -1
  23. package/dist/mod.d.ts +2 -2
  24. package/dist/mod.d.ts.map +1 -1
  25. package/dist/mod.js +2 -2
  26. package/dist/mod.js.map +1 -1
  27. package/dist/node.d.ts +56 -23
  28. package/dist/node.d.ts.map +1 -1
  29. package/dist/node.js +323 -115
  30. package/dist/node.js.map +1 -1
  31. package/dist/node.test.d.ts +1 -1
  32. package/dist/node.test.d.ts.map +1 -1
  33. package/dist/node.test.js +489 -157
  34. package/dist/node.test.js.map +1 -1
  35. package/dist/utils.d.ts +4 -4
  36. package/dist/utils.d.ts.map +1 -1
  37. package/dist/utils.js +7 -1
  38. package/dist/utils.js.map +1 -1
  39. package/dist/websocket-edge.d.ts +56 -0
  40. package/dist/websocket-edge.d.ts.map +1 -0
  41. package/dist/websocket-edge.js +93 -0
  42. package/dist/websocket-edge.js.map +1 -0
  43. package/package.json +9 -6
  44. package/src/channel/direct-channel-internal.ts +356 -0
  45. package/src/channel/direct-channel.ts +234 -0
  46. package/src/channel/proxy-channel.ts +344 -234
  47. package/src/common.ts +24 -17
  48. package/src/mesh-schema.ts +73 -20
  49. package/src/mod.ts +2 -2
  50. package/src/node.test.ts +723 -190
  51. package/src/node.ts +482 -156
  52. package/src/utils.ts +13 -2
  53. package/src/websocket-edge.ts +191 -0
  54. package/dist/channel/message-channel.d.ts +0 -20
  55. package/dist/channel/message-channel.d.ts.map +0 -1
  56. package/dist/channel/message-channel.js +0 -183
  57. package/dist/channel/message-channel.js.map +0 -1
  58. package/dist/websocket-connection.d.ts +0 -51
  59. package/dist/websocket-connection.d.ts.map +0 -1
  60. package/dist/websocket-connection.js +0 -74
  61. package/dist/websocket-connection.js.map +0 -1
  62. package/dist/websocket-server.d.ts +0 -7
  63. package/dist/websocket-server.d.ts.map +0 -1
  64. package/dist/websocket-server.js +0 -24
  65. package/dist/websocket-server.js.map +0 -1
  66. package/src/channel/message-channel.ts +0 -354
  67. package/src/websocket-connection.ts +0 -158
  68. package/src/websocket-server.ts +0 -40
  69. package/tsconfig.json +0 -11
@@ -1,354 +0,0 @@
1
- import { casesHandled, shouldNeverHappen } from '@livestore/utils'
2
- import type { PubSub, Schema, Scope } from '@livestore/utils/effect'
3
- import {
4
- Deferred,
5
- Effect,
6
- Either,
7
- Fiber,
8
- FiberHandle,
9
- Queue,
10
- Schedule,
11
- Stream,
12
- SubscriptionRef,
13
- WebChannel,
14
- } from '@livestore/utils/effect'
15
-
16
- import { type ChannelName, type MeshNodeName, type MessageQueueItem, packetAsOtelAttributes } from '../common.js'
17
- import * as MeshSchema from '../mesh-schema.js'
18
-
19
- interface MakeMessageChannelArgs {
20
- nodeName: MeshNodeName
21
- queue: Queue.Queue<MessageQueueItem>
22
- newConnectionAvailablePubSub: PubSub.PubSub<MeshNodeName>
23
- channelName: ChannelName
24
- target: MeshNodeName
25
- sendPacket: (packet: typeof MeshSchema.MessageChannelPacket.Type) => Effect.Effect<void>
26
- checkTransferableConnections: (
27
- packet: typeof MeshSchema.MessageChannelPacket.Type,
28
- ) => typeof MeshSchema.MessageChannelResponseNoTransferables.Type | undefined
29
- schema: {
30
- send: Schema.Schema<any, any>
31
- listen: Schema.Schema<any, any>
32
- }
33
- }
34
-
35
- export const makeMessageChannel = ({
36
- nodeName,
37
- queue,
38
- newConnectionAvailablePubSub,
39
- target,
40
- checkTransferableConnections,
41
- channelName,
42
- schema,
43
- sendPacket,
44
- }: MakeMessageChannelArgs) =>
45
- Effect.gen(function* () {
46
- const reconnectTriggerQueue = yield* Queue.unbounded<void>()
47
- const reconnect = Queue.offer(reconnectTriggerQueue, void 0)
48
-
49
- type ChannelState =
50
- | { _tag: 'Established' }
51
- | {
52
- _tag: 'Initial'
53
- deferred: Deferred.Deferred<MessagePort, typeof MeshSchema.MessageChannelResponseNoTransferables.Type>
54
- }
55
- | {
56
- _tag: 'RequestSent'
57
- deferred: Deferred.Deferred<MessagePort, typeof MeshSchema.MessageChannelResponseNoTransferables.Type>
58
- }
59
- | {
60
- _tag: 'ResponseSent'
61
- // Set in the case where this side already received a request, and created a port.
62
- // Might be used or discarded based on tie-breaking logic below.
63
- locallyCreatedPort: MessagePort
64
- deferred: Deferred.Deferred<MessagePort, typeof MeshSchema.MessageChannelResponseNoTransferables.Type>
65
- }
66
-
67
- const makeInitialState = Effect.gen(function* () {
68
- const deferred = yield* Deferred.make<MessagePort, typeof MeshSchema.MessageChannelResponseNoTransferables.Type>()
69
- return { _tag: 'Initial', deferred } as ChannelState
70
- })
71
-
72
- const channelStateRef = { current: yield* makeInitialState }
73
-
74
- const makeMessageChannelInternal: Effect.Effect<
75
- WebChannel.WebChannel<any, any, never>,
76
- never,
77
- Scope.Scope
78
- > = Effect.gen(function* () {
79
- const processMessagePacket = ({ packet, respondToSender }: MessageQueueItem) =>
80
- Effect.gen(function* () {
81
- const channelState = channelStateRef.current
82
-
83
- // yield* Effect.log(`${nodeName}:processing packet ${packet._tag}, channel state: ${channelState._tag}`)
84
-
85
- switch (packet._tag) {
86
- // Since there can be concurrent MessageChannel responses from both sides,
87
- // we need to decide which side's port we want to use and which side's port we want to ignore.
88
- // This is only relevant in the case where both sides already sent their responses.
89
- // In this case we're using the target name as a "tie breaker" to decide which side's port to use.
90
- // We do this by sorting the target names lexicographically and use the first one as the winner.
91
- case 'MessageChannelResponseSuccess': {
92
- if (channelState._tag === 'Initial') {
93
- return shouldNeverHappen(
94
- `Expected to find message channel request from ${target}, but was in ${channelState._tag} state`,
95
- )
96
- }
97
-
98
- if (channelState._tag === 'Established') {
99
- const deferred = yield* Deferred.make<
100
- MessagePort,
101
- typeof MeshSchema.MessageChannelResponseNoTransferables.Type
102
- >()
103
-
104
- channelStateRef.current = { _tag: 'RequestSent', deferred }
105
-
106
- yield* reconnect
107
-
108
- return
109
- }
110
-
111
- const thisSideAlsoResponded = channelState._tag === 'ResponseSent'
112
-
113
- const usePortFromThisSide = thisSideAlsoResponded && nodeName > target
114
- yield* Effect.annotateCurrentSpan({ usePortFromThisSide })
115
-
116
- const winnerPort = usePortFromThisSide ? channelState.locallyCreatedPort : packet.port
117
- yield* Deferred.succeed(channelState.deferred, winnerPort)
118
-
119
- return
120
- }
121
- case 'MessageChannelResponseNoTransferables': {
122
- if (channelState._tag === 'Established') return
123
-
124
- yield* Deferred.fail(channelState!.deferred, packet)
125
- channelStateRef.current = yield* makeInitialState
126
- return
127
- }
128
- case 'MessageChannelRequest': {
129
- const mc = new MessageChannel()
130
-
131
- const shouldReconnect = channelState._tag === 'Established'
132
-
133
- const deferred =
134
- channelState._tag === 'Established'
135
- ? yield* Deferred.make<MessagePort, typeof MeshSchema.MessageChannelResponseNoTransferables.Type>()
136
- : channelState.deferred
137
-
138
- channelStateRef.current = { _tag: 'ResponseSent', locallyCreatedPort: mc.port1, deferred }
139
-
140
- yield* respondToSender(
141
- MeshSchema.MessageChannelResponseSuccess.make({
142
- reqId: packet.id,
143
- target,
144
- source: nodeName,
145
- channelName: packet.channelName,
146
- hops: [],
147
- remainingHops: packet.hops,
148
- port: mc.port2,
149
- }),
150
- )
151
-
152
- // If there's an established channel, we use the new request as a signal
153
- // to drop the old channel and use the new one
154
- if (shouldReconnect) {
155
- yield* reconnect
156
- }
157
-
158
- break
159
- }
160
- default: {
161
- return casesHandled(packet)
162
- }
163
- }
164
- }).pipe(
165
- Effect.withSpan(`handleMessagePacket:${packet._tag}:${packet.source}→${packet.target}`, {
166
- attributes: packetAsOtelAttributes(packet),
167
- }),
168
- )
169
-
170
- yield* Stream.fromQueue(queue).pipe(
171
- Stream.tap(processMessagePacket),
172
- Stream.runDrain,
173
- Effect.tapCauseLogPretty,
174
- Effect.forkScoped,
175
- )
176
-
177
- const channelFromPort = (port: MessagePort) =>
178
- Effect.gen(function* () {
179
- channelStateRef.current = { _tag: 'Established' }
180
-
181
- // NOTE to support re-connects we need to ack each message
182
- const channel = yield* WebChannel.messagePortChannelWithAck({ port, schema })
183
-
184
- return channel
185
- })
186
-
187
- const channelState = channelStateRef.current
188
-
189
- if (channelState._tag === 'Initial' || channelState._tag === 'RequestSent') {
190
- // Important to make a new deferred here as the old one might have been used already
191
- // TODO model this better
192
- const deferred =
193
- channelState._tag === 'RequestSent'
194
- ? yield* Deferred.make<MessagePort, typeof MeshSchema.MessageChannelResponseNoTransferables.Type>()
195
- : channelState.deferred
196
-
197
- channelStateRef.current = { _tag: 'RequestSent', deferred }
198
-
199
- const connectionRequest = Effect.gen(function* () {
200
- const packet = MeshSchema.MessageChannelRequest.make({ source: nodeName, target, channelName, hops: [] })
201
-
202
- const noTransferableResponse = checkTransferableConnections(packet)
203
- if (noTransferableResponse !== undefined) {
204
- yield* Effect.spanEvent(`No transferable connections found for ${packet.source}→${packet.target}`)
205
- yield* Deferred.fail(deferred, noTransferableResponse)
206
- return
207
- }
208
-
209
- yield* sendPacket(packet)
210
- })
211
-
212
- yield* connectionRequest
213
-
214
- const retryOnNewConnectionFiber = yield* Stream.fromPubSub(newConnectionAvailablePubSub).pipe(
215
- Stream.tap(() => Effect.spanEvent(`RetryOnNewConnection`)),
216
- Stream.tap(() => connectionRequest),
217
- Stream.runDrain,
218
- Effect.forkScoped,
219
- )
220
-
221
- const portResult = yield* deferred.pipe(Effect.either)
222
- yield* Fiber.interrupt(retryOnNewConnectionFiber)
223
-
224
- if (portResult._tag === 'Right') {
225
- return yield* channelFromPort(portResult.right)
226
- } else {
227
- // We'll keep retrying with a new connection
228
- yield* Stream.fromPubSub(newConnectionAvailablePubSub).pipe(Stream.take(1), Stream.runDrain)
229
-
230
- yield* reconnect
231
-
232
- return yield* Effect.interrupt
233
- }
234
- } else {
235
- // In this case we've already received a request from the other side (before we had a chance to send our request),
236
- // so we already created a MessageChannel,responded with one port
237
- // and are now using the other port to create the channel.
238
- if (channelState._tag === 'ResponseSent') {
239
- return yield* channelFromPort(channelState.locallyCreatedPort)
240
- } else {
241
- return shouldNeverHappen(
242
- `Expected pending message channel to be in ResponseSent state, but was in ${channelState._tag} state`,
243
- )
244
- }
245
- }
246
- })
247
-
248
- const internalChannelSref = yield* SubscriptionRef.make<WebChannel.WebChannel<any, any> | false>(false)
249
-
250
- const listenQueue = yield* Queue.unbounded<any>()
251
-
252
- let connectCounter = 0
253
-
254
- const connect = Effect.gen(function* () {
255
- const connectCount = ++connectCounter
256
- yield* Effect.spanEvent(`Connecting#${connectCount}`)
257
-
258
- yield* SubscriptionRef.set(internalChannelSref, false)
259
-
260
- yield* Effect.addFinalizer(() => Effect.spanEvent(`Disconnected#${connectCount}`))
261
-
262
- const internalChannel = yield* makeMessageChannelInternal
263
-
264
- yield* SubscriptionRef.set(internalChannelSref, internalChannel)
265
-
266
- yield* Effect.spanEvent(`Connected#${connectCount}`)
267
-
268
- yield* internalChannel.listen.pipe(
269
- Stream.flatten(),
270
- Stream.tap((msg) => Queue.offer(listenQueue, msg)),
271
- Stream.runDrain,
272
- Effect.tapCauseLogPretty,
273
- Effect.forkScoped,
274
- )
275
-
276
- yield* Effect.never
277
- }).pipe(Effect.scoped)
278
-
279
- const fiberHandle = yield* FiberHandle.make<void, never>()
280
-
281
- const runConnect = Effect.gen(function* () {
282
- // Cleanly shutdown the previous connection first
283
- // Otherwise the old and new connection will "overlap"
284
- yield* FiberHandle.clear(fiberHandle)
285
- yield* FiberHandle.run(fiberHandle, connect)
286
- })
287
-
288
- yield* runConnect
289
-
290
- // Then listen for reconnects
291
- yield* Stream.fromQueue(reconnectTriggerQueue).pipe(
292
- Stream.tap(() => runConnect),
293
- Stream.runDrain,
294
- Effect.tapCauseLogPretty,
295
- Effect.forkScoped,
296
- )
297
-
298
- // Wait for the initial connection to be established or for an error to occur
299
- yield* Effect.raceFirst(
300
- SubscriptionRef.waitUntil(internalChannelSref, (channel) => channel !== false),
301
- FiberHandle.join(fiberHandle),
302
- )
303
-
304
- const parentSpan = yield* Effect.currentSpan.pipe(Effect.orDie)
305
-
306
- const send = (message: any) =>
307
- Effect.gen(function* () {
308
- const sendFiberHandle = yield* FiberHandle.make<void, never>()
309
-
310
- const sentDeferred = yield* Deferred.make<void>()
311
-
312
- const trySend = Effect.gen(function* () {
313
- const channel = (yield* SubscriptionRef.waitUntil(
314
- internalChannelSref,
315
- (channel) => channel !== false,
316
- )) as WebChannel.WebChannel<any, any>
317
-
318
- const innerSend = Effect.gen(function* () {
319
- yield* channel.send(message)
320
- yield* Deferred.succeed(sentDeferred, void 0)
321
- })
322
-
323
- yield* innerSend.pipe(Effect.timeout(100), Effect.retry(Schedule.exponential(100)), Effect.orDie)
324
- }).pipe(Effect.tapErrorCause(Effect.logError))
325
-
326
- const rerunOnNewChannelFiber = yield* internalChannelSref.changes.pipe(
327
- Stream.filter((_) => _ === false),
328
- Stream.tap(() => FiberHandle.run(sendFiberHandle, trySend)),
329
- Stream.runDrain,
330
- Effect.fork,
331
- )
332
-
333
- yield* FiberHandle.run(sendFiberHandle, trySend)
334
-
335
- yield* sentDeferred
336
-
337
- yield* Fiber.interrupt(rerunOnNewChannelFiber)
338
- }).pipe(Effect.scoped, Effect.withParentSpan(parentSpan))
339
-
340
- const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Either.right))
341
-
342
- const closedDeferred = yield* Deferred.make<void>()
343
-
344
- const webChannel = {
345
- [WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,
346
- send,
347
- listen,
348
- closedDeferred,
349
- supportsTransferables: true,
350
- schema,
351
- } satisfies WebChannel.WebChannel<any, any>
352
-
353
- return webChannel as WebChannel.WebChannel<any, any>
354
- }).pipe(Effect.withSpanScoped('makeMessageChannel'))
@@ -1,158 +0,0 @@
1
- import type { Scope } from '@livestore/utils/effect'
2
- import {
3
- Deferred,
4
- Effect,
5
- Either,
6
- FiberHandle,
7
- Queue,
8
- Schedule,
9
- Schema,
10
- Stream,
11
- WebChannel,
12
- WebSocket,
13
- } from '@livestore/utils/effect'
14
- import type NodeWebSocket from 'ws'
15
-
16
- import * as MeshSchema from './mesh-schema.js'
17
- import type { MeshNode } from './node.js'
18
-
19
- export class WSConnectionInit extends Schema.TaggedStruct('WSConnectionInit', {
20
- from: Schema.String,
21
- }) {}
22
-
23
- export class WSConnectionPayload extends Schema.TaggedStruct('WSConnectionPayload', {
24
- from: Schema.String,
25
- payload: Schema.Any,
26
- }) {}
27
-
28
- export class WSConnectionMessage extends Schema.Union(WSConnectionInit, WSConnectionPayload) {}
29
-
30
- export const MessageMsgPack = Schema.MsgPack(WSConnectionMessage)
31
-
32
- export type SocketType =
33
- | {
34
- _tag: 'leaf'
35
- from: string
36
- }
37
- | {
38
- _tag: 'relay'
39
- }
40
-
41
- export const connectViaWebSocket = ({
42
- node,
43
- url,
44
- reconnect = Schedule.exponential(100),
45
- }: {
46
- node: MeshNode
47
- url: string
48
- reconnect?: Schedule.Schedule<unknown> | false
49
- }): Effect.Effect<void, never, Scope.Scope> =>
50
- Effect.gen(function* () {
51
- const fiberHandle = yield* FiberHandle.make()
52
-
53
- const connect = Effect.gen(function* () {
54
- const socket = yield* WebSocket.makeWebSocket({ url, reconnect })
55
-
56
- // NOTE we want to use `runFork` here so this Effect is not part of the fiber that will be interrupted
57
- socket.addEventListener('close', () => FiberHandle.run(fiberHandle, connect).pipe(Effect.runFork))
58
-
59
- const connection = yield* makeWebSocketConnection(socket, { _tag: 'leaf', from: node.nodeName })
60
-
61
- yield* node.addConnection({ target: 'ws', connectionChannel: connection.webChannel, replaceIfExists: true })
62
-
63
- yield* Effect.never
64
- }).pipe(Effect.scoped, Effect.withSpan('@livestore/webmesh:websocket-connection:connect'))
65
-
66
- yield* FiberHandle.run(fiberHandle, connect)
67
- })
68
-
69
- export const makeWebSocketConnection = (
70
- socket: globalThis.WebSocket | NodeWebSocket.WebSocket,
71
- socketType: SocketType,
72
- ): Effect.Effect<
73
- {
74
- webChannel: WebChannel.WebChannel<typeof MeshSchema.Packet.Type, typeof MeshSchema.Packet.Type>
75
- from: string
76
- },
77
- never,
78
- Scope.Scope
79
- > =>
80
- Effect.gen(function* () {
81
- socket.binaryType = 'arraybuffer'
82
-
83
- const fromDeferred = yield* Deferred.make<string>()
84
-
85
- yield* Stream.fromEventListener<MessageEvent>(socket as any, 'message').pipe(
86
- Stream.map((msg) => Schema.decodeUnknownEither(MessageMsgPack)(new Uint8Array(msg.data))),
87
- Stream.flatten(),
88
- Stream.tap((msg) =>
89
- Effect.gen(function* () {
90
- if (msg._tag === 'WSConnectionInit') {
91
- yield* Deferred.succeed(fromDeferred, msg.from)
92
- } else {
93
- const decodedPayload = yield* Schema.decode(MeshSchema.Packet)(msg.payload)
94
- yield* Queue.offer(listenQueue, decodedPayload)
95
- }
96
- }),
97
- ),
98
- Stream.runDrain,
99
- Effect.tapCauseLogPretty,
100
- Effect.forkScoped,
101
- )
102
-
103
- const listenQueue = yield* Queue.unbounded<typeof MeshSchema.Packet.Type>().pipe(
104
- Effect.acquireRelease(Queue.shutdown),
105
- )
106
-
107
- const initHandshake = (from: string) =>
108
- socket.send(Schema.encodeSync(MessageMsgPack)({ _tag: 'WSConnectionInit', from }))
109
-
110
- if (socketType._tag === 'leaf') {
111
- initHandshake(socketType.from)
112
- }
113
-
114
- const deferredResult = yield* fromDeferred
115
- const from = socketType._tag === 'leaf' ? socketType.from : deferredResult
116
-
117
- if (socketType._tag === 'relay') {
118
- initHandshake(from)
119
- }
120
-
121
- const isConnectedLatch = yield* Effect.makeLatch(true)
122
-
123
- const closedDeferred = yield* Deferred.make<void>()
124
-
125
- yield* Effect.eventListener<any>(
126
- socket,
127
- 'close',
128
- () =>
129
- Effect.gen(function* () {
130
- yield* isConnectedLatch.close
131
- yield* Deferred.succeed(closedDeferred, undefined)
132
- }),
133
- { once: true },
134
- )
135
-
136
- const send = (message: typeof MeshSchema.Packet.Type) =>
137
- Effect.gen(function* () {
138
- yield* isConnectedLatch.await
139
- const payload = yield* Schema.encode(MeshSchema.Packet)(message)
140
- socket.send(Schema.encodeSync(MessageMsgPack)({ _tag: 'WSConnectionPayload', payload, from }))
141
- })
142
-
143
- const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Either.right))
144
-
145
- const webChannel = {
146
- [WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,
147
- send,
148
- listen,
149
- closedDeferred,
150
- schema: { listen: MeshSchema.Packet, send: MeshSchema.Packet },
151
- supportsTransferables: false,
152
- } satisfies WebChannel.WebChannel<typeof MeshSchema.Packet.Type, typeof MeshSchema.Packet.Type>
153
-
154
- return {
155
- webChannel: webChannel as WebChannel.WebChannel<typeof MeshSchema.Packet.Type, typeof MeshSchema.Packet.Type>,
156
- from,
157
- }
158
- })
@@ -1,40 +0,0 @@
1
- import type { Scope } from '@livestore/utils/effect'
2
- import { Effect } from '@livestore/utils/effect'
3
- import * as WebSocket from 'ws'
4
-
5
- import { makeMeshNode } from './node.js'
6
- import { makeWebSocketConnection } from './websocket-connection.js'
7
-
8
- export const makeWebSocketServer = ({
9
- relayNodeName,
10
- }: {
11
- relayNodeName: string
12
- }): Effect.Effect<WebSocket.WebSocketServer, never, Scope.Scope> =>
13
- Effect.gen(function* () {
14
- const server = new WebSocket.WebSocketServer({ noServer: true })
15
-
16
- const node = yield* makeMeshNode(relayNodeName)
17
-
18
- const runtime = yield* Effect.runtime<never>()
19
-
20
- // TODO handle node disconnects (i.e. remove respective connection)
21
- server.on('connection', (socket) => {
22
- Effect.gen(function* () {
23
- const { webChannel, from } = yield* makeWebSocketConnection(socket, { _tag: 'relay' })
24
-
25
- yield* node.addConnection({ target: from, connectionChannel: webChannel, replaceIfExists: true })
26
- yield* Effect.log(`WS Relay ${relayNodeName}: added connection from '${from}'`)
27
-
28
- socket.addEventListener('close', () =>
29
- Effect.gen(function* () {
30
- yield* node.removeConnection(from)
31
- yield* Effect.log(`WS Relay ${relayNodeName}: removed connection from '${from}'`)
32
- }).pipe(Effect.provide(runtime), Effect.runFork),
33
- )
34
-
35
- yield* Effect.never
36
- }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.provide(runtime), Effect.runFork)
37
- })
38
-
39
- return server
40
- })
package/tsconfig.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "resolveJsonModule": true,
7
- "tsBuildInfoFile": "./dist/.tsbuildinfo"
8
- },
9
- "include": ["./src"],
10
- "references": [{ "path": "../common" }, { "path": "../utils" }]
11
- }