@dxos/echo-db 2.33.1-dev.eb88db79 → 2.33.2-dev.0286e021

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 (217) hide show
  1. package/dist/src/echo.d.ts +8 -8
  2. package/dist/src/echo.d.ts.map +1 -1
  3. package/dist/src/echo.js +15 -16
  4. package/dist/src/echo.js.map +1 -1
  5. package/dist/src/echo.test.js +7 -13
  6. package/dist/src/echo.test.js.map +1 -1
  7. package/dist/src/halo/contact-manager.d.ts +4 -4
  8. package/dist/src/halo/contact-manager.d.ts.map +1 -1
  9. package/dist/src/halo/contact-manager.js +10 -4
  10. package/dist/src/halo/contact-manager.js.map +1 -1
  11. package/dist/src/halo/halo-factory.d.ts +14 -9
  12. package/dist/src/halo/halo-factory.d.ts.map +1 -1
  13. package/dist/src/halo/halo-factory.js +47 -14
  14. package/dist/src/halo/halo-factory.js.map +1 -1
  15. package/dist/src/halo/halo-party.d.ts +35 -7
  16. package/dist/src/halo/halo-party.d.ts.map +1 -1
  17. package/dist/src/halo/halo-party.js +97 -18
  18. package/dist/src/halo/halo-party.js.map +1 -1
  19. package/dist/src/halo/halo.d.ts +14 -12
  20. package/dist/src/halo/halo.d.ts.map +1 -1
  21. package/dist/src/halo/halo.js +31 -29
  22. package/dist/src/halo/halo.js.map +1 -1
  23. package/dist/src/halo/halo.test.d.ts +2 -0
  24. package/dist/src/halo/halo.test.d.ts.map +1 -0
  25. package/dist/src/halo/halo.test.js +162 -0
  26. package/dist/src/halo/halo.test.js.map +1 -0
  27. package/dist/src/halo/identity-manager.d.ts +10 -8
  28. package/dist/src/halo/identity-manager.d.ts.map +1 -1
  29. package/dist/src/halo/identity-manager.js +24 -20
  30. package/dist/src/halo/identity-manager.js.map +1 -1
  31. package/dist/src/halo/identity.d.ts +12 -6
  32. package/dist/src/halo/identity.d.ts.map +1 -1
  33. package/dist/src/halo/identity.js +11 -15
  34. package/dist/src/halo/identity.js.map +1 -1
  35. package/dist/src/halo/index.d.ts +1 -1
  36. package/dist/src/halo/index.d.ts.map +1 -1
  37. package/dist/src/halo/index.js +1 -1
  38. package/dist/src/halo/index.js.map +1 -1
  39. package/dist/src/halo/party-opener.js +1 -1
  40. package/dist/src/halo/party-opener.js.map +1 -1
  41. package/dist/src/halo/preferences.d.ts +9 -9
  42. package/dist/src/halo/preferences.d.ts.map +1 -1
  43. package/dist/src/halo/preferences.js +25 -15
  44. package/dist/src/halo/preferences.js.map +1 -1
  45. package/dist/src/index.d.ts +1 -1
  46. package/dist/src/index.js +1 -1
  47. package/dist/src/invitations/greeting-initiator.d.ts +14 -4
  48. package/dist/src/invitations/greeting-initiator.d.ts.map +1 -1
  49. package/dist/src/invitations/greeting-initiator.js +15 -16
  50. package/dist/src/invitations/greeting-initiator.js.map +1 -1
  51. package/dist/src/invitations/greeting-responder.d.ts +3 -3
  52. package/dist/src/invitations/greeting-responder.d.ts.map +1 -1
  53. package/dist/src/invitations/greeting-responder.js +3 -4
  54. package/dist/src/invitations/greeting-responder.js.map +1 -1
  55. package/dist/src/invitations/halo-recovery-initiator.d.ts +6 -4
  56. package/dist/src/invitations/halo-recovery-initiator.d.ts.map +1 -1
  57. package/dist/src/invitations/halo-recovery-initiator.js +15 -26
  58. package/dist/src/invitations/halo-recovery-initiator.js.map +1 -1
  59. package/dist/src/invitations/index.d.ts +1 -1
  60. package/dist/src/invitations/index.js +1 -1
  61. package/dist/src/invitations/{invitation-manager.d.ts → invitation-factory.d.ts} +5 -6
  62. package/dist/src/invitations/invitation-factory.d.ts.map +1 -0
  63. package/dist/src/invitations/{invitation-manager.js → invitation-factory.js} +9 -15
  64. package/dist/src/invitations/invitation-factory.js.map +1 -0
  65. package/dist/src/invitations/offline-invitation-claimer.d.ts +3 -3
  66. package/dist/src/invitations/offline-invitation-claimer.js +1 -1
  67. package/dist/src/parties/{party-internal.d.ts → data-party.d.ts} +13 -11
  68. package/dist/src/parties/data-party.d.ts.map +1 -0
  69. package/dist/src/parties/{party-internal.js → data-party.js} +27 -37
  70. package/dist/src/parties/data-party.js.map +1 -0
  71. package/dist/src/parties/index.d.ts +1 -2
  72. package/dist/src/parties/index.d.ts.map +1 -1
  73. package/dist/src/parties/index.js +1 -2
  74. package/dist/src/parties/index.js.map +1 -1
  75. package/dist/src/parties/party-factory.d.ts +10 -11
  76. package/dist/src/parties/party-factory.d.ts.map +1 -1
  77. package/dist/src/parties/party-factory.js +25 -16
  78. package/dist/src/parties/party-factory.js.map +1 -1
  79. package/dist/src/parties/party-manager.d.ts +9 -9
  80. package/dist/src/parties/party-manager.d.ts.map +1 -1
  81. package/dist/src/parties/party-manager.js +13 -13
  82. package/dist/src/parties/party-manager.js.map +1 -1
  83. package/dist/src/parties/party-manager.test.js +69 -68
  84. package/dist/src/parties/party-manager.test.js.map +1 -1
  85. package/dist/src/{halo → parties}/party-preferences.d.ts +3 -3
  86. package/dist/src/parties/party-preferences.d.ts.map +1 -0
  87. package/dist/src/{halo → parties}/party-preferences.js +3 -3
  88. package/dist/src/parties/party-preferences.js.map +1 -0
  89. package/dist/src/pipeline/index.d.ts +3 -1
  90. package/dist/src/pipeline/index.d.ts.map +1 -1
  91. package/dist/src/pipeline/index.js +3 -1
  92. package/dist/src/pipeline/index.js.map +1 -1
  93. package/dist/src/{metadata → pipeline}/metadata-store.d.ts +0 -0
  94. package/dist/src/{metadata → pipeline}/metadata-store.d.ts.map +1 -1
  95. package/dist/src/{metadata → pipeline}/metadata-store.js +0 -0
  96. package/dist/src/{metadata → pipeline}/metadata-store.js.map +1 -1
  97. package/dist/src/{metadata → pipeline}/metadata-store.test.d.ts +0 -0
  98. package/dist/src/pipeline/metadata-store.test.d.ts.map +1 -0
  99. package/dist/src/{metadata → pipeline}/metadata-store.test.js +0 -0
  100. package/dist/src/{metadata → pipeline}/metadata-store.test.js.map +1 -1
  101. package/dist/src/{parties → pipeline}/party-core.d.ts +0 -0
  102. package/dist/src/pipeline/party-core.d.ts.map +1 -0
  103. package/dist/src/{parties → pipeline}/party-core.js +8 -4
  104. package/dist/src/pipeline/party-core.js.map +1 -0
  105. package/dist/src/{parties → pipeline}/party-core.test.d.ts +0 -0
  106. package/dist/src/pipeline/party-core.test.d.ts.map +1 -0
  107. package/dist/src/{parties → pipeline}/party-core.test.js +3 -4
  108. package/dist/src/pipeline/party-core.test.js.map +1 -0
  109. package/dist/src/pipeline/party-feed-provider.d.ts +3 -2
  110. package/dist/src/pipeline/party-feed-provider.d.ts.map +1 -1
  111. package/dist/src/pipeline/party-feed-provider.js +40 -19
  112. package/dist/src/pipeline/party-feed-provider.js.map +1 -1
  113. package/dist/src/protocol/auth-plugin.d.ts +7 -0
  114. package/dist/src/protocol/auth-plugin.d.ts.map +1 -0
  115. package/dist/src/protocol/auth-plugin.js +16 -0
  116. package/dist/src/protocol/auth-plugin.js.map +1 -0
  117. package/dist/src/protocol/authenticator.d.ts +14 -0
  118. package/dist/src/protocol/authenticator.d.ts.map +1 -0
  119. package/dist/src/protocol/authenticator.js +31 -0
  120. package/dist/src/protocol/authenticator.js.map +1 -0
  121. package/dist/src/protocol/authenticator.test.d.ts +2 -0
  122. package/dist/src/protocol/authenticator.test.d.ts.map +1 -0
  123. package/dist/src/protocol/authenticator.test.js +45 -0
  124. package/dist/src/protocol/authenticator.test.js.map +1 -0
  125. package/dist/src/protocol/credentials-signer.d.ts +31 -0
  126. package/dist/src/protocol/credentials-signer.d.ts.map +1 -0
  127. package/dist/src/protocol/credentials-signer.js +53 -0
  128. package/dist/src/protocol/credentials-signer.js.map +1 -0
  129. package/dist/src/protocol/halo-recovery-plugin.d.ts +10 -0
  130. package/dist/src/protocol/halo-recovery-plugin.d.ts.map +1 -0
  131. package/dist/src/protocol/halo-recovery-plugin.js +18 -0
  132. package/dist/src/protocol/halo-recovery-plugin.js.map +1 -0
  133. package/dist/src/protocol/index.d.ts +7 -0
  134. package/dist/src/protocol/index.d.ts.map +1 -0
  135. package/dist/src/{metadata → protocol}/index.js +7 -2
  136. package/dist/src/protocol/index.js.map +1 -0
  137. package/dist/src/protocol/offline-invitation-plugin.d.ts +9 -0
  138. package/dist/src/protocol/offline-invitation-plugin.d.ts.map +1 -0
  139. package/dist/src/protocol/offline-invitation-plugin.js +17 -0
  140. package/dist/src/protocol/offline-invitation-plugin.js.map +1 -0
  141. package/dist/src/{pipeline → protocol}/party-protocol-factory.d.ts +7 -17
  142. package/dist/src/protocol/party-protocol-factory.d.ts.map +1 -0
  143. package/dist/src/{pipeline → protocol}/party-protocol-factory.js +7 -47
  144. package/dist/src/protocol/party-protocol-factory.js.map +1 -0
  145. package/dist/src/snapshots/snapshot-generator.d.ts +1 -1
  146. package/dist/src/snapshots/snapshot-generator.d.ts.map +1 -1
  147. package/dist/src/testing/testing-factories.js.map +1 -1
  148. package/dist/src/testing/testing.d.ts +2 -2
  149. package/dist/src/testing/testing.d.ts.map +1 -1
  150. package/dist/src/testing/testing.js +1 -7
  151. package/dist/src/testing/testing.js.map +1 -1
  152. package/dist/tsconfig.tsbuildinfo +1 -1
  153. package/package.json +20 -20
  154. package/src/echo.test.ts +8 -14
  155. package/src/echo.ts +107 -109
  156. package/src/halo/contact-manager.ts +9 -5
  157. package/src/halo/halo-factory.ts +82 -19
  158. package/src/halo/halo-party.ts +137 -21
  159. package/src/halo/halo.test.ts +198 -0
  160. package/src/halo/halo.ts +40 -38
  161. package/src/halo/identity-manager.ts +33 -28
  162. package/src/halo/identity.ts +19 -23
  163. package/src/halo/index.ts +1 -1
  164. package/src/halo/party-opener.ts +1 -1
  165. package/src/halo/preferences.ts +29 -18
  166. package/src/index.ts +1 -1
  167. package/src/invitations/greeting-initiator.ts +37 -32
  168. package/src/invitations/greeting-responder.ts +4 -5
  169. package/src/invitations/halo-recovery-initiator.ts +16 -24
  170. package/src/invitations/index.ts +1 -1
  171. package/src/invitations/{invitation-manager.ts → invitation-factory.ts} +8 -15
  172. package/src/invitations/offline-invitation-claimer.ts +3 -3
  173. package/src/parties/{party-internal.ts → data-party.ts} +30 -55
  174. package/src/parties/index.ts +1 -2
  175. package/src/parties/party-factory.ts +31 -21
  176. package/src/parties/party-manager.test.ts +99 -73
  177. package/src/parties/party-manager.ts +17 -17
  178. package/src/{halo → parties}/party-preferences.ts +3 -3
  179. package/src/pipeline/index.ts +3 -1
  180. package/src/{metadata → pipeline}/metadata-store.test.ts +0 -0
  181. package/src/{metadata → pipeline}/metadata-store.ts +0 -0
  182. package/src/{parties → pipeline}/party-core.test.ts +1 -2
  183. package/src/{parties → pipeline}/party-core.ts +12 -7
  184. package/src/pipeline/party-feed-provider.ts +32 -21
  185. package/src/protocol/auth-plugin.ts +14 -0
  186. package/src/protocol/authenticator.test.ts +66 -0
  187. package/src/protocol/authenticator.ts +56 -0
  188. package/src/protocol/credentials-signer.ts +60 -0
  189. package/src/protocol/halo-recovery-plugin.ts +20 -0
  190. package/src/protocol/index.ts +10 -0
  191. package/src/protocol/offline-invitation-plugin.ts +19 -0
  192. package/src/{pipeline → protocol}/party-protocol-factory.ts +10 -82
  193. package/src/snapshots/snapshot-generator.ts +1 -1
  194. package/src/testing/testing-factories.ts +2 -2
  195. package/src/testing/testing.ts +3 -9
  196. package/dist/src/halo/party-preferences.d.ts.map +0 -1
  197. package/dist/src/halo/party-preferences.js.map +0 -1
  198. package/dist/src/invitations/invitation-manager.d.ts.map +0 -1
  199. package/dist/src/invitations/invitation-manager.js.map +0 -1
  200. package/dist/src/metadata/index.d.ts +0 -2
  201. package/dist/src/metadata/index.d.ts.map +0 -1
  202. package/dist/src/metadata/index.js.map +0 -1
  203. package/dist/src/metadata/metadata-store.test.d.ts.map +0 -1
  204. package/dist/src/parties/authenticator.d.ts +0 -5
  205. package/dist/src/parties/authenticator.d.ts.map +0 -1
  206. package/dist/src/parties/authenticator.js +0 -27
  207. package/dist/src/parties/authenticator.js.map +0 -1
  208. package/dist/src/parties/party-core.d.ts.map +0 -1
  209. package/dist/src/parties/party-core.js.map +0 -1
  210. package/dist/src/parties/party-core.test.d.ts.map +0 -1
  211. package/dist/src/parties/party-core.test.js.map +0 -1
  212. package/dist/src/parties/party-internal.d.ts.map +0 -1
  213. package/dist/src/parties/party-internal.js.map +0 -1
  214. package/dist/src/pipeline/party-protocol-factory.d.ts.map +0 -1
  215. package/dist/src/pipeline/party-protocol-factory.js.map +0 -1
  216. package/src/metadata/index.ts +0 -5
  217. package/src/parties/authenticator.ts +0 -31
