@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.
- package/dist/lib/browser/{chunk-BDGSOBZV.mjs → chunk-XSFLJVDP.mjs} +105 -61
- package/dist/lib/browser/chunk-XSFLJVDP.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +1 -1
- package/dist/lib/node/{chunk-EBZ7KFXS.cjs → chunk-F3WGFGEN.cjs} +87 -43
- package/dist/lib/node/chunk-F3WGFGEN.cjs.map +7 -0
- package/dist/lib/node/index.cjs +45 -45
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +8 -8
- package/dist/lib/node-esm/{chunk-LDBJYNPL.mjs → chunk-3HDLTAT2.mjs} +105 -61
- package/dist/lib/node-esm/chunk-3HDLTAT2.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1 -1
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +1 -0
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +2 -0
- package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +1 -0
- package/dist/types/src/version.d.ts +1 -1
- package/package.json +39 -38
- package/src/packlets/identity/identity.test.ts +4 -0
- package/src/packlets/spaces/edge-feed-replicator.test.ts +244 -0
- package/src/packlets/spaces/edge-feed-replicator.ts +50 -17
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-BDGSOBZV.mjs.map +0 -7
- package/dist/lib/node/chunk-EBZ7KFXS.cjs.map +0 -7
- 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-
|
|
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,
|
|
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 @@
|
|
|
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.
|
|
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.
|
|
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.
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/codec-protobuf": "0.6.12-main.
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/echo-
|
|
60
|
-
"@dxos/echo-
|
|
61
|
-
"@dxos/
|
|
62
|
-
"@dxos/echo-schema": "0.6.12-main.
|
|
63
|
-
"@dxos/feed-store": "0.6.12-main.
|
|
64
|
-
"@dxos/
|
|
65
|
-
"@dxos/edge-client": "0.6.12-main.
|
|
66
|
-
"@dxos/
|
|
67
|
-
"@dxos/keyring": "0.6.12-main.
|
|
68
|
-
"@dxos/keys": "0.6.12-main.
|
|
69
|
-
"@dxos/lock-file": "0.6.12-main.
|
|
70
|
-
"@dxos/
|
|
71
|
-
"@dxos/
|
|
72
|
-
"@dxos/
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/node-std": "0.6.12-main.
|
|
75
|
-
"@dxos/
|
|
76
|
-
"@dxos/
|
|
77
|
-
"@dxos/
|
|
78
|
-
"@dxos/
|
|
79
|
-
"@dxos/teleport": "0.6.12-main.
|
|
80
|
-
"@dxos/
|
|
81
|
-
"@dxos/
|
|
82
|
-
"@dxos/
|
|
83
|
-
"@dxos/
|
|
84
|
-
"@dxos/
|
|
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
|
-
"
|
|
90
|
-
"@dxos/
|
|
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 {
|
|
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 {
|
|
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(
|
|
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.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
|
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.
|
|
1
|
+
export const DXOS_VERSION = "0.6.12-main.78ddbdf";
|