@dxos/edge-client 0.6.13 → 0.6.14-main.69511f5

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 (61) hide show
  1. package/dist/lib/browser/chunk-ZWJXA37R.mjs +113 -0
  2. package/dist/lib/browser/chunk-ZWJXA37R.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +469 -160
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +125 -0
  7. package/dist/lib/browser/testing/index.mjs.map +7 -0
  8. package/dist/lib/node/chunk-ANV2HBEH.cjs +136 -0
  9. package/dist/lib/node/chunk-ANV2HBEH.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +468 -158
  11. package/dist/lib/node/index.cjs.map +4 -4
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/testing/index.cjs +155 -0
  14. package/dist/lib/node/testing/index.cjs.map +7 -0
  15. package/dist/lib/node-esm/chunk-HNVT57AU.mjs +115 -0
  16. package/dist/lib/node-esm/chunk-HNVT57AU.mjs.map +7 -0
  17. package/dist/lib/node-esm/index.mjs +787 -0
  18. package/dist/lib/node-esm/index.mjs.map +7 -0
  19. package/dist/lib/node-esm/meta.json +1 -0
  20. package/dist/lib/node-esm/testing/index.mjs +126 -0
  21. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  22. package/dist/types/src/auth.d.ts +22 -0
  23. package/dist/types/src/auth.d.ts.map +1 -0
  24. package/dist/types/src/defs.d.ts.map +1 -1
  25. package/dist/types/src/edge-client.d.ts +14 -13
  26. package/dist/types/src/edge-client.d.ts.map +1 -1
  27. package/dist/types/src/edge-http-client.d.ts +48 -0
  28. package/dist/types/src/edge-http-client.d.ts.map +1 -0
  29. package/dist/types/src/edge-identity.d.ts +15 -0
  30. package/dist/types/src/edge-identity.d.ts.map +1 -0
  31. package/dist/types/src/errors.d.ts +4 -1
  32. package/dist/types/src/errors.d.ts.map +1 -1
  33. package/dist/types/src/index.d.ts +4 -0
  34. package/dist/types/src/index.d.ts.map +1 -1
  35. package/dist/types/src/protocol.d.ts +2 -2
  36. package/dist/types/src/protocol.d.ts.map +1 -1
  37. package/dist/types/src/testing/index.d.ts +2 -0
  38. package/dist/types/src/testing/index.d.ts.map +1 -0
  39. package/dist/types/src/testing/test-utils.d.ts +21 -0
  40. package/dist/types/src/testing/test-utils.d.ts.map +1 -0
  41. package/dist/types/src/utils.d.ts +2 -0
  42. package/dist/types/src/utils.d.ts.map +1 -0
  43. package/package.json +27 -17
  44. package/src/auth.ts +135 -0
  45. package/src/defs.ts +2 -3
  46. package/src/edge-client.test.ts +50 -18
  47. package/src/edge-client.ts +76 -24
  48. package/src/edge-http-client.ts +210 -0
  49. package/src/edge-identity.ts +31 -0
  50. package/src/errors.ts +8 -2
  51. package/src/index.ts +4 -0
  52. package/src/persistent-lifecycle.test.ts +2 -2
  53. package/src/protocol.test.ts +1 -2
  54. package/src/protocol.ts +2 -2
  55. package/src/testing/index.ts +5 -0
  56. package/src/testing/test-utils.ts +114 -0
  57. package/src/utils.ts +10 -0
  58. package/src/websocket.test.ts +5 -4
  59. package/dist/types/src/test-utils.d.ts +0 -11
  60. package/dist/types/src/test-utils.d.ts.map +0 -1
  61. package/src/test-utils.ts +0 -49
