@dxos/client-services 0.1.2 → 0.1.3

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 (282) hide show
  1. package/dist/src/index.d.ts +4 -1
  2. package/dist/src/index.d.ts.map +1 -1
  3. package/dist/src/index.js +4 -1
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/packlets/deprecated/index.d.ts +5 -0
  6. package/dist/src/packlets/deprecated/index.d.ts.map +1 -0
  7. package/dist/src/packlets/{services/impl → deprecated}/index.js +1 -3
  8. package/dist/src/packlets/deprecated/index.js.map +1 -0
  9. package/dist/src/packlets/{invitations/common.d.ts → deprecated/invitations.d.ts} +14 -2
  10. package/dist/src/packlets/deprecated/invitations.d.ts.map +1 -0
  11. package/dist/src/packlets/{invitations/common.js → deprecated/invitations.js} +1 -1
  12. package/dist/src/packlets/deprecated/invitations.js.map +1 -0
  13. package/dist/src/packlets/{services/impl → deprecated}/profile.d.ts +5 -5
  14. package/dist/src/packlets/deprecated/profile.d.ts.map +1 -0
  15. package/dist/src/packlets/{services/impl → deprecated}/profile.js +10 -10
  16. package/dist/src/packlets/deprecated/profile.js.map +1 -0
  17. package/dist/src/packlets/deprecated/space.d.ts +23 -0
  18. package/dist/src/packlets/deprecated/space.d.ts.map +1 -0
  19. package/dist/src/packlets/deprecated/space.js +206 -0
  20. package/dist/src/packlets/deprecated/space.js.map +1 -0
  21. package/dist/src/packlets/{services/impl → deprecated}/system.d.ts +2 -2
  22. package/dist/src/packlets/deprecated/system.d.ts.map +1 -0
  23. package/dist/src/packlets/{services/impl → deprecated}/system.js +3 -3
  24. package/dist/src/packlets/deprecated/system.js.map +1 -0
  25. package/dist/src/packlets/deprecated/tracing.d.ts +12 -0
  26. package/dist/src/packlets/deprecated/tracing.d.ts.map +1 -0
  27. package/dist/src/packlets/{services/impl → deprecated}/tracing.js +3 -3
  28. package/dist/src/packlets/deprecated/tracing.js.map +1 -0
  29. package/dist/src/packlets/{services/impl → devtools}/devtools.d.ts +11 -7
  30. package/dist/src/packlets/devtools/devtools.d.ts.map +1 -0
  31. package/dist/src/packlets/devtools/devtools.js +86 -0
  32. package/dist/src/packlets/devtools/devtools.js.map +1 -0
  33. package/dist/src/packlets/devtools/feeds.d.ts.map +1 -1
  34. package/dist/src/packlets/devtools/feeds.js +28 -26
  35. package/dist/src/packlets/devtools/feeds.js.map +1 -1
  36. package/dist/src/packlets/devtools/index.d.ts +1 -1
  37. package/dist/src/packlets/devtools/index.d.ts.map +1 -1
  38. package/dist/src/packlets/devtools/index.js +1 -1
  39. package/dist/src/packlets/devtools/index.js.map +1 -1
  40. package/dist/src/packlets/devtools/spaces.d.ts +2 -2
  41. package/dist/src/packlets/devtools/spaces.d.ts.map +1 -1
  42. package/dist/src/packlets/devtools/spaces.js +13 -13
  43. package/dist/src/packlets/devtools/spaces.js.map +1 -1
  44. package/dist/src/packlets/identity/devices-service-impl.d.ts +10 -0
  45. package/dist/src/packlets/identity/devices-service-impl.d.ts.map +1 -0
  46. package/dist/src/packlets/identity/devices-service-impl.js +47 -0
  47. package/dist/src/packlets/identity/devices-service-impl.js.map +1 -0
  48. package/dist/src/packlets/identity/identity-manager.test.js +7 -11
  49. package/dist/src/packlets/identity/identity-manager.test.js.map +1 -1
  50. package/dist/src/packlets/identity/identity.d.ts +2 -0
  51. package/dist/src/packlets/identity/identity.d.ts.map +1 -1
  52. package/dist/src/packlets/identity/identity.js +4 -1
  53. package/dist/src/packlets/identity/identity.js.map +1 -1
  54. package/dist/src/packlets/identity/identity.test.js +2 -3
  55. package/dist/src/packlets/identity/identity.test.js.map +1 -1
  56. package/dist/src/packlets/invitations/encoder.d.ts +9 -0
  57. package/dist/src/packlets/invitations/encoder.d.ts.map +1 -0
  58. package/dist/src/packlets/invitations/encoder.js +27 -0
  59. package/dist/src/packlets/invitations/encoder.js.map +1 -0
  60. package/dist/src/packlets/invitations/encoder.test.d.ts +2 -0
  61. package/dist/src/packlets/invitations/encoder.test.d.ts.map +1 -0
  62. package/dist/src/packlets/invitations/encoder.test.js +19 -0
  63. package/dist/src/packlets/invitations/encoder.test.js.map +1 -0
  64. package/dist/src/packlets/invitations/halo-invitations.d.ts +3 -3
  65. package/dist/src/packlets/invitations/halo-invitations.d.ts.map +1 -1
  66. package/dist/src/packlets/invitations/halo-invitations.js +14 -10
  67. package/dist/src/packlets/invitations/halo-invitations.js.map +1 -1
  68. package/dist/src/packlets/invitations/halo-invitations.test.d.ts +2 -0
  69. package/dist/src/packlets/invitations/halo-invitations.test.d.ts.map +1 -0
  70. package/dist/src/packlets/{services/testing/halo.test.js → invitations/halo-invitations.test.js} +6 -6
  71. package/dist/src/packlets/invitations/halo-invitations.test.js.map +1 -0
  72. package/dist/src/packlets/invitations/index.d.ts +6 -3
  73. package/dist/src/packlets/invitations/index.d.ts.map +1 -1
  74. package/dist/src/packlets/invitations/index.js +6 -3
  75. package/dist/src/packlets/invitations/index.js.map +1 -1
  76. package/dist/src/packlets/invitations/invitations-proxy.d.ts +14 -0
  77. package/dist/src/packlets/invitations/invitations-proxy.d.ts.map +1 -0
  78. package/dist/src/packlets/invitations/invitations-proxy.js +126 -0
  79. package/dist/src/packlets/invitations/invitations-proxy.js.map +1 -0
  80. package/dist/src/packlets/invitations/invitations.d.ts +90 -0
  81. package/dist/src/packlets/invitations/invitations.d.ts.map +1 -0
  82. package/dist/src/packlets/invitations/invitations.js +55 -0
  83. package/dist/src/packlets/invitations/invitations.js.map +1 -0
  84. package/dist/src/packlets/invitations/space-invitations-handler.d.ts +24 -0
  85. package/dist/src/packlets/invitations/space-invitations-handler.d.ts.map +1 -0
  86. package/dist/src/packlets/invitations/space-invitations-handler.js +232 -0
  87. package/dist/src/packlets/invitations/space-invitations-handler.js.map +1 -0
  88. package/dist/src/packlets/invitations/space-invitations-handler.test.d.ts +2 -0
  89. package/dist/src/packlets/invitations/space-invitations-handler.test.d.ts.map +1 -0
  90. package/dist/src/packlets/invitations/space-invitations-handler.test.js +131 -0
  91. package/dist/src/packlets/invitations/space-invitations-handler.test.js.map +1 -0
  92. package/dist/src/packlets/invitations/space-invitations-proxy.d.ts +11 -0
  93. package/dist/src/packlets/invitations/space-invitations-proxy.d.ts.map +1 -0
  94. package/dist/src/packlets/invitations/space-invitations-proxy.js +17 -0
  95. package/dist/src/packlets/invitations/space-invitations-proxy.js.map +1 -0
  96. package/dist/src/packlets/invitations/space-invitations-service.d.ts +22 -0
  97. package/dist/src/packlets/invitations/space-invitations-service.d.ts.map +1 -0
  98. package/dist/src/packlets/invitations/space-invitations-service.js +189 -0
  99. package/dist/src/packlets/invitations/space-invitations-service.js.map +1 -0
  100. package/dist/src/packlets/invitations/space-invitations-service.test.d.ts +2 -0
  101. package/dist/src/packlets/invitations/space-invitations-service.test.d.ts.map +1 -0
  102. package/dist/src/packlets/invitations/space-invitations-service.test.js +100 -0
  103. package/dist/src/packlets/invitations/space-invitations-service.test.js.map +1 -0
  104. package/dist/src/packlets/services/index.d.ts +3 -2
  105. package/dist/src/packlets/services/index.d.ts.map +1 -1
  106. package/dist/src/packlets/services/index.js +3 -2
  107. package/dist/src/packlets/services/index.js.map +1 -1
  108. package/dist/src/packlets/services/service-context.d.ts +4 -7
  109. package/dist/src/packlets/services/service-context.d.ts.map +1 -1
  110. package/dist/src/packlets/services/service-context.js +9 -25
  111. package/dist/src/packlets/services/service-context.js.map +1 -1
  112. package/dist/src/packlets/services/service-definitions.d.ts +30 -0
  113. package/dist/src/packlets/services/service-definitions.d.ts.map +1 -0
  114. package/dist/src/packlets/services/{services.js → service-definitions.js} +9 -5
  115. package/dist/src/packlets/services/service-definitions.js.map +1 -0
  116. package/dist/src/packlets/services/service-host.d.ts +9 -12
  117. package/dist/src/packlets/services/service-host.d.ts.map +1 -1
  118. package/dist/src/packlets/services/service-host.js +55 -54
  119. package/dist/src/packlets/services/service-host.js.map +1 -1
  120. package/dist/src/packlets/services/service-host.test.js +7 -17
  121. package/dist/src/packlets/services/service-host.test.js.map +1 -1
  122. package/dist/src/packlets/services/service-proxy.d.ts +17 -0
  123. package/dist/src/packlets/services/service-proxy.d.ts.map +1 -0
  124. package/dist/src/packlets/services/service-proxy.js +48 -0
  125. package/dist/src/packlets/services/service-proxy.js.map +1 -0
  126. package/dist/src/packlets/services/service-registry.d.ts +12 -0
  127. package/dist/src/packlets/services/service-registry.d.ts.map +1 -0
  128. package/dist/src/packlets/services/service-registry.js +24 -0
  129. package/dist/src/packlets/services/service-registry.js.map +1 -0
  130. package/dist/src/packlets/services/service-registry.test.d.ts +2 -0
  131. package/dist/src/packlets/services/service-registry.test.d.ts.map +1 -0
  132. package/dist/src/packlets/services/service-registry.test.js +75 -0
  133. package/dist/src/packlets/services/service-registry.test.js.map +1 -0
  134. package/dist/src/packlets/services/signer.d.ts +0 -3
  135. package/dist/src/packlets/services/signer.d.ts.map +1 -1
  136. package/dist/src/packlets/spaces/index.d.ts +2 -0
  137. package/dist/src/packlets/spaces/index.d.ts.map +1 -0
  138. package/dist/src/packlets/spaces/index.js +21 -0
  139. package/dist/src/packlets/spaces/index.js.map +1 -0
  140. package/dist/src/packlets/spaces/spaces-service.d.ts +11 -0
  141. package/dist/src/packlets/spaces/spaces-service.d.ts.map +1 -0
  142. package/dist/src/packlets/spaces/spaces-service.js +22 -0
  143. package/dist/src/packlets/spaces/spaces-service.js.map +1 -0
  144. package/dist/src/packlets/storage/storage.js +1 -1
  145. package/dist/src/packlets/storage/storage.js.map +1 -1
  146. package/dist/src/packlets/testing/index.d.ts +2 -0
  147. package/dist/src/packlets/testing/index.d.ts.map +1 -0
  148. package/dist/src/packlets/testing/index.js +21 -0
  149. package/dist/src/packlets/testing/index.js.map +1 -0
  150. package/dist/src/packlets/testing/test-builder.d.ts +14 -0
  151. package/dist/src/packlets/testing/test-builder.d.ts.map +1 -0
  152. package/dist/src/packlets/testing/test-builder.js +65 -0
  153. package/dist/src/packlets/testing/test-builder.js.map +1 -0
  154. package/dist/src/packlets/vault/iframe-runtime.d.ts +19 -0
  155. package/dist/src/packlets/vault/iframe-runtime.d.ts.map +1 -0
  156. package/dist/src/packlets/vault/iframe-runtime.js +54 -0
  157. package/dist/src/packlets/vault/iframe-runtime.js.map +1 -0
  158. package/dist/src/packlets/vault/index.d.ts +4 -0
  159. package/dist/src/packlets/vault/index.d.ts.map +1 -0
  160. package/dist/src/packlets/vault/index.js +23 -0
  161. package/dist/src/packlets/vault/index.js.map +1 -0
  162. package/dist/src/packlets/vault/services.d.ts +13 -0
  163. package/dist/src/packlets/vault/services.d.ts.map +1 -0
  164. package/dist/src/packlets/vault/services.js +15 -0
  165. package/dist/src/packlets/vault/services.js.map +1 -0
  166. package/dist/src/packlets/vault/worker-runtime.d.ts +32 -0
  167. package/dist/src/packlets/vault/worker-runtime.d.ts.map +1 -0
  168. package/dist/src/packlets/vault/worker-runtime.js +94 -0
  169. package/dist/src/packlets/vault/worker-runtime.js.map +1 -0
  170. package/dist/src/packlets/vault/worker-session.d.ts +29 -0
  171. package/dist/src/packlets/vault/worker-session.d.ts.map +1 -0
  172. package/dist/src/packlets/vault/worker-session.js +84 -0
  173. package/dist/src/packlets/vault/worker-session.js.map +1 -0
  174. package/package.json +26 -26
  175. package/src/index.ts +5 -2
  176. package/src/packlets/{services/impl → deprecated}/index.ts +1 -4
  177. package/src/packlets/{invitations/common.ts → deprecated/invitations.ts} +18 -3
  178. package/src/packlets/{services/impl → deprecated}/profile.ts +13 -13
  179. package/src/packlets/deprecated/space.ts +245 -0
  180. package/src/packlets/{services/impl → deprecated}/system.ts +2 -2
  181. package/src/packlets/{services/impl → deprecated}/tracing.ts +2 -2
  182. package/src/packlets/{services/impl → devtools}/devtools.ts +33 -34
  183. package/src/packlets/devtools/feeds.ts +31 -27
  184. package/src/packlets/devtools/index.ts +1 -1
  185. package/src/packlets/devtools/spaces.ts +19 -18
  186. package/src/packlets/identity/devices-service-impl.ts +54 -0
  187. package/src/packlets/identity/identity-manager.test.ts +7 -8
  188. package/src/packlets/identity/identity.test.ts +2 -3
  189. package/src/packlets/identity/identity.ts +5 -1
  190. package/src/packlets/invitations/encoder.test.ts +22 -0
  191. package/src/packlets/invitations/encoder.ts +26 -0
  192. package/src/packlets/{services/testing/halo.test.ts → invitations/halo-invitations.test.ts} +5 -5
  193. package/src/packlets/invitations/halo-invitations.ts +12 -10
  194. package/src/packlets/invitations/index.ts +6 -3
  195. package/src/packlets/invitations/invitations-proxy.ts +157 -0
  196. package/src/packlets/invitations/invitations.ts +149 -0
  197. package/src/packlets/invitations/space-invitations-handler.test.ts +154 -0
  198. package/src/packlets/invitations/space-invitations-handler.ts +266 -0
  199. package/src/packlets/invitations/space-invitations-proxy.ts +16 -0
  200. package/src/packlets/invitations/space-invitations-service.test.ts +129 -0
  201. package/src/packlets/invitations/space-invitations-service.ts +196 -0
  202. package/src/packlets/services/index.ts +3 -2
  203. package/src/packlets/services/service-context.ts +9 -31
  204. package/src/packlets/services/service-definitions.ts +54 -0
  205. package/src/packlets/services/service-host.test.ts +12 -24
  206. package/src/packlets/services/service-host.ts +64 -64
  207. package/src/packlets/services/service-proxy.ts +54 -0
  208. package/src/packlets/services/service-registry.test.ts +95 -0
  209. package/src/packlets/services/service-registry.ts +24 -0
  210. package/src/packlets/services/signer.ts +1 -3
  211. package/src/packlets/spaces/index.ts +5 -0
  212. package/src/packlets/spaces/spaces-service.ts +28 -0
  213. package/src/packlets/storage/storage.ts +1 -1
  214. package/src/packlets/testing/index.ts +5 -0
  215. package/src/packlets/testing/test-builder.ts +79 -0
  216. package/src/packlets/vault/iframe-runtime.ts +68 -0
  217. package/src/packlets/vault/index.ts +7 -0
  218. package/src/packlets/vault/services.ts +26 -0
  219. package/src/packlets/vault/worker-runtime.ts +111 -0
  220. package/src/packlets/vault/worker-session.ts +113 -0
  221. package/dist/src/packlets/devtools/devtools-host-events.d.ts +0 -5
  222. package/dist/src/packlets/devtools/devtools-host-events.d.ts.map +0 -1
  223. package/dist/src/packlets/devtools/devtools-host-events.js +0 -14
  224. package/dist/src/packlets/devtools/devtools-host-events.js.map +0 -1
  225. package/dist/src/packlets/invitations/common.d.ts.map +0 -1
  226. package/dist/src/packlets/invitations/common.js.map +0 -1
  227. package/dist/src/packlets/invitations/invitation-wrapper.d.ts +0 -36
  228. package/dist/src/packlets/invitations/invitation-wrapper.d.ts.map +0 -1
  229. package/dist/src/packlets/invitations/invitation-wrapper.js +0 -104
  230. package/dist/src/packlets/invitations/invitation-wrapper.js.map +0 -1
  231. package/dist/src/packlets/invitations/space-invitations.d.ts +0 -25
  232. package/dist/src/packlets/invitations/space-invitations.d.ts.map +0 -1
  233. package/dist/src/packlets/invitations/space-invitations.js +0 -136
  234. package/dist/src/packlets/invitations/space-invitations.js.map +0 -1
  235. package/dist/src/packlets/services/impl/devtools.d.ts.map +0 -1
  236. package/dist/src/packlets/services/impl/devtools.js +0 -77
  237. package/dist/src/packlets/services/impl/devtools.js.map +0 -1
  238. package/dist/src/packlets/services/impl/halo.d.ts +0 -20
  239. package/dist/src/packlets/services/impl/halo.d.ts.map +0 -1
  240. package/dist/src/packlets/services/impl/halo.js +0 -93
  241. package/dist/src/packlets/services/impl/halo.js.map +0 -1
  242. package/dist/src/packlets/services/impl/index.d.ts +0 -6
  243. package/dist/src/packlets/services/impl/index.d.ts.map +0 -1
  244. package/dist/src/packlets/services/impl/index.js.map +0 -1
  245. package/dist/src/packlets/services/impl/party.d.ts +0 -25
  246. package/dist/src/packlets/services/impl/party.d.ts.map +0 -1
  247. package/dist/src/packlets/services/impl/party.js +0 -265
  248. package/dist/src/packlets/services/impl/party.js.map +0 -1
  249. package/dist/src/packlets/services/impl/profile.d.ts.map +0 -1
  250. package/dist/src/packlets/services/impl/profile.js.map +0 -1
  251. package/dist/src/packlets/services/impl/system.d.ts.map +0 -1
  252. package/dist/src/packlets/services/impl/system.js.map +0 -1
  253. package/dist/src/packlets/services/impl/tracing.d.ts +0 -12
  254. package/dist/src/packlets/services/impl/tracing.d.ts.map +0 -1
  255. package/dist/src/packlets/services/impl/tracing.js.map +0 -1
  256. package/dist/src/packlets/services/service-factory.d.ts +0 -15
  257. package/dist/src/packlets/services/service-factory.d.ts.map +0 -1
  258. package/dist/src/packlets/services/service-factory.js +0 -25
  259. package/dist/src/packlets/services/service-factory.js.map +0 -1
  260. package/dist/src/packlets/services/services.d.ts +0 -19
  261. package/dist/src/packlets/services/services.d.ts.map +0 -1
  262. package/dist/src/packlets/services/services.js.map +0 -1
  263. package/dist/src/packlets/services/testing/halo.test.d.ts +0 -2
  264. package/dist/src/packlets/services/testing/halo.test.d.ts.map +0 -1
  265. package/dist/src/packlets/services/testing/halo.test.js.map +0 -1
  266. package/dist/src/packlets/services/testing/spaces.test.d.ts +0 -2
  267. package/dist/src/packlets/services/testing/spaces.test.d.ts.map +0 -1
  268. package/dist/src/packlets/services/testing/spaces.test.js +0 -68
  269. package/dist/src/packlets/services/testing/spaces.test.js.map +0 -1
  270. package/dist/src/packlets/services/testing/testing.d.ts +0 -8
  271. package/dist/src/packlets/services/testing/testing.d.ts.map +0 -1
  272. package/dist/src/packlets/services/testing/testing.js +0 -23
  273. package/dist/src/packlets/services/testing/testing.js.map +0 -1
  274. package/src/packlets/devtools/devtools-host-events.ts +0 -8
  275. package/src/packlets/invitations/invitation-wrapper.ts +0 -140
  276. package/src/packlets/invitations/space-invitations.ts +0 -159
  277. package/src/packlets/services/impl/halo.ts +0 -107
  278. package/src/packlets/services/impl/party.ts +0 -305
  279. package/src/packlets/services/service-factory.ts +0 -35
  280. package/src/packlets/services/services.ts +0 -39
  281. package/src/packlets/services/testing/spaces.test.ts +0 -87
  282. package/src/packlets/services/testing/testing.ts +0 -28
