@dxos/client-services 0.4.10-main.fa5a270 → 0.4.10-main.fe71b4c

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 (98) hide show
  1. package/dist/lib/browser/{chunk-7PYX6UUA.mjs → chunk-7S34JE6M.mjs} +980 -656
  2. package/dist/lib/browser/chunk-7S34JE6M.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +60 -22
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/packlets/testing/index.mjs +12 -5
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-4TX623I7.cjs → chunk-DQMGKBOV.cjs} +922 -685
  9. package/dist/lib/node/chunk-DQMGKBOV.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +96 -58
  11. package/dist/lib/node/index.cjs.map +3 -3
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/packlets/testing/index.cjs +16 -9
  14. package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
  15. package/dist/types/src/index.d.ts +1 -0
  16. package/dist/types/src/index.d.ts.map +1 -1
  17. package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts +5 -0
  18. package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts.map +1 -0
  19. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts +5 -0
  20. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -0
  21. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts +15 -0
  22. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -0
  23. package/dist/types/src/packlets/{services → diagnostics}/diagnostics.d.ts +1 -1
  24. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -0
  25. package/dist/types/src/packlets/diagnostics/index.d.ts +4 -0
  26. package/dist/types/src/packlets/diagnostics/index.d.ts.map +1 -0
  27. package/dist/types/src/packlets/indexing/util.d.ts +3 -2
  28. package/dist/types/src/packlets/indexing/util.d.ts.map +1 -1
  29. package/dist/types/src/packlets/invitations/invitation-extension.d.ts +1 -0
  30. package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +1 -1
  31. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +4 -2
  32. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  33. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  34. package/dist/types/src/packlets/services/index.d.ts +1 -1
  35. package/dist/types/src/packlets/services/index.d.ts.map +1 -1
  36. package/dist/types/src/packlets/services/service-context.d.ts +7 -5
  37. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  38. package/dist/types/src/packlets/services/service-host.d.ts +5 -1
  39. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  40. package/dist/types/src/packlets/services/util.d.ts +1 -0
  41. package/dist/types/src/packlets/services/util.d.ts.map +1 -1
  42. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  43. package/dist/types/src/packlets/storage/index.d.ts +1 -0
  44. package/dist/types/src/packlets/storage/index.d.ts.map +1 -1
  45. package/dist/types/src/packlets/storage/level.d.ts +4 -0
  46. package/dist/types/src/packlets/storage/level.d.ts.map +1 -0
  47. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  48. package/dist/types/src/packlets/storage/util.d.ts +4 -0
  49. package/dist/types/src/packlets/storage/util.d.ts.map +1 -0
  50. package/dist/types/src/packlets/system/system-service.d.ts +1 -1
  51. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  52. package/dist/types/src/packlets/testing/test-builder.d.ts +4 -2
  53. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  54. package/dist/types/src/packlets/vault/shared-worker-connection.d.ts +5 -5
  55. package/dist/types/src/packlets/vault/shared-worker-connection.d.ts.map +1 -1
  56. package/dist/types/src/packlets/vault/worker-runtime.d.ts +2 -0
  57. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  58. package/dist/types/src/packlets/vault/worker-session.d.ts +2 -0
  59. package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
  60. package/dist/types/src/version.d.ts +1 -1
  61. package/package.json +36 -34
  62. package/src/index.ts +1 -0
  63. package/src/packlets/devices/devices-service.test.ts +1 -1
  64. package/src/packlets/diagnostics/browser-diagnostics-broadcast.ts +94 -0
  65. package/src/packlets/diagnostics/diagnostics-broadcast.ts +20 -0
  66. package/src/packlets/diagnostics/diagnostics-collector.ts +65 -0
  67. package/src/packlets/{services → diagnostics}/diagnostics.ts +2 -2
  68. package/src/packlets/diagnostics/index.ts +7 -0
  69. package/src/packlets/identity/identity-service.test.ts +1 -1
  70. package/src/packlets/indexing/util.ts +6 -6
  71. package/src/packlets/invitations/device-invitation-protocol.test.ts +1 -1
  72. package/src/packlets/invitations/invitation-extension.ts +28 -1
  73. package/src/packlets/invitations/invitations-handler.ts +73 -32
  74. package/src/packlets/invitations/invitations-service.ts +5 -5
  75. package/src/packlets/network/network-service.test.ts +1 -1
  76. package/src/packlets/services/automerge-host.test.ts +9 -3
  77. package/src/packlets/services/index.ts +1 -1
  78. package/src/packlets/services/service-context.test.ts +9 -6
  79. package/src/packlets/services/service-context.ts +18 -11
  80. package/src/packlets/services/service-host.ts +52 -9
  81. package/src/packlets/services/service-registry.test.ts +1 -1
  82. package/src/packlets/services/util.ts +2 -0
  83. package/src/packlets/spaces/data-space-manager.test.ts +4 -4
  84. package/src/packlets/spaces/data-space.ts +51 -2
  85. package/src/packlets/spaces/spaces-service.test.ts +1 -1
  86. package/src/packlets/storage/index.ts +1 -0
  87. package/src/packlets/storage/level.ts +19 -0
  88. package/src/packlets/storage/storage.ts +3 -9
  89. package/src/packlets/storage/util.ts +19 -0
  90. package/src/packlets/system/system-service.ts +1 -1
  91. package/src/packlets/testing/test-builder.ts +23 -5
  92. package/src/packlets/vault/shared-worker-connection.ts +3 -8
  93. package/src/packlets/vault/worker-runtime.ts +27 -2
  94. package/src/packlets/vault/worker-session.ts +6 -0
  95. package/src/version.ts +1 -1
  96. package/dist/lib/browser/chunk-7PYX6UUA.mjs.map +0 -7
  97. package/dist/lib/node/chunk-4TX623I7.cjs.map +0 -7
  98. package/dist/types/src/packlets/services/diagnostics.d.ts.map +0 -1
