@neurodevs/node-lsl 14.1.5 → 16.0.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 (44) hide show
  1. package/build/__tests__/AbstractPackageTest.d.ts +1 -0
  2. package/build/__tests__/AbstractPackageTest.js +7 -0
  3. package/build/__tests__/AbstractPackageTest.js.map +1 -1
  4. package/build/__tests__/impl/LslStreamInfo.test.d.ts +1 -0
  5. package/build/__tests__/impl/LslStreamInfo.test.js +33 -6
  6. package/build/__tests__/impl/LslStreamInfo.test.js.map +1 -1
  7. package/build/__tests__/impl/LslWebSocketBridge.test.d.ts +10 -2
  8. package/build/__tests__/impl/LslWebSocketBridge.test.js +78 -12
  9. package/build/__tests__/impl/LslWebSocketBridge.test.js.map +1 -1
  10. package/build/impl/LslStreamInfo.d.ts +17 -8
  11. package/build/impl/LslStreamInfo.js +12 -6
  12. package/build/impl/LslStreamInfo.js.map +1 -1
  13. package/build/impl/LslStreamInlet.js +2 -1
  14. package/build/impl/LslStreamInlet.js.map +1 -1
  15. package/build/impl/LslStreamOutlet.d.ts +3 -2
  16. package/build/impl/LslStreamOutlet.js +4 -4
  17. package/build/impl/LslStreamOutlet.js.map +1 -1
  18. package/build/impl/LslWebSocketBridge.d.ts +18 -6
  19. package/build/impl/LslWebSocketBridge.js +69 -27
  20. package/build/impl/LslWebSocketBridge.js.map +1 -1
  21. package/build/testDoubles/StreamInfo/FakeStreamInfo.d.ts +8 -0
  22. package/build/testDoubles/StreamInfo/FakeStreamInfo.js +11 -0
  23. package/build/testDoubles/StreamInfo/FakeStreamInfo.js.map +1 -1
  24. package/build/testDoubles/StreamInfo/SpyStreamInfo.d.ts +2 -0
  25. package/build/testDoubles/StreamInfo/SpyStreamInfo.js +5 -0
  26. package/build/testDoubles/StreamInfo/SpyStreamInfo.js.map +1 -1
  27. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.d.ts +2 -0
  28. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js +18 -4
  29. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js.map +1 -1
  30. package/build/testDoubles/WebSockets/FakeWebSocket.d.ts +4 -0
  31. package/build/testDoubles/WebSockets/FakeWebSocket.js +12 -1
  32. package/build/testDoubles/WebSockets/FakeWebSocket.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/__tests__/AbstractPackageTest.ts +10 -0
  35. package/src/__tests__/impl/LslStreamInfo.test.ts +40 -6
  36. package/src/__tests__/impl/LslWebSocketBridge.test.ts +111 -13
  37. package/src/impl/LslStreamInfo.ts +28 -12
  38. package/src/impl/LslStreamInlet.ts +4 -1
  39. package/src/impl/LslStreamOutlet.ts +6 -5
  40. package/src/impl/LslWebSocketBridge.ts +104 -38
  41. package/src/testDoubles/StreamInfo/FakeStreamInfo.ts +14 -0
  42. package/src/testDoubles/StreamInfo/SpyStreamInfo.ts +7 -0
  43. package/src/testDoubles/WebSocketBridge/FakeWebSocketBridge.ts +24 -5
  44. package/src/testDoubles/WebSockets/FakeWebSocket.ts +14 -1
@@ -18,6 +18,7 @@ export default class LslWebSocketBridgeTest extends AbstractPackageTest {
18
18
  await super.beforeEach()
19
19
 
20
20
  this.setFakeStreamInlet()
21
+ this.setFakeWebSocket()
21
22
  this.setFakeWebSocketServer()
22
23
  this.setSpyLslWebSocketBridge()
23
24
 
@@ -51,7 +52,7 @@ export default class LslWebSocketBridgeTest extends AbstractPackageTest {
51
52
  type,
52
53
  sourceId,
53
54
  },
54
- this.baseOptions
55
+ this.inletOptions
55
56
  )
56
57
  }
57
58
 
