@dxos/edge-client 0.8.4-main.a4bbb77 → 0.8.4-main.abd8ff62ef

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 (50) hide show
  1. package/dist/lib/{browser/chunk-IKP53CBQ.mjs → neutral/chunk-ZIQ5T3A7.mjs} +20 -83
  2. package/dist/lib/{browser/chunk-IKP53CBQ.mjs.map → neutral/chunk-ZIQ5T3A7.mjs.map} +2 -2
  3. package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs +1 -1
  4. package/dist/lib/{browser → neutral}/index.mjs +365 -439
  5. package/dist/lib/neutral/index.mjs.map +7 -0
  6. package/dist/lib/neutral/meta.json +1 -0
  7. package/dist/lib/{browser → neutral}/testing/index.mjs +6 -31
  8. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  9. package/dist/types/src/auth.d.ts.map +1 -1
  10. package/dist/types/src/edge-client.d.ts +5 -2
  11. package/dist/types/src/edge-client.d.ts.map +1 -1
  12. package/dist/types/src/edge-http-client.d.ts +61 -30
  13. package/dist/types/src/edge-http-client.d.ts.map +1 -1
  14. package/dist/types/src/edge-identity.d.ts.map +1 -1
  15. package/dist/types/src/edge-ws-connection.d.ts +20 -0
  16. package/dist/types/src/edge-ws-connection.d.ts.map +1 -1
  17. package/dist/types/src/edge-ws-muxer.d.ts.map +1 -1
  18. package/dist/types/src/errors.d.ts.map +1 -1
  19. package/dist/types/src/http-client.d.ts +10 -7
  20. package/dist/types/src/http-client.d.ts.map +1 -1
  21. package/dist/types/src/protocol.d.ts +1 -1
  22. package/dist/types/src/protocol.d.ts.map +1 -1
  23. package/dist/types/src/testing/test-server.d.ts.map +1 -1
  24. package/dist/types/src/testing/test-utils.d.ts +3 -3
  25. package/dist/types/src/testing/test-utils.d.ts.map +1 -1
  26. package/dist/types/src/utils.d.ts +1 -1
  27. package/dist/types/src/utils.d.ts.map +1 -1
  28. package/dist/types/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +28 -31
  30. package/src/edge-client.test.ts +20 -15
  31. package/src/edge-client.ts +55 -8
  32. package/src/edge-http-client.test.ts +3 -2
  33. package/src/edge-http-client.ts +207 -78
  34. package/src/edge-ws-connection.ts +120 -6
  35. package/src/http-client.test.ts +9 -6
  36. package/src/http-client.ts +18 -8
  37. package/src/testing/test-utils.ts +7 -7
  38. package/dist/lib/browser/index.mjs.map +0 -7
  39. package/dist/lib/browser/meta.json +0 -1
  40. package/dist/lib/browser/testing/index.mjs.map +0 -7
  41. package/dist/lib/node-esm/chunk-DR5YNW5K.mjs +0 -332
  42. package/dist/lib/node-esm/chunk-DR5YNW5K.mjs.map +0 -7
  43. package/dist/lib/node-esm/edge-ws-muxer.mjs +0 -12
  44. package/dist/lib/node-esm/edge-ws-muxer.mjs.map +0 -7
  45. package/dist/lib/node-esm/index.mjs +0 -1251
  46. package/dist/lib/node-esm/index.mjs.map +0 -7
  47. package/dist/lib/node-esm/meta.json +0 -1
  48. package/dist/lib/node-esm/testing/index.mjs +0 -186
  49. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  50. /package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs.map +0 -0
