@dxos/client-services 0.6.12-main.15a606f → 0.6.12-main.78ddbdf

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 (28) hide show
  1. package/dist/lib/browser/{chunk-BDGSOBZV.mjs → chunk-XSFLJVDP.mjs} +105 -61
  2. package/dist/lib/browser/chunk-XSFLJVDP.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1 -1
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/testing/index.mjs +1 -1
  6. package/dist/lib/node/{chunk-EBZ7KFXS.cjs → chunk-F3WGFGEN.cjs} +87 -43
  7. package/dist/lib/node/chunk-F3WGFGEN.cjs.map +7 -0
  8. package/dist/lib/node/index.cjs +45 -45
  9. package/dist/lib/node/meta.json +1 -1
  10. package/dist/lib/node/testing/index.cjs +8 -8
  11. package/dist/lib/node-esm/{chunk-LDBJYNPL.mjs → chunk-3HDLTAT2.mjs} +105 -61
  12. package/dist/lib/node-esm/chunk-3HDLTAT2.mjs.map +7 -0
  13. package/dist/lib/node-esm/index.mjs +1 -1
  14. package/dist/lib/node-esm/meta.json +1 -1
  15. package/dist/lib/node-esm/testing/index.mjs +1 -1
  16. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +1 -0
  17. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  18. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +2 -0
  19. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +1 -0
  20. package/dist/types/src/version.d.ts +1 -1
  21. package/package.json +39 -38
  22. package/src/packlets/identity/identity.test.ts +4 -0
  23. package/src/packlets/spaces/edge-feed-replicator.test.ts +244 -0
  24. package/src/packlets/spaces/edge-feed-replicator.ts +50 -17
  25. package/src/version.ts +1 -1
  26. package/dist/lib/browser/chunk-BDGSOBZV.mjs.map +0 -7
  27. package/dist/lib/node/chunk-EBZ7KFXS.cjs.map +0 -7
  28. package/dist/lib/node-esm/chunk-LDBJYNPL.mjs.map +0 -7
@@ -6,7 +6,7 @@ import {
6
6
  InvitationsManager,
7
7
  ServiceContext,
8
8
  SpaceInvitationProtocol
9
- } from "../chunk-LDBJYNPL.mjs";
9
+ } from "../chunk-3HDLTAT2.mjs";
10
10
 
11
11
  // packages/sdk/client-services/src/packlets/testing/credential-utils.ts
12
12
  import { createCredential } from "@dxos/credentials";
