@dxos/client-services 0.4.10-main.3e0c8a5 → 0.4.10-main.3e35a2f
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-6GHZYBUW.mjs → chunk-S3G2RM7S.mjs} +744 -559
- package/dist/lib/browser/chunk-S3G2RM7S.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +11 -3
- package/dist/lib/browser/index.mjs.map +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +11 -5
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-D5VK6MZQ.cjs → chunk-3T6D6GIB.cjs} +688 -591
- package/dist/lib/node/chunk-3T6D6GIB.cjs.map +7 -0
- package/dist/lib/node/index.cjs +46 -38
- package/dist/lib/node/index.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +15 -9
- package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts +5 -0
- package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts.map +1 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts +5 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts +15 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -0
- package/dist/types/src/packlets/{services → diagnostics}/diagnostics.d.ts +1 -1
- package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -0
- package/dist/types/src/packlets/diagnostics/index.d.ts +4 -0
- package/dist/types/src/packlets/diagnostics/index.d.ts.map +1 -0
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts +0 -1
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
- package/dist/types/src/packlets/services/index.d.ts +1 -1
- package/dist/types/src/packlets/services/index.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +7 -5
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +5 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/services/util.d.ts +1 -0
- package/dist/types/src/packlets/services/util.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/index.d.ts +1 -0
- package/dist/types/src/packlets/storage/index.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/level.d.ts +4 -0
- package/dist/types/src/packlets/storage/level.d.ts.map +1 -0
- package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/util.d.ts +4 -0
- package/dist/types/src/packlets/storage/util.d.ts.map +1 -0
- package/dist/types/src/packlets/system/system-service.d.ts +1 -1
- package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +5 -2
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/package.json +36 -34
- package/src/index.ts +1 -0
- package/src/packlets/devices/devices-service.test.ts +1 -1
- package/src/packlets/diagnostics/browser-diagnostics-broadcast.ts +94 -0
- package/src/packlets/diagnostics/diagnostics-broadcast.ts +20 -0
- package/src/packlets/diagnostics/diagnostics-collector.ts +65 -0
- package/src/packlets/{services → diagnostics}/diagnostics.ts +2 -2
- package/src/packlets/diagnostics/index.ts +7 -0
- package/src/packlets/identity/identity-service.test.ts +1 -1
- package/src/packlets/invitations/device-invitation-protocol.test.ts +1 -1
- package/src/packlets/invitations/invitations-handler.ts +5 -12
- package/src/packlets/invitations/invitations-service.ts +5 -5
- package/src/packlets/network/network-service.test.ts +1 -1
- package/src/packlets/services/automerge-host.test.ts +9 -3
- package/src/packlets/services/index.ts +1 -1
- package/src/packlets/services/service-context.test.ts +9 -6
- package/src/packlets/services/service-context.ts +12 -5
- package/src/packlets/services/service-host.ts +49 -13
- package/src/packlets/services/service-registry.test.ts +1 -1
- package/src/packlets/services/util.ts +2 -0
- package/src/packlets/spaces/data-space-manager.test.ts +4 -4
- package/src/packlets/spaces/spaces-service.test.ts +1 -1
- package/src/packlets/storage/index.ts +1 -0
- package/src/packlets/storage/level.ts +19 -0
- package/src/packlets/storage/storage.ts +3 -9
- package/src/packlets/storage/util.ts +19 -0
- package/src/packlets/system/system-service.ts +1 -1
- package/src/packlets/testing/test-builder.ts +23 -5
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-6GHZYBUW.mjs.map +0 -7
- package/dist/lib/node/chunk-D5VK6MZQ.cjs.map +0 -7
- package/dist/types/src/packlets/services/diagnostics.d.ts.map +0 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Trigger } from '@dxos/async';
|
|
6
|
+
import { log } from '@dxos/log';
|
|
7
|
+
import { type SystemService } from '@dxos/protocols/proto/dxos/client/services';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
type CollectDiagnosticsBroadcastSender,
|
|
11
|
+
type CollectDiagnosticsBroadcastHandler,
|
|
12
|
+
} from './diagnostics-collector';
|
|
13
|
+
|
|
14
|
+
const CHANNEL_NAME = 'dxos.diagnostics.broadcast';
|
|
15
|
+
|
|
16
|
+
enum MessageType {
|
|
17
|
+
PROBE = 'probe',
|
|
18
|
+
PROBE_ACK = 'probe-ack',
|
|
19
|
+
REQUEST_DIAGNOSTICS = 'request-diagnostics',
|
|
20
|
+
RECEIVE_DIAGNOSTICS = 'receive-diagnostics',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Message {
|
|
24
|
+
type: MessageType;
|
|
25
|
+
payload?: any;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const createCollectDiagnosticsBroadcastSender = (): CollectDiagnosticsBroadcastSender => {
|
|
29
|
+
return {
|
|
30
|
+
broadcastDiagnosticsRequest: async () => {
|
|
31
|
+
let expectedResponse = MessageType.PROBE_ACK;
|
|
32
|
+
let channel: BroadcastChannel | undefined;
|
|
33
|
+
try {
|
|
34
|
+
const trigger = new Trigger<Message>();
|
|
35
|
+
channel = new BroadcastChannel(CHANNEL_NAME);
|
|
36
|
+
channel.onmessage = (msg) => {
|
|
37
|
+
if (expectedResponse === msg.data.type) {
|
|
38
|
+
trigger.wake(msg.data);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
channel.postMessage({ type: MessageType.PROBE });
|
|
42
|
+
await trigger.wait({ timeout: 200 });
|
|
43
|
+
expectedResponse = MessageType.RECEIVE_DIAGNOSTICS;
|
|
44
|
+
trigger.reset();
|
|
45
|
+
channel.postMessage({ type: MessageType.REQUEST_DIAGNOSTICS });
|
|
46
|
+
const diagnostics = await trigger.wait({ timeout: 5000 });
|
|
47
|
+
return diagnostics.payload;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
const errorDescription = e instanceof Error ? e.message : JSON.stringify(e);
|
|
50
|
+
return { expectedResponse, errorDescription };
|
|
51
|
+
} finally {
|
|
52
|
+
safeClose(channel);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const createCollectDiagnosticsBroadcastHandler = (
|
|
59
|
+
systemService: SystemService,
|
|
60
|
+
): CollectDiagnosticsBroadcastHandler => {
|
|
61
|
+
let channel: BroadcastChannel | undefined;
|
|
62
|
+
return {
|
|
63
|
+
start: () => {
|
|
64
|
+
channel = new BroadcastChannel(CHANNEL_NAME);
|
|
65
|
+
channel.onmessage = async (message) => {
|
|
66
|
+
try {
|
|
67
|
+
if (message.data.type === MessageType.PROBE) {
|
|
68
|
+
channel?.postMessage({ type: MessageType.PROBE_ACK });
|
|
69
|
+
} else if (message.data.type === MessageType.REQUEST_DIAGNOSTICS) {
|
|
70
|
+
const diagnostics = await systemService.getDiagnostics({});
|
|
71
|
+
channel?.postMessage({
|
|
72
|
+
type: MessageType.RECEIVE_DIAGNOSTICS,
|
|
73
|
+
payload: diagnostics,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
log.catch(error);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
stop: () => {
|
|
82
|
+
safeClose(channel);
|
|
83
|
+
channel = undefined;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const safeClose = (channel?: BroadcastChannel) => {
|
|
89
|
+
try {
|
|
90
|
+
channel?.close();
|
|
91
|
+
} catch (e) {
|
|
92
|
+
// ignored
|
|
93
|
+
}
|
|
94
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
import { type SystemService } from '@dxos/protocols/proto/dxos/client/services';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type CollectDiagnosticsBroadcastSender,
|
|
8
|
+
type CollectDiagnosticsBroadcastHandler,
|
|
9
|
+
} from './diagnostics-collector';
|
|
10
|
+
|
|
11
|
+
export const createCollectDiagnosticsBroadcastSender = (): CollectDiagnosticsBroadcastSender => {
|
|
12
|
+
return { broadcastDiagnosticsRequest: async () => undefined };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const createCollectDiagnosticsBroadcastHandler = (_: SystemService): CollectDiagnosticsBroadcastHandler => {
|
|
16
|
+
return {
|
|
17
|
+
start: () => {},
|
|
18
|
+
stop: () => {},
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type ClientServicesProvider } from '@dxos/client-protocol';
|
|
6
|
+
import { type Config, ConfigResource } from '@dxos/config';
|
|
7
|
+
import { GetDiagnosticsRequest } from '@dxos/protocols/proto/dxos/client/services';
|
|
8
|
+
import { TRACE_PROCESSOR } from '@dxos/tracing';
|
|
9
|
+
import { type JsonKeyOptions, jsonKeyReplacer, nonNullable } from '@dxos/util';
|
|
10
|
+
|
|
11
|
+
import { createCollectDiagnosticsBroadcastSender } from './diagnostics-broadcast';
|
|
12
|
+
import { ClientServicesProviderResource } from '../services';
|
|
13
|
+
|
|
14
|
+
export interface CollectDiagnosticsBroadcastSender {
|
|
15
|
+
broadcastDiagnosticsRequest(): any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CollectDiagnosticsBroadcastHandler {
|
|
19
|
+
start(): void;
|
|
20
|
+
stop(): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class DiagnosticsCollector {
|
|
24
|
+
private static broadcastSender = createCollectDiagnosticsBroadcastSender();
|
|
25
|
+
|
|
26
|
+
public static async collect(
|
|
27
|
+
config: Config | Config[] = findConfigs(),
|
|
28
|
+
services: ClientServicesProvider | null = findSystemServiceProvider(),
|
|
29
|
+
options: JsonKeyOptions = {},
|
|
30
|
+
): Promise<any> {
|
|
31
|
+
const serviceDiagnostics = await services?.services?.SystemService?.getDiagnostics({
|
|
32
|
+
keys: options.humanize
|
|
33
|
+
? GetDiagnosticsRequest.KEY_OPTION.HUMANIZE
|
|
34
|
+
: options.truncate
|
|
35
|
+
? GetDiagnosticsRequest.KEY_OPTION.TRUNCATE
|
|
36
|
+
: undefined,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const clientDiagnostics = {
|
|
40
|
+
config,
|
|
41
|
+
trace: TRACE_PROCESSOR.getDiagnostics(),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const diagnostics =
|
|
45
|
+
serviceDiagnostics != null
|
|
46
|
+
? { client: clientDiagnostics, services: serviceDiagnostics }
|
|
47
|
+
: {
|
|
48
|
+
client: clientDiagnostics,
|
|
49
|
+
broadcast: await this.broadcastSender.broadcastDiagnosticsRequest(),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return JSON.parse(JSON.stringify(diagnostics, jsonKeyReplacer(options)));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const findSystemServiceProvider = (): ClientServicesProvider | null => {
|
|
57
|
+
const serviceProviders = TRACE_PROCESSOR.findByAnnotation(ClientServicesProviderResource);
|
|
58
|
+
const providerResource = serviceProviders.find((r) => r.instance.deref()?.services?.SystemService != null);
|
|
59
|
+
return providerResource?.instance?.deref() ?? null;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const findConfigs = (): Config[] => {
|
|
63
|
+
const configs = TRACE_PROCESSOR.findByAnnotation(ConfigResource);
|
|
64
|
+
return configs.map((r) => r.instance.deref()).filter(nonNullable);
|
|
65
|
+
};
|
|
@@ -25,9 +25,9 @@ import { type Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
|
25
25
|
import { type Resource, type Span } from '@dxos/protocols/proto/dxos/tracing';
|
|
26
26
|
import { TRACE_PROCESSOR } from '@dxos/tracing';
|
|
27
27
|
|
|
28
|
-
import { getPlatform } from './platform';
|
|
29
|
-
import { type ServiceContext } from './service-context';
|
|
30
28
|
import { DXOS_VERSION } from '../../version';
|
|
29
|
+
import { type ServiceContext } from '../services';
|
|
30
|
+
import { getPlatform } from '../services/platform';
|
|
31
31
|
import { type DataSpace } from '../spaces';
|
|
32
32
|
|
|
33
33
|
const DEFAULT_TIMEOUT = 1_000;
|
|
@@ -22,7 +22,7 @@ describe('IdentityService', () => {
|
|
|
22
22
|
let identityService: IdentityService;
|
|
23
23
|
|
|
24
24
|
beforeEach(async () => {
|
|
25
|
-
serviceContext = createServiceContext();
|
|
25
|
+
serviceContext = await createServiceContext();
|
|
26
26
|
await serviceContext.open(new Context());
|
|
27
27
|
identityService = new IdentityServiceImpl(
|
|
28
28
|
(options) => serviceContext.createIdentity(options),
|
|
@@ -20,7 +20,7 @@ const closeAfterTest = async (peer: ServiceContext) => {
|
|
|
20
20
|
|
|
21
21
|
describe('services/device', () => {
|
|
22
22
|
test('creates identity', async () => {
|
|
23
|
-
const peer = createServiceContext();
|
|
23
|
+
const peer = await createServiceContext();
|
|
24
24
|
await peer.open(new Context());
|
|
25
25
|
afterTest(() => peer.close());
|
|
26
26
|
|
|
@@ -76,7 +76,8 @@ export class InvitationsHandler {
|
|
|
76
76
|
swarmKey = PublicKey.random(),
|
|
77
77
|
persistent = true,
|
|
78
78
|
created = new Date(),
|
|
79
|
-
lifetime = 86400, // 1 day
|
|
79
|
+
lifetime = 86400, // 1 day,
|
|
80
|
+
multiUse = false,
|
|
80
81
|
} = options ?? {};
|
|
81
82
|
const authCode =
|
|
82
83
|
options?.authCode ??
|
|
@@ -91,9 +92,10 @@ export class InvitationsHandler {
|
|
|
91
92
|
swarmKey,
|
|
92
93
|
authCode,
|
|
93
94
|
timeout,
|
|
94
|
-
persistent,
|
|
95
|
+
persistent: persistent && type !== Invitation.Type.OFFLINE, // offline invitations are persisted in control feed
|
|
95
96
|
created,
|
|
96
97
|
lifetime,
|
|
98
|
+
multiUse,
|
|
97
99
|
...protocol.getInvitationContext(),
|
|
98
100
|
};
|
|
99
101
|
|
|
@@ -162,7 +164,7 @@ export class InvitationsHandler {
|
|
|
162
164
|
}
|
|
163
165
|
log.trace('dxos.sdk.invitations-handler.host.onOpen', trace.error({ id: traceId, error: err }));
|
|
164
166
|
} finally {
|
|
165
|
-
if (
|
|
167
|
+
if (!multiUse) {
|
|
166
168
|
// Wait for graceful close before disposing.
|
|
167
169
|
await swarmConnection.close();
|
|
168
170
|
await ctx.dispose();
|
|
@@ -424,12 +426,3 @@ export class InvitationsHandler {
|
|
|
424
426
|
return observable;
|
|
425
427
|
}
|
|
426
428
|
}
|
|
427
|
-
|
|
428
|
-
export const invitationExpired = (invitation: Invitation) => {
|
|
429
|
-
return (
|
|
430
|
-
invitation.created &&
|
|
431
|
-
invitation.lifetime &&
|
|
432
|
-
invitation.lifetime !== 0 &&
|
|
433
|
-
invitation.created.getTime() + invitation.lifetime * 1000 < Date.now()
|
|
434
|
-
);
|
|
435
|
-
};
|
|
@@ -6,7 +6,7 @@ import { Event, scheduleTask } from '@dxos/async';
|
|
|
6
6
|
import { type AuthenticatingInvitation, type CancellableInvitation } from '@dxos/client-protocol';
|
|
7
7
|
import { Stream } from '@dxos/codec-protobuf';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
|
-
import { type MetadataStore } from '@dxos/echo-pipeline';
|
|
9
|
+
import { hasInvitationExpired, type MetadataStore } from '@dxos/echo-pipeline';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
import {
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
19
19
|
|
|
20
20
|
import { type InvitationProtocol } from './invitation-protocol';
|
|
21
|
-
import {
|
|
21
|
+
import { type InvitationsHandler } from './invitations-handler';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Adapts invitation service observable to client/service stream.
|
|
@@ -91,7 +91,7 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
this._createInvitations.delete(invitation.get().invitationId);
|
|
94
|
-
if (invitation.get().
|
|
94
|
+
if (!invitation.get().multiUse) {
|
|
95
95
|
this._removedCreated.emit(invitation.get());
|
|
96
96
|
}
|
|
97
97
|
},
|
|
@@ -103,7 +103,7 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
103
103
|
const persistentInvitations = this._metadataStore.getInvitations();
|
|
104
104
|
|
|
105
105
|
// get saved persistent invitations, filter and remove from storage those that have expired.
|
|
106
|
-
const freshInvitations = persistentInvitations.filter(async (invitation) => !
|
|
106
|
+
const freshInvitations = persistentInvitations.filter(async (invitation) => !hasInvitationExpired(invitation));
|
|
107
107
|
|
|
108
108
|
const cInvitations = freshInvitations.map((persistentInvitation) => {
|
|
109
109
|
invariant(!this._createInvitations.get(persistentInvitation.invitationId), 'invitation already exists');
|
|
@@ -148,7 +148,7 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
148
148
|
() => {
|
|
149
149
|
close();
|
|
150
150
|
this._acceptInvitations.delete(invitation.get().invitationId);
|
|
151
|
-
if (invitation.get().
|
|
151
|
+
if (!invitation.get().multiUse) {
|
|
152
152
|
this._removedAccepted.emit(invitation.get());
|
|
153
153
|
}
|
|
154
154
|
},
|
|
@@ -18,7 +18,7 @@ describe('NetworkService', () => {
|
|
|
18
18
|
let networkService: NetworkService;
|
|
19
19
|
|
|
20
20
|
beforeEach(async () => {
|
|
21
|
-
serviceContext = createServiceContext();
|
|
21
|
+
serviceContext = await createServiceContext();
|
|
22
22
|
await serviceContext.open(new Context());
|
|
23
23
|
networkService = new NetworkServiceImpl(serviceContext.networkManager, serviceContext.signalManager);
|
|
24
24
|
});
|
|
@@ -6,8 +6,8 @@ import { expect } from 'chai';
|
|
|
6
6
|
|
|
7
7
|
import { asyncTimeout, sleep } from '@dxos/async';
|
|
8
8
|
import { AutomergeHost, DataServiceImpl } from '@dxos/echo-pipeline';
|
|
9
|
+
import { createTestLevel } from '@dxos/echo-pipeline/testing';
|
|
9
10
|
import { AutomergeContext } from '@dxos/echo-schema';
|
|
10
|
-
import { StorageType, createStorage } from '@dxos/random-access-storage';
|
|
11
11
|
import { afterTest, describe, test } from '@dxos/test';
|
|
12
12
|
|
|
13
13
|
describe('AutomergeHost', () => {
|
|
@@ -19,10 +19,16 @@ describe('AutomergeHost', () => {
|
|
|
19
19
|
// creates repo and document | replicates repo | finds document in repo
|
|
20
20
|
//
|
|
21
21
|
|
|
22
|
-
const
|
|
22
|
+
const level = createTestLevel();
|
|
23
|
+
await level.open();
|
|
24
|
+
afterTest(() => level.close());
|
|
23
25
|
|
|
24
|
-
const host = new AutomergeHost({
|
|
26
|
+
const host = new AutomergeHost({
|
|
27
|
+
db: level.sublevel('automerge'),
|
|
28
|
+
});
|
|
29
|
+
await host.open();
|
|
25
30
|
afterTest(() => host.close());
|
|
31
|
+
|
|
26
32
|
const dataService = new DataServiceImpl(host);
|
|
27
33
|
const client = new AutomergeContext(dataService);
|
|
28
34
|
afterTest(() => client.close());
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { MemorySignalManagerContext } from '@dxos/messaging';
|
|
6
6
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
7
|
-
import { describe, test } from '@dxos/test';
|
|
7
|
+
import { describe, openAndClose, test } from '@dxos/test';
|
|
8
8
|
|
|
9
9
|
import { createServiceContext } from '../testing';
|
|
10
10
|
import { performInvitation } from '../testing/invitation-utils';
|
|
@@ -12,10 +12,10 @@ import { performInvitation } from '../testing/invitation-utils';
|
|
|
12
12
|
describe('services/ServiceContext', () => {
|
|
13
13
|
test('new space is synchronized on device invitations', async () => {
|
|
14
14
|
const networkContext = new MemorySignalManagerContext();
|
|
15
|
-
const device1 = createServiceContext({ signalContext: networkContext });
|
|
15
|
+
const device1 = await createServiceContext({ signalContext: networkContext });
|
|
16
16
|
await device1.createIdentity();
|
|
17
17
|
|
|
18
|
-
const device2 = createServiceContext({ signalContext: networkContext });
|
|
18
|
+
const device2 = await createServiceContext({ signalContext: networkContext });
|
|
19
19
|
await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
|
|
20
20
|
|
|
21
21
|
const space1 = await device1.dataSpaceManager!.createSpace();
|
|
@@ -26,13 +26,16 @@ describe('services/ServiceContext', () => {
|
|
|
26
26
|
|
|
27
27
|
test('joined space is synchronized on device invitations', async () => {
|
|
28
28
|
const networkContext = new MemorySignalManagerContext();
|
|
29
|
-
const device1 = createServiceContext({ signalContext: networkContext });
|
|
29
|
+
const device1 = await createServiceContext({ signalContext: networkContext });
|
|
30
|
+
await openAndClose(device1.automergeHost);
|
|
30
31
|
await device1.createIdentity();
|
|
31
32
|
|
|
32
|
-
const device2 = createServiceContext({ signalContext: networkContext });
|
|
33
|
+
const device2 = await createServiceContext({ signalContext: networkContext });
|
|
34
|
+
await openAndClose(device2.automergeHost);
|
|
33
35
|
await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
|
|
34
36
|
|
|
35
|
-
const identity2 = createServiceContext({ signalContext: networkContext });
|
|
37
|
+
const identity2 = await createServiceContext({ signalContext: networkContext });
|
|
38
|
+
await openAndClose(identity2.automergeHost);
|
|
36
39
|
await identity2.createIdentity();
|
|
37
40
|
const space1 = await identity2.dataSpaceManager!.createSpace();
|
|
38
41
|
await Promise.all(
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Level } from 'level';
|
|
6
|
+
|
|
5
7
|
import { Trigger } from '@dxos/async';
|
|
6
|
-
import { Context } from '@dxos/context';
|
|
8
|
+
import { Context, Resource } from '@dxos/context';
|
|
7
9
|
import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
|
|
8
10
|
import { failUndefined } from '@dxos/debug';
|
|
9
11
|
import { AutomergeHost, MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
|
|
@@ -47,7 +49,7 @@ export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpa
|
|
|
47
49
|
// TODO(dmaretskyi): Gets duplicated in CJS build between normal and testing bundles.
|
|
48
50
|
@safeInstanceof('dxos.client-services.ServiceContext')
|
|
49
51
|
@Trace.resource()
|
|
50
|
-
export class ServiceContext {
|
|
52
|
+
export class ServiceContext extends Resource {
|
|
51
53
|
public readonly initialized = new Trigger();
|
|
52
54
|
public readonly metadataStore: MetadataStore;
|
|
53
55
|
/**
|
|
@@ -78,10 +80,13 @@ export class ServiceContext {
|
|
|
78
80
|
|
|
79
81
|
constructor(
|
|
80
82
|
public readonly storage: Storage,
|
|
83
|
+
public readonly level: Level<string, string>,
|
|
81
84
|
public readonly networkManager: NetworkManager,
|
|
82
85
|
public readonly signalManager: SignalManager,
|
|
83
86
|
public readonly _runtimeParams?: IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams,
|
|
84
87
|
) {
|
|
88
|
+
super();
|
|
89
|
+
|
|
85
90
|
// TODO(burdon): Move strings to constants.
|
|
86
91
|
this.metadataStore = new MetadataStore(storage.createDirectory('metadata'));
|
|
87
92
|
this.snapshotStore = new SnapshotStore(storage.createDirectory('snapshots'));
|
|
@@ -115,10 +120,11 @@ export class ServiceContext {
|
|
|
115
120
|
this._runtimeParams as IdentityManagerRuntimeParams,
|
|
116
121
|
);
|
|
117
122
|
|
|
118
|
-
this.indexMetadata = new IndexMetadataStore({
|
|
123
|
+
this.indexMetadata = new IndexMetadataStore({ db: level.sublevel('index-metadata') });
|
|
119
124
|
|
|
120
125
|
this.automergeHost = new AutomergeHost({
|
|
121
126
|
directory: storage.createDirectory('automerge'),
|
|
127
|
+
db: level.sublevel('automerge'),
|
|
122
128
|
metadata: this.indexMetadata,
|
|
123
129
|
});
|
|
124
130
|
|
|
@@ -145,7 +151,7 @@ export class ServiceContext {
|
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
@Trace.span()
|
|
148
|
-
async
|
|
154
|
+
protected override async _open(ctx: Context) {
|
|
149
155
|
await this._checkStorageVersion();
|
|
150
156
|
|
|
151
157
|
log('opening...');
|
|
@@ -153,6 +159,7 @@ export class ServiceContext {
|
|
|
153
159
|
await this.signalManager.open();
|
|
154
160
|
await this.networkManager.open();
|
|
155
161
|
|
|
162
|
+
await this.automergeHost.open();
|
|
156
163
|
await this.metadataStore.load();
|
|
157
164
|
await this.spaceManager.open();
|
|
158
165
|
await this.identityManager.open(ctx);
|
|
@@ -163,7 +170,7 @@ export class ServiceContext {
|
|
|
163
170
|
log('opened');
|
|
164
171
|
}
|
|
165
172
|
|
|
166
|
-
async
|
|
173
|
+
protected override async _close() {
|
|
167
174
|
log('closing...');
|
|
168
175
|
if (this._deviceSpaceSync && this.identityManager.identity) {
|
|
169
176
|
await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
|
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
// Copyright 2021 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import { type Level } from 'level';
|
|
6
|
+
|
|
7
|
+
import { Event, synchronized } from '@dxos/async';
|
|
8
|
+
import { clientServiceBundle, defaultKey, type ClientServices, Properties } from '@dxos/client-protocol';
|
|
7
9
|
import { type Config } from '@dxos/config';
|
|
8
10
|
import { Context } from '@dxos/context';
|
|
9
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
DataServiceImpl,
|
|
13
|
+
type ObjectStructure,
|
|
14
|
+
encodeReference,
|
|
15
|
+
type SpaceDoc,
|
|
16
|
+
type MyLevel,
|
|
17
|
+
} from '@dxos/echo-pipeline';
|
|
10
18
|
import * as E from '@dxos/echo-schema';
|
|
11
|
-
import { createRawObjectDoc } from '@dxos/echo-schema';
|
|
12
19
|
import { IndexServiceImpl } from '@dxos/indexing';
|
|
13
20
|
import { invariant } from '@dxos/invariant';
|
|
14
21
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -22,18 +29,22 @@ import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
|
|
|
22
29
|
import { assignDeep } from '@dxos/util';
|
|
23
30
|
import { WebsocketRpcClient } from '@dxos/websocket-rpc';
|
|
24
31
|
|
|
25
|
-
import { createDiagnostics } from './diagnostics';
|
|
26
32
|
import { ServiceContext, type ServiceContextRuntimeParams } from './service-context';
|
|
27
33
|
import { ServiceRegistry } from './service-registry';
|
|
28
34
|
import { DevicesServiceImpl } from '../devices';
|
|
29
35
|
import { DevtoolsHostEvents, DevtoolsServiceImpl } from '../devtools';
|
|
36
|
+
import {
|
|
37
|
+
type CollectDiagnosticsBroadcastHandler,
|
|
38
|
+
createCollectDiagnosticsBroadcastHandler,
|
|
39
|
+
createDiagnostics,
|
|
40
|
+
} from '../diagnostics';
|
|
30
41
|
import { IdentityServiceImpl, type CreateIdentityOptions } from '../identity';
|
|
31
42
|
import { InvitationsServiceImpl } from '../invitations';
|
|
32
43
|
import { Lock, type ResourceLock } from '../locks';
|
|
33
44
|
import { LoggingServiceImpl } from '../logging';
|
|
34
45
|
import { NetworkServiceImpl } from '../network';
|
|
35
46
|
import { SpacesServiceImpl } from '../spaces';
|
|
36
|
-
import { createStorageObjects } from '../storage';
|
|
47
|
+
import { createLevel, createStorageObjects } from '../storage';
|
|
37
48
|
import { SystemServiceImpl } from '../system';
|
|
38
49
|
|
|
39
50
|
export type ClientServicesHostParams = {
|
|
@@ -45,6 +56,7 @@ export type ClientServicesHostParams = {
|
|
|
45
56
|
signalManager?: SignalManager;
|
|
46
57
|
connectionLog?: boolean;
|
|
47
58
|
storage?: Storage;
|
|
59
|
+
level?: MyLevel;
|
|
48
60
|
lockKey?: string;
|
|
49
61
|
callbacks?: ClientServicesHostCallbacks;
|
|
50
62
|
runtimeParams?: ServiceContextRuntimeParams;
|
|
@@ -77,11 +89,13 @@ export class ClientServicesHost {
|
|
|
77
89
|
private _signalManager?: SignalManager;
|
|
78
90
|
private _networkManager?: NetworkManager;
|
|
79
91
|
private _storage?: Storage;
|
|
92
|
+
private _level?: Level<string, string>;
|
|
80
93
|
private _callbacks?: ClientServicesHostCallbacks;
|
|
81
94
|
private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
|
|
82
95
|
|
|
83
96
|
private _serviceContext!: ServiceContext;
|
|
84
97
|
private readonly _runtimeParams?: ServiceContextRuntimeParams;
|
|
98
|
+
private diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
|
|
85
99
|
|
|
86
100
|
@Trace.info()
|
|
87
101
|
private _opening = false;
|
|
@@ -94,12 +108,14 @@ export class ClientServicesHost {
|
|
|
94
108
|
transportFactory,
|
|
95
109
|
signalManager,
|
|
96
110
|
storage,
|
|
111
|
+
level,
|
|
97
112
|
// TODO(wittjosiah): Turn this on by default.
|
|
98
113
|
lockKey,
|
|
99
114
|
callbacks,
|
|
100
115
|
runtimeParams,
|
|
101
116
|
}: ClientServicesHostParams = {}) {
|
|
102
117
|
this._storage = storage;
|
|
118
|
+
this._level = level;
|
|
103
119
|
this._callbacks = callbacks;
|
|
104
120
|
this._runtimeParams = runtimeParams;
|
|
105
121
|
|
|
@@ -139,6 +155,7 @@ export class ClientServicesHost {
|
|
|
139
155
|
},
|
|
140
156
|
});
|
|
141
157
|
|
|
158
|
+
this.diagnosticsBroadcastHandler = createCollectDiagnosticsBroadcastHandler(this._systemService);
|
|
142
159
|
this._loggingService = new LoggingServiceImpl();
|
|
143
160
|
|
|
144
161
|
this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
|
|
@@ -227,12 +244,19 @@ export class ClientServicesHost {
|
|
|
227
244
|
|
|
228
245
|
this._opening = true;
|
|
229
246
|
log('opening...', { lockKey: this._resourceLock?.lockKey });
|
|
247
|
+
|
|
248
|
+
if (!this._level) {
|
|
249
|
+
this._level = await createLevel(this._config.get('runtime.client.storage', {})!);
|
|
250
|
+
}
|
|
251
|
+
await this._level.open();
|
|
252
|
+
|
|
230
253
|
await this._resourceLock?.acquire();
|
|
231
254
|
|
|
232
255
|
await this._loggingService.open();
|
|
233
256
|
|
|
234
257
|
this._serviceContext = new ServiceContext(
|
|
235
258
|
this._storage,
|
|
259
|
+
this._level,
|
|
236
260
|
this._networkManager,
|
|
237
261
|
this._signalManager,
|
|
238
262
|
this._runtimeParams,
|
|
@@ -302,6 +326,7 @@ export class ClientServicesHost {
|
|
|
302
326
|
});
|
|
303
327
|
void this._devtoolsProxy.open();
|
|
304
328
|
}
|
|
329
|
+
this.diagnosticsBroadcastHandler.start();
|
|
305
330
|
|
|
306
331
|
this._opening = false;
|
|
307
332
|
this._open = true;
|
|
@@ -320,10 +345,12 @@ export class ClientServicesHost {
|
|
|
320
345
|
|
|
321
346
|
const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
|
|
322
347
|
log('closing...', { deviceKey });
|
|
348
|
+
this.diagnosticsBroadcastHandler.stop();
|
|
323
349
|
await this._devtoolsProxy?.close();
|
|
324
350
|
this._serviceRegistry.setServices({ SystemService: this._systemService });
|
|
325
351
|
await this._loggingService.close();
|
|
326
352
|
await this._serviceContext.close();
|
|
353
|
+
await this._level?.close();
|
|
327
354
|
this._open = false;
|
|
328
355
|
this._statusUpdate.emit();
|
|
329
356
|
log('closed', { deviceKey });
|
|
@@ -353,15 +380,24 @@ export class ClientServicesHost {
|
|
|
353
380
|
const document = await this._serviceContext.automergeHost.repo.find<SpaceDoc>(automergeIndex as any);
|
|
354
381
|
await document.whenReady();
|
|
355
382
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
383
|
+
// TODO(dmaretskyi): Better API for low-level data access.
|
|
384
|
+
const properties: ObjectStructure = {
|
|
385
|
+
system: {
|
|
386
|
+
type: encodeReference(E.getTypeReference(Properties)!),
|
|
387
|
+
},
|
|
388
|
+
data: {
|
|
389
|
+
[defaultKey]: identity.identityKey.toHex(),
|
|
390
|
+
},
|
|
391
|
+
meta: {
|
|
392
|
+
keys: [],
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
const propertiesId = PublicKey.random().toHex();
|
|
360
396
|
document.change((doc: SpaceDoc) => {
|
|
361
|
-
assignDeep(doc, ['objects',
|
|
397
|
+
assignDeep(doc, ['objects', propertiesId], properties);
|
|
362
398
|
});
|
|
363
|
-
|
|
364
|
-
await
|
|
399
|
+
|
|
400
|
+
await this._serviceContext.automergeHost.repo.flush();
|
|
365
401
|
|
|
366
402
|
return identity;
|
|
367
403
|
}
|
|
@@ -31,7 +31,7 @@ const serviceBundle = createServiceBundle<TestServices>({
|
|
|
31
31
|
describe('service registry', () => {
|
|
32
32
|
test('builds a service registry', async () => {
|
|
33
33
|
const remoteSource = 'https://remote.source';
|
|
34
|
-
const serviceContext = createServiceContext();
|
|
34
|
+
const serviceContext = await createServiceContext();
|
|
35
35
|
await serviceContext.open(new Context());
|
|
36
36
|
|
|
37
37
|
const serviceRegistry = new ServiceRegistry(serviceBundle, {
|