@dxos/client-services 0.6.11 → 0.6.12-main.5a87ad5
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-QYVLLBAA.mjs → chunk-5W6ZU2YY.mjs} +5154 -5061
- package/dist/lib/browser/chunk-5W6ZU2YY.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +3 -3
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +4 -5
- package/dist/lib/browser/testing/index.mjs.map +2 -2
- package/dist/lib/node/{chunk-F7G2TXVG.cjs → chunk-MQZYBWFJ.cjs} +4980 -4887
- package/dist/lib/node/chunk-MQZYBWFJ.cjs.map +7 -0
- package/dist/lib/node/index.cjs +46 -46
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +11 -12
- package/dist/lib/node/testing/index.cjs.map +2 -2
- package/dist/lib/node-esm/chunk-2YR6LJEW.mjs +7809 -0
- package/dist/lib/node-esm/chunk-2YR6LJEW.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +416 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/testing/index.mjs +418 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.node.test.d.ts +2 -0
- package/dist/types/src/packlets/identity/authenticator.node.test.d.ts.map +1 -0
- package/dist/types/src/packlets/identity/identity-manager.d.ts +19 -7
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +7 -1
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +3 -4
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +1 -2
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +1 -2
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +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/packlets/spaces/epoch-migrations.d.ts +1 -1
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +1 -2
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/testing/setup.d.ts +3 -0
- package/dist/types/src/testing/setup.d.ts.map +1 -0
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/package.json +43 -39
- package/src/packlets/devices/devices-service.test.ts +4 -5
- package/src/packlets/diagnostics/diagnostics-broadcast.ts +1 -0
- package/src/packlets/identity/{authenticator.test.ts → authenticator.node.test.ts} +2 -3
- package/src/packlets/identity/identity-manager.test.ts +5 -6
- package/src/packlets/identity/identity-manager.ts +35 -19
- package/src/packlets/identity/identity-service.test.ts +4 -8
- package/src/packlets/identity/identity.test.ts +128 -239
- package/src/packlets/identity/identity.ts +38 -8
- package/src/packlets/invitations/device-invitation-protocol.test.ts +7 -4
- package/src/packlets/invitations/invitation-host-extension.ts +0 -3
- package/src/packlets/invitations/invitations-handler.test.ts +14 -7
- package/src/packlets/invitations/invitations-handler.ts +1 -1
- package/src/packlets/invitations/space-invitation-protocol.test.ts +4 -3
- package/src/packlets/logging/logging.test.ts +1 -2
- package/src/packlets/network/network-service.test.ts +2 -3
- package/src/packlets/services/service-context.test.ts +3 -1
- package/src/packlets/services/service-context.ts +24 -12
- package/src/packlets/services/service-host.test.ts +8 -12
- package/src/packlets/services/service-host.ts +2 -2
- package/src/packlets/services/service-registry.test.ts +1 -2
- package/src/packlets/spaces/data-space-manager.test.ts +2 -2
- package/src/packlets/spaces/data-space-manager.ts +4 -1
- package/src/packlets/spaces/data-space.ts +7 -2
- package/src/packlets/spaces/edge-feed-replicator.test.ts +244 -0
- package/src/packlets/spaces/edge-feed-replicator.ts +59 -21
- package/src/packlets/spaces/epoch-migrations.ts +2 -2
- package/src/packlets/spaces/notarization-plugin.test.ts +2 -3
- package/src/packlets/spaces/spaces-service.test.ts +5 -9
- package/src/packlets/storage/storage.ts +0 -1
- package/src/packlets/system/system-service.test.ts +1 -2
- package/src/packlets/testing/test-builder.ts +1 -2
- package/src/packlets/worker/worker-runtime.ts +2 -2
- package/src/testing/setup.ts +11 -0
- package/src/version.ts +1 -5
- package/dist/lib/browser/chunk-QYVLLBAA.mjs.map +0 -7
- package/dist/lib/node/chunk-F7G2TXVG.cjs.map +0 -7
- package/dist/types/src/packlets/identity/authenticator.test.d.ts +0 -2
- package/dist/types/src/packlets/identity/authenticator.test.d.ts.map +0 -1
- package/dist/types/src/packlets/services/automerge-host.test.d.ts +0 -2
- package/dist/types/src/packlets/services/automerge-host.test.d.ts.map +0 -1
- package/src/packlets/services/automerge-host.test.ts +0 -60
|
@@ -2,20 +2,21 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { expect } from '
|
|
5
|
+
import { onTestFinished, describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { asyncChain, Trigger } from '@dxos/async';
|
|
8
8
|
import { raise } from '@dxos/debug';
|
|
9
9
|
import { AlreadyJoinedError } from '@dxos/protocols';
|
|
10
10
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
11
|
-
import { afterTest, describe, test } from '@dxos/test';
|
|
12
11
|
|
|
13
12
|
import { type ServiceContext } from '../services';
|
|
14
13
|
import { createIdentity, createPeers } from '../testing';
|
|
15
14
|
import { acceptInvitation, createInvitation, performInvitation } from '../testing/invitation-utils';
|
|
16
15
|
|
|
17
16
|
const closeAfterTest = async (peer: ServiceContext) => {
|
|
18
|
-
|
|
17
|
+
onTestFinished(async () => {
|
|
18
|
+
await peer.close();
|
|
19
|
+
});
|
|
19
20
|
return peer;
|
|
20
21
|
};
|
|
21
22
|
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { expect } from '
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { Trigger } from '@dxos/async';
|
|
8
8
|
import { log, LogLevel } from '@dxos/log';
|
|
9
9
|
import { type LogEntry } from '@dxos/protocols/proto/dxos/client/services';
|
|
10
|
-
import { beforeEach, describe, test } from '@dxos/test';
|
|
11
10
|
|
|
12
11
|
import { LoggingServiceImpl } from './logging-service';
|
|
13
12
|
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { expect } from '
|
|
5
|
+
import { afterEach, onTestFinished, beforeEach, describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { Trigger } from '@dxos/async';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
9
|
import { type NetworkService, ConnectionState } from '@dxos/protocols/proto/dxos/client/services';
|
|
10
|
-
import { afterEach, afterTest, beforeEach, describe, test } from '@dxos/test';
|
|
11
10
|
|
|
12
11
|
import { NetworkServiceImpl } from './network-service';
|
|
13
12
|
import { type ServiceContext } from '../services';
|
|
@@ -41,7 +40,7 @@ describe('NetworkService', () => {
|
|
|
41
40
|
query.subscribe(({ swarm }) => {
|
|
42
41
|
result.wake(swarm);
|
|
43
42
|
});
|
|
44
|
-
|
|
43
|
+
onTestFinished(() => query.close());
|
|
45
44
|
expect(await result.wait()).to.equal(ConnectionState.ONLINE);
|
|
46
45
|
|
|
47
46
|
result = new Trigger<ConnectionState | undefined>();
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { describe, test } from 'vitest';
|
|
6
|
+
|
|
5
7
|
import { MemorySignalManagerContext, MemorySignalManager } from '@dxos/messaging';
|
|
6
8
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
7
|
-
import {
|
|
9
|
+
import { openAndClose } from '@dxos/test-utils';
|
|
8
10
|
|
|
9
11
|
import { createServiceContext, performInvitation } from '../testing';
|
|
10
12
|
|
|
@@ -6,8 +6,14 @@ import { Trigger } from '@dxos/async';
|
|
|
6
6
|
import { Context, Resource } from '@dxos/context';
|
|
7
7
|
import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
|
|
8
8
|
import { failUndefined } from '@dxos/debug';
|
|
9
|
-
import {
|
|
10
|
-
|
|
9
|
+
import {
|
|
10
|
+
EchoEdgeReplicator,
|
|
11
|
+
EchoHost,
|
|
12
|
+
MeshEchoReplicator,
|
|
13
|
+
MetadataStore,
|
|
14
|
+
SpaceManager,
|
|
15
|
+
valueEncoding,
|
|
16
|
+
} from '@dxos/echo-pipeline';
|
|
11
17
|
import type { EdgeConnection } from '@dxos/edge-client';
|
|
12
18
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
13
19
|
import { invariant } from '@dxos/invariant';
|
|
@@ -31,8 +37,8 @@ import { safeInstanceof } from '@dxos/util';
|
|
|
31
37
|
import {
|
|
32
38
|
IdentityManager,
|
|
33
39
|
type CreateIdentityOptions,
|
|
34
|
-
type IdentityManagerRuntimeParams,
|
|
35
40
|
type JoinIdentityParams,
|
|
41
|
+
type IdentityManagerParams,
|
|
36
42
|
} from '../identity';
|
|
37
43
|
import {
|
|
38
44
|
DeviceInvitationProtocol,
|
|
@@ -43,7 +49,10 @@ import {
|
|
|
43
49
|
} from '../invitations';
|
|
44
50
|
import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
|
|
45
51
|
|
|
46
|
-
export type ServiceContextRuntimeParams =
|
|
52
|
+
export type ServiceContextRuntimeParams = Pick<
|
|
53
|
+
IdentityManagerParams,
|
|
54
|
+
'devicePresenceOfflineTimeout' | 'devicePresenceAnnounceInterval'
|
|
55
|
+
> &
|
|
47
56
|
DataSpaceManagerRuntimeParams & {
|
|
48
57
|
invitationConnectionDefaultParams?: Partial<TeleportParams>;
|
|
49
58
|
disableP2pReplication?: boolean;
|
|
@@ -116,13 +125,16 @@ export class ServiceContext extends Resource {
|
|
|
116
125
|
disableP2pReplication: this._runtimeParams?.disableP2pReplication,
|
|
117
126
|
});
|
|
118
127
|
|
|
119
|
-
this.identityManager = new IdentityManager(
|
|
120
|
-
this.metadataStore,
|
|
121
|
-
this.keyring,
|
|
122
|
-
this.feedStore,
|
|
123
|
-
this.spaceManager,
|
|
124
|
-
this._runtimeParams
|
|
125
|
-
|
|
128
|
+
this.identityManager = new IdentityManager({
|
|
129
|
+
metadataStore: this.metadataStore,
|
|
130
|
+
keyring: this.keyring,
|
|
131
|
+
feedStore: this.feedStore,
|
|
132
|
+
spaceManager: this.spaceManager,
|
|
133
|
+
devicePresenceOfflineTimeout: this._runtimeParams?.devicePresenceOfflineTimeout,
|
|
134
|
+
devicePresenceAnnounceInterval: this._runtimeParams?.devicePresenceAnnounceInterval,
|
|
135
|
+
edgeConnection: this._edgeConnection,
|
|
136
|
+
edgeFeatures: this._edgeFeatures,
|
|
137
|
+
callbacks: {
|
|
126
138
|
onIdentityConstruction: (identity) => {
|
|
127
139
|
if (this._edgeConnection) {
|
|
128
140
|
log.info('Setting identity on edge connection', {
|
|
@@ -141,7 +153,7 @@ export class ServiceContext extends Resource {
|
|
|
141
153
|
}
|
|
142
154
|
},
|
|
143
155
|
},
|
|
144
|
-
);
|
|
156
|
+
});
|
|
145
157
|
|
|
146
158
|
this.echoHost = new EchoHost({ kv: this.level });
|
|
147
159
|
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import chai, { expect } from 'chai';
|
|
6
|
-
import chaiAsPromised from 'chai-as-promised';
|
|
7
5
|
import { rmSync } from 'node:fs';
|
|
6
|
+
import { afterEach, onTestFinished, describe, expect, test } from 'vitest';
|
|
8
7
|
|
|
9
8
|
import { asyncTimeout, latch, Trigger } from '@dxos/async';
|
|
10
9
|
import { Config } from '@dxos/config';
|
|
@@ -14,13 +13,10 @@ import { type PublicKey } from '@dxos/keys';
|
|
|
14
13
|
import { MemorySignalManagerContext } from '@dxos/messaging';
|
|
15
14
|
import { type Identity } from '@dxos/protocols/proto/dxos/client/services';
|
|
16
15
|
import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
17
|
-
import { afterTest, describe, test } from '@dxos/test';
|
|
18
16
|
import { isNode } from '@dxos/util';
|
|
19
17
|
|
|
20
18
|
import { createMockCredential, createServiceHost } from '../testing';
|
|
21
19
|
|
|
22
|
-
chai.use(chaiAsPromised);
|
|
23
|
-
|
|
24
20
|
describe('ClientServicesHost', () => {
|
|
25
21
|
const dataRoot = '/tmp/dxos/client-services/service-host/storage';
|
|
26
22
|
|
|
@@ -38,7 +34,7 @@ describe('ClientServicesHost', () => {
|
|
|
38
34
|
test('queryCredentials', async () => {
|
|
39
35
|
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
40
36
|
await host.open(new Context());
|
|
41
|
-
|
|
37
|
+
onTestFinished(() => host.close());
|
|
42
38
|
|
|
43
39
|
await host.services.IdentityService!.createIdentity({});
|
|
44
40
|
const { spaceKey } = await host.services.SpacesService!.createSpace();
|
|
@@ -49,7 +45,7 @@ describe('ClientServicesHost', () => {
|
|
|
49
45
|
tick();
|
|
50
46
|
// console.log(credential);
|
|
51
47
|
});
|
|
52
|
-
|
|
48
|
+
onTestFinished(() => stream.close());
|
|
53
49
|
|
|
54
50
|
await done();
|
|
55
51
|
});
|
|
@@ -57,7 +53,7 @@ describe('ClientServicesHost', () => {
|
|
|
57
53
|
test('write and query credentials', async () => {
|
|
58
54
|
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
59
55
|
await host.open(new Context());
|
|
60
|
-
|
|
56
|
+
onTestFinished(() => host.close());
|
|
61
57
|
|
|
62
58
|
await host.services.IdentityService!.createIdentity({});
|
|
63
59
|
|
|
@@ -86,7 +82,7 @@ describe('ClientServicesHost', () => {
|
|
|
86
82
|
queriedCredential.wake(credential);
|
|
87
83
|
}
|
|
88
84
|
});
|
|
89
|
-
|
|
85
|
+
onTestFinished(() => credentials.close());
|
|
90
86
|
|
|
91
87
|
await queriedCredential.wait();
|
|
92
88
|
});
|
|
@@ -94,7 +90,7 @@ describe('ClientServicesHost', () => {
|
|
|
94
90
|
test('sign presentation', async () => {
|
|
95
91
|
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
96
92
|
await host.open(new Context());
|
|
97
|
-
|
|
93
|
+
onTestFinished(() => host.close());
|
|
98
94
|
|
|
99
95
|
await host.services.IdentityService!.createIdentity({});
|
|
100
96
|
|
|
@@ -147,9 +143,9 @@ describe('ClientServicesHost', () => {
|
|
|
147
143
|
trigger.wake(identity.identity);
|
|
148
144
|
}
|
|
149
145
|
});
|
|
150
|
-
await expect(asyncTimeout(trigger.wait(), 200)).
|
|
146
|
+
await expect(asyncTimeout(trigger.wait(), 200)).rejects.toBeInstanceOf(Error);
|
|
151
147
|
await stream?.close();
|
|
152
148
|
await host.close();
|
|
153
149
|
}
|
|
154
|
-
})
|
|
150
|
+
});
|
|
155
151
|
});
|
|
@@ -15,8 +15,8 @@ import { EdgeSignalManager, WebsocketSignalManager, type SignalManager } from '@
|
|
|
15
15
|
import {
|
|
16
16
|
SwarmNetworkManager,
|
|
17
17
|
createIceProvider,
|
|
18
|
-
createSimplePeerTransportFactory,
|
|
19
18
|
type TransportFactory,
|
|
19
|
+
createRtcTransportFactory,
|
|
20
20
|
} from '@dxos/network-manager';
|
|
21
21
|
import { trace } from '@dxos/protocols';
|
|
22
22
|
import { SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
|
|
@@ -218,7 +218,7 @@ export class ClientServicesHost {
|
|
|
218
218
|
|
|
219
219
|
const {
|
|
220
220
|
connectionLog = true,
|
|
221
|
-
transportFactory =
|
|
221
|
+
transportFactory = createRtcTransportFactory(
|
|
222
222
|
{ iceServers: this._config?.get('runtime.services.ice') },
|
|
223
223
|
this._config?.get('runtime.services.iceProviders') &&
|
|
224
224
|
createIceProvider(this._config!.get('runtime.services.iceProviders')!),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { expect } from '
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { Event } from '@dxos/async';
|
|
8
8
|
import { type ClientServices } from '@dxos/client-protocol';
|
|
@@ -12,7 +12,6 @@ import { log } from '@dxos/log';
|
|
|
12
12
|
import { schema } from '@dxos/protocols/proto';
|
|
13
13
|
import { type SystemService, SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
|
|
14
14
|
import { createLinkedPorts, createProtoRpcPeer, createServiceBundle } from '@dxos/rpc';
|
|
15
|
-
import { describe, test } from '@dxos/test';
|
|
16
15
|
|
|
17
16
|
import { ServiceRegistry } from './service-registry';
|
|
18
17
|
import { SystemServiceImpl } from '../system';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { expect } from '
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { asyncTimeout, latch } from '@dxos/async';
|
|
8
8
|
import { createAdmissionCredentials } from '@dxos/credentials';
|
|
@@ -10,7 +10,7 @@ import { AuthStatus } from '@dxos/echo-pipeline';
|
|
|
10
10
|
import { writeMessages } from '@dxos/feed-store';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
import { SpaceState } from '@dxos/protocols/proto/dxos/client/services';
|
|
13
|
-
import {
|
|
13
|
+
import { openAndClose } from '@dxos/test-utils';
|
|
14
14
|
|
|
15
15
|
import { TestBuilder, type TestPeer } from '../testing';
|
|
16
16
|
|
|
@@ -14,8 +14,11 @@ import {
|
|
|
14
14
|
type DelegateInvitationCredential,
|
|
15
15
|
type MemberInfo,
|
|
16
16
|
} from '@dxos/credentials';
|
|
17
|
-
import { convertLegacyReferences, findInlineObjectOfType, type EchoEdgeReplicator, type EchoHost } from '@dxos/echo-db';
|
|
18
17
|
import {
|
|
18
|
+
convertLegacyReferences,
|
|
19
|
+
findInlineObjectOfType,
|
|
20
|
+
type EchoEdgeReplicator,
|
|
21
|
+
type EchoHost,
|
|
19
22
|
AuthStatus,
|
|
20
23
|
CredentialServerExtension,
|
|
21
24
|
type MeshEchoReplicator,
|
|
@@ -7,8 +7,13 @@ import { AUTH_TIMEOUT } from '@dxos/client-protocol';
|
|
|
7
7
|
import { Context, ContextDisposedError, cancelWithContext } from '@dxos/context';
|
|
8
8
|
import type { SpecificCredential } from '@dxos/credentials';
|
|
9
9
|
import { timed, warnAfterTimeout } from '@dxos/debug';
|
|
10
|
-
import {
|
|
11
|
-
|
|
10
|
+
import {
|
|
11
|
+
type EchoHost,
|
|
12
|
+
type DatabaseRoot,
|
|
13
|
+
createMappedFeedWriter,
|
|
14
|
+
type MetadataStore,
|
|
15
|
+
type Space,
|
|
16
|
+
} from '@dxos/echo-pipeline';
|
|
12
17
|
import { SpaceDocVersion } from '@dxos/echo-protocol';
|
|
13
18
|
import type { EdgeConnection } from '@dxos/edge-client';
|
|
14
19
|
import { type FeedStore, type FeedWrapper } from '@dxos/feed-store';
|
|
@@ -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
|
+
});
|