@dxos/edge-client 0.8.3 → 0.8.4-main.1c7ec43d41
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.
- package/dist/lib/{browser/chunk-VHS3XEIX.mjs → neutral/chunk-ZIQ5T3A7.mjs} +20 -50
- package/dist/lib/{browser/chunk-VHS3XEIX.mjs.map → neutral/chunk-ZIQ5T3A7.mjs.map} +3 -3
- package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs +1 -1
- package/dist/lib/neutral/index.mjs +1189 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/{browser → neutral}/testing/index.mjs +53 -33
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/auth.d.ts.map +1 -1
- package/dist/types/src/edge-client.d.ts +18 -15
- package/dist/types/src/edge-client.d.ts.map +1 -1
- package/dist/types/src/edge-http-client.d.ts +98 -37
- package/dist/types/src/edge-http-client.d.ts.map +1 -1
- package/dist/types/src/edge-http-client.test.d.ts +2 -0
- package/dist/types/src/edge-http-client.test.d.ts.map +1 -0
- package/dist/types/src/edge-identity.d.ts.map +1 -1
- package/dist/types/src/edge-ws-connection.d.ts +21 -0
- package/dist/types/src/edge-ws-connection.d.ts.map +1 -1
- package/dist/types/src/edge-ws-muxer.d.ts.map +1 -1
- package/dist/types/src/errors.d.ts.map +1 -1
- package/dist/types/src/http-client.d.ts +25 -0
- package/dist/types/src/http-client.d.ts.map +1 -0
- package/dist/types/src/http-client.test.d.ts +2 -0
- package/dist/types/src/http-client.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/protocol.d.ts +1 -1
- package/dist/types/src/protocol.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-server.d.ts +9 -0
- package/dist/types/src/testing/test-server.d.ts.map +1 -0
- package/dist/types/src/testing/test-utils.d.ts +3 -3
- package/dist/types/src/testing/test-utils.d.ts.map +1 -1
- package/dist/types/src/utils.d.ts +1 -1
- package/dist/types/src/utils.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +33 -29
- package/src/edge-client.test.ts +20 -15
- package/src/edge-client.ts +90 -43
- package/src/edge-http-client.test.ts +23 -0
- package/src/edge-http-client.ts +502 -164
- package/src/edge-ws-connection.ts +131 -9
- package/src/edge-ws-muxer.ts +1 -1
- package/src/http-client.test.ts +58 -0
- package/src/http-client.ts +77 -0
- package/src/index.ts +4 -3
- package/src/testing/index.ts +1 -0
- package/src/testing/test-server.ts +45 -0
- package/src/testing/test-utils.ts +9 -9
- package/src/websocket.test.ts +1 -1
- package/dist/lib/browser/index.mjs +0 -1034
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node/chunk-XNHBUTNB.cjs +0 -317
- package/dist/lib/node/chunk-XNHBUTNB.cjs.map +0 -7
- package/dist/lib/node/edge-ws-muxer.cjs +0 -33
- package/dist/lib/node/edge-ws-muxer.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -1060
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/testing/index.cjs +0 -169
- package/dist/lib/node/testing/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-HGQUUFIJ.mjs +0 -299
- package/dist/lib/node-esm/chunk-HGQUUFIJ.mjs.map +0 -7
- package/dist/lib/node-esm/edge-ws-muxer.mjs +0 -12
- package/dist/lib/node-esm/edge-ws-muxer.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -1035
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -141
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- /package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs.map +0 -0
package/package.json
CHANGED
|
@@ -1,61 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/edge-client",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4-main.1c7ec43d41",
|
|
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":
|
|
13
|
+
"sideEffects": false,
|
|
10
14
|
"type": "module",
|
|
11
15
|
"exports": {
|
|
12
16
|
".": {
|
|
17
|
+
"source": "./src/index.ts",
|
|
13
18
|
"types": "./dist/types/src/index.d.ts",
|
|
14
|
-
"
|
|
15
|
-
"node": "./dist/lib/node-esm/index.mjs"
|
|
19
|
+
"default": "./dist/lib/neutral/index.mjs"
|
|
16
20
|
},
|
|
17
21
|
"./muxer": {
|
|
22
|
+
"source": "./src/edge-ws-muxer.ts",
|
|
18
23
|
"types": "./dist/types/src/edge-ws-muxer.d.ts",
|
|
19
|
-
"
|
|
20
|
-
"node": "./dist/lib/node-esm/edge-ws-muxer.mjs"
|
|
24
|
+
"default": "./dist/lib/neutral/edge-ws-muxer.mjs"
|
|
21
25
|
},
|
|
22
26
|
"./testing": {
|
|
27
|
+
"source": "./src/testing/index.ts",
|
|
23
28
|
"types": "./dist/types/src/testing/index.d.ts",
|
|
24
|
-
"
|
|
25
|
-
"node": "./dist/lib/node-esm/testing/index.mjs"
|
|
29
|
+
"default": "./dist/lib/neutral/testing/index.mjs"
|
|
26
30
|
}
|
|
27
31
|
},
|
|
28
32
|
"types": "dist/types/src/index.d.ts",
|
|
29
|
-
"typesVersions": {
|
|
30
|
-
"*": {
|
|
31
|
-
"testing": [
|
|
32
|
-
"dist/types/src/testing/index.d.ts"
|
|
33
|
-
]
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
33
|
"files": [
|
|
37
34
|
"dist",
|
|
38
35
|
"src",
|
|
39
36
|
"README.md"
|
|
40
37
|
],
|
|
41
38
|
"dependencies": {
|
|
39
|
+
"@effect/platform": "0.94.4",
|
|
40
|
+
"@opentelemetry/api": "^1.9.0",
|
|
42
41
|
"isomorphic-ws": "^5.0.0",
|
|
43
|
-
"ws": "^8.
|
|
44
|
-
"@dxos/async": "0.8.
|
|
45
|
-
"@dxos/context": "0.8.
|
|
46
|
-
"@dxos/credentials": "0.8.
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/invariant": "0.8.
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/
|
|
42
|
+
"ws": "^8.17.1",
|
|
43
|
+
"@dxos/async": "0.8.4-main.1c7ec43d41",
|
|
44
|
+
"@dxos/context": "0.8.4-main.1c7ec43d41",
|
|
45
|
+
"@dxos/credentials": "0.8.4-main.1c7ec43d41",
|
|
46
|
+
"@dxos/crypto": "0.8.4-main.1c7ec43d41",
|
|
47
|
+
"@dxos/invariant": "0.8.4-main.1c7ec43d41",
|
|
48
|
+
"@dxos/effect": "0.8.4-main.1c7ec43d41",
|
|
49
|
+
"@dxos/debug": "0.8.4-main.1c7ec43d41",
|
|
50
|
+
"@dxos/keyring": "0.8.4-main.1c7ec43d41",
|
|
51
|
+
"@dxos/keys": "0.8.4-main.1c7ec43d41",
|
|
52
|
+
"@dxos/log": "0.8.4-main.1c7ec43d41",
|
|
53
|
+
"@dxos/node-std": "0.8.4-main.1c7ec43d41",
|
|
54
|
+
"@dxos/tracing": "0.8.4-main.1c7ec43d41",
|
|
55
|
+
"@dxos/util": "0.8.4-main.1c7ec43d41",
|
|
56
|
+
"@dxos/protocols": "0.8.4-main.1c7ec43d41"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
|
-
"@dxos/test-utils": "0.8.
|
|
59
|
+
"@dxos/test-utils": "0.8.4-main.1c7ec43d41"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"effect": "3.20.0"
|
|
59
63
|
},
|
|
60
64
|
"publishConfig": {
|
|
61
65
|
"access": "public"
|
package/src/edge-client.test.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
|
package/src/edge-client.ts
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
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';
|
|
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';
|
|
9
17
|
import { EdgeStatus } from '@dxos/protocols/proto/dxos/client/services';
|
|
@@ -17,9 +25,21 @@ 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
|
|
|
34
|
+
export type MessengerConfig = {
|
|
35
|
+
socketEndpoint: string;
|
|
36
|
+
timeout?: number;
|
|
37
|
+
protocol?: Protocol;
|
|
38
|
+
disableAuth?: boolean;
|
|
39
|
+
/** Sent as `X-DXOS-Client-Tag` on the WebSocket upgrade (Node/`ws` only; ignored in browsers). */
|
|
40
|
+
clientTag?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
23
43
|
export interface EdgeConnection extends Required<Lifecycle> {
|
|
24
44
|
statusChanged: Event<EdgeStatus>;
|
|
25
45
|
get info(): any;
|
|
@@ -28,18 +48,11 @@ export interface EdgeConnection extends Required<Lifecycle> {
|
|
|
28
48
|
get isOpen(): boolean;
|
|
29
49
|
get status(): EdgeStatus;
|
|
30
50
|
setIdentity(identity: EdgeIdentity): void;
|
|
51
|
+
send(ctx: Context, message: Message): Promise<void>;
|
|
31
52
|
onMessage(listener: MessageListener): () => void;
|
|
32
53
|
onReconnected(listener: ReconnectListener): () => void;
|
|
33
|
-
send(message: Message): Promise<void>;
|
|
34
54
|
}
|
|
35
55
|
|
|
36
|
-
export type MessengerConfig = {
|
|
37
|
-
socketEndpoint: string;
|
|
38
|
-
timeout?: number;
|
|
39
|
-
protocol?: Protocol;
|
|
40
|
-
disableAuth?: boolean;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
56
|
/**
|
|
44
57
|
* Messenger client for EDGE:
|
|
45
58
|
* - While open, uses PersistentLifecycle to keep an open EdgeWsConnection, reconnecting on failures.
|
|
@@ -81,9 +94,18 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
81
94
|
}
|
|
82
95
|
|
|
83
96
|
get status(): EdgeStatus {
|
|
84
|
-
return
|
|
85
|
-
|
|
86
|
-
|
|
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() {
|
|
@@ -94,7 +116,7 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
94
116
|
return this._identity.peerKey;
|
|
95
117
|
}
|
|
96
118
|
|
|
97
|
-
setIdentity(identity: EdgeIdentity)
|
|
119
|
+
setIdentity(identity: EdgeIdentity) {
|
|
98
120
|
if (identity.identityKey !== this._identity.identityKey || identity.peerKey !== this._identity.peerKey) {
|
|
99
121
|
log('Edge identity changed', { identity, oldIdentity: this._identity });
|
|
100
122
|
this._identity = identity;
|
|
@@ -103,12 +125,45 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
103
125
|
}
|
|
104
126
|
}
|
|
105
127
|
|
|
106
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Send message.
|
|
130
|
+
* NOTE: The message is guaranteed to be delivered but the service must respond with a message to confirm processing.
|
|
131
|
+
*/
|
|
132
|
+
public async send(ctx: Context, message: Message) {
|
|
133
|
+
if (this._ready.state !== TriggerState.RESOLVED) {
|
|
134
|
+
log('waiting for websocket');
|
|
135
|
+
await this._ready.wait({ timeout: this._config.timeout ?? DEFAULT_TIMEOUT });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!this._currentConnection) {
|
|
139
|
+
throw new EdgeConnectionClosedError();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
message.source &&
|
|
144
|
+
(message.source.peerKey !== this._identity.peerKey || message.source.identityKey !== this.identityKey)
|
|
145
|
+
) {
|
|
146
|
+
throw new EdgeIdentityChangedError();
|
|
147
|
+
}
|
|
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
|
+
|
|
158
|
+
this._currentConnection.send(message);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public onMessage(listener: MessageListener) {
|
|
107
162
|
this._messageListeners.add(listener);
|
|
108
163
|
return () => this._messageListeners.delete(listener);
|
|
109
164
|
}
|
|
110
165
|
|
|
111
|
-
public onReconnected(listener: () => void)
|
|
166
|
+
public onReconnected(listener: () => void) {
|
|
112
167
|
this._reconnectListeners.add(listener);
|
|
113
168
|
if (this._ready.state === TriggerState.RESOLVED) {
|
|
114
169
|
// Microtask so that listener is always called asynchronously, no matter the state of the ready trigger
|
|
@@ -123,6 +178,7 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
123
178
|
}
|
|
124
179
|
});
|
|
125
180
|
}
|
|
181
|
+
|
|
126
182
|
return () => this._reconnectListeners.delete(listener);
|
|
127
183
|
}
|
|
128
184
|
|
|
@@ -134,6 +190,18 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
134
190
|
this._persistentLifecycle.open().catch((err) => {
|
|
135
191
|
log.warn('Error while opening connection', { err });
|
|
136
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
|
+
);
|
|
137
205
|
}
|
|
138
206
|
|
|
139
207
|
/**
|
|
@@ -163,7 +231,11 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
163
231
|
log('Opening websocket', { url: url.toString(), protocolHeader });
|
|
164
232
|
const connection = new EdgeWsConnection(
|
|
165
233
|
identity,
|
|
166
|
-
{
|
|
234
|
+
{
|
|
235
|
+
url,
|
|
236
|
+
protocolHeader,
|
|
237
|
+
headers: this._config.clientTag ? { 'X-DXOS-Client-Tag': this._config.clientTag } : undefined,
|
|
238
|
+
},
|
|
167
239
|
{
|
|
168
240
|
onConnected: () => {
|
|
169
241
|
if (this._isActive(connection)) {
|
|
@@ -200,7 +272,6 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
200
272
|
// Race with restartRequired so that restart is not blocked by _connect execution.
|
|
201
273
|
// Wait on ready to attempt a reconnect if it times out.
|
|
202
274
|
await Promise.race([this._ready.wait({ timeout: this._config.timeout ?? DEFAULT_TIMEOUT }), restartRequired]);
|
|
203
|
-
|
|
204
275
|
return connection;
|
|
205
276
|
}
|
|
206
277
|
|
|
@@ -237,30 +308,6 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
237
308
|
}
|
|
238
309
|
}
|
|
239
310
|
|
|
240
|
-
/**
|
|
241
|
-
* Send message.
|
|
242
|
-
* NOTE: The message is guaranteed to be delivered but the service must respond with a message to confirm processing.
|
|
243
|
-
*/
|
|
244
|
-
public async send(message: Message): Promise<void> {
|
|
245
|
-
if (this._ready.state !== TriggerState.RESOLVED) {
|
|
246
|
-
log('waiting for websocket to become ready');
|
|
247
|
-
await this._ready.wait({ timeout: this._config.timeout ?? DEFAULT_TIMEOUT });
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (!this._currentConnection) {
|
|
251
|
-
throw new EdgeConnectionClosedError();
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (
|
|
255
|
-
message.source &&
|
|
256
|
-
(message.source.peerKey !== this._identity.peerKey || message.source.identityKey !== this.identityKey)
|
|
257
|
-
) {
|
|
258
|
-
throw new EdgeIdentityChangedError();
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
this._currentConnection.send(message);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
311
|
private async _createAuthHeader(path: string): Promise<string | undefined> {
|
|
265
312
|
const httpUrl = new URL(path, this._baseHttpUrl);
|
|
266
313
|
httpUrl.protocol = getEdgeUrlWithProtocol(this._baseWsUrl.toString(), 'http');
|
|
@@ -277,7 +324,7 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
277
324
|
}
|
|
278
325
|
|
|
279
326
|
const encodePresentationWsAuthHeader = (encodedPresentation: Uint8Array): string => {
|
|
280
|
-
// = and / characters are not allowed in the WebSocket subprotocol header.
|
|
327
|
+
// '=' and '/' characters are not allowed in the WebSocket subprotocol header.
|
|
281
328
|
const encodedToken = Buffer.from(encodedPresentation).toString('base64').replace(/=*$/, '').replaceAll('/', '|');
|
|
282
329
|
return `base64url.bearer.authorization.dxos.org.${encodedToken}`;
|
|
283
330
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, it } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { createEphemeralEdgeIdentity } from './auth';
|
|
8
|
+
import { EdgeHttpClient } from './edge-http-client';
|
|
9
|
+
|
|
10
|
+
// TODO(burdon): Factor out config.
|
|
11
|
+
const DEV_SERVER = 'https://edge.dxos.workers.dev';
|
|
12
|
+
|
|
13
|
+
describe.skipIf(process.env.CI)('EdgeHttpClient', () => {
|
|
14
|
+
it.skip('should get status', async ({ expect }) => {
|
|
15
|
+
const client = new EdgeHttpClient(DEV_SERVER);
|
|
16
|
+
const identity = await createEphemeralEdgeIdentity();
|
|
17
|
+
client.setIdentity(identity);
|
|
18
|
+
|
|
19
|
+
const { Context } = await import('@dxos/context');
|
|
20
|
+
const result = await client.getStatus(Context.default());
|
|
21
|
+
expect(result).toBeDefined();
|
|
22
|
+
});
|
|
23
|
+
});
|