@@ -23,6 +23,7 @@ export declare class EdgeFeedReplicator extends Resource {
23
23
  constructor({ messenger, spaceId }: EdgeFeedReplicatorParams);
24
24
  protected _open(): Promise<void>;
25
25
  protected _close(): Promise<void>;
26
+ private _resetConnection;
26
27
  addFeed(feed: FeedWrapper<any>): Promise<void>;
27
28
  private _getPushMutex;
28
29
  private _replicateFeed;
@@ -1 +1 @@
1
- {"version":3,"file":"edge-feed-replicator.d.ts","sourceRoot":"","sources":["../../../../../src/packlets/spaces/edge-feed-replicator.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,QAAQ,EAAgB,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAQrD,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE,cAAc,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,qBAAa,kBAAmB,SAAQ,QAAQ;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+D;IAEtF,OAAO,CAAC,cAAc,CAAC,CAAsB;IAC7C,OAAO,CAAC,UAAU,CAAS;IAC3B;;OAEG;IACH,OAAO,CAAC,aAAa,CAAqD;IAE1E;;OAEG;IACH,OAAO,CAAC,UAAU,CAAoD;gBAE1D,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,wBAAwB;cAMnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;cA+BtB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAS1C,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC;IASpC,OAAO,CAAC,aAAa;YAIP,cAAc;YAad,YAAY;IAoB1B,OAAO,CAAC,UAAU;YAmDJ,WAAW;YA0BX,gBAAgB;YAkBhB,mBAAmB;CAalC"}
1
+ {"version":3,"file":"edge-feed-replicator.d.ts","sourceRoot":"","sources":["../../../../../src/packlets/spaces/edge-feed-replicator.ts"],"names":[],"mappings":"AAOA,OAAO,EAAW,QAAQ,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAWrD,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE,cAAc,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,qBAAa,kBAAmB,SAAQ,QAAQ;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+D;IAEtF,OAAO,CAAC,cAAc,CAAC,CAAsB;IAC7C,OAAO,CAAC,UAAU,CAAS;IAC3B;;OAEG;IACH,OAAO,CAAC,aAAa,CAAqD;IAE1E;;OAEG;IACH,OAAO,CAAC,UAAU,CAAoD;gBAE1D,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,wBAAwB;cAMnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;cAmDtB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;YAIlC,gBAAgB;IAOxB,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC;IASpC,OAAO,CAAC,aAAa;YAIP,cAAc;YAWd,YAAY;IAyB1B,OAAO,CAAC,UAAU;YAuDJ,WAAW;YA0BX,gBAAgB;YAkBhB,mBAAmB;CAalC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=edge-feed-replicator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge-feed-replicator.test.d.ts","sourceRoot":"","sources":["../../../../../src/packlets/spaces/edge-feed-replicator.test.ts"],"names":[],"mappings":""}
@@ -1,2 +1,2 @@
1
- export declare const DXOS_VERSION = "0.6.12-main.15a606f";
1
+ export declare const DXOS_VERSION = "0.6.12-main.78ddbdf";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/client-services",
3
- "version": "0.6.12-main.15a606f",
3
+ "version": "0.6.12-main.78ddbdf",
4
4
  "description": "DXOS client services implementation",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -47,47 +47,48 @@
47
47
  "dependencies": {
48
48
  "cbor-x": "^1.5.4",
49
49
  "platform": "^1.3.6",
50
- "@dxos/async": "0.6.12-main.15a606f",
51
- "@dxos/automerge": "0.6.12-main.15a606f",
52
- "@dxos/client-protocol": "0.6.12-main.15a606f",
53
- "@dxos/codec-protobuf": "0.6.12-main.15a606f",
54
- "@dxos/context": "0.6.12-main.15a606f",
55
- "@dxos/config": "0.6.12-main.15a606f",
56
- "@dxos/credentials": "0.6.12-main.15a606f",
57
- "@dxos/crypto": "0.6.12-main.15a606f",
58
- "@dxos/echo-db": "0.6.12-main.15a606f",
59
- "@dxos/echo-pipeline": "0.6.12-main.15a606f",
60
- "@dxos/echo-protocol": "0.6.12-main.15a606f",
61
- "@dxos/debug": "0.6.12-main.15a606f",
62
- "@dxos/echo-schema": "0.6.12-main.15a606f",
63
- "@dxos/feed-store": "0.6.12-main.15a606f",
64
- "@dxos/invariant": "0.6.12-main.15a606f",
65
- "@dxos/edge-client": "0.6.12-main.15a606f",
66
- "@dxos/indexing": "0.6.12-main.15a606f",
67
- "@dxos/keyring": "0.6.12-main.15a606f",
68
- "@dxos/keys": "0.6.12-main.15a606f",
69
- "@dxos/lock-file": "0.6.12-main.15a606f",
70
- "@dxos/kv-store": "0.6.12-main.15a606f",
71
- "@dxos/log": "0.6.12-main.15a606f",
72
- "@dxos/network-manager": "0.6.12-main.15a606f",
73
- "@dxos/messaging": "0.6.12-main.15a606f",
74
- "@dxos/node-std": "0.6.12-main.15a606f",
75
- "@dxos/protocols": "0.6.12-main.15a606f",
76
- "@dxos/rpc": "0.6.12-main.15a606f",
77
- "@dxos/random-access-storage": "0.6.12-main.15a606f",
78
- "@dxos/teleport-extension-gossip": "0.6.12-main.15a606f",
79
- "@dxos/teleport": "0.6.12-main.15a606f",
80
- "@dxos/teleport-extension-object-sync": "0.6.12-main.15a606f",
81
- "@dxos/timeframe": "0.6.12-main.15a606f",
82
- "@dxos/util": "0.6.12-main.15a606f",
83
- "@dxos/websocket-rpc": "0.6.12-main.15a606f",
84
- "@dxos/tracing": "0.6.12-main.15a606f"
50
+ "@dxos/async": "0.6.12-main.78ddbdf",
51
+ "@dxos/client-protocol": "0.6.12-main.78ddbdf",
52
+ "@dxos/config": "0.6.12-main.78ddbdf",
53
+ "@dxos/codec-protobuf": "0.6.12-main.78ddbdf",
54
+ "@dxos/automerge": "0.6.12-main.78ddbdf",
55
+ "@dxos/context": "0.6.12-main.78ddbdf",
56
+ "@dxos/crypto": "0.6.12-main.78ddbdf",
57
+ "@dxos/credentials": "0.6.12-main.78ddbdf",
58
+ "@dxos/debug": "0.6.12-main.78ddbdf",
59
+ "@dxos/echo-db": "0.6.12-main.78ddbdf",
60
+ "@dxos/echo-pipeline": "0.6.12-main.78ddbdf",
61
+ "@dxos/echo-protocol": "0.6.12-main.78ddbdf",
62
+ "@dxos/echo-schema": "0.6.12-main.78ddbdf",
63
+ "@dxos/feed-store": "0.6.12-main.78ddbdf",
64
+ "@dxos/indexing": "0.6.12-main.78ddbdf",
65
+ "@dxos/edge-client": "0.6.12-main.78ddbdf",
66
+ "@dxos/invariant": "0.6.12-main.78ddbdf",
67
+ "@dxos/keyring": "0.6.12-main.78ddbdf",
68
+ "@dxos/keys": "0.6.12-main.78ddbdf",
69
+ "@dxos/lock-file": "0.6.12-main.78ddbdf",
70
+ "@dxos/log": "0.6.12-main.78ddbdf",
71
+ "@dxos/kv-store": "0.6.12-main.78ddbdf",
72
+ "@dxos/messaging": "0.6.12-main.78ddbdf",
73
+ "@dxos/network-manager": "0.6.12-main.78ddbdf",
74
+ "@dxos/node-std": "0.6.12-main.78ddbdf",
75
+ "@dxos/random-access-storage": "0.6.12-main.78ddbdf",
76
+ "@dxos/protocols": "0.6.12-main.78ddbdf",
77
+ "@dxos/teleport": "0.6.12-main.78ddbdf",
78
+ "@dxos/rpc": "0.6.12-main.78ddbdf",
79
+ "@dxos/teleport-extension-gossip": "0.6.12-main.78ddbdf",
80
+ "@dxos/timeframe": "0.6.12-main.78ddbdf",
81
+ "@dxos/tracing": "0.6.12-main.78ddbdf",
82
+ "@dxos/teleport-extension-object-sync": "0.6.12-main.78ddbdf",
83
+ "@dxos/util": "0.6.12-main.78ddbdf",
84
+ "@dxos/websocket-rpc": "0.6.12-main.78ddbdf"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@types/platform": "^1.3.4",
88
88
  "@types/readable-stream": "^2.3.9",
89
- "@dxos/signal": "0.6.12-main.15a606f",
90
- "@dxos/test-utils": "0.6.12-main.15a606f"
89
+ "get-port-please": "^3.1.1",
90
+ "@dxos/signal": "0.6.12-main.78ddbdf",
91
+ "@dxos/test-utils": "0.6.12-main.78ddbdf"
91
92
  },
92
93
  "publishConfig": {
93
94
  "access": "public"
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { onTestFinished, describe, expect, test } from 'vitest';
6
6
 
7
+ import { Event } from '@dxos/async';
7
8
  import { Context } from '@dxos/context';
8
9
  import { CredentialGenerator, verifyCredential } from '@dxos/credentials';
9
10
  import {
@@ -107,8 +108,10 @@ describe('identity/identity', () => {
107
108
 
108
109
  test('edge feed replicator', async () => {
109
110
  let replicationStarted = false;
111
+ const connectedEvent = new Event();
110
112
  const setup = await setupIdentity({
111
113
  edgeConnection: {
114
+ connected: connectedEvent,
112
115
  open: async () => {},
113
116
  close: async () => {},
114
117
  addListener: (_: MessageListener): (() => void) => {
@@ -121,6 +124,7 @@ describe('identity/identity', () => {
121
124
  });
122
125
 
123
126
  await writeGenesisCredential(setup);
127
+ connectedEvent.emit();
124
128
 
125
129
  await expect.poll(() => replicationStarted).toBeTruthy();
126
130
  });
@@ -0,0 +1,244 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { decode as decodeCbor, encode as encodeCbor } from 'cbor-x';
6
+ import { getRandomPort } from 'get-port-please';
7
+ import { describe, test, onTestFinished, vi, expect } from 'vitest';
8
+
9
+ import { Trigger, sleep } from '@dxos/async';
10
+ import { Context } from '@dxos/context';
11
+ import { valueEncoding } from '@dxos/echo-pipeline';
12
+ import { EdgeClient, EdgeIdentityChangedError } from '@dxos/edge-client';
13
+ import { createTestEdgeWsServer } from '@dxos/edge-client/testing';
14
+ import { FeedFactory, FeedStore } from '@dxos/feed-store';
15
+ import { type FeedWrapper } from '@dxos/feed-store';
16
+ import { Keyring } from '@dxos/keyring';
17
+ import { PublicKey, SpaceId } from '@dxos/keys';
18
+ import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
19
+ import { createStorage } from '@dxos/random-access-storage';
20
+ import { openAndClose } from '@dxos/test-utils';
21
+ import { Timeframe } from '@dxos/timeframe';
22
+ import { range } from '@dxos/util';
23
+
24
+ import { EdgeFeedReplicator } from './edge-feed-replicator';
25
+
26
+ describe('EdgeFeedReplicator', () => {
27
+ test('requests metadata after connection is open', async () => {
28
+ const { endpoint, admitConnection, messageSink } = await createEdge();
29
+ const { messenger, sendSpy } = await createClient(endpoint);
30
+
31
+ await attachReplicator(messenger);
32
+
33
+ await sleep(50);
34
+
35
+ expect(sendSpy).not.toHaveBeenCalled();
36
+ expect(messageSink.length).toEqual(0);
37
+
38
+ admitConnection.wake();
39
+ await expect.poll(() => sendSpy.mock.calls.length).toEqual(1);
40
+ expect(messageSink.length).toEqual(1);
41
+ expect(messageSink[0].type).toEqual('get-metadata');
42
+ });
43
+
44
+ test('sends a block', async () => {
45
+ const { endpoint, admitConnection, messageSink } = await createEdge();
46
+ const { messenger } = await createClient(endpoint);
47
+
48
+ const { feed } = await attachReplicator(messenger);
49
+
50
+ admitConnection.wake();
51
+ await appendMessage(feed);
52
+
53
+ await expect.poll(() => messageSink.length).toEqual(2);
54
+ expect(messageSink[1].type).toEqual('data');
55
+ });
56
+
57
+ test('re-requests metadata on reconnect', async () => {
58
+ const { endpoint, admitConnection, messageSink } = await createEdge();
59
+ const { messenger } = await createClient(endpoint);
60
+
61
+ await attachReplicator(messenger);
62
+
63
+ admitConnection.wake();
64
+ await expect.poll(() => messageSink.length).toEqual(1);
65
+
66
+ updateIdentity(messenger);
67
+ await messenger.reconnect.waitForCount(1);
68
+
69
+ await expect.poll(() => messageSink.length).toEqual(2);
70
+ expect(messageSink[1].type).toEqual('get-metadata');
71
+ });
72
+
73
+ test('recovers after query sending failure during identity change', async () => {
74
+ const { endpoint, admitConnection, messageSink } = await createEdge();
75
+ const { messenger, sendSpy } = await createClient(endpoint);
76
+
77
+ await attachReplicator(messenger);
78
+
79
+ sendSpy.mockImplementationOnce(() => {
80
+ throw new EdgeIdentityChangedError(); // Hard to mock the exact race condition for when this error is thrown
81
+ });
82
+ admitConnection.wake();
83
+
84
+ await expect.poll(() => sendSpy.mock.calls.length).toEqual(1);
85
+ expect(messageSink.length).toEqual(0);
86
+ updateIdentity(messenger);
87
+
88
+ await expect.poll(() => messageSink.length).toEqual(1);
89
+ expect(messageSink[0].type).toEqual('get-metadata');
90
+ });
91
+
92
+ test('recovers after response sending failure during identity change', async () => {
93
+ const { endpoint, admitConnection, messageSink, sendResponseMessage } = await createEdge();
94
+ const { messenger, sendSpy } = await createClient(endpoint);
95
+
96
+ const { feed } = await attachReplicator(messenger);
97
+ await appendMessage(feed);
98
+
99
+ sendSpy.mockImplementationOnce(async (request: any) => {
100
+ sendResponseMessage(request, encodeCbor({ type: 'metadata', feedKey: feed.key.toHex(), length: 0 }));
101
+ return Promise.resolve();
102
+ });
103
+ sendSpy.mockImplementationOnce(async () => {
104
+ throw new EdgeIdentityChangedError();
105
+ });
106
+ admitConnection.wake();
107
+
108
+ await expect.poll(() => sendSpy.mock.calls.length).toEqual(2);
109
+ expect(messageSink.length).toEqual(0);
110
+ updateIdentity(messenger);
111
+
112
+ await expect.poll(() => messageSink.length).toEqual(2);
113
+ expect(messageSink[1].type).toEqual('data');
114
+ });
115
+
116
+ test('propagates errors unrelated to reconnect', async () => {
117
+ const { endpoint, admitConnection } = await createEdge();
118
+ const { messenger, sendSpy } = await createClient(endpoint);
119
+
120
+ const { replicator } = await attachReplicator(messenger, { skipOpen: true });
121
+ const raised = new Trigger();
122
+ await replicator.open(new Context({ onError: () => raised.wake() }));
123
+ onTestFinished(async () => {
124
+ await replicator.close();
125
+ });
126
+
127
+ sendSpy.mockImplementationOnce(() => {
128
+ throw new Error();
129
+ });
130
+ admitConnection.wake();
131
+
132
+ await raised.wait();
133
+ });
134
+
135
+ test('identity update before connected', async () => {
136
+ const { endpoint, admitConnection, messageSink } = await createEdge();
137
+ const { messenger } = await createClient(endpoint);
138
+
139
+ await attachReplicator(messenger);
140
+ updateIdentity(messenger);
141
+ await sleep(100);
142
+ admitConnection.wake();
143
+
144
+ await expect.poll(() => messageSink.length).toEqual(2);
145
+ expect(messageSink.map((m) => m.type)).toStrictEqual(range(2, () => 'get-metadata'));
146
+ });
147
+
148
+ test('block appended during reconnect', async () => {
149
+ const { endpoint, admitConnection, feedLength } = await createEdge();
150
+ const { messenger } = await createClient(endpoint);
151
+
152
+ const { feed } = await attachReplicator(messenger);
153
+ admitConnection.wake();
154
+ await sleep(10);
155
+
156
+ admitConnection.reset();
157
+ updateIdentity(messenger);
158
+ await appendMessage(feed);
159
+ await sleep(20);
160
+ admitConnection.wake();
161
+
162
+ await expect.poll(() => feedLength()).toEqual(1);
163
+ });
164
+
165
+ test('reconnect during block append', async () => {
166
+ const { endpoint, admitConnection, feedLength } = await createEdge();
167
+ const { messenger } = await createClient(endpoint);
168
+
169
+ const { feed } = await attachReplicator(messenger);
170
+ admitConnection.wake();
171
+ await sleep(10);
172
+
173
+ void appendMessage(feed);
174
+ updateIdentity(messenger);
175
+
176
+ await expect.poll(() => feedLength()).toEqual(1);
177
+ });
178
+
179
+ const createEdge = async () => {
180
+ const port = await getRandomPort('127.0.0.1');
181
+ let lastBlockIndex = -1;
182
+ const admitConnection = new Trigger();
183
+ const { cleanup, endpoint, messageSink, sendResponseMessage } = await createTestEdgeWsServer(port, {
184
+ admitConnection,
185
+ payloadDecoder: decodeCbor,
186
+ messageHandler: async (message: any) => {
187
+ if (message.type === 'get-metadata') {
188
+ return encodeCbor({ type: 'metadata', feedKey: message.feedKey, length: lastBlockIndex + 1 });
189
+ } else {
190
+ lastBlockIndex = Math.max(lastBlockIndex, message.blocks[message.blocks.length - 1].index);
191
+ }
192
+ },
193
+ });
194
+ onTestFinished(cleanup);
195
+
196
+ return {
197
+ endpoint,
198
+ messageSink,
199
+ admitConnection,
200
+ sendResponseMessage,
201
+ feedLength: () => lastBlockIndex + 1,
202
+ };
203
+ };
204
+
205
+ const createClient = async (endpoint: string) => {
206
+ const peerKey = PublicKey.random().toHex();
207
+ const messenger = new EdgeClient(peerKey, peerKey, { socketEndpoint: endpoint });
208
+ const sendSpy = vi.spyOn(messenger, 'send');
209
+ await openAndClose(messenger);
210
+ return { messenger, sendSpy };
211
+ };
212
+
213
+ const attachReplicator = async (messenger: EdgeClient, options?: { skipOpen?: boolean }) => {
214
+ const spaceId = SpaceId.random();
215
+ const feed = await createNewFeed();
216
+ const replicator = new EdgeFeedReplicator({ messenger, spaceId });
217
+ await replicator.addFeed(feed);
218
+ if (!options?.skipOpen) {
219
+ await openAndClose(replicator);
220
+ }
221
+ return { feed, replicator };
222
+ };
223
+
224
+ const createNewFeed = async () => {
225
+ const storage = createStorage();
226
+ const keyring = new Keyring();
227
+ const feedStore = new FeedStore<FeedMessage>({
228
+ factory: new FeedFactory<FeedMessage>({
229
+ root: storage.createDirectory(),
230
+ signer: keyring,
231
+ hypercore: { valueEncoding },
232
+ }),
233
+ });
234
+ onTestFinished(() => feedStore.close());
235
+ return feedStore.openFeed(await keyring.createKey(), { writable: true });
236
+ };
237
+
238
+ const updateIdentity = (messenger: EdgeClient) => {
239
+ const identityKey = PublicKey.random().toHex();
240
+ messenger.setIdentity({ peerKey: messenger.peerKey, identityKey });
241
+ };
242
+
243
+ const appendMessage = (feed: FeedWrapper<FeedMessage>) => feed.append({ timeframe: new Timeframe() });
244
+ });
@@ -5,15 +5,19 @@
5
5
  import { decode as decodeCbor, encode as encodeCbor } from 'cbor-x';
6
6
 
7
7
  import { Event, Mutex, scheduleMicroTask } from '@dxos/async';
8
- import { Resource, type Context } from '@dxos/context';
8
+ import { Context, Resource } from '@dxos/context';
9
9
  import { type EdgeConnection } from '@dxos/edge-client';
10
+ import { EdgeConnectionClosedError, EdgeIdentityChangedError } from '@dxos/edge-client';
10
11
  import { type FeedWrapper } from '@dxos/feed-store';
11
12
  import { invariant } from '@dxos/invariant';
12
13
  import { PublicKey, type SpaceId } from '@dxos/keys';
13
14
  import { log } from '@dxos/log';
14
15
  import { EdgeService } from '@dxos/protocols';
15
16
  import { buf } from '@dxos/protocols/buf';
16
- import { MessageSchema as RouterMessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
17
+ import {
18
+ MessageSchema as RouterMessageSchema,
19
+ type Message as RouterMessage,
20
+ } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
17
21
  import type { FeedBlock, ProtocolMessage } from '@dxos/protocols/feed-replication';
18
22
  import { ComplexMap, arrayToBuffer, bufferToArray, defaultMap, rangeFromTo } from '@dxos/util';
19
23
 
@@ -48,7 +52,7 @@ export class EdgeFeedReplicator extends Resource {
48
52
  protected override async _open(): Promise<void> {
49
53
  // TODO: handle reconnects
50
54
  this._ctx.onDispose(
51
- this._messenger.addListener(async (message) => {
55
+ this._messenger.addListener((message: RouterMessage) => {
52
56
  if (!message.serviceId) {
53
57
  return;
54
58
  }
@@ -69,16 +73,38 @@ export class EdgeFeedReplicator extends Resource {
69
73
  }),
70
74
  );
71
75
 
72
- this._connected = true;
73
- this._connectionCtx = this._ctx.derive();
74
- for (const feed of this._feeds.values()) {
75
- await this._replicateFeed(feed);
76
- }
76
+ this._messenger.connected.on(this._ctx, async () => {
77
+ await this._resetConnection();
78
+
79
+ this._connected = true;
80
+ const connectionCtx = new Context({
81
+ onError: async (err: any) => {
82
+ if (connectionCtx !== this._connectionCtx) {
83
+ return;
84
+ }
85
+ if (err instanceof EdgeIdentityChangedError || err instanceof EdgeConnectionClosedError) {
86
+ log('resetting on reconnect');
87
+ await this._resetConnection();
88
+ } else {
89
+ this._ctx.raise(err);
90
+ }
91
+ },
92
+ });
93
+ this._connectionCtx = connectionCtx;
94
+ log('connection context created');
95
+ scheduleMicroTask(connectionCtx, async () => {
96
+ for (const feed of this._feeds.values()) {
97
+ await this._replicateFeed(connectionCtx, feed);
98
+ }
99
+ });
100
+ });
77
101
  }
78
102
 
79
103
  protected override async _close(): Promise<void> {
80
- this._connected = false;
104
+ await this._resetConnection();
105
+ }
81
106
 
107
+ private async _resetConnection() {
82
108
  this._connected = false;
83
109
  await this._connectionCtx?.dispose();
84
110
  this._connectionCtx = undefined;
@@ -89,8 +115,8 @@ export class EdgeFeedReplicator extends Resource {
89
115
  log.info('addFeed', { key: feed.key });
90
116
  this._feeds.set(feed.key, feed);
91
117
 
92
- if (this._connected) {
93
- await this._replicateFeed(feed);
118
+ if (this._connected && this._connectionCtx) {
119
+ await this._replicateFeed(this._connectionCtx, feed);
94
120
  }
95
121
  }
96
122
 
@@ -98,20 +124,23 @@ export class EdgeFeedReplicator extends Resource {
98
124
  return defaultMap(this._pushMutex, key, () => new Mutex());
99
125
  }
100
126
 
101
- private async _replicateFeed(feed: FeedWrapper<any>) {
102
- invariant(this._connectionCtx);
103
-
127
+ private async _replicateFeed(ctx: Context, feed: FeedWrapper<any>) {
104
128
  await this._sendMessage({
105
129
  type: 'get-metadata',
106
130
  feedKey: feed.key.toHex(),
107
131
  });
108
132
 
109
- Event.wrap(feed.core as any, 'append').on(this._connectionCtx, async () => {
133
+ Event.wrap(feed.core as any, 'append').on(ctx, async () => {
110
134
  await this._pushBlocksIfNeeded(feed);
111
135
  });
112
136
  }
113
137
 
114
138
  private async _sendMessage(message: ProtocolMessage) {
139
+ if (!this._connectionCtx) {
140
+ log.info('message dropped because connection was disposed');
141
+ return;
142
+ }
143
+
115
144
  const logPayload =
116
145
  message.type === 'data' ? { feedKey: message.feedKey, blocks: message.blocks.map((b) => b.index) } : { message };
117
146
  log.info('sending message', logPayload);
@@ -132,7 +161,11 @@ export class EdgeFeedReplicator extends Resource {
132
161
  }
133
162
 
134
163
  private _onMessage(message: ProtocolMessage) {
135
- scheduleMicroTask(this._ctx, async () => {
164
+ if (!this._connectionCtx) {
165
+ log.warn('received message after connection context was disposed');
166
+ return;
167
+ }
168
+ scheduleMicroTask(this._connectionCtx, async () => {
136
169
  switch (message.type) {
137
170
  case 'metadata': {
138
171
  log.info('received metadata', { message });
@@ -227,7 +260,7 @@ export class EdgeFeedReplicator extends Resource {
227
260
  }
228
261
 
229
262
  private async _pushBlocksIfNeeded(feed: FeedWrapper<any>) {
230
- using _guard = await this._getPushMutex(feed.key).acquire();
263
+ using _ = await this._getPushMutex(feed.key).acquire();
231
264
 
232
265
  if (!this._remoteLength.has(feed.key)) {
233
266
  log('blocks not pushed because remote length is unknown');
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const DXOS_VERSION = "0.6.12-main.15a606f";
1
+ export const DXOS_VERSION = "0.6.12-main.78ddbdf";