@@ -21,6 +21,8 @@ export declare class WorkerSession {
21
21
  private readonly _serviceHost;
22
22
  readonly onClose: Callback<() => Promise<void>>;
23
23
  origin?: string;
24
+ observabilityGroup?: string;
25
+ signalTelemetryEnabled?: boolean;
24
26
  lockKey?: string;
25
27
  bridgeService?: BridgeService;
26
28
  constructor({ serviceHost, systemPort, appPort, shellPort, readySignal }: WorkerSessionParams);
@@ -1 +1 @@
1
- {"version":3,"file":"worker-session.d.ts","sourceRoot":"","sources":["../../../../../src/packlets/vault/worker-session.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,OAAO,EAAE,MAAM,aAAa,CAAC;AASpD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAyC,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EAAE,QAAQ,EAAqB,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,KAAK,kBAAkB,EAA+C,MAAM,aAAa,CAAC;AAEnG,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,kBAAkB,CAAC;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IAEjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;CACzC,CAAC;AAEF;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkB;IAC7C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAkB;IACnD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoC;IAC/D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAiB;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAElD,SAAgB,OAAO,iBAAsB,QAAQ,IAAI,CAAC,EAAI;IAGvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,aAAa,CAAC,EAAE,aAAa,CAAC;gBAEzB,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,mBAAmB;IAkEvF,IAAI;IAeJ,KAAK;YAYG,eAAe;IAQ7B,OAAO,CAAC,kBAAkB;CAO3B"}
1
+ {"version":3,"file":"worker-session.d.ts","sourceRoot":"","sources":["../../../../../src/packlets/vault/worker-session.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,OAAO,EAAE,MAAM,aAAa,CAAC;AASpD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAyC,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EAAE,QAAQ,EAAqB,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,KAAK,kBAAkB,EAA+C,MAAM,aAAa,CAAC;AAEnG,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,kBAAkB,CAAC;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IAEjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;CACzC,CAAC;AAEF;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkB;IAC7C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAkB;IACnD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoC;IAC/D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAiB;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAElD,SAAgB,OAAO,iBAAsB,QAAQ,IAAI,CAAC,EAAI;IAGvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAGjC,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,aAAa,CAAC,EAAE,aAAa,CAAC;gBAEzB,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,mBAAmB;IAoEvF,IAAI;IAeJ,KAAK;YAYG,eAAe;IAQ7B,OAAO,CAAC,kBAAkB;CAO3B"}
@@ -1,2 +1,2 @@
1
- export declare const DXOS_VERSION = "0.4.10-main.fa5a270";
1
+ export declare const DXOS_VERSION = "0.4.10-main.fe71b4c";
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.4.10-main.fa5a270",
3
+ "version": "0.4.10-main.fe71b4c",
4
4
  "description": "DXOS client services implementation",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -10,6 +10,7 @@
10
10
  "browser": {
11
11
  "jsondown": false,
12
12
  "./src/packlets/locks/node.ts": "./src/packlets/locks/browser.ts",
13
+ "./src/packlets/diagnostics/diagnostics-broadcast.ts": "./src/packlets/diagnostics/browser-diagnostics-broadcast.ts",
13
14
  "./dist/lib/node/index.cjs": "./dist/lib/browser/index.mjs",
14
15
  "./testing.js": "./dist/lib/browser/packlets/testing/index.mjs"
15
16
  },
@@ -21,44 +22,45 @@
21
22
  "src"
22
23
  ],
23
24
  "dependencies": {
25
+ "level": "^8.0.1",
24
26
  "platform": "^1.3.6",
25
- "@dxos/async": "0.4.10-main.fa5a270",
26
- "@dxos/client-protocol": "0.4.10-main.fa5a270",
27
- "@dxos/automerge": "0.4.10-main.fa5a270",
28
- "@dxos/config": "0.4.10-main.fa5a270",
29
- "@dxos/codec-protobuf": "0.4.10-main.fa5a270",
30
- "@dxos/context": "0.4.10-main.fa5a270",
31
- "@dxos/credentials": "0.4.10-main.fa5a270",
32
- "@dxos/crypto": "0.4.10-main.fa5a270",
33
- "@dxos/debug": "0.4.10-main.fa5a270",
34
- "@dxos/echo-db": "0.4.10-main.fa5a270",
35
- "@dxos/echo-pipeline": "0.4.10-main.fa5a270",
36
- "@dxos/echo-schema": "0.4.10-main.fa5a270",
37
- "@dxos/feed-store": "0.4.10-main.fa5a270",
38
- "@dxos/indexing": "0.4.10-main.fa5a270",
39
- "@dxos/invariant": "0.4.10-main.fa5a270",
40
- "@dxos/keyring": "0.4.10-main.fa5a270",
41
- "@dxos/keys": "0.4.10-main.fa5a270",
42
- "@dxos/lock-file": "0.4.10-main.fa5a270",
43
- "@dxos/messaging": "0.4.10-main.fa5a270",
44
- "@dxos/network-manager": "0.4.10-main.fa5a270",
45
- "@dxos/log": "0.4.10-main.fa5a270",
46
- "@dxos/node-std": "0.4.10-main.fa5a270",
47
- "@dxos/random-access-storage": "0.4.10-main.fa5a270",
48
- "@dxos/protocols": "0.4.10-main.fa5a270",
49
- "@dxos/rpc": "0.4.10-main.fa5a270",
50
- "@dxos/teleport": "0.4.10-main.fa5a270",
51
- "@dxos/teleport-extension-gossip": "0.4.10-main.fa5a270",
52
- "@dxos/teleport-extension-object-sync": "0.4.10-main.fa5a270",
53
- "@dxos/tracing": "0.4.10-main.fa5a270",
54
- "@dxos/websocket-rpc": "0.4.10-main.fa5a270",
55
- "@dxos/timeframe": "0.4.10-main.fa5a270",
56
- "@dxos/util": "0.4.10-main.fa5a270"
27
+ "@dxos/async": "0.4.10-main.fe71b4c",
28
+ "@dxos/client-protocol": "0.4.10-main.fe71b4c",
29
+ "@dxos/context": "0.4.10-main.fe71b4c",
30
+ "@dxos/automerge": "0.4.10-main.fe71b4c",
31
+ "@dxos/codec-protobuf": "0.4.10-main.fe71b4c",
32
+ "@dxos/config": "0.4.10-main.fe71b4c",
33
+ "@dxos/crypto": "0.4.10-main.fe71b4c",
34
+ "@dxos/credentials": "0.4.10-main.fe71b4c",
35
+ "@dxos/debug": "0.4.10-main.fe71b4c",
36
+ "@dxos/echo-db": "0.4.10-main.fe71b4c",
37
+ "@dxos/echo-schema": "0.4.10-main.fe71b4c",
38
+ "@dxos/echo-pipeline": "0.4.10-main.fe71b4c",
39
+ "@dxos/feed-store": "0.4.10-main.fe71b4c",
40
+ "@dxos/invariant": "0.4.10-main.fe71b4c",
41
+ "@dxos/indexing": "0.4.10-main.fe71b4c",
42
+ "@dxos/keys": "0.4.10-main.fe71b4c",
43
+ "@dxos/lock-file": "0.4.10-main.fe71b4c",
44
+ "@dxos/log": "0.4.10-main.fe71b4c",
45
+ "@dxos/messaging": "0.4.10-main.fe71b4c",
46
+ "@dxos/keyring": "0.4.10-main.fe71b4c",
47
+ "@dxos/network-manager": "0.4.10-main.fe71b4c",
48
+ "@dxos/node-std": "0.4.10-main.fe71b4c",
49
+ "@dxos/protocols": "0.4.10-main.fe71b4c",
50
+ "@dxos/teleport": "0.4.10-main.fe71b4c",
51
+ "@dxos/rpc": "0.4.10-main.fe71b4c",
52
+ "@dxos/random-access-storage": "0.4.10-main.fe71b4c",
53
+ "@dxos/teleport-extension-gossip": "0.4.10-main.fe71b4c",
54
+ "@dxos/teleport-extension-object-sync": "0.4.10-main.fe71b4c",
55
+ "@dxos/tracing": "0.4.10-main.fe71b4c",
56
+ "@dxos/util": "0.4.10-main.fe71b4c",
57
+ "@dxos/websocket-rpc": "0.4.10-main.fe71b4c",
58
+ "@dxos/timeframe": "0.4.10-main.fe71b4c"
57
59
  },
58
60
  "devDependencies": {
59
61
  "@types/platform": "^1.3.4",
60
62
  "@types/readable-stream": "^2.3.9",
61
- "@dxos/signal": "0.4.10-main.fa5a270"
63
+ "@dxos/signal": "0.4.10-main.fe71b4c"
62
64
  },
63
65
  "publishConfig": {
64
66
  "access": "public"
package/src/index.ts CHANGED
@@ -10,3 +10,4 @@ export * from './packlets/spaces';
10
10
  export * from './packlets/storage';
11
11
  export * from './packlets/vault';
12
12
  export * from './packlets/locks';
13
+ export * from './packlets/diagnostics';
@@ -19,7 +19,7 @@ describe('DevicesService', () => {
19
19
  let devicesService: DevicesService;
20
20
 
21
21
  beforeEach(async () => {
22
- serviceContext = createServiceContext();
22
+ serviceContext = await createServiceContext();
23
23
  await serviceContext.open(new Context());
24
24
  devicesService = new DevicesServiceImpl(serviceContext.identityManager);
25
25
  });
@@ -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;
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './diagnostics';
6
+ export * from './diagnostics-collector';
7
+ export * from './diagnostics-broadcast';
@@ -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),
@@ -7,32 +7,32 @@ import { type DocHandle } from '@dxos/automerge/automerge-repo';
7
7
  import { warnAfterTimeout } from '@dxos/debug';
8
8
  import { type AutomergeHost } from '@dxos/echo-pipeline';
9
9
  import { type ObjectSnapshot } from '@dxos/indexing';
10
- import { idCodec } from '@dxos/protocols';
10
+ import { type ObjectPointerEncoded, idCodec } from '@dxos/protocols';
11
11
 
12
12
  /**
13
13
  * Factory for `loadDocuments` iterator.
14
14
  */
15
- export const createLoadDocuments = (automergeHost: AutomergeHost) =>
15
+ export const createSelectedDocumentsIterator = (automergeHost: AutomergeHost) =>
16
16
  /**
17
17
  * Get object data blobs from Automerge Repo by ids.
18
18
  * @param ids
19
19
  */
20
20
  // TODO(mykola): Unload automerge handles after usage.
21
- async function* loadDocuments(ids: string[]) {
21
+ async function* loadDocuments(ids: ObjectPointerEncoded[]) {
22
22
  for (const id of ids) {
23
23
  const { documentId, objectId } = idCodec.decode(id);
24
24
  const handle = automergeHost.repo.find(documentId as any);
25
25
  await warnAfterTimeout(5000, 'to long to load doc', () => handle.whenReady());
26
26
  const doc = handle.docSync();
27
27
  const hash = getHeads(doc).join('');
28
- yield [{ id, object: doc.objects[objectId], currentHash: hash }];
28
+ yield doc.objects?.[objectId] ? [{ id, object: doc.objects[objectId], currentHash: hash }] : [];
29
29
  }
30
30
  };
31
31
 
32
32
  /**
33
33
  * Factory for `getAllDocuments` iterator.
34
34
  */
35
- export const createGetAllDocuments = (automergeHost: AutomergeHost) =>
35
+ export const createDocumentsIterator = (automergeHost: AutomergeHost) =>
36
36
  /**
37
37
  * Recursively get all object data blobs from Automerge Repo.
38
38
  * @param ids
@@ -57,7 +57,7 @@ export const createGetAllDocuments = (automergeHost: AutomergeHost) =>
57
57
  return {
58
58
  id: idCodec.encode({ documentId: handle.documentId, objectId }),
59
59
  object,
60
- currentHash: heads.at(-1)!,
60
+ currentHash: heads.join(''),
61
61
  };
62
62
  });
63
63
  }
@@ -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
 
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { Trigger } from '@dxos/async';
6
6
  import { cancelWithContext, Context } from '@dxos/context';
7
+ import { randomBytes, verify } from '@dxos/crypto';
7
8
  import { invariant } from '@dxos/invariant';
8
9
  import { PublicKey } from '@dxos/keys';
9
10
  import { log } from '@dxos/log';
@@ -51,6 +52,8 @@ export class InvitationHostExtension extends RpcExtension<
51
52
  private _remoteOptions?: Options;
52
53
  private _remoteOptionsTrigger = new Trigger();
53
54
 
55
+ private _challenge?: Buffer = undefined;
56
+
54
57
  public invitation?: Invitation = undefined;
55
58
 
56
59
  public guestProfile?: ProfileDocument = undefined;
@@ -113,13 +116,17 @@ export class InvitationHostExtension extends RpcExtension<
113
116
 
114
117
  this._callbacks.onStateUpdate({ ...this.invitation, state: Invitation.State.READY_FOR_AUTHENTICATION });
115
118
 
119
+ this._challenge =
120
+ this.invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY ? randomBytes(32) : undefined;
121
+
116
122
  log.trace('dxos.sdk.invitation-handler.host.introduce', trace.end({ id: traceId }));
117
123
  return {
118
124
  authMethod: this.invitation.authMethod,
125
+ challenge: this._challenge,
119
126
  };
120
127
  },
121
128
 
122
- authenticate: async ({ authCode: code }) => {
129
+ authenticate: async ({ authCode: code, signedChallenge }) => {
123
130
  const traceId = PublicKey.random().toHex();
124
131
  log.trace('dxos.sdk.invitation-handler.host.authenticate', trace.begin({ id: traceId }));
125
132
  log('received authentication request', { authCode: code });
@@ -145,6 +152,26 @@ export class InvitationHostExtension extends RpcExtension<
145
152
  break;
146
153
  }
147
154
 
155
+ case Invitation.AuthMethod.KNOWN_PUBLIC_KEY: {
156
+ if (!this.invitation.guestKeypair) {
157
+ status = AuthenticationResponse.Status.INTERNAL_ERROR;
158
+ break;
159
+ }
160
+ const isSignatureValid =
161
+ this._challenge &&
162
+ verify(
163
+ this._challenge,
164
+ Buffer.from(signedChallenge ?? []),
165
+ this.invitation.guestKeypair.publicKey.asBuffer(),
166
+ );
167
+ if (isSignatureValid) {
168
+ this.authenticationPassed = true;
169
+ } else {
170
+ status = AuthenticationResponse.Status.INVALID_SIGNATURE;
171
+ }
172
+ break;
173
+ }
174
+
148
175
  default: {
149
176
  log.error('invalid authentication method', { authMethod: this.invitation.authMethod });
150
177
  status = AuthenticationResponse.Status.INTERNAL_ERROR;
@@ -11,6 +11,7 @@ import {
11
11
  } from '@dxos/client-protocol';
12
12
  import { Context } from '@dxos/context';
13
13
  import { generatePasscode } from '@dxos/credentials';
14
+ import { createKeyPair, sign } from '@dxos/crypto';
14
15
  import { invariant } from '@dxos/invariant';
15
16
  import { PublicKey } from '@dxos/keys';
16
17
  import { log } from '@dxos/log';
@@ -21,9 +22,9 @@ import {
21
22
  type SwarmConnection,
22
23
  } from '@dxos/network-manager';
23
24
  import { InvalidInvitationExtensionRoleError, trace } from '@dxos/protocols';
24
- import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
25
+ import { type AdmissionKeypair, Invitation } from '@dxos/protocols/proto/dxos/client/services';
25
26
  import { type DeviceProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
26
- import { AuthenticationResponse } from '@dxos/protocols/proto/dxos/halo/invitations';
27
+ import { AuthenticationResponse, type IntroductionResponse } from '@dxos/protocols/proto/dxos/halo/invitations';
27
28
 
28
29
  import {
29
30
  InvitationGuestExtension,
@@ -74,9 +75,11 @@ export class InvitationsHandler {
74
75
  state = Invitation.State.INIT,
75
76
  timeout = INVITATION_TIMEOUT,
76
77
  swarmKey = PublicKey.random(),
77
- persistent = true,
78
+ persistent = options?.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY, // default no not storing keypairs
78
79
  created = new Date(),
79
- lifetime = 86400, // 1 day
80
+ guestKeypair = undefined,
81
+ lifetime = 86400, // 1 day,
82
+ multiUse = false,
80
83
  } = options ?? {};
81
84
  const authCode =
82
85
  options?.authCode ??
@@ -91,9 +94,12 @@ export class InvitationsHandler {
91
94
  swarmKey,
92
95
  authCode,
93
96
  timeout,
94
- persistent,
97
+ persistent: persistent && type !== Invitation.Type.DELEGATED, // delegated invitations are persisted in control feed
98
+ guestKeypair:
99
+ guestKeypair ?? (authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY ? createAdmissionKeypair() : undefined),
95
100
  created,
96
101
  lifetime,
102
+ multiUse,
97
103
  ...protocol.getInvitationContext(),
98
104
  };
99
105
 
@@ -162,7 +168,7 @@ export class InvitationsHandler {
162
168
  }
163
169
  log.trace('dxos.sdk.invitations-handler.host.onOpen', trace.error({ id: traceId, error: err }));
164
170
  } finally {
165
- if (type !== Invitation.Type.MULTIUSE) {
171
+ if (!multiUse) {
166
172
  // Wait for graceful close before disposing.
167
173
  await swarmConnection.close();
168
174
  await ctx.dispose();
@@ -317,26 +323,13 @@ export class InvitationsHandler {
317
323
 
318
324
  // 2. Get authentication code.
319
325
  if (isAuthenticationRequired(invitation)) {
320
- for (let attempt = 1; attempt <= MAX_OTP_ATTEMPTS; attempt++) {
321
- log('guest waiting for authentication code...');
322
- setState({ state: Invitation.State.READY_FOR_AUTHENTICATION });
323
- const authCode = await authenticated.wait({ timeout });
324
-
325
- log('sending authentication request');
326
- setState({ state: Invitation.State.AUTHENTICATING });
327
- const response = await extension.rpc.InvitationHostService.authenticate({ authCode });
328
- if (response.status === undefined || response.status === AuthenticationResponse.Status.OK) {
326
+ switch (invitation.authMethod) {
327
+ case Invitation.AuthMethod.SHARED_SECRET:
328
+ await this._handleGuestOtpAuth(extension, setState, authenticated, { timeout });
329
+ break;
330
+ case Invitation.AuthMethod.KNOWN_PUBLIC_KEY:
331
+ await this._handleGuestKpkAuth(extension, setState, invitation, introductionResponse);
329
332
  break;
330
- }
331
-
332
- if (response.status === AuthenticationResponse.Status.INVALID_OTP) {
333
- if (attempt === MAX_OTP_ATTEMPTS) {
334
- throw new Error(`Maximum retry attempts: ${MAX_OTP_ATTEMPTS}`);
335
- } else {
336
- log('retrying invalid code', { attempt });
337
- authenticated.reset();
338
- }
339
- }
340
333
  }
341
334
  }
342
335
 
@@ -423,13 +416,61 @@ export class InvitationsHandler {
423
416
 
424
417
  return observable;
425
418
  }
419
+
420
+ private async _handleGuestOtpAuth(
421
+ extension: InvitationGuestExtension,
422
+ setState: (newState: Partial<Invitation>) => void,
423
+ authenticated: Trigger<string>,
424
+ options: { timeout: number },
425
+ ) {
426
+ for (let attempt = 1; attempt <= MAX_OTP_ATTEMPTS; attempt++) {
427
+ log('guest waiting for authentication code...');
428
+ setState({ state: Invitation.State.READY_FOR_AUTHENTICATION });
429
+ const authCode = await authenticated.wait(options);
430
+
431
+ log('sending authentication request');
432
+ setState({ state: Invitation.State.AUTHENTICATING });
433
+ const response = await extension.rpc.InvitationHostService.authenticate({ authCode });
434
+ if (response.status === undefined || response.status === AuthenticationResponse.Status.OK) {
435
+ break;
436
+ }
437
+
438
+ if (response.status === AuthenticationResponse.Status.INVALID_OTP) {
439
+ if (attempt === MAX_OTP_ATTEMPTS) {
440
+ throw new Error(`Maximum retry attempts: ${MAX_OTP_ATTEMPTS}`);
441
+ } else {
442
+ log('retrying invalid code', { attempt });
443
+ authenticated.reset();
444
+ }
445
+ }
446
+ }
447
+ }
448
+
449
+ private async _handleGuestKpkAuth(
450
+ extension: InvitationGuestExtension,
451
+ setState: (newState: Partial<Invitation>) => void,
452
+ invitation: Invitation,
453
+ introductionResponse: IntroductionResponse,
454
+ ) {
455
+ if (invitation.guestKeypair?.privateKey == null) {
456
+ throw new Error('keypair missing in the invitation');
457
+ }
458
+ if (introductionResponse.challenge == null) {
459
+ throw new Error('challenge missing in the introduction');
460
+ }
461
+ log('sending authentication request');
462
+ setState({ state: Invitation.State.AUTHENTICATING });
463
+ const signature = sign(Buffer.from(introductionResponse.challenge), invitation.guestKeypair.privateKey);
464
+ const response = await extension.rpc.InvitationHostService.authenticate({
465
+ signedChallenge: signature,
466
+ });
467
+ if (response.status !== AuthenticationResponse.Status.OK) {
468
+ throw new Error(`Authentication failed with code: ${response.status}`);
469
+ }
470
+ }
426
471
  }
427
472
 
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
- );
473
+ export const createAdmissionKeypair = (): AdmissionKeypair => {
474
+ const keypair = createKeyPair();
475
+ return { publicKey: PublicKey.from(keypair.publicKey), privateKey: keypair.secretKey };
435
476
  };