@@ -2,10 +2,22 @@
2
2
  // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
+ import assert from 'assert';
6
+
7
+ import { Event, synchronized } from '@dxos/async';
5
8
  import { KeyHint } from '@dxos/credentials';
6
9
  import { PublicKey } from '@dxos/crypto';
7
-
8
- import { PartyInternal } from '../parties';
10
+ import { timed } from '@dxos/debug';
11
+ import { Timeframe } from '@dxos/echo-protocol';
12
+ import { ModelFactory } from '@dxos/model-factory';
13
+ import { NetworkManager } from '@dxos/network-manager';
14
+
15
+ import { InvitationAuthenticator, InvitationDescriptor, InvitationFactory, InvitationOptions } from '../invitations';
16
+ import { PARTY_ITEM_TYPE } from '../parties';
17
+ import { PartyFeedProvider, PartyProtocolFactory, PartyCore, PartyOptions } from '../pipeline';
18
+ import { createAuthenticator, createAuthPlugin, createCredentialsProvider, createHaloRecoveryPlugin } from '../protocol';
19
+ import { CredentialsSigner } from '../protocol/credentials-signer';
20
+ import { SnapshotStore } from '../snapshots';
9
21
  import { ContactManager } from './contact-manager';
10
22
  import { Preferences } from './preferences';