@@ -93,7 +94,7 @@ export default class LslWebSocketBridgeTest extends AbstractPackageTest {
93
94
  protected static async createsWebSocketServer() {
94
95
  assert.isEqualDeep(
95
96
  FakeWebSocketServer.callsToConstructor[0],
96
- { port: this.wssPort },
97
+ { port: this.listenPort },
97
98
  'Did not create WebSocketServer!'
98
99
  )
99
100
  }
@@ -129,8 +130,14 @@ export default class LslWebSocketBridgeTest extends AbstractPackageTest {
129
130
  this.activate()
130
131
  this.simulateOnDataCallback()
131
132
 
133
+ const calls = FakeWebSocket.callsToSend.filter((call) =>
134
+ Array.from(FakeWebSocketServer.clients).some(
135
+ (client) => client.id === call.id
136
+ )
137
+ )
138
+
132
139
  assert.isEqual(
133
- FakeWebSocket.callsToSend.length,
140
+ calls.length,
134
141
  0,
135
142
  'Sent data to clients that are not ready!'
136
143
  )
@@ -153,22 +160,105 @@ export default class LslWebSocketBridgeTest extends AbstractPackageTest {
153
160
 
154
161
  assert.doesThrow(() => {
155
162
  this.activate()
156
- }, `\n\n Cannot re-activate bridge after destroying it! \n\n Please create and activate a new instance. \n`)
163
+ }, `\n\n Cannot activate bridge after destroying it! \n\n Please create and activate a new instance. \n`)
157
164
  }
158
165
 
159
166
  @test()