@@ -0,0 +1,210 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { sleep } from '@dxos/async';
6
+ import { Context } from '@dxos/context';
7
+ import { type PublicKey, type SpaceId } from '@dxos/keys';
8
+ import { log } from '@dxos/log';
9
+ import {
10
+ EdgeCallFailedError,
11
+ type EdgeHttpResponse,
12
+ type GetNotarizationResponseBody,
13
+ type PostNotarizationRequestBody,
14
+ type JoinSpaceRequest,
15
+ type JoinSpaceResponseBody,
16
+ EdgeAuthChallengeError,
17
+ type CreateAgentResponseBody,
18
+ type CreateAgentRequestBody,
19
+ type GetAgentStatusResponseBody,
20
+ type RecoverIdentityRequest,
21
+ type RecoverIdentityResponseBody,
22
+ } from '@dxos/protocols';
23
+
24
+ import { type EdgeIdentity, handleAuthChallenge } from './edge-identity';
25
+ import { getEdgeUrlWithProtocol } from './utils';
26
+
27
+ const DEFAULT_RETRY_TIMEOUT = 1500;
28
+ const DEFAULT_RETRY_JITTER = 500;
29
+ const DEFAULT_MAX_RETRIES_COUNT = 3;
30
+
31
+ export class EdgeHttpClient {
32
+ private readonly _baseUrl: string;
33
+
34
+ private _edgeIdentity: EdgeIdentity | undefined;
35
+ /**
36
+ * Auth header is cached until receiving the next 401 from EDGE, at which point it gets refreshed.
37
+ */
38
+ private _authHeader: string | undefined;
39
+
40
+ constructor(baseUrl: string) {
41
+ this._baseUrl = getEdgeUrlWithProtocol(baseUrl, 'http');
42
+ log('created', { url: this._baseUrl });
43
+ }
44
+
45
+ setIdentity(identity: EdgeIdentity) {
46
+ this._edgeIdentity = identity;
47
+ }
48
+
49
+ public createAgent(body: CreateAgentRequestBody, args?: EdgeHttpGetArgs): Promise<CreateAgentResponseBody> {
50
+ return this._call('/agents/create', { ...args, method: 'POST', body });
51
+ }
52
+
53
+ public getAgentStatus(
54
+ request: { ownerIdentityKey: PublicKey },
55
+ args?: EdgeHttpGetArgs,
56
+ ): Promise<GetAgentStatusResponseBody> {
57
+ return this._call(`/users/${request.ownerIdentityKey.toHex()}/agent/status`, { ...args, method: 'GET' });
58
+ }
59
+
60
+ public getCredentialsForNotarization(spaceId: SpaceId, args?: EdgeHttpGetArgs): Promise<GetNotarizationResponseBody> {
61
+ return this._call(`/spaces/${spaceId}/notarization`, { ...args, method: 'GET' });
62
+ }
63
+
64
+ public async notarizeCredentials(
65
+ spaceId: SpaceId,
66
+ body: PostNotarizationRequestBody,
67
+ args?: EdgeHttpGetArgs,
68
+ ): Promise<void> {
69
+ await this._call(`/spaces/${spaceId}/notarization`, { ...args, body, method: 'POST' });
70
+ }
71
+
72
+ public async joinSpaceByInvitation(
73
+ spaceId: SpaceId,
74
+ body: JoinSpaceRequest,
75
+ args?: EdgeHttpGetArgs,
76
+ ): Promise<JoinSpaceResponseBody> {
77
+ return this._call(`/spaces/${spaceId}/join`, { ...args, body, method: 'POST' });
78
+ }
79
+
80
+ public async recoverIdentity(
81
+ body: RecoverIdentityRequest,
82
+ args?: EdgeHttpGetArgs,
83
+ ): Promise<RecoverIdentityResponseBody> {
84
+ return this._call('/identity/recover', { ...args, body, method: 'POST' });
85
+ }
86
+
87
+ private async _call<T>(path: string, args: EdgeHttpCallArgs): Promise<T> {
88
+ const requestContext = args.context ?? new Context();
89
+ const shouldRetry = createRetryHandler(args);
90
+ const url = `${this._baseUrl}${path.startsWith('/') ? path.slice(1) : path}`;
91
+
92
+ log.info('call', { method: args.method, path, request: args.body });
93
+
94
+ let handledAuth = false;
95
+ let authHeader = this._authHeader;
96
+ while (true) {
97
+ let processingError: EdgeCallFailedError;
98
+ let retryAfterHeaderValue: number = Number.NaN;
99
+ try {
100
+ const request = createRequest(args, authHeader);
101
+ const response = await fetch(url, request);
102
+
103
+ retryAfterHeaderValue = Number(response.headers.get('Retry-After'));
104
+
105
+ if (response.ok) {
106
+ const body = (await response.json()) as EdgeHttpResponse<T>;
107
+ if (body.success) {
108
+ return body.data;
109
+ }
110
+
111
+ log.info('unsuccessful edge response', { path, body });
112
+
113
+ if (body.errorData?.type === 'auth_challenge' && typeof body.errorData?.challenge === 'string') {
114
+ processingError = new EdgeAuthChallengeError(body.errorData.challenge, body.errorData);
115
+ } else {
116
+ processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
117
+ }
118
+ } else if (response.status === 401 && !handledAuth) {
119
+ authHeader = await this._handleUnauthorized(response);
120
+ handledAuth = true;
121
+ continue;
122
+ } else {
123
+ processingError = EdgeCallFailedError.fromHttpFailure(response);
124
+ }
125
+ } catch (error: any) {
126
+ processingError = EdgeCallFailedError.fromProcessingFailureCause(error);
127
+ }
128
+
129
+ if (processingError.isRetryable && (await shouldRetry(requestContext, retryAfterHeaderValue))) {
130
+ log.info('retrying edge request', { path, processingError });
131
+ } else {
132
+ throw processingError;
133
+ }
134
+ }
135
+ }
136
+
137
+ private async _handleUnauthorized(response: Response) {
138
+ if (!this._edgeIdentity) {
139
+ log.warn('edge unauthorized response received before identity was set');
140
+ throw EdgeCallFailedError.fromHttpFailure(response);
141
+ }
142
+ const challenge = await handleAuthChallenge(response, this._edgeIdentity);
143
+ this._authHeader = encodeAuthHeader(challenge);
144
+ log('auth header updated');
145
+ return this._authHeader;
146
+ }
147
+ }
148
+
149
+ const createRetryHandler = (args: EdgeHttpCallArgs) => {
150
+ if (!args.retry || args.retry.count < 1) {
151
+ return async () => false;
152
+ }
153
+ let retries = 0;
154
+ const maxRetries = args.retry.count ?? DEFAULT_MAX_RETRIES_COUNT;
155
+ const baseTimeout = args.retry.timeout ?? DEFAULT_RETRY_TIMEOUT;
156
+ const jitter = args.retry.jitter ?? DEFAULT_RETRY_JITTER;
157
+ return async (ctx: Context, retryAfter: number) => {
158
+ if (++retries > maxRetries || ctx.disposed) {
159
+ return false;
160
+ }
161
+
162
+ if (retryAfter) {
163
+ await sleep(retryAfter);
164
+ } else {
165
+ const timeout = baseTimeout + Math.random() * jitter;
166
+ await sleep(timeout);
167
+ }
168
+
169
+ return true;
170
+ };
171
+ };
172
+
173
+ export type RetryConfig = {
174
+ /**
175
+ * A number of call retries, not counting the initial request.
176
+ */
177
+ count: number;
178
+ /**
179
+ * Delay before retries in ms.
180
+ */
181
+ timeout?: number;
182
+ /**
183
+ * A random amount of time before retrying to help prevent large bursts of requests.
184
+ */
185
+ jitter?: number;
186
+ };
187
+
188
+ export type EdgeHttpGetArgs = { context?: Context; retry?: RetryConfig };
189
+
190
+ export type EdgeHttpPostArgs = { context?: Context; body?: any; retry?: RetryConfig };
191
+
192
+ type EdgeHttpCallArgs = {
193
+ method: string;
194
+ body?: any;
195
+ context?: Context;
196
+ retry?: RetryConfig;
197
+ };
198
+
199
+ const createRequest = (args: EdgeHttpCallArgs, authHeader: string | undefined): RequestInit => {
200
+ return {
201
+ method: args.method,
202
+ body: args.body && JSON.stringify(args.body),
203
+ headers: authHeader ? { Authorization: authHeader } : undefined,
204
+ };
205
+ };
206
+
207
+ const encodeAuthHeader = (challenge: Uint8Array) => {
208
+ const encodedChallenge = Buffer.from(challenge).toString('base64');
209
+ return `VerifiablePresentation pb;base64,${encodedChallenge}`;
210
+ };
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { invariant } from '@dxos/invariant';
6
+ import { schema } from '@dxos/protocols/proto';
7
+ import { type Presentation } from '@dxos/protocols/proto/dxos/halo/credentials';
8
+
9
+ export interface EdgeIdentity {
10
+ peerKey: string;
11
+ identityKey: string;
12
+ /**
13
+ * Returns credential presentation issued by the identity key.
14
+ * Presentation must have the provided challenge.
15
+ * Presentation may include ServiceAccess credentials.
16
+ */
17
+ presentCredentials({ challenge }: { challenge: Uint8Array }): Promise<Presentation>;
18
+ }
19
+
20
+ export const handleAuthChallenge = async (failedResponse: Response, identity: EdgeIdentity): Promise<Uint8Array> => {
21
+ invariant(failedResponse.status === 401);
22
+
23
+ const headerValue = failedResponse.headers.get('Www-Authenticate');
24
+ invariant(headerValue?.startsWith('VerifiablePresentation challenge='));
25
+
26
+ const challenge = headerValue?.slice('VerifiablePresentation challenge='.length);
27
+ invariant(challenge);
28
+
29
+ const presentation = await identity.presentCredentials({ challenge: Buffer.from(challenge, 'base64') });
30
+ return schema.getCodecForType('dxos.halo.credentials.Presentation').encode(presentation);
31
+ };
package/src/errors.ts CHANGED
@@ -2,8 +2,14 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export class WebsocketClosedError extends Error {
5
+ export class EdgeConnectionClosedError extends Error {
6
6
  constructor() {
7
- super('WebSocket connection closed');
7
+ super('Edge connection closed.');
8
+ }
9
+ }
10
+
11
+ export class EdgeIdentityChangedError extends Error {
12
+ constructor() {
13
+ super('Edge identity changed.');
8
14
  }
9
15
  }
package/src/index.ts CHANGED
@@ -7,3 +7,7 @@ export * from '@dxos/protocols/buf/dxos/edge/messenger_pb';
7
7
  export * from './edge-client';
8
8
  export * from './defs';
9
9
  export * from './protocol';
10
+ export * from './errors';
11
+ export * from './auth';
12
+ export * from './edge-http-client';
13
+ export * from './edge-identity';
@@ -2,11 +2,11 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
5
+ import { describe, expect, test } from 'vitest';
6
6
 
7
7
  import { sleep, Trigger } from '@dxos/async';
8
8
  import { log } from '@dxos/log';
9
- import { describe, openAndClose, test } from '@dxos/test';
9
+ import { openAndClose } from '@dxos/test-utils';
10
10
 
11
11
  import { PersistentLifecycle } from './persistent-lifecycle';
12
12
 
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
5
+ import { describe, expect, test } from 'vitest';
6
6
 
7
7
  import { buf } from '@dxos/protocols/buf';
8
8
  import {
@@ -11,7 +11,6 @@ import {
11
11
  SwarmRequestSchema,
12
12
  SwarmResponseSchema,
13
13
  } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
14
- import { describe, test } from '@dxos/test';
15
14
 
16
15
  import { Protocol } from './protocol';
17
16
 
package/src/protocol.ts CHANGED
@@ -4,10 +4,10 @@
4
4
 
5
5
  import { invariant } from '@dxos/invariant';
6
6
  import { buf, bufWkt } from '@dxos/protocols/buf';
7
- import { type Message, MessageSchema, type Peer as PeerProto } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
7
+ import { type Message, MessageSchema, type PeerSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
8
8
  import { bufferToArray } from '@dxos/util';
9
9
 
10
- export type PeerData = Partial<PeerProto>;
10
+ export type PeerData = buf.MessageInitShape<typeof PeerSchema>;
11
11
 
12
12
  export const getTypename = (typeName: string) => `type.googleapis.com/${typeName}`;
13
13
 
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './test-utils';
@@ -0,0 +1,114 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import WebSocket from 'isomorphic-ws';
6
+
7
+ import { Trigger } from '@dxos/async';
8
+ import { log } from '@dxos/log';
9
+ import { buf } from '@dxos/protocols/buf';
10
+ import { MessageSchema, TextMessageSchema, type Message } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
11
+
12
+ import { protocol } from '../defs';
13
+ import { toUint8Array } from '../protocol';
14
+
15
+ export const DEFAULT_PORT = 8080;
16
+
17
+ type TestEdgeWsServerParams = {
18
+ admitConnection?: Trigger;
19
+ payloadDecoder?: (payload: Uint8Array) => any;
20
+ messageHandler?: (payload: any) => Promise<Uint8Array | undefined>;
21
+ };
22
+
23
+ export const createTestEdgeWsServer = async (port = DEFAULT_PORT, params?: TestEdgeWsServerParams) => {
24
+ const server = new WebSocket.Server({ port, verifyClient: createConnectionDelayHandler(params) });
25
+
26
+ let connection: WebSocket | undefined;
27
+
28
+ const messageSink: any[] = [];
29
+ const closeTrigger = new Trigger();
30
+ const sendResponseMessage = createResponseSender(() => connection!);
31
+
32
+ server.on('connection', (ws) => {
33
+ connection = ws;
34
+ ws.on('error', (err) => log.catch(err));
35
+ ws.on('message', async (data) => {
36
+ if (String(data) === '__ping__') {
37
+ ws.send('__pong__');
38
+ return;
39
+ }
40
+ const { request, requestPayload } = await decodeRequest(params, data);
41
+ if (params?.messageHandler) {
42
+ const responsePayload = await params.messageHandler(requestPayload);
43
+ if (responsePayload && connection) {
44
+ sendResponseMessage(request, responsePayload);
45
+ }
46
+ }
47
+ log('message', { payload: requestPayload });
48
+ messageSink.push(requestPayload);
49
+ });
50
+
51
+ ws.on('close', () => {
52
+ connection = undefined;
53
+ closeTrigger.wake();
54
+ });
55
+ });
56
+
57
+ return {
58
+ server,
59
+ messageSink,
60
+ endpoint: `ws://localhost:${port}`,
61
+ cleanup: () => server.close(),
62
+ currentConnection: () => connection,
63
+ sendResponseMessage,
64
+ sendMessage: (msg: Message) => {
65
+ connection!.send(buf.toBinary(MessageSchema, msg));
66
+ },
67
+ closeConnection: () => {
68
+ closeTrigger.reset();
69
+ connection!.close(1011);
70
+ return closeTrigger.wait();
71
+ },
72
+ };
73
+ };
74
+
75
+ const createConnectionDelayHandler = (params: TestEdgeWsServerParams | undefined) => {
76
+ return (_: any, callback: (admit: boolean) => void) => {
77
+ if (params?.admitConnection) {
78
+ log('delaying edge connection admission');
79
+ void params.admitConnection.wait().then(() => {
80
+ callback(true);
81
+ log('edge connection admitted');
82
+ });
83
+ } else {
84
+ callback(true);
85
+ }
86
+ };
87
+ };
88
+
89
+ const createResponseSender = (connection: () => WebSocket) => {
90
+ return (request: Message, responsePayload: Uint8Array) => {
91
+ const recipient = request.source!;
92
+ connection().send(
93
+ buf.toBinary(
94
+ MessageSchema,
95
+ buf.create(MessageSchema, {
96
+ source: {
97
+ identityKey: recipient.identityKey,
98
+ peerKey: recipient.peerKey,
99
+ },
100
+ serviceId: request.serviceId!,
101
+ payload: { value: responsePayload },
102
+ }),
103
+ ),
104
+ );
105
+ };
106
+ };
107
+
108
+ const decodeRequest = async (params: TestEdgeWsServerParams | undefined, data: any) => {
109
+ const request = buf.fromBinary(MessageSchema, await toUint8Array(data));
110
+ const requestPayload = params?.payloadDecoder
111
+ ? params.payloadDecoder(request.payload!.value!)
112
+ : protocol.getPayload(request, TextMessageSchema);
113
+ return { request, requestPayload };
114
+ };
package/src/utils.ts ADDED
@@ -0,0 +1,10 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export const getEdgeUrlWithProtocol = (baseUrl: string, protocol: 'http' | 'ws') => {
6
+ const isSecure = baseUrl.startsWith('https') || baseUrl.startsWith('wss');
7
+ const url = new URL(baseUrl);
8
+ url.protocol = protocol + (isSecure ? 's' : '');
9
+ return url.toString();
10
+ };
@@ -2,17 +2,18 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
6
5
  import WebSocket from 'isomorphic-ws';
6
+ import { describe, expect, test, onTestFinished } from 'vitest';
7
7
 
8
8
  import { Trigger, TriggerState } from '@dxos/async';
9
- import { describe, test } from '@dxos/test';
10
9
 
11
- import { createTestWsServer } from './test-utils';
10
+ import { createTestEdgeWsServer } from './testing';
12
11
 
13
12
  describe('WebSocket', () => {
14
13
  test('swap `onclose` handler ', async () => {
15
- const { endpoint } = await createTestWsServer();
14
+ const { endpoint, cleanup } = await createTestEdgeWsServer(8003);
15
+ onTestFinished(cleanup);
16
+
16
17
  const ws = new WebSocket(endpoint);
17
18
  const opened = new Trigger();
18
19
  ws.onopen = () => {
@@ -1,11 +0,0 @@
1
- import WebSocket from 'isomorphic-ws';
2
- export declare const DEFAULT_PORT = 8080;
3
- export declare const createTestWsServer: (port?: number) => Promise<{
4
- server: WebSocket.Server;
5
- /**
6
- * Close the server connection.
7
- */
8
- error: () => Promise<void>;
9
- endpoint: string;
10
- }>;
11
- //# sourceMappingURL=test-utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../../src/test-utils.ts"],"names":[],"mappings":"AAIA,OAAO,SAAS,MAAM,eAAe,CAAC;AAWtC,eAAO,MAAM,YAAY,OAAO,CAAC;AAEjC,eAAO,MAAM,kBAAkB;;IAsB3B;;OAEG;;;EAON,CAAC"}
package/src/test-utils.ts DELETED
@@ -1,49 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import WebSocket from 'isomorphic-ws';
6
-
7
- import { Trigger } from '@dxos/async';
8
- import { log } from '@dxos/log';
9
- import { buf } from '@dxos/protocols/buf';
10
- import { MessageSchema, TextMessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
11
- import { afterTest } from '@dxos/test';
12
-
13
- import { protocol } from './defs';
14
- import { toUint8Array } from './protocol';
15
-
16
- export const DEFAULT_PORT = 8080;
17
-
18
- export const createTestWsServer = async (port = DEFAULT_PORT) => {
19
- const server = new WebSocket.Server({ port });
20
- let connection: WebSocket;
21
- const closeTrigger = new Trigger();
22
- server.on('connection', (ws) => {
23
- connection = ws;
24
- ws.on('error', (err) => log.catch(err));
25
- ws.on('message', async (data) => {
26
- if (String(data) === '__ping__') {
27
- ws.send('__pong__');
28
- return;
29
- }
30
- log('message', {
31
- payload: protocol.getPayload(buf.fromBinary(MessageSchema, await toUint8Array(data)), TextMessageSchema),
32
- });
33
- });
34
- ws.on('close', () => closeTrigger.wake());
35
- });
36
-
37
- afterTest(() => server.close());
38
- return {
39
- server,
40
- /**
41
- * Close the server connection.
42
- */
43
- error: async () => {
44
- connection.close(1011);
45
- await closeTrigger.wait();
46
- },
47
- endpoint: `ws://localhost:${port}`,
48
- };
49
- };