11
23
 
@@ -23,23 +35,56 @@ export interface JoinedParty {
23
35
  }
24
36
 
25
37
  /**
26
- * Wraps PartyInternal and provides all HALO-related functionality.
38
+ * Provides all HALO-related functionality.
27
39
  */
28
40
  export class HaloParty {
41
+ public readonly update = new Event<void>();
42
+
43
+ private readonly _partyCore: PartyCore;
44
+ private _invitationManager?: InvitationFactory;
45
+ private _protocol?: PartyProtocolFactory;
46
+
29
47
  private readonly _contactManager: ContactManager;
30
48
  private readonly _preferences: Preferences;
31
49
 
32
50
  constructor (
33
- private readonly _party: PartyInternal,
34
- private readonly _identityKey: PublicKey,
35
- deviceKey: PublicKey
51
+ modelFactory: ModelFactory,
52
+ snapshotStore: SnapshotStore,
53
+ private readonly _feedProvider: PartyFeedProvider,
54
+ private readonly _credentialsSigner: CredentialsSigner,
55
+ private readonly _networkManager: NetworkManager,
56
+ private readonly _hints: KeyHint[] = [],
57
+ _initialTimeframe: Timeframe | undefined,
58
+ _options: PartyOptions
36
59
  ) {
37
- this._contactManager = new ContactManager(this._party);
38
- this._preferences = new Preferences(this._party, deviceKey);
60
+ this._partyCore = new PartyCore(
61
+ _credentialsSigner.getIdentityKey().publicKey,
62
+ _feedProvider,
63
+ modelFactory,
64
+ snapshotStore,
65
+ _credentialsSigner.getIdentityKey().publicKey,
66
+ _initialTimeframe,
67
+ _options
68
+ );
69
+
70
+ this._contactManager = new ContactManager(() => this.isOpen ? this.database : undefined);
71
+ this._preferences = new Preferences(
72
+ () => this.isOpen ? this.database : undefined,
73
+ _credentialsSigner.getDeviceKey().publicKey
74
+ );
75
+ }
76
+
77
+ /**
78
+ * Party key.
79
+ * Always equal to the identity key.
80
+ * @deprecated Should remove.
81
+ */
82
+ get key () {
83
+ return this._partyCore.key;
39
84
  }
40
85
 
41
86
  get isOpen () {
42
- return this._party.isOpen;
87
+ return !!(this._partyCore.isOpen && this._protocol);
43
88
  }
44
89
 
45
90
  get contacts () {
@@ -52,7 +97,7 @@ export class HaloParty {
52
97
 
53
98
  // TODO(burdon): Remove.
54
99
  get database () {
55
- return this._party.database;
100
+ return this._partyCore.database;
56
101
  }
57
102
 
58
103
  //
@@ -60,32 +105,103 @@ export class HaloParty {
60
105
  // (eg, identity, credentials, device management.)
61
106
  //
62
107
 
63
- get invitationManager () {
64
- return this._party.invitationManager;
65
- }
66
-
67
108
  get identityInfo () {
68
- return this._party.processor.infoMessages.get(this._identityKey.toHex());
109
+ return this._partyCore.processor.infoMessages.get(this._credentialsSigner.getIdentityKey().publicKey.toHex());
69
110
  }
70
111
 
71
112
  get identityGenesis () {
72
- return this._party.processor.credentialMessages.get(this._identityKey.toHex());
113
+ return this._partyCore.processor.credentialMessages.get(this._credentialsSigner.getIdentityKey().publicKey.toHex());
73
114
  }
74
115
 
75
116
  get memberKeys () {
76
- return this._party.processor.memberKeys;
117
+ return this._partyCore.processor.memberKeys;
77
118
  }
78
119
 
79
120
  get credentialMessages () {
80
- return this._party.processor.credentialMessages;
121
+ return this._partyCore.processor.credentialMessages;
81
122
  }
82
123
 
83
124
  get feedKeys () {
84
- return this._party.processor.feedKeys;
125
+ return this._partyCore.processor.feedKeys;
126
+ }
127
+
128
+ async getWriteFeedKey () {
129
+ const feed = await this._feedProvider.createOrOpenWritableFeed();
130
+ return feed.key;
85
131
  }
86
132
 
87
- // TODO(burdon): Life-cycle: this must be in the same class/stack as open (currently IdentityManager).
133
+ get processor () {
134
+ return this._partyCore.processor;
135
+ }
136
+
137
+ /**
138
+ * Opens the pipeline and connects the streams.
139
+ */
140
+ @synchronized
141
+ @timed(5_000)
142
+ async open () {
143
+ if (this.isOpen) {
144
+ return this;
145
+ }
146
+
147
+ await this._partyCore.open(this._hints);
148
+
149
+ this._invitationManager = new InvitationFactory(
150
+ this._partyCore.processor,
151
+ this._credentialsSigner,
152
+ this._networkManager
153
+ );
154
+
155
+ //
156
+ // Network/swarm.
157
+ //
158
+
159
+ const writeFeed = await this._partyCore.getWriteFeed();
160
+ const peerId = this._credentialsSigner.getDeviceKey().publicKey;
161
+ this._protocol = new PartyProtocolFactory(
162
+ this._partyCore.key,
163
+ this._networkManager,
164
+ this._feedProvider,
165
+ peerId,
166
+ createCredentialsProvider(this._credentialsSigner, this._partyCore.key, writeFeed.key),
167
+ this._partyCore.processor.getActiveFeedSet()
168
+ );
169
+
170
+ // Replication.
171
+ await this._protocol.start([
172
+ createAuthPlugin(createAuthenticator(this._partyCore.processor, this._credentialsSigner), peerId),
173
+ createHaloRecoveryPlugin(this._credentialsSigner.getIdentityKey().publicKey, this._invitationManager, peerId)
174
+ ]);
175
+
176
+ // Issue an 'update' whenever the properties change.
177
+ this.database.select({ type: PARTY_ITEM_TYPE }).exec().update.on(() => this.update.emit());
178
+
179
+ this.update.emit();
180
+ return this;
181
+ }
182
+
183
+ /**
184
+ * Closes the pipeline and streams.
185
+ */
186
+ @synchronized
88
187
  async close () {
89
- await this._party.close();
188
+ if (!this.isOpen) {
189
+ return this;
190
+ }
191
+
192
+ await this._partyCore.close();
193
+ await this._protocol?.stop();
194
+
195
+ this._protocol = undefined;
196
+ this._invitationManager = undefined;
197
+
198
+ this.update.emit();
199
+
200
+ return this;
201
+ }
202
+
203
+ async createInvitation (authenticationDetails: InvitationAuthenticator, options?: InvitationOptions): Promise<InvitationDescriptor> {
204
+ assert(this._invitationManager, 'HALO party not open.');
205
+ return this._invitationManager.createInvitation(authenticationDetails, options);
90
206
  }
91
207
  }
@@ -0,0 +1,198 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import expect from 'expect';
6
+ import { it as test } from 'mocha';
7
+
8
+ import { defaultSecretProvider, Keyring } from '@dxos/credentials';
9
+ import { generateSeedPhrase, keyPairFromSeedPhrase, PublicKey } from '@dxos/crypto';
10
+ import { codec } from '@dxos/echo-protocol';
11
+ import { FeedStore } from '@dxos/feed-store';
12
+ import { ModelFactory } from '@dxos/model-factory';
13
+ import { NetworkManager } from '@dxos/network-manager';
14
+ import { ObjectModel } from '@dxos/object-model';
15
+ import { afterTest, testTimeout } from '@dxos/testutils';
16
+
17
+ import { defaultInvitationAuthenticator } from '../invitations';
18
+ import { MetadataStore, PartyFeedProvider } from '../pipeline';
19
+ import { SnapshotStore } from '../snapshots';
20
+ import { createRamStorage } from '../util';
21
+ import { HALO } from './halo';
22
+
23
+ describe('HALO', () => {
24
+ const setup = () => {
25
+ const modelFactory = new ModelFactory()
26
+ .registerModel(ObjectModel);
27
+
28
+ const networkManager = new NetworkManager();
29
+ const snapshotStore = new SnapshotStore(createRamStorage());
30
+ const metadataStore = new MetadataStore(createRamStorage());
31
+ const keyring = new Keyring();
32
+ const feedStore = new FeedStore(createRamStorage(), { valueEncoding: codec });
33
+
34
+ const feedProviderFactory = (partyKey: PublicKey) => new PartyFeedProvider(
35
+ metadataStore,
36
+ keyring,
37
+ feedStore,
38
+ partyKey
39
+ );
40
+
41
+ return new HALO({
42
+ keyring: keyring,
43
+ networkManager: networkManager,
44
+ metadataStore: metadataStore,
45
+ feedProviderFactory,
46
+ modelFactory: modelFactory,
47
+ snapshotStore: snapshotStore,
48
+ options: {}
49
+ });
50
+ };
51
+
52
+ const setupOpen = async () => {
53
+ const halo = setup();
54
+ await halo.open();
55
+ afterTest(() => halo.close());
56
+ return halo;
57
+ };
58
+
59
+ test('open & close', async () => {
60
+ const halo = setup();
61
+
62
+ await halo.open();
63
+ await halo.close();
64
+ });
65
+
66
+ test('create profile', async () => {
67
+ const halo = setup();
68
+ await halo.open();
69
+ afterTest(() => halo.close());
70
+
71
+ const profile = await halo.createProfile({ username: 'Test user' });
72
+ expect(profile.username).toEqual('Test user');
73
+ expect(profile.publicKey).toBeInstanceOf(PublicKey);
74
+ });
75
+
76
+ test('reload with profile', async () => {
77
+ const halo = setup();
78
+ await halo.open();
79
+
80
+ const initialProfile = await halo.createProfile({ username: 'Test user' });
81
+ await halo.close();
82
+
83
+ await halo.open();
84
+ const profile = halo.getProfile();
85
+ expect(profile).toBeDefined();
86
+ expect(profile!.username).toEqual('Test user');
87
+ expect(profile!.publicKey.equals(initialProfile.publicKey)).toBeTruthy();
88
+
89
+ await halo.close();
90
+ });
91
+
92
+ test('invite another device', async () => {
93
+ const deviceA = setup();
94
+ await deviceA.open();
95
+ afterTest(() => deviceA.close());
96
+ const profileA = await deviceA.createProfile({ username: 'Test user' });
97
+
98
+ const deviceB = setup();
99
+ await deviceB.open();
100
+ afterTest(() => deviceB.close());
101
+
102
+ const invitation = await deviceA.createInvitation(defaultInvitationAuthenticator);
103
+ await deviceB.join(invitation, defaultSecretProvider);
104
+
105
+ const profileB = deviceB.getProfile();
106
+ expect(profileB).toBeDefined();
107
+ expect(profileB!.username).toEqual('Test user');
108
+ expect(profileB!.publicKey.equals(profileA.publicKey)).toBeTruthy();
109
+ });
110
+
111
+ test('invite 2 devices in a chain', async () => {
112
+ const deviceA = await setupOpen();
113
+ const deviceB = await setupOpen();
114
+ const deviceC = await setupOpen();
115
+
116
+ const profileA = await deviceA.createProfile({ username: 'Test user' });
117
+
118
+ {
119
+ const invitation = await deviceA.createInvitation(defaultInvitationAuthenticator);
120
+ await deviceB.join(invitation, defaultSecretProvider);
121
+ }
122
+
123
+ {
124
+ const invitation = await deviceB.createInvitation(defaultInvitationAuthenticator);
125
+ await deviceC.join(invitation, defaultSecretProvider);
126
+ }
127
+
128
+ const profileC = deviceC.getProfile();
129
+ expect(profileC).toBeDefined();
130
+ expect(profileC!.username).toEqual('Test user');
131
+ expect(profileC!.publicKey.equals(profileA.publicKey)).toBeTruthy();
132
+ });
133
+
134
+ test('recover HALO', async () => {
135
+ const deviceA = await setupOpen();
136
+ const deviceB = await setupOpen();
137
+
138
+ const seedPhrase = generateSeedPhrase();
139
+ const profileA = await deviceA.createProfile({ username: 'Test user', ...keyPairFromSeedPhrase(seedPhrase) });
140
+
141
+ await deviceB.recover(seedPhrase);
142
+ const profileB = deviceB.getProfile();
143
+ expect(profileB).toBeDefined();
144
+ expect(profileB!.username).toEqual('Test user');
145
+ expect(profileB!.publicKey.equals(profileA.publicKey)).toBeTruthy();
146
+ });
147
+
148
+ test('HALO database is synced between 2 devices', async () => {
149
+ const deviceA = await setupOpen();
150
+ const deviceB = await setupOpen();
151
+ await deviceA.createProfile({ username: 'Test user' });
152
+ const invitation = await deviceA.createInvitation(defaultInvitationAuthenticator);
153
+ await deviceB.join(invitation, defaultSecretProvider);
154
+
155
+ {
156
+ const itemA = await deviceA.identity!.halo!.database.createItem({ type: 'example:test-1' });
157
+ const itemB = await testTimeout(deviceB.identity!.halo!.database.waitForItem({ type: 'example:test-1' }));
158
+ expect(itemA.id).toEqual(itemB.id);
159
+ }
160
+
161
+ {
162
+ const itemB = await deviceB.identity!.halo.database.createItem({ type: 'example:test-2' });
163
+ const itemA = await testTimeout(deviceA.identity!.halo.database.waitForItem({ type: 'example:test-2' }));
164
+ expect(itemB.id).toEqual(itemA.id);
165
+ }
166
+ });
167
+
168
+ describe('Preferences', () => {
169
+ test('global and device work on single device', async () => {
170
+ const halo = await setupOpen();
171
+ await halo.createProfile();
172
+
173
+ const globalPreferences: ObjectModel = halo.identity!.preferences!.getGlobalPreferences()!.model;
174
+ await globalPreferences.set('key', 'value');
175
+ expect(globalPreferences.get('key')).toEqual('value');
176
+
177
+ const devicePreferences: ObjectModel = halo.identity!.preferences!.getDevicePreferences()!.model;
178
+ await devicePreferences.set('key', 'value2');
179
+ expect(devicePreferences.get('key')).toEqual('value2');
180
+ });
181
+
182
+ test('global preferences are synced between devices', async () => {
183
+ const deviceA = await setupOpen();
184
+ const deviceB = await setupOpen();
185
+ await deviceA.createProfile({ username: 'Test user' });
186
+ const invitation = await deviceA.createInvitation(defaultInvitationAuthenticator);
187
+ await deviceB.join(invitation, defaultSecretProvider);
188
+
189
+ const preferencesA: ObjectModel = deviceA.identity!.preferences!.getGlobalPreferences()!.model;
190
+ const preferencesB: ObjectModel = deviceB.identity!.preferences!.getGlobalPreferences()!.model;
191
+
192
+ const update = preferencesB.update.waitForCount(1);
193
+ await preferencesA.set('key', 'value');
194
+ await testTimeout(update);
195
+ expect(preferencesB.get('key')).toEqual('value');
196
+ });
197
+ });
198
+ });
package/src/halo/halo.ts CHANGED
@@ -8,12 +8,15 @@ import debug from 'debug';
8
8
  import { synchronized } from '@dxos/async';
9
9
  import { KeyRecord, Keyring, KeyType, SecretProvider } from '@dxos/credentials';
10
10
  import { createKeyPair, KeyPair, PublicKey } from '@dxos/crypto';
11
+ import { raise } from '@dxos/debug';
12
+ import { ModelFactory } from '@dxos/model-factory';
11
13
  import { NetworkManager } from '@dxos/network-manager';
12
14
 
13
15
  import { ResultSet } from '../api';
14
16
  import { InvitationAuthenticator, InvitationDescriptor, InvitationOptions } from '../invitations';
15
- import { MetadataStore } from '../metadata';
16
- import { PartyFactory, OpenProgress, PartyManager } from '../parties';
17
+ import { OpenProgress } from '../parties';
18
+ import { MetadataStore, PartyOptions, PartyFeedProvider } from '../pipeline';
19
+ import { SnapshotStore } from '../snapshots';
17
20
  import { Contact } from './contact-manager';
18
21
  import { HaloFactory } from './halo-factory';
19
22
  import { IdentityManager } from './identity-manager';
@@ -28,10 +31,12 @@ export interface ProfileInfo {
28
31
 
29
32
  export interface HaloConfiguration {
30
33
  keyring: Keyring,
31
- partyFactory: PartyFactory,
32
34
  networkManager: NetworkManager,
33
- partyManager: PartyManager,
34
- metadataStore: MetadataStore
35
+ metadataStore: MetadataStore,
36
+ modelFactory: ModelFactory,
37
+ snapshotStore: SnapshotStore,
38
+ feedProviderFactory: (partyKey: PublicKey) => PartyFeedProvider,
39
+ options: PartyOptions
35
40
  }
36
41
 
37
42
  /**
@@ -39,23 +44,26 @@ export interface HaloConfiguration {
39
44
  */
40
45
  export class HALO {
41
46
  private readonly _keyring: Keyring;
42
- private readonly _partyManager: PartyManager;
43
47
  private readonly _identityManager: IdentityManager;
44
48
 
45
49
  constructor ({
46
50
  keyring,
47
- partyManager,
48
- partyFactory,
49
51
  networkManager,
50
- metadataStore
52
+ metadataStore,
53
+ modelFactory,
54
+ snapshotStore,
55
+ feedProviderFactory,
56
+ options
51
57
  }: HaloConfiguration) {
52
58
  this._keyring = keyring;
53
- this._partyManager = partyManager;
54
59
 
55
60
  const haloFactory = new HaloFactory(
56
- partyFactory,
57
61
  networkManager,
58
- this._keyring
62
+ modelFactory,
63
+ snapshotStore,
64
+ feedProviderFactory,
65
+ keyring,
66
+ options
59
67
  );
60
68
 
61
69
  this._identityManager = new IdentityManager(this._keyring, haloFactory, metadataStore);
@@ -98,7 +106,7 @@ export class HALO {
98
106
  * User's IDENTITY keypair.
99
107
  */
100
108
  get identityKey (): KeyRecord | undefined {
101
- return this.identity.identityKey;
109
+ return this.identity?.identityKey;
102
110
  }
103
111
 
104
112
  /**
@@ -106,7 +114,7 @@ export class HALO {
106
114
  */
107
115
  // TODO(burdon): Rename username (here and in data structure).
108
116
  get identityDisplayName (): string | undefined {
109
- return this.identity.displayName;
117
+ return this.identity?.displayName;
110
118
  }
111
119
 
112
120
  /**
@@ -135,8 +143,7 @@ export class HALO {
135
143
  * Closes HALO. Automatically called when client is destroyed.
136
144
  */
137
145
  async close () {
138
- // TODO(marik-d): Should be `_identityManager.close()`.
139
- await this.identity.halo?.close();
146
+ await this._identityManager.close();
140
147
  }
141
148
 
142
149
  /**
@@ -155,13 +162,12 @@ export class HALO {
155
162
  *
156
163
  * NOTE: This method does not initialize the HALO party.
157
164
  */
158
- // TODO(burdon): Why is this separate from createHalo?
159
- async createIdentity (keyPair: KeyPair) {
165
+ private async _createIdentityKeypair (keyPair: KeyPair) {
160
166
  const { publicKey, secretKey } = keyPair;
161
167
  assert(publicKey, 'Invalid publicKey');
162
168
  assert(secretKey, 'Invalid secretKey');
163
169
 
164
- if (this.identity.identityKey) {
170
+ if (this.identity?.identityKey) {
165
171
  // TODO(burdon): Bad API: Semantics change based on options.
166
172
  // TODO(burdon): `createProfile` isn't part of this package.
167
173
  throw new Error('Identity key already exists. Call createProfile without a keypair to only create a halo party.');
@@ -173,28 +179,25 @@ export class HALO {
173
179
  /**
174
180
  * Creates the initial HALO party.
175
181
  */
176
- // TODO(burdon): Return Halo API object?
177
- async create (displayName?: string) {
182
+ private async _createHaloParty (displayName?: string) {
178
183
  // TODO(burdon): Why not assert?
179
- if (this.identity.halo) {
180
- throw new Error('HALO party already exists');
181
- }
182
- if (!this.identity.identityKey) {
183
- throw new Error('Cannot create HALO. Identity key not found.');
184
+ if (this.identity) {
185
+ throw new Error('Identity already initialized');
184
186
  }
185
187
 
188
+ const identityKey = this._identityManager.getIdentityKey() ?? raise(new Error('Cannot create HALO. Identity key not found.'));
186
189
  await this._identityManager.createHalo({
187
- identityDisplayName: displayName || this.identity.identityKey.publicKey.humanize()
190
+ identityDisplayName: displayName || identityKey.publicKey.humanize()
188
191
  });
189
192
  }
190
193
 
191
194
  /**
192
195
  * Joins an existing identity HALO from a recovery seed phrase.
193
196
  */
197
+ // TODO(dmaretskyi): Do not return HALO party here.
194
198
  async recover (seedPhrase: string) {
195
- assert(this._partyManager.isOpen, 'ECHO not open.');
196
- assert(!this.identity.halo, 'HALO already exists.');
197
- assert(!this.identity.identityKey, 'Identity key already exists.');
199
+ assert(!this.identity?.halo, 'HALO already exists.');
200
+ assert(!this.identity?.identityKey, 'Identity key already exists.');
198
201
 
199
202
  return this._identityManager.recoverHalo(seedPhrase);
200
203
  }
@@ -202,9 +205,9 @@ export class HALO {
202
205
  /**
203
206
  * Joins an existing identity HALO by invitation.
204
207
  */
208
+ // TODO(dmaretskyi): Do not return HALO party here.
205
209
  async join (invitationDescriptor: InvitationDescriptor, secretProvider: SecretProvider) {
206
- assert(this._partyManager.isOpen, 'ECHO not open.');
207
- assert(!this.identity.halo, 'HALO already exists.');
210
+ assert(!this.identity?.halo, 'HALO already exists.');
208
211
 
209
212
  return this._identityManager.joinHalo(invitationDescriptor, secretProvider);
210
213
  }
@@ -213,9 +216,8 @@ export class HALO {
213
216
  * Create an invitation to an exiting identity HALO.
214
217
  */
215
218
  async createInvitation (authenticationDetails: InvitationAuthenticator, options?: InvitationOptions) {
216
- assert(this.identity.halo, 'HALO not initialized.');
217
-
218
- return this.identity.halo.invitationManager.createInvitation(authenticationDetails, options);
219
+ assert(this.identity?.halo, 'HALO not initialized.');
220
+ return this.identity.halo.createInvitation(authenticationDetails, options);
219
221
  }
220
222
 
221
223
  /**
@@ -223,7 +225,7 @@ export class HALO {
223
225
  */
224
226
  // TODO(burdon): Expose ContactManager directly.
225
227
  queryContacts (): ResultSet<Contact> {
226
- assert(this.identity.contacts, 'HALO not initialized.');
228
+ assert(this.identity?.contacts, 'HALO not initialized.');
227
229
 
228
230
  return this.identity.contacts.queryContacts();
229
231
  }
@@ -245,8 +247,8 @@ export class HALO {
245
247
  }
246
248
 
247
249
  const keyPair = publicKey ? { publicKey: Buffer.from(publicKey), secretKey: Buffer.from(secretKey!) } : createKeyPair();
248
- await this.createIdentity(keyPair);
249
- await this.create(username);
250
+ await this._createIdentityKeypair(keyPair);
251
+ await this._createHaloParty(username);
250
252
 
251
253
  const profile = this.getProfile();
252
254
  assert(profile);