160
- protected static async acceptsOptionalWssPort() {
161
- FakeWebSocketServer.resetTestDouble()
162
-
163
- const wssPort = randomInt(1000, 9999)
167
+ protected static async throwsIfDeactivateIsCalledAfterDestroy() {
168
+ this.destroy()
164
169
 
165
- this.LslWebSocketBridge({ wssPort })
170
+ assert.doesThrow(() => {
171
+ this.deactivate()
172
+ }, `\n\n Cannot deactivate bridge after destroying it! \n\n Please create and activate a new instance. \n`)
173
+ }
166
174
 
175
+ @test()
176
+ protected static async createsWebSocketServerFromPort() {
167
177
  assert.isEqual(
168
178
  FakeWebSocketServer.callsToConstructor[0]?.port,
169
- wssPort,
170
- 'Did not set port for WebSocketServer!'
179
+ this.listenPort,
180
+ 'Did not create WebSocketServer with port!'
181
+ )
182
+ }
183
+
184
+ @test()
185
+ protected static async createsSocketsFromRemoteUrls() {
186
+ assert.isEqualDeep(
187
+ FakeWebSocket.callsToConstructor,
188
+ this.connectUrls,
189
+ 'Did not create WebSockets for remote URLs!'
190
+ )
191
+ }
192
+
193
+ @test()
194
+ protected static async activateSendsDataToRemoteSockets() {
195
+ this.activate()
196
+
197
+ const { samples, timestamps } = this.simulateOnDataCallback()
198
+
199
+ const expectedPayload = JSON.stringify({
200
+ samples,
201
+ timestamps,
202
+ })
203
+
204
+ assert.isAbove(
205
+ FakeWebSocket.callsToSend.length,
206
+ 0,
207
+ 'Did not send data to remote sockets!'
171
208
  )
209
+
210
+ for (const call of FakeWebSocket.callsToSend) {
211
+ assert.isEqualDeep(
212
+ call.data,
213
+ expectedPayload,
214
+ 'Did not send data to remote sockets!'
215
+ )
216
+ }
217
+ }
218
+
219
+ @test()
220
+ protected static async doesNotSendToRemoteSocketsThatAreNotReady() {
221
+ const sockets = this.instance[
222
+ 'remoteSockets'
223
+ ]! as unknown as FakeWebSocket[]
224
+
225
+ for (const socket of sockets) {
226
+ socket.readyState = WebSocket.CONNECTING
227
+ }
228
+
229
+ this.activate()
230
+ this.simulateOnDataCallback()
231
+
232
+ const calls = FakeWebSocket.callsToSend.filter((call) =>
233
+ sockets.some((socket) => socket.id === call.id)
234
+ )
235
+
236
+ assert.isEqual(
237
+ calls.length,
238
+ 0,
239
+ 'Sent data to sockets that are not ready!'
240
+ )
241
+ }
242
+
243
+ @test()
244
+ protected static async destroyClosesRemoteWebSockets() {
245
+ this.destroy()
246
+
247
+ assert.isEqual(
248
+ FakeWebSocket.numCallsToClose,
249
+ this.connectUrls.length,
250
+ 'Did not close remote WebSockets!'
251
+ )
252
+ }
253
+
254
+ @test()
255
+ protected static async throwsIfNeitherPortNorRemoteUrlsPassed() {
256
+ assert.doesThrow(() => {
257
+ this.LslWebSocketBridge({
258
+ listenPort: undefined,
259
+ connectUrls: undefined,
260
+ })
261
+ }, `At least one of listenPort or connectUrls must be provided!`)
172
262
  }
173
263
 
174
264
  private static activate() {
@@ -191,9 +281,11 @@ export default class LslWebSocketBridgeTest extends AbstractPackageTest {
191
281
  return { samples, timestamps }
192
282
  }
193
283
 
194
- private static readonly wssPort = 8080
284
+ private static readonly listenPort = randomInt(1000, 5000)
195
285
 
196
- private static readonly baseOptions = {
286
+ private static readonly connectUrls = [this.generateId(), this.generateId()]
287
+
288
+ private static readonly inletOptions = {
197
289
  channelNames: this.channelNames,
198
290
  channelFormat: 'float32' as const,
199
291
  sampleRateHz: 100 * Math.random(),
@@ -203,6 +295,12 @@ export default class LslWebSocketBridgeTest extends AbstractPackageTest {
203
295
  sourceId: this.generateId(),
204
296
  }
205
297
 
298
+ private static readonly baseOptions = {
299
+ ...this.inletOptions,
300
+ listenPort: this.listenPort,
301
+ connectUrls: this.connectUrls,
302
+ }
303
+
206
304
  private static LslWebSocketBridge(
207
305
  options?: Partial<WebSocketBridgeOptions>
208
306
  ) {
@@ -7,14 +7,18 @@ import LiblslAdapter from './LiblslAdapter.js'
7
7
  export default class LslStreamInfo implements StreamInfo {
8
8
  public static Class?: StreamInfoConstructor
9
9
 
10
- protected name: string
11
- protected type: string
12
- protected sourceId: string
13
- protected units: string
10
+ private static instanceCache: Record<string, StreamInfo> = {}
11
+
12
+ public readonly name: string
13
+ public readonly type: string
14
+ public readonly sourceId: string
15
+ public readonly channelNames: string[]
16
+ public readonly channelCount: number
17
+ public readonly channelFormat: ChannelFormat
18
+ public readonly sampleRateHz: number
19
+ public readonly units: string
20
+
14
21
  protected boundInfo!: BoundStreamInfo
15
- private channelNames: string[]
16
- private channelFormat: ChannelFormat
17
- private sampleRateHz: number
18
22
 
19
23
  private readonly defaultName = `lsl-stream-info-${generateId()}`
20
24
  private readonly defaultType = generateId()
@@ -38,6 +42,7 @@ export default class LslStreamInfo implements StreamInfo {
38
42
  this.type = type
39
43
  this.sourceId = sourceId
40
44
  this.channelNames = channelNames
45
+ this.channelCount = channelNames.length
41
46
  this.channelFormat = channelFormat
42
47
  this.sampleRateHz = sampleRateHz
43
48
  this.units = units
@@ -47,7 +52,14 @@ export default class LslStreamInfo implements StreamInfo {
47
52
  }
48
53
 
49
54
  public static Create(options: StreamInfoOptions) {
50
- return new (this.Class ?? this)(options)
55
+ const key = JSON.stringify(options)
56
+ let instance = this.instanceCache[key]
57
+
58
+ if (instance === undefined) {
59
+ instance = new (this.Class ?? this)(options)
60
+ this.instanceCache[key] = instance
61
+ }
62
+ return instance
51
63
  }
52
64
 
53
65
  private createStreamInfo() {
@@ -61,10 +73,6 @@ export default class LslStreamInfo implements StreamInfo {
61
73
  })
62
74
  }
63
75
 
64
- private get channelCount() {
65
- return this.channelNames.length
66
- }
67
-
68
76
  private get lslChannelFormat() {
69
77
  return CHANNEL_FORMATS_MAP[this.channelFormat]
70
78
  }
@@ -91,6 +99,14 @@ export default class LslStreamInfo implements StreamInfo {
91
99
 
92
100
  export interface StreamInfo {
93
101
  destroy(): void
102
+ readonly name: string
103
+ readonly type: string
104
+ readonly sourceId: string
105
+ readonly units: string
106
+ readonly channelNames: string[]
107
+ readonly channelCount: number
108
+ readonly channelFormat: ChannelFormat
109
+ readonly sampleRateHz: number
94
110
  readonly boundStreamInfo: BoundStreamInfo
95
111
  }
96
112
 
@@ -73,8 +73,11 @@ export default class LslStreamInlet implements StreamInlet {
73
73
 
74
74
  public static Create(options: StreamInletOptions, onData: OnDataCallback) {
75
75
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
76
- const { maxBufferedMs, chunkSize, ...infoOptions } = options
76
+ const { maxBufferedMs, chunkSize, ...rest } = options
77
+ const infoOptions: StreamInfoOptions = rest
78
+
77
79
  const info = this.LslStreamInfo(infoOptions)
80
+
78
81
  return new (this.Class ?? this)(info, options, onData)
79
82
  }
80
83
 
@@ -34,7 +34,7 @@ export default class LslStreamOutlet implements StreamOutlet {
34
34
  public readonly units: string = 'N/A'
35
35
 
36
36
  private info: StreamInfo
37
- private outlet!: BoundOutlet
37
+ private boundOutlet!: BoundOutlet
38
38
  private pushSampleMethod!: (options: unknown) => LslErrorCode
39
39
 
40
40
  private lsl = LiblslAdapter.getInstance()
@@ -108,7 +108,7 @@ export default class LslStreamOutlet implements StreamOutlet {
108
108
  }
109
109
 
110
110
  private createStreamOutlet() {
111
- this.outlet = this.lsl.createOutlet({
111
+ this.boundOutlet = this.lsl.createOutlet({
112
112
  info: this.boundStreamInfo,
113
113
  chunkSize: this.chunkSize,
114
114
  maxBufferedMs: this.maxBufferedMs,
@@ -138,7 +138,7 @@ export default class LslStreamOutlet implements StreamOutlet {
138
138
  const timestamp = this.lsl.localClock()
139
139
 
140
140
  const err = this.pushSampleMethod({
141
- outlet: this.outlet,
141
+ outlet: this.boundOutlet,
142
142
  sample,
143
143
  timestamp,
144
144
  })
@@ -155,7 +155,7 @@ export default class LslStreamOutlet implements StreamOutlet {
155
155
  }
156
156
 
157
157
  private destroyBoundOutlet() {
158
- this.lsl.destroyOutlet({ outlet: this.outlet })
158
+ this.lsl.destroyOutlet({ outlet: this.boundOutlet })
159
159
  }
160
160
 
161
161
  private static async waitToAllowSetup(waitAfterConstructionMs: number) {
@@ -194,12 +194,13 @@ export interface StreamOutlet {
194
194
  readonly type: string
195
195
  readonly sourceId: string
196
196
  readonly channelNames: string[]
197
+ readonly channelCount: number
197
198
  readonly channelFormat: ChannelFormat
198
199
  readonly sampleRateHz: number
200
+ readonly units: string
199
201
  readonly chunkSize: number
200
202
  readonly maxBufferedMs: number
201
203
  readonly manufacturer: string
202
- readonly units: string
203
204
  }
204
205
 
205
206
  export type StreamOutletConstructor = new (
@@ -1,58 +1,92 @@
1
- import { WebSocketServer } from 'ws'
1
+ import WebSocket, { WebSocketServer } from 'ws'
2
2
  import { ChannelFormat } from './LiblslAdapter.js'
3
- import LslStreamInlet, { StreamInlet } from './LslStreamInlet.js'
3
+ import LslStreamInlet, {
4
+ StreamInlet,
5
+ StreamInletOptions,
6
+ } from './LslStreamInlet.js'
4
7
 
5
8
  export default class LslWebSocketBridge implements WebSocketBridge {
6
9
  public static Class?: WebSocketBridgeConstructor
10
+ public static WS = WebSocket
7
11
  public static WSS = WebSocketServer
8
12
 
9
13
  private inlet: StreamInlet
10
- private wss: WebSocketServer
14
+ private localServer?: WebSocketServer
15
+ private remoteSockets?: WebSocket[]
16
+
11
17
  private isDestroyed = false
12
18
 
13
19
  protected constructor(options: WebSocketBridgeConstructorOptions) {
14
- const { inlet, wss } = options
20
+ const { inlet, localServer, remoteSockets } = options
15
21
 
16
22
  this.inlet = inlet
17
- this.wss = wss
23
+ this.localServer = localServer
24
+ this.remoteSockets = remoteSockets
25
+
26
+ this.throwIfNoLocalServerOrRemoteSockets()
18
27
  }
19
28
 
20
29
  public static Create(options: WebSocketBridgeOptions) {
21
- const { wssPort = 8080 } = options ?? {}
22
- const wss = this.WebSocketServer(wssPort)
23
- const inlet = this.LslStreamInlet(options, wss)
30
+ const { listenPort, connectUrls, ...rest } = options
31
+ const inletOptions: StreamInletOptions = rest
32
+
33
+ const localServer = this.WebSocketServer(listenPort)
34
+ const remoteSockets = this.createSocketsFrom(connectUrls)
35
+
36
+ const inlet = this.LslStreamInlet(
37
+ inletOptions,
38
+ localServer,
39
+ remoteSockets
40
+ )
41
+
42
+ return new (this.Class ?? this)({
43
+ ...options,
44
+ inlet,
45
+ localServer,
46
+ remoteSockets,
47
+ })
48
+ }
24
49
 
25
- return new (this.Class ?? this)({ ...options, inlet, wss })
50
+ private throwIfNoLocalServerOrRemoteSockets() {
51
+ if (!(this.localServer || this.remoteSockets)) {
52
+ throw new Error(this.insufficientOptionsError)
53
+ }
26
54
  }
27
55
 
56
+ private readonly insufficientOptionsError = `At least one of listenPort or connectUrls must be provided!`
57
+
28
58
  public activate() {
29
- this.throwIfBridgeIsDestroyed()
59
+ this.throwIfBridgeIsDestroyed(this.cannotActivateMessage)
30
60
  this.startPullingData()
31
61
  }
32
62
 
33
- private throwIfBridgeIsDestroyed() {
63
+ private throwIfBridgeIsDestroyed(err: string) {
34
64
  if (this.isDestroyed) {
35
- throw new Error(
36
- `\n\n Cannot re-activate bridge after destroying it! \n\n Please create and activate a new instance. \n`
37
- )
65
+ throw new Error(err)
38
66
  }
39
67
  }
40
68
 
69
+ private readonly cannotActivateMessage = `\n\n Cannot activate bridge after destroying it! \n\n Please create and activate a new instance. \n`
70
+
41
71
  private startPullingData() {
42
72
  this.inlet.startPulling()
43
73
  }
44
74
 
45
75
  public deactivate() {
76
+ this.throwIfBridgeIsDestroyed(this.cannotDeactivateMessage)
46
77
  this.stopPullingData()
47
78
  }
48
79
 
80
+ private readonly cannotDeactivateMessage = `\n\n Cannot deactivate bridge after destroying it! \n\n Please create and activate a new instance. \n`
81
+
49
82
  private stopPullingData() {
50
83
  this.inlet.stopPulling()
51
84
  }
52
85
 
53
86
  public destroy() {
54
87
  this.destroyBoundInlet()
55
- this.closeWebSocketServer()
88
+ this.closeLocalServerIfExists()
89
+ this.closeRemoteSocketsIfExists()
56
90
  this.isDestroyed = true
57
91
  }
58
92
 
@@ -60,44 +94,74 @@ export default class LslWebSocketBridge implements WebSocketBridge {
60
94
  this.inlet.destroy()
61
95
  }
62
96
 
63
- private closeWebSocketServer() {
64
- this.wss.close()
97
+ private closeLocalServerIfExists() {
98
+ this.localServer?.close()
65
99
  }
66
100
 
67
- private static createOnDataCallback(wss: WebSocketServer) {
101
+ private closeRemoteSocketsIfExists() {
102
+ this.remoteSockets?.forEach((socket) => socket.close())
103
+ }
104
+
105
+ private static createOnDataCallback(
106
+ localServer?: WebSocketServer,
107
+ remoteSockets?: WebSocket[]
108
+ ) {
68
109
  return (samples: Float32Array, timestamps: Float64Array) => {
69
- this.broadcastToClients(wss, samples, timestamps)
110
+ const payload = JSON.stringify({ samples, timestamps })
111
+
112
+ if (localServer) {
113
+ this.broadcastToClients(localServer, payload)
114
+ }
115
+
116
+ if (remoteSockets) {
117
+ this.sendToSockets(remoteSockets, payload)
118
+ }
70
119
  }
71
120
  }
72
121
 
73
122
  private static broadcastToClients(
74
- wss: WebSocketServer,
75
- samples: Float32Array<ArrayBufferLike>,
76
- timestamps: Float64Array<ArrayBufferLike>
123
+ localServer: WebSocketServer,
124
+ payload: string
77
125
  ) {
78
- for (const client of wss.clients) {
126
+ for (const client of localServer.clients) {
79
127
  if (client.readyState === WebSocket.OPEN) {
80
- client.send(
81
- JSON.stringify({
82
- samples,
83
- timestamps,
84
- })
85
- )
128
+ client.send(payload)
86
129
  }
87
130
  }
88
131
  }
89
132
 
133
+ private static sendToSockets(remoteSockets: WebSocket[], payload: string) {
134
+ for (const socket of remoteSockets) {
135
+ if (socket.readyState === WebSocket.OPEN) {
136
+ socket.send(payload)
137
+ }
138
+ }
139
+ }
140
+
141
+ private static createSocketsFrom(connectUrls?: string | string[]) {
142
+ return connectUrls
143
+ ? (Array.isArray(connectUrls) ? connectUrls : [connectUrls]).map(
144
+ (url) => this.WebSocket(url)
145
+ )
146
+ : undefined
147
+ }
148
+
149
+ private static WebSocket(url: string) {
150
+ return new this.WS(url)
151
+ }
152
+
153
+ private static WebSocketServer(listenPort?: number) {
154
+ return listenPort ? new this.WSS({ port: listenPort }) : undefined
155
+ }
156
+
90
157
  private static LslStreamInlet(
91
- options: WebSocketBridgeOptions,
92
- wss: WebSocketServer
158
+ options: StreamInletOptions,
159
+ localServer?: WebSocketServer,
160
+ remoteSockets?: WebSocket[]
93
161
  ) {
94
- const onData = this.createOnDataCallback(wss)
162
+ const onData = this.createOnDataCallback(localServer, remoteSockets)
95
163
  return LslStreamInlet.Create(options, onData)
96
164
  }
97
-
98
- private static WebSocketServer(wssPort: number) {
99
- return new this.WSS({ port: wssPort })
100
- }
101
165
  }
102
166
 
103
167
  export interface WebSocketBridge {
@@ -118,11 +182,13 @@ export interface WebSocketBridgeOptions {
118
182
  name?: string
119
183
  type?: string
120
184
  sourceId?: string
121
- wssPort?: number
185
+ listenPort?: number
186
+ connectUrls?: string | string[]
122
187
  }
123
188
 
124
189
  export interface WebSocketBridgeConstructorOptions
125
190
  extends WebSocketBridgeOptions {
126
191
  inlet: StreamInlet
127
- wss: WebSocketServer
192
+ localServer?: WebSocketServer
193
+ remoteSockets?: WebSocket[]
128
194
  }
@@ -1,3 +1,5 @@
1
+ import generateId from '@neurodevs/generate-id'
2
+
1
3
  import { BoundStreamInfo } from 'impl/LiblslAdapter.js'
2
4
  import { StreamInfo, StreamInfoOptions } from '../../impl/LslStreamInfo.js'
3
5
 
@@ -7,6 +9,14 @@ export default class FakeStreamInfo implements StreamInfo {
7
9
 
8
10
  public static boundStreamInfo = {} as BoundStreamInfo
9
11
 
12
+ public name = generateId()
13
+ public type = generateId()
14
+ public sourceId = generateId()
15
+ public channelNames: string[] = [generateId(), generateId()]
16
+ public channelFormat = 'float32' as const
17
+ public sampleRateHz = 0
18
+ public units = 'N/A'
19
+
10
20
  public constructor(options: StreamInfoOptions) {
11
21
  FakeStreamInfo.callsToConstructor.push(options)
12
22
  }
@@ -15,6 +25,10 @@ export default class FakeStreamInfo implements StreamInfo {
15
25
  FakeStreamInfo.numCallsToDestroy++
16
26
  }
17
27
 
28
+ public get channelCount() {
29
+ return this.channelNames.length
30
+ }
31
+
18
32
  public get boundStreamInfo() {
19
33
  return FakeStreamInfo.boundStreamInfo
20
34
  }
@@ -1,8 +1,11 @@
1
1
  import LslStreamInfo, { StreamInfoOptions } from '../../impl/LslStreamInfo.js'
2
2
 
3
3
  export default class SpyStreamInfo extends LslStreamInfo {
4
+ public static numCallsToConstructor = 0
5
+
4
6
  public constructor(options: StreamInfoOptions) {
5
7
  super(options)
8
+ SpyStreamInfo.numCallsToConstructor++
6
9
  }
7
10
 
8
11
  public getBoundInfo() {
@@ -24,4 +27,8 @@ export default class SpyStreamInfo extends LslStreamInfo {
24
27
  public getUnits() {
25
28
  return this.units
26
29
  }
30
+
31
+ public static resetTestDouble() {
32
+ SpyStreamInfo.numCallsToConstructor = 0
33
+ }
27
34
  }
@@ -1,4 +1,9 @@
1
- import { WebSocketBridge } from '../../impl/LslWebSocketBridge.js'
1
+ import WebSocket, { WebSocketServer } from 'ws'
2
+ import LslWebSocketBridge, {
3
+ WebSocketBridge,
4
+ } from '../../impl/LslWebSocketBridge.js'
5
+ import FakeWebSocket from '../WebSockets/FakeWebSocket.js'
6
+ import FakeWebSocketServer from '../WebSockets/FakeWebSocketServer.js'
2
7
 
3
8
  export default class FakeWebSocketBridge implements WebSocketBridge {
4
9
  public static numCallsToConstructor = 0
@@ -23,9 +28,23 @@ export default class FakeWebSocketBridge implements WebSocketBridge {
23
28
  }
24
29
 
25
30
  public static resetTestDouble() {
26
- FakeWebSocketBridge.numCallsToConstructor = 0
27
- FakeWebSocketBridge.numCallsToActivate = 0
28
- FakeWebSocketBridge.numCallsToDeactivate = 0
29
- FakeWebSocketBridge.numCallsToDestroy = 0
31
+ this.numCallsToConstructor = 0
32
+ this.numCallsToActivate = 0
33
+ this.numCallsToDeactivate = 0
34
+ this.numCallsToDestroy = 0
35
+
36
+ this.setFakeWebSocket()
37
+ this.setFakeWebSocketServer()
38
+ }
39
+
40
+ private static setFakeWebSocket() {
41
+ LslWebSocketBridge.WS = FakeWebSocket as unknown as typeof WebSocket
42
+ FakeWebSocket.resetTestDouble()
43
+ }
44
+
45
+ private static setFakeWebSocketServer() {
46
+ LslWebSocketBridge.WSS =
47
+ FakeWebSocketServer as unknown as typeof WebSocketServer
48
+ FakeWebSocketServer.resetTestDouble()
30
49
  }
31
50
  }
@@ -2,16 +2,29 @@ import generateId from '@neurodevs/generate-id'
2
2
  import WebSocket from 'ws'
3
3
 
4
4
  export default class FakeWebSocket {
5
+ public static callsToConstructor: (string | undefined)[] = []
5
6
  public static callsToSend: { id: string; data: unknown }[] = []
7
+ public static numCallsToClose = 0
6
8
 
7
9
  public readyState: number = WebSocket.OPEN
8
10
  public id = generateId()
9
11
 
12
+ public constructor(url?: string) {
13
+ FakeWebSocket.callsToConstructor.push(url)
14
+ }
15
+
10
16
  public send(data: unknown) {
11
17
  FakeWebSocket.callsToSend.push({ id: this.id, data })
12
18
  }
13
19
 
20
+ public close() {
21
+ FakeWebSocket.numCallsToClose++
22
+ this.readyState = WebSocket.CLOSED
23
+ }
24
+
14
25
  public static resetTestDouble() {
15
- FakeWebSocket.callsToSend = []
26
+ this.callsToConstructor = []
27
+ this.callsToSend = []
28
+ this.numCallsToClose = 0
16
29
  }
17
30
  }