package/package.json CHANGED
@@ -1,68 +1,65 @@
1
1
  {
2
2
  "name": "@dxos/edge-client",
3
- "version": "0.8.4-main.a4bbb77",
3
+ "version": "0.8.4-main.abd8ff62ef",
4
4
  "description": "EDGE Client",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
7
11
  "license": "MIT",
8
12
  "author": "DXOS.org",
9
- "sideEffects": true,
13
+ "sideEffects": false,
10
14
  "type": "module",
11
15
  "exports": {
12
16
  ".": {
13
17
  "source": "./src/index.ts",
14
18
  "types": "./dist/types/src/index.d.ts",
15
- "browser": "./dist/lib/browser/index.mjs",
16
- "node": "./dist/lib/node-esm/index.mjs"
19
+ "default": "./dist/lib/neutral/index.mjs"
17
20
  },
18
21
  "./muxer": {
19
22
  "source": "./src/edge-ws-muxer.ts",
20
23
  "types": "./dist/types/src/edge-ws-muxer.d.ts",
21
- "browser": "./dist/lib/browser/edge-ws-muxer.mjs",
22
- "node": "./dist/lib/node-esm/edge-ws-muxer.mjs"
24
+ "default": "./dist/lib/neutral/edge-ws-muxer.mjs"
23
25
  },
24
26
  "./testing": {
25
27
  "source": "./src/testing/index.ts",
26
28
  "types": "./dist/types/src/testing/index.d.ts",
27
- "browser": "./dist/lib/browser/testing/index.mjs",
28
- "node": "./dist/lib/node-esm/testing/index.mjs"
29
+ "default": "./dist/lib/neutral/testing/index.mjs"
29
30
  }
30
31
  },
31
32
  "types": "dist/types/src/index.d.ts",
32
- "typesVersions": {
33
- "*": {
34
- "testing": [
35
- "dist/types/src/testing/index.d.ts"
36
- ]
37
- }
38
- },
39
33
  "files": [
40
34
  "dist",
41
35
  "src",
42
36
  "README.md"
43
37
  ],
44
38
  "dependencies": {
45
- "@effect/platform": "^0.92.1",
39
+ "@effect/platform": "0.94.4",
40
+ "@opentelemetry/api": "^1.9.0",
46
41
  "isomorphic-ws": "^5.0.0",
47
- "ws": "^8.14.2",
48
- "@dxos/async": "0.8.4-main.a4bbb77",
49
- "@dxos/context": "0.8.4-main.a4bbb77",
50
- "@dxos/credentials": "0.8.4-main.a4bbb77",
51
- "@dxos/crypto": "0.8.4-main.a4bbb77",
52
- "@dxos/debug": "0.8.4-main.a4bbb77",
53
- "@dxos/invariant": "0.8.4-main.a4bbb77",
54
- "@dxos/log": "0.8.4-main.a4bbb77",
55
- "@dxos/keyring": "0.8.4-main.a4bbb77",
56
- "@dxos/keys": "0.8.4-main.a4bbb77",
57
- "@dxos/node-std": "0.8.4-main.a4bbb77",
58
- "@dxos/protocols": "0.8.4-main.a4bbb77",
59
- "@dxos/util": "0.8.4-main.a4bbb77"
42
+ "ws": "^8.17.1",
43
+ "@dxos/async": "0.8.4-main.abd8ff62ef",
44
+ "@dxos/context": "0.8.4-main.abd8ff62ef",
45
+ "@dxos/crypto": "0.8.4-main.abd8ff62ef",
46
+ "@dxos/credentials": "0.8.4-main.abd8ff62ef",
47
+ "@dxos/debug": "0.8.4-main.abd8ff62ef",
48
+ "@dxos/effect": "0.8.4-main.abd8ff62ef",
49
+ "@dxos/invariant": "0.8.4-main.abd8ff62ef",
50
+ "@dxos/keys": "0.8.4-main.abd8ff62ef",
51
+ "@dxos/keyring": "0.8.4-main.abd8ff62ef",
52
+ "@dxos/node-std": "0.8.4-main.abd8ff62ef",
53
+ "@dxos/log": "0.8.4-main.abd8ff62ef",
54
+ "@dxos/protocols": "0.8.4-main.abd8ff62ef",
55
+ "@dxos/tracing": "0.8.4-main.abd8ff62ef",
56
+ "@dxos/util": "0.8.4-main.abd8ff62ef"
60
57
  },
61
58
  "devDependencies": {
62
- "@dxos/test-utils": "0.8.4-main.a4bbb77"
59
+ "@dxos/test-utils": "0.8.4-main.abd8ff62ef"
63
60
  },
64
61
  "peerDependencies": {
65
- "effect": "^3.13.3"
62
+ "effect": "3.20.0"
66
63
  },
67
64
  "publishConfig": {
68
65
  "access": "public"
@@ -5,6 +5,7 @@
5
5
  import { describe, expect, onTestFinished, test } from 'vitest';
6
6
 
7
7
  import { Trigger } from '@dxos/async';
8
+ import { Context } from '@dxos/context';
8
9
  import { Keyring } from '@dxos/keyring';
9
10
  import { TextMessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
10
11
  import { EdgeStatus } from '@dxos/protocols/proto/dxos/client/services';
@@ -25,13 +26,13 @@ describe('EdgeClient', () => {
25
26
  onTestFinished(cleanup);
26
27
 
27
28
  const { client, reconnectTrigger } = await openNewClient(endpoint);
28
- await client.send(textMessage('Hello world 1'));
29
+ await client.send(Context.default(), textMessage('Hello world 1'));
29
30
  expect(client.isOpen).is.true;
30
31
 
31
32
  reconnectTrigger.reset();
32
33
  await closeConnection();
33
34
  await reconnectTrigger.wait();
34
- await expect(client.send(textMessage('Hello world 2'))).resolves.not.toThrow();
35
+ await expect(client.send(Context.default(), textMessage('Hello world 2'))).resolves.not.toThrow();
35
36
  });
36
37
 
37
38
  test('isConnected', async () => {
@@ -41,17 +42,17 @@ describe('EdgeClient', () => {
41
42
 
42
43
  const { client } = await openNewClient(endpoint);
43
44
 
44
- expect(client.status).toBe(EdgeStatus.NOT_CONNECTED);
45
+ expect(client.status.state).toBe(EdgeStatus.ConnectionState.NOT_CONNECTED);
45
46
  admitConnection.wake();
46
- await expect.poll(() => client.status).toBe(EdgeStatus.CONNECTED);
47
+ await expect.poll(() => client.status.state).toBe(EdgeStatus.ConnectionState.CONNECTED);
47
48
 
48
49
  admitConnection.reset();
49
50
  await closeConnection();
50
51
  expect(client.isOpen).is.true;
51
- await expect.poll(() => client.status).toBe(EdgeStatus.NOT_CONNECTED);
52
+ await expect.poll(() => client.status.state).toBe(EdgeStatus.ConnectionState.NOT_CONNECTED);
52
53
 
53
54
  admitConnection.wake();
54
- await expect.poll(() => client.status).toBe(EdgeStatus.CONNECTED);
55
+ await expect.poll(() => client.status.state).toBe(EdgeStatus.ConnectionState.CONNECTED);
55
56
  });
56
57
 
57
58
  test('set identity reconnects', async () => {
@@ -59,13 +60,13 @@ describe('EdgeClient', () => {
59
60
  onTestFinished(cleanup);
60
61
 
61
62
  const { client, reconnectTrigger } = await openNewClient(endpoint);
62
- await client.send(textMessage('Hello world 1'));
63
+ await client.send(Context.default(), textMessage('Hello world 1'));
63
64
  expect(client.isOpen).is.true;
64
65
 
65
66
  reconnectTrigger.reset();
66
67
  client.setIdentity(await createEphemeralEdgeIdentity());
67
68
  await reconnectTrigger.wait();
68
- await expect(client.send(textMessage('Hello world 2'))).resolves.not.toThrow();
69
+ await expect(client.send(Context.default(), textMessage('Hello world 2'))).resolves.not.toThrow();
69
70
  });
70
71
 
71
72
  test('send blocks until connection becomes ready', async () => {
@@ -75,7 +76,7 @@ describe('EdgeClient', () => {
75
76
 
76
77
  const { client } = await openNewClient(endpoint);
77
78
  setTimeout(() => admitConnection.wake(), 20);
78
- await client.send(textMessage('Hello world 1'));
79
+ await client.send(Context.default(), textMessage('Hello world 1'));
79
80
  await expect.poll(() => messageSink.length).toBe(1);
80
81
  });
81
82
 
@@ -86,11 +87,13 @@ describe('EdgeClient', () => {
86
87
 
87
88
  const { client } = await openNewClient(endpoint);
88
89
  setTimeout(async () => client.setIdentity(await createEphemeralEdgeIdentity()));
89
- await expect(client.send(textMessage('Hello world 1'))).rejects.toThrow(EdgeIdentityChangedError);
90
+ await expect(client.send(Context.default(), textMessage('Hello world 1'))).rejects.toThrow(
91
+ EdgeIdentityChangedError,
92
+ );
90
93
 
91
94
  // Test recovers.
92
95
  setTimeout(() => admitConnection.wake(), 20);
93
- await client.send(textMessage('Hello world 1'));
96
+ await client.send(Context.default(), textMessage('Hello world 1'));
94
97
  await expect.poll(() => messageSink.length).toBe(1);
95
98
  });
96
99
 
@@ -101,7 +104,9 @@ describe('EdgeClient', () => {
101
104
 
102
105
  const { client } = await openNewClient(endpoint);
103
106
  setTimeout(() => client.close());
104
- await expect(client.send(textMessage('Hello world 1'))).rejects.toThrow(EdgeConnectionClosedError);
107
+ await expect(client.send(Context.default(), textMessage('Hello world 1'))).rejects.toThrow(
108
+ EdgeConnectionClosedError,
109
+ );
105
110
  });
106
111
 
107
112
  test('onReconnect trigger', async () => {
@@ -129,12 +134,12 @@ describe('EdgeClient', () => {
129
134
  onTestFinished(cleanup);
130
135
 
131
136
  const { client, identity: oldIdentity } = await openNewClient(endpoint);
132
- await client.send(textMessage('Hello world 1', oldIdentity));
137
+ await client.send(Context.default(), textMessage('Hello world 1', oldIdentity));
133
138
  expect(client.isOpen).is.true;
134
139
 
135
140
  const newIdentity = await createEphemeralEdgeIdentity();
136
141
  client.setIdentity(newIdentity);
137
- await client.send(textMessage('Hello world 2', newIdentity));
142
+ await client.send(Context.default(), textMessage('Hello world 2', newIdentity));
138
143
  await expect.poll(() => messageSourceLog.length).toBe(2);
139
144
  expect(messageSourceLog.map((m) => m.peerKey)).toStrictEqual([oldIdentity.peerKey, newIdentity.peerKey]);
140
145
  });
@@ -147,7 +152,7 @@ describe('EdgeClient', () => {
147
152
 
148
153
  const client = new EdgeClient(identity, { socketEndpoint: process.env.EDGE_ENDPOINT! });
149
154
  await openAndClose(client);
150
- await client.send(textMessage('Hello world 1'));
155
+ await client.send(Context.default(), textMessage('Hello world 1'));
151
156
  expect(client.isOpen).is.true;
152
157
  });
153
158
 
@@ -2,7 +2,15 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Event, PersistentLifecycle, Trigger, TriggerState, scheduleMicroTask } from '@dxos/async';
5
+ import {
6
+ Event,
7
+ PersistentLifecycle,
8
+ Trigger,
9
+ TriggerState,
10
+ scheduleMicroTask,
11
+ scheduleTaskInterval,
12
+ } from '@dxos/async';
13
+ import { Context, TRACE_SPAN_ATTRIBUTE, type TraceContextData } from '@dxos/context';
6
14
  import { type Lifecycle, Resource } from '@dxos/context';
7
15
  import { log, logInfo } from '@dxos/log';
8
16
  import { type Message } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
@@ -17,6 +25,9 @@ import { getEdgeUrlWithProtocol } from './utils';
17
25
 
18
26
  const DEFAULT_TIMEOUT = 10_000;
19
27
 
28
+ // Refresh status every second: rtt, rate counters.
29
+ const STATUS_REFRESH_INTERVAL = 1000;
30
+
20
31
  export type MessageListener = (message: Message) => void;
21
32
  export type ReconnectListener = () => void;
22
33
 
@@ -25,6 +36,8 @@ export type MessengerConfig = {
25
36
  timeout?: number;
26
37
  protocol?: Protocol;
27
38
  disableAuth?: boolean;
39
+ /** Sent as `X-DXOS-Client-Tag` on the WebSocket upgrade (Node/`ws` only; ignored in browsers). */
40
+ clientTag?: string;
28
41
  };
29
42
 
30
43
  export interface EdgeConnection extends Required<Lifecycle> {
@@ -35,7 +48,7 @@ export interface EdgeConnection extends Required<Lifecycle> {
35
48
  get isOpen(): boolean;
36
49
  get status(): EdgeStatus;
37
50
  setIdentity(identity: EdgeIdentity): void;
38
- send(message: Message): Promise<void>;
51
+ send(ctx: Context, message: Message): Promise<void>;
39
52
  onMessage(listener: MessageListener): () => void;
40
53
  onReconnected(listener: ReconnectListener): () => void;
41
54
  }
@@ -80,10 +93,19 @@ export class EdgeClient extends Resource implements EdgeConnection {
80
93
  };
81
94
  }
82
95
 
83
- get status() {
84
- return Boolean(this._currentConnection) && this._ready.state === TriggerState.RESOLVED
85
- ? EdgeStatus.CONNECTED
86
- : EdgeStatus.NOT_CONNECTED;
96
+ get status(): EdgeStatus {
97
+ return {
98
+ state:
99
+ Boolean(this._currentConnection) && this._ready.state === TriggerState.RESOLVED
100
+ ? EdgeStatus.ConnectionState.CONNECTED
101
+ : EdgeStatus.ConnectionState.NOT_CONNECTED,
102
+ uptime: this._currentConnection?.uptime ?? 0,
103
+ rtt: this._currentConnection?.rtt ?? 0,
104
+ rateBytesUp: this._currentConnection?.uploadRate ?? 0,
105
+ rateBytesDown: this._currentConnection?.downloadRate ?? 0,
106
+ messagesSent: this._currentConnection?.messagesSent ?? 0,
107
+ messagesReceived: this._currentConnection?.messagesReceived ?? 0,
108
+ };
87
109
  }
88
110
 
89
111
  get identityKey() {
@@ -107,7 +129,7 @@ export class EdgeClient extends Resource implements EdgeConnection {
107
129
  * Send message.
108
130
  * NOTE: The message is guaranteed to be delivered but the service must respond with a message to confirm processing.
109
131
  */
110
- public async send(message: Message) {
132
+ public async send(ctx: Context, message: Message) {
111
133
  if (this._ready.state !== TriggerState.RESOLVED) {
112
134
  log('waiting for websocket');
113
135
  await this._ready.wait({ timeout: this._config.timeout ?? DEFAULT_TIMEOUT });
@@ -124,6 +146,15 @@ export class EdgeClient extends Resource implements EdgeConnection {
124
146
  throw new EdgeIdentityChangedError();
125
147
  }
126
148
 
149
+ const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE) as TraceContextData | undefined;
150
+ if (traceCtx) {
151
+ message.traceContext = {
152
+ $typeName: 'dxos.edge.messenger.TraceContext',
153
+ traceparent: traceCtx.traceparent,
154
+ tracestate: traceCtx.tracestate,
155
+ };
156
+ }
157
+
127
158
  this._currentConnection.send(message);
128
159
  }
129
160
 
@@ -159,6 +190,18 @@ export class EdgeClient extends Resource implements EdgeConnection {
159
190
  this._persistentLifecycle.open().catch((err) => {
160
191
  log.warn('Error while opening connection', { err });
161
192
  });
193
+
194
+ // Notify about status changes (rtt, rate counters).
195
+ scheduleTaskInterval(
196
+ this._ctx,
197
+ async () => {
198
+ if (!this._currentConnection) {
199
+ return;
200
+ }
201
+ this.statusChanged.emit(this.status);
202
+ },
203
+ STATUS_REFRESH_INTERVAL,
204
+ );
162
205
  }
163
206
 
164
207
  /**
@@ -188,7 +231,11 @@ export class EdgeClient extends Resource implements EdgeConnection {
188
231
  log('Opening websocket', { url: url.toString(), protocolHeader });
189
232
  const connection = new EdgeWsConnection(
190
233
  identity,
191
- { url, protocolHeader },
234
+ {
235
+ url,
236
+ protocolHeader,
237
+ headers: this._config.clientTag ? { 'X-DXOS-Client-Tag': this._config.clientTag } : undefined,
238
+ },
192
239
  {
193
240
  onConnected: () => {
194
241
  if (this._isActive(connection)) {
@@ -11,12 +11,13 @@ import { EdgeHttpClient } from './edge-http-client';
11
11
  const DEV_SERVER = 'https://edge.dxos.workers.dev';
12
12
 
13
13
  describe.skipIf(process.env.CI)('EdgeHttpClient', () => {
14
- it.only('should get status', async ({ expect }) => {
14
+ it.skip('should get status', async ({ expect }) => {
15
15
  const client = new EdgeHttpClient(DEV_SERVER);
16
16
  const identity = await createEphemeralEdgeIdentity();
17
17
  client.setIdentity(identity);
18
18
 
19
- const result = await client.getStatus();
19
+ const { Context } = await import('@dxos/context');
20
+ const result = await client.getStatus(Context.default());
20
21
  expect(result).toBeDefined();
21
22
  });
22
23
  });