@@ -0,0 +1,149 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import assert from 'assert';
6
+
7
+ import {
8
+ AsyncEvents,
9
+ CancellableObservable,
10
+ CancellableObservableEvents,
11
+ Observable,
12
+ CancellableObservableProvider
13
+ } from '@dxos/async';
14
+ import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
15
+
16
+ export const AUTHENTICATION_CODE_LENGTH = 6;
17
+
18
+ export type CreateInvitationsOptions = {
19
+ type?: Invitation.Type;
20
+ timeout?: number;
21
+ };
22
+
23
+ /**
24
+ * Common invitation events (callbacks) for creating and accepting invitations.
25
+ */
26
+ // TODO(burdon): Remove optionals.
27
+ export interface InvitationEvents extends AsyncEvents, CancellableObservableEvents {
28
+ onConnecting?(invitation: Invitation): void;
29
+ onConnected?(invitation: Invitation): void;
30
+ onAuthenticating?(invitation: Invitation): void;
31
+ onSuccess(invitation: Invitation): void; // TODO(burdon): Collides with AsyncEvents.
32
+ }
33
+
34
+ /**
35
+ * Base class for all invitation observables and providers.
36
+ * Observable that supports inspection of the current value.
37
+ */
38
+ export interface InvitationObservable extends CancellableObservable<InvitationEvents> {
39
+ get invitation(): Invitation | undefined;
40
+ }
41
+
42
+ export class InvitationObservableProvider
43
+ extends CancellableObservableProvider<InvitationEvents>
44
+ implements InvitationObservable
45
+ {
46
+ private _invitation?: Invitation;
47
+
48
+ get invitation(): Invitation | undefined {
49
+ return this._invitation;
50
+ }
51
+
52
+ setInvitation(invitation: Invitation) {
53
+ this._invitation = invitation;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Cancelable observer.
59
+ */
60
+ export type CancellableInvitationObservable = InvitationObservable;
61
+
62
+ /**
63
+ * Cancelable observer that relays authentication requests.
64
+ */
65
+ export interface AuthenticatingInvitationObservable extends CancellableInvitationObservable {
66
+ authenticate(code: string): Promise<void>;
67
+ }
68
+
69
+ export interface AuthenticatingInvitationProviderActions {
70
+ onCancel(): Promise<void>;
71
+ onAuthenticate(code: string): Promise<void>;
72
+ }
73
+
74
+ export class AuthenticatingInvitationProvider
75
+ extends InvitationObservableProvider
76
+ implements AuthenticatingInvitationObservable
77
+ {
78
+ // prettier-ignore
79
+ constructor(
80
+ private readonly _actions: AuthenticatingInvitationProviderActions
81
+ ) {
82
+ super(() => this._actions.onCancel());
83
+ }
84
+
85
+ async authenticate(authenticationCode: string): Promise<void> {
86
+ return this._actions.onAuthenticate(authenticationCode);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Common interface for Halo and Space invitation proxies and handlers.
92
+ * Handles the life-cycle of Space invitations between peers.
93
+ *
94
+ * Host
95
+ * - Creates an invitation containing a swarm topic.
96
+ * - Joins the swarm with the topic and waits for guest's admission request.
97
+ * - Responds with admission offer then waits for guest's credentials.
98
+ * - Writes credentials to control feed then exits.
99
+ *
100
+ * Guest
101
+ * - Joins the swarm with the topic.
102
+ * - NOTE: The topic is transmitted out-of-band (e.g., via a QR code).
103
+ * - Sends an admission request.
104
+ * - If Space handler then creates a local cloned space (with genesis block).
105
+ * - Sends admission credentials (containing local device and feed keys).
106
+ *
107
+ * TODO(burdon): Show proxy/service relationship and reference design doc/diagram.
108
+ *
109
+ * ```
110
+ * [Guest] [Host]
111
+ * |-------------------------------------RequestAdmission-->|
112
+ * |<--AdmissionOffer---------------------------------------|
113
+ * |
114
+ * |--------------------------PresentAdmissionCredentials-->|
115
+ * ```
116
+ */
117
+ export interface InvitationsHandler<T> {
118
+ // TODO(burdon): Pass in options.
119
+ createInvitation(context: T, options?: CreateInvitationsOptions): InvitationObservable;
120
+ acceptInvitation(invitation: Invitation): AuthenticatingInvitationObservable;
121
+ }
122
+
123
+ /**
124
+ * Common interface for Halo and Space proxies.
125
+ */
126
+ export interface InvitationsProxy<T> extends InvitationsHandler<T> {
127
+ createInvitationObject(context: T): Invitation;
128
+ }
129
+
130
+ /**
131
+ * Util to wrap observable with promise.
132
+ * @deprecated
133
+ */
134
+ // TODO(burdon): Replace with InvitationObservableProvider.
135
+ export const invitationObservable = async (observable: Observable<InvitationEvents>): Promise<Invitation> => {
136
+ return new Promise((resolve, reject) => {
137
+ const unsubscribe = observable.subscribe({
138
+ onSuccess: (invitation: Invitation) => {
139
+ assert(invitation.state === Invitation.State.SUCCESS);
140
+ unsubscribe();
141
+ resolve(invitation);
142
+ },
143
+ onError: (err: Error) => {
144
+ unsubscribe();
145
+ reject(err);
146
+ }
147
+ });
148
+ });
149
+ };
@@ -0,0 +1,154 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import assert from 'assert';
6
+ import { expect } from 'chai';
7
+
8
+ import { asyncChain, Trigger } from '@dxos/async';
9
+ import { raise } from '@dxos/debug';
10
+ import { PublicKey } from '@dxos/keys';
11
+ import { ObjectModel } from '@dxos/object-model';
12
+ import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
13
+ import { afterTest } from '@dxos/testutils';
14
+
15
+ import { ServiceContext } from '../services';
16
+ import { createIdentity, createPeers, syncItems } from '../testing';
17
+
18
+ const closeAfterTest = async (peer: ServiceContext) => {
19
+ afterTest(() => peer.close());
20
+ return peer;
21
+ };
22
+
23
+ describe('services/space-invitations-handler', function () {
24
+ it('genesis', async function () {
25
+ const [peer] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(1));
26
+
27
+ const space = await peer.spaceManager!.createSpace();
28
+ expect(space.database).not.to.be.undefined;
29
+ expect(peer.spaceManager!.spaces.has(space.key)).to.be.true;
30
+ await space.close();
31
+ });
32
+
33
+ it('genesis with database mutations', async function () {
34
+ const [peer] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(1));
35
+ const space = await peer.spaceManager!.createSpace();
36
+
37
+ {
38
+ const item = await space.database!.createItem<ObjectModel>({ type: 'test' });
39
+ void item.model.set('name', 'test');
40
+ }
41
+
42
+ {
43
+ const [item] = space.database!.select({ type: 'test' }).exec().entities;
44
+ expect(item.model.get('name')).to.eq('test');
45
+ }
46
+
47
+ await space.close();
48
+ });
49
+
50
+ it('creates and accepts invitation', async function () {
51
+ const [host, guest] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(2));
52
+
53
+ const complete1 = new Trigger<PublicKey>();
54
+ const complete2 = new Trigger<PublicKey>();
55
+
56
+ const authenticationCode = new Trigger<string>();
57
+
58
+ const space1 = await host.spaceManager!.createSpace();
59
+ const observable1 = host.spaceInvitations!.createInvitation(space1);
60
+ observable1.subscribe({
61
+ onConnecting: async (invitation1: Invitation) => {
62
+ const observable2 = guest.spaceInvitations!.acceptInvitation(invitation1);
63
+ observable2.subscribe({
64
+ onConnecting: async (invitation2: Invitation) => {},
65
+ onConnected: async (invitation2: Invitation) => {
66
+ expect(invitation1.swarmKey).to.eq(invitation2.swarmKey);
67
+ },
68
+ onAuthenticating: async (invitation2: Invitation) => {
69
+ await observable2.authenticate(await authenticationCode.wait());
70
+ },
71
+ onSuccess: (invitation: Invitation) => {
72
+ complete2.wake(invitation.spaceKey!);
73
+ },
74
+ onCancelled: () => raise(new Error()),
75
+ onTimeout: (err: Error) => raise(new Error(err.message)),
76
+ onError: (err: Error) => raise(new Error(err.message))
77
+ });
78
+ },
79
+ onConnected: (invitation: Invitation) => {
80
+ assert(invitation.authenticationCode);
81
+ authenticationCode.wake(invitation.authenticationCode);
82
+ },
83
+ onSuccess: (invitation: Invitation) => {
84
+ complete1.wake(space1.key);
85
+ },
86
+ onCancelled: () => raise(new Error()),
87
+ onTimeout: (err: Error) => raise(new Error(err.message)),
88
+ onError: (err: Error) => raise(new Error(err.message))
89
+ });
90
+
91
+ {
92
+ const [spaceKey1, spaceKey2] = await Promise.all([complete1.wait(), complete2.wait()]);
93
+ expect(spaceKey1).to.deep.eq(spaceKey2);
94
+
95
+ const space1 = host.spaceManager!.spaces.get(spaceKey1)!;
96
+ const space2 = guest.spaceManager!.spaces.get(spaceKey2)!;
97
+ expect(space1).not.to.be.undefined;
98
+ expect(space2).not.to.be.undefined;
99
+
100
+ await syncItems(space1, space2);
101
+
102
+ await space1.close();
103
+ await space2.close();
104
+ }
105
+ });
106
+
107
+ it('cancels invitation', async function () {
108
+ const [host, guest] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(2));
109
+
110
+ const cancelled = new Trigger();
111
+ const connecting1 = new Trigger<Invitation>(); // peer 1 connected.
112
+ const connecting2 = new Trigger<Invitation>(); // peer 2 connected.
113
+
114
+ const space1 = await host.spaceManager!.createSpace();
115
+ const observable1 = await host.spaceInvitations!.createInvitation(space1);
116
+ observable1.subscribe({
117
+ onConnecting: async (invitation1: Invitation) => {
118
+ connecting1.wake(invitation1);
119
+
120
+ const observable2 = await guest.spaceInvitations!.acceptInvitation(invitation1);
121
+ observable2.subscribe({
122
+ onConnecting: async (invitation2: Invitation) => {
123
+ expect(invitation1.swarmKey).to.eq(invitation2.swarmKey);
124
+ connecting2.wake(invitation2);
125
+ },
126
+ onConnected: async (invitation2: Invitation) => {},
127
+ onSuccess: () => {},
128
+ onCancelled: () => raise(new Error()),
129
+ onTimeout: (err: Error) => raise(new Error(err.message)),
130
+ onError: (err: Error) => raise(new Error(err.message))
131
+ });
132
+ },
133
+ onConnected: async (invitation1: Invitation) => {},
134
+ onCancelled: () => {
135
+ cancelled.wake();
136
+ },
137
+ onSuccess: () => raise(new Error()),
138
+ onTimeout: (err: Error) => raise(new Error(err.message)),
139
+ onError: (err: Error) => raise(new Error(err.message))
140
+ });
141
+
142
+ const invitation1 = await connecting1.wait();
143
+ const invitation2 = await connecting2.wait();
144
+ expect(invitation1.swarmKey).to.eq(invitation2.swarmKey);
145
+
146
+ // TODO(burdon): Simulate network latency.
147
+ setTimeout(async () => {
148
+ await observable1.cancel();
149
+ });
150
+
151
+ await cancelled.wait();
152
+ await space1.close();
153
+ });
154
+ });
@@ -0,0 +1,266 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import assert from 'assert';
6
+
7
+ import { sleep, Trigger } from '@dxos/async';
8
+ import { createAdmissionCredentials, generatePasscode } from '@dxos/credentials';
9
+ import { SigningContext, Space, SpaceManager } from '@dxos/echo-db';
10
+ import { writeMessages } from '@dxos/feed-store';
11
+ import { PublicKey } from '@dxos/keys';
12
+ import { log } from '@dxos/log';
13
+ import { createProtocolFactory, NetworkManager, StarTopology, SwarmConnection } from '@dxos/network-manager';
14
+ import { createRpcPlugin, RpcPlugin } from '@dxos/protocol-plugin-rpc';
15
+ import { schema } from '@dxos/protocols';
16
+ import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
17
+ import { createProtoRpcPeer } from '@dxos/rpc';
18
+
19
+ import {
20
+ AuthenticatingInvitationProvider,
21
+ CreateInvitationsOptions,
22
+ InvitationsHandler,
23
+ InvitationObservable,
24
+ InvitationObservableProvider,
25
+ AUTHENTICATION_CODE_LENGTH
26
+ } from './invitations';
27
+
28
+ // TODO(burdon): Pass options from client.
29
+ // TODO(burdon): Don't close until RPC has complete.
30
+ const ON_CLOSE_DELAY = 500;
31
+ const INVITATION_TIMEOUT = 3 * 60_000; // 3 mins.
32
+
33
+ /**
34
+ * Handles the life-cycle of Space invitations between peers.
35
+ */
36
+ export class SpaceInvitationsHandler implements InvitationsHandler<Space> {
37
+ constructor(
38
+ private readonly _spaceManager: SpaceManager,
39
+ private readonly _networkManager: NetworkManager,
40
+ private readonly _signingContext: SigningContext
41
+ ) {}
42
+
43
+ /**
44
+ * Creates an invitation and listens for a join request from the invited (guest) peer.
45
+ */
46
+ createInvitation(space: Space, options?: CreateInvitationsOptions): InvitationObservable {
47
+ let swarmConnection: SwarmConnection | undefined;
48
+ const { type, timeout = INVITATION_TIMEOUT } = options ?? {};
49
+ assert(type !== Invitation.Type.OFFLINE);
50
+
51
+ const invitation: Invitation = {
52
+ type,
53
+ // type: Invitation.Type.INTERACTIVE_TESTING,
54
+ invitationId: PublicKey.random().toHex(),
55
+ swarmKey: PublicKey.random(),
56
+ spaceKey: space.key,
57
+ authenticationCode: generatePasscode(AUTHENTICATION_CODE_LENGTH)
58
+ };
59
+
60
+ // TODO(burdon): Stop anything pending.
61
+ const observable = new InvitationObservableProvider(async () => {
62
+ await swarmConnection?.close();
63
+ });
64
+
65
+ let authenticationCode: string;
66
+ const complete = new Trigger<PublicKey>();
67
+ const plugin = new RpcPlugin(async (port) => {
68
+ const peer = createProtoRpcPeer({
69
+ exposed: {
70
+ SpaceHostService: schema.getService('dxos.halo.invitations.SpaceHostService')
71
+ },
72
+ handlers: {
73
+ SpaceHostService: {
74
+ requestAdmission: async () => {
75
+ log('responding with admission offer', {
76
+ host: this._signingContext.deviceKey,
77
+ spaceKey: space.key
78
+ });
79
+
80
+ // TODO(burdon): Is this the right place to set this state?
81
+ observable.callback.onAuthenticating?.(invitation);
82
+ return {
83
+ spaceKey: space.key,
84
+ genesisFeedKey: space.genesisFeedKey
85
+ };
86
+ },
87
+
88
+ authenticate: async ({ authenticationCode: code }) => {
89
+ log('received authentication request', { authenticationCode: code });
90
+ authenticationCode = code;
91
+ },
92
+
93
+ presentAdmissionCredentials: async ({ identityKey, deviceKey, controlFeedKey, dataFeedKey }) => {
94
+ try {
95
+ // Check authenticated.
96
+ if (invitation.type === undefined || invitation.type === Invitation.Type.INTERACTIVE) {
97
+ if (
98
+ invitation.authenticationCode === undefined ||
99
+ authenticationCode !== invitation.authenticationCode
100
+ ) {
101
+ throw new Error('authentication code not set');
102
+ }
103
+ }
104
+
105
+ log('writing guest credentials', { host: this._signingContext.deviceKey, guest: deviceKey });
106
+ // TODO(burdon): Check if already admitted.
107
+ await writeMessages(
108
+ space.controlPipeline.writer,
109
+ await createAdmissionCredentials(
110
+ this._signingContext.credentialSigner,
111
+ identityKey,
112
+ deviceKey,
113
+ space.key,
114
+ controlFeedKey,
115
+ dataFeedKey
116
+ )
117
+ );
118
+
119
+ // Updating credentials complete.
120
+ complete.wake(deviceKey);
121
+ } catch (err) {
122
+ // TODO(burdon): Generic RPC callback to report error to client.
123
+ observable.callback.onError(err);
124
+ throw err; // Propagate error to guest.
125
+ }
126
+ }
127
+ }
128
+ },
129
+ port
130
+ });
131
+
132
+ try {
133
+ await peer.open();
134
+ log('connected', { host: this._signingContext.deviceKey });
135
+ observable.callback.onConnected?.(invitation);
136
+ const deviceKey = await complete.wait({ timeout });
137
+ log('admitted guest', { host: this._signingContext.deviceKey, guest: deviceKey });
138
+ observable.callback.onSuccess(invitation);
139
+ } catch (err) {
140
+ if (!observable.cancelled) {
141
+ log.error('failed', err);
142
+ observable.callback.onError(err);
143
+ }
144
+ } finally {
145
+ await sleep(ON_CLOSE_DELAY);
146
+ await peer.close();
147
+ await swarmConnection!.close();
148
+ }
149
+ });
150
+
151
+ setTimeout(async () => {
152
+ const topic = invitation.swarmKey!;
153
+ const peerId = PublicKey.random(); // Use anonymous key.
154
+ swarmConnection = await this._networkManager.openSwarmConnection({
155
+ topic,
156
+ peerId: topic,
157
+ protocol: createProtocolFactory(topic, peerId, [plugin]),
158
+ topology: new StarTopology(topic)
159
+ });
160
+
161
+ observable.callback.onConnecting?.(invitation);
162
+ });
163
+
164
+ return observable;
165
+ }
166
+
167
+ /**
168
+ * Waits for the host peer (inviter) to accept our join request.
169
+ * The local guest peer (invitee) then sends the local space invitation to the host,
170
+ * which then writes the guest's credentials to the space.
171
+ */
172
+ acceptInvitation(invitation: Invitation): AuthenticatingInvitationProvider {
173
+ let swarmConnection: SwarmConnection | undefined;
174
+
175
+ const authenticated = new Trigger<string>();
176
+ const observable = new AuthenticatingInvitationProvider({
177
+ onCancel: async () => {
178
+ await swarmConnection?.close();
179
+ },
180
+
181
+ // TODO(burdon): Consider retry.
182
+ onAuthenticate: async (code: string) => {
183
+ authenticated.wake(code);
184
+ }
185
+ });
186
+
187
+ let connectionCount = 0;
188
+ const complete = new Trigger();
189
+ const plugin = createRpcPlugin(async (port) => {
190
+ const peer = createProtoRpcPeer({
191
+ requested: {
192
+ SpaceHostService: schema.getService('dxos.halo.invitations.SpaceHostService')
193
+ },
194
+ port
195
+ });
196
+
197
+ try {
198
+ await peer.open();
199
+ // TODO(burdon): Bug where guest may create multiple connections.
200
+ if (++connectionCount > 1) {
201
+ throw new Error(`multiple connections detected: ${connectionCount}`);
202
+ }
203
+
204
+ log('connected', { guest: this._signingContext.deviceKey });
205
+ observable.callback.onConnected?.(invitation);
206
+
207
+ // 1. Send request.
208
+ log('sending admission request', { guest: this._signingContext.deviceKey });
209
+ const { spaceKey, genesisFeedKey } = await peer.rpc.SpaceHostService.requestAdmission();
210
+
211
+ // 2. Get authentication code.
212
+ // TODO(burdon): Test timeout (options for timeouts at different steps).
213
+ if (invitation.type === undefined || invitation.type === Invitation.Type.INTERACTIVE) {
214
+ log('waiting for authentication code...');
215
+ observable.callback.onAuthenticating?.(invitation);
216
+ const authenticationCode = await authenticated.wait({ timeout: INVITATION_TIMEOUT });
217
+ log('sending authentication request');
218
+ await peer.rpc.SpaceHostService.authenticate({ authenticationCode });
219
+ }
220
+
221
+ // 3. Create local space.
222
+ // TODO(burdon): Abandon if does not complete (otherwise retry will fail).
223
+ const space = await this._spaceManager.acceptSpace({ spaceKey, genesisFeedKey });
224
+
225
+ // 4. Send admission credentials to host (with local space keys).
226
+ log('presenting admission credentials', { guest: this._signingContext.deviceKey, spaceKey: space.key });
227
+ await peer.rpc.SpaceHostService.presentAdmissionCredentials({
228
+ identityKey: this._signingContext.identityKey,
229
+ deviceKey: this._signingContext.deviceKey,
230
+ controlFeedKey: space.controlFeedKey,
231
+ dataFeedKey: space.dataFeedKey
232
+ });
233
+
234
+ // 5. Success.
235
+ log('admitted by host', { guest: this._signingContext.deviceKey, spaceKey: space.key });
236
+ complete.wake();
237
+ } catch (err) {
238
+ if (!observable.cancelled) {
239
+ log.warn('failed', err);
240
+ observable.callback.onError(err);
241
+ }
242
+ } finally {
243
+ await peer.close();
244
+ }
245
+ });
246
+
247
+ setTimeout(async () => {
248
+ assert(invitation.swarmKey);
249
+ const topic = invitation.swarmKey;
250
+ const peerId = PublicKey.random(); // Use anonymous key.
251
+ swarmConnection = await this._networkManager.openSwarmConnection({
252
+ topic,
253
+ peerId: PublicKey.random(),
254
+ protocol: createProtocolFactory(topic, peerId, [plugin]),
255
+ topology: new StarTopology(topic)
256
+ });
257
+
258
+ observable.callback.onConnecting?.(invitation);
259
+ await complete.wait();
260
+ observable.callback.onSuccess(invitation);
261
+ await swarmConnection.close();
262
+ });
263
+
264
+ return observable;
265
+ }
266
+ }
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { PublicKey } from '@dxos/keys';
6
+
7
+ import { AbstractInvitationsProxy } from './invitations-proxy';
8
+
9
+ /**
10
+ * Adapts invitation service observable to client/service stream.
11
+ */
12
+ export class SpaceInvitationsProxy extends AbstractInvitationsProxy<PublicKey> {
13
+ createInvitationObject(spaceKey: PublicKey) {
14
+ return { spaceKey };
15
+ }
16
+ }