@dxos/client-services 0.1.4 → 0.1.6

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 (160) hide show
  1. package/LICENSE +5 -18
  2. package/README.md +14 -10
  3. package/README.yml +5 -0
  4. package/dist/src/packlets/deprecated/profile.d.ts +2 -5
  5. package/dist/src/packlets/deprecated/profile.d.ts.map +1 -1
  6. package/dist/src/packlets/deprecated/profile.js +1 -73
  7. package/dist/src/packlets/deprecated/profile.js.map +1 -1
  8. package/dist/src/packlets/deprecated/system.d.ts +4 -0
  9. package/dist/src/packlets/deprecated/system.d.ts.map +1 -1
  10. package/dist/src/packlets/deprecated/system.js +6 -0
  11. package/dist/src/packlets/deprecated/system.js.map +1 -1
  12. package/dist/src/packlets/deprecated/tracing.d.ts +1 -1
  13. package/dist/src/packlets/deprecated/tracing.js +1 -1
  14. package/dist/src/packlets/devtools/devtools.d.ts +3 -0
  15. package/dist/src/packlets/devtools/devtools.d.ts.map +1 -1
  16. package/dist/src/packlets/devtools/devtools.js +3 -1
  17. package/dist/src/packlets/devtools/devtools.js.map +1 -1
  18. package/dist/src/packlets/identity/devices-service-impl.d.ts.map +1 -1
  19. package/dist/src/packlets/identity/devices-service-impl.js.map +1 -1
  20. package/dist/src/packlets/identity/identity-manager.d.ts +2 -2
  21. package/dist/src/packlets/identity/identity-manager.d.ts.map +1 -1
  22. package/dist/src/packlets/identity/identity-manager.js +44 -43
  23. package/dist/src/packlets/identity/identity-manager.js.map +1 -1
  24. package/dist/src/packlets/identity/identity.d.ts +3 -0
  25. package/dist/src/packlets/identity/identity.d.ts.map +1 -1
  26. package/dist/src/packlets/identity/identity.js +51 -1
  27. package/dist/src/packlets/identity/identity.js.map +1 -1
  28. package/dist/src/packlets/invitations/halo-invitations-handler.d.ts +23 -0
  29. package/dist/src/packlets/invitations/halo-invitations-handler.d.ts.map +1 -0
  30. package/dist/src/packlets/invitations/halo-invitations-handler.js +231 -0
  31. package/dist/src/packlets/invitations/halo-invitations-handler.js.map +1 -0
  32. package/dist/src/packlets/invitations/halo-invitations-handler.test.d.ts +2 -0
  33. package/dist/src/packlets/invitations/halo-invitations-handler.test.d.ts.map +1 -0
  34. package/dist/src/packlets/invitations/halo-invitations-handler.test.js +69 -0
  35. package/dist/src/packlets/invitations/halo-invitations-handler.test.js.map +1 -0
  36. package/dist/src/packlets/invitations/halo-invitations-proxy.d.ts +8 -0
  37. package/dist/src/packlets/invitations/halo-invitations-proxy.d.ts.map +1 -0
  38. package/dist/src/packlets/invitations/halo-invitations-proxy.js +17 -0
  39. package/dist/src/packlets/invitations/halo-invitations-proxy.js.map +1 -0
  40. package/dist/src/packlets/invitations/halo-invitations-service.d.ts +13 -0
  41. package/dist/src/packlets/invitations/halo-invitations-service.d.ts.map +1 -0
  42. package/dist/src/packlets/invitations/halo-invitations-service.js +20 -0
  43. package/dist/src/packlets/invitations/halo-invitations-service.js.map +1 -0
  44. package/dist/src/packlets/invitations/halo-invitations-service.test.d.ts +2 -0
  45. package/dist/src/packlets/invitations/halo-invitations-service.test.d.ts.map +1 -0
  46. package/dist/src/packlets/invitations/halo-invitations-service.test.js +83 -0
  47. package/dist/src/packlets/invitations/halo-invitations-service.test.js.map +1 -0
  48. package/dist/src/packlets/invitations/index.d.ts +5 -1
  49. package/dist/src/packlets/invitations/index.d.ts.map +1 -1
  50. package/dist/src/packlets/invitations/index.js +5 -1
  51. package/dist/src/packlets/invitations/index.js.map +1 -1
  52. package/dist/src/packlets/invitations/invitations-handler.d.ts +48 -0
  53. package/dist/src/packlets/invitations/invitations-handler.d.ts.map +1 -0
  54. package/dist/src/packlets/invitations/invitations-handler.js +18 -0
  55. package/dist/src/packlets/invitations/invitations-handler.js.map +1 -0
  56. package/dist/src/packlets/invitations/invitations-proxy.d.ts +11 -7
  57. package/dist/src/packlets/invitations/invitations-proxy.d.ts.map +1 -1
  58. package/dist/src/packlets/invitations/invitations-proxy.js +5 -10
  59. package/dist/src/packlets/invitations/invitations-proxy.js.map +1 -1
  60. package/dist/src/packlets/invitations/invitations-service.d.ts +25 -0
  61. package/dist/src/packlets/invitations/invitations-service.d.ts.map +1 -0
  62. package/dist/src/packlets/invitations/invitations-service.js +174 -0
  63. package/dist/src/packlets/invitations/invitations-service.js.map +1 -0
  64. package/dist/src/packlets/invitations/invitations.d.ts +10 -42
  65. package/dist/src/packlets/invitations/invitations.d.ts.map +1 -1
  66. package/dist/src/packlets/invitations/invitations.js +5 -2
  67. package/dist/src/packlets/invitations/invitations.js.map +1 -1
  68. package/dist/src/packlets/invitations/space-invitations-handler.d.ts +6 -6
  69. package/dist/src/packlets/invitations/space-invitations-handler.d.ts.map +1 -1
  70. package/dist/src/packlets/invitations/space-invitations-handler.js +18 -19
  71. package/dist/src/packlets/invitations/space-invitations-handler.js.map +1 -1
  72. package/dist/src/packlets/invitations/space-invitations-handler.test.js +5 -5
  73. package/dist/src/packlets/invitations/space-invitations-handler.test.js.map +1 -1
  74. package/dist/src/packlets/invitations/space-invitations-proxy.d.ts +1 -1
  75. package/dist/src/packlets/invitations/space-invitations-proxy.d.ts.map +1 -1
  76. package/dist/src/packlets/invitations/space-invitations-proxy.js +2 -2
  77. package/dist/src/packlets/invitations/space-invitations-proxy.js.map +1 -1
  78. package/dist/src/packlets/invitations/space-invitations-service.d.ts +8 -14
  79. package/dist/src/packlets/invitations/space-invitations-service.d.ts.map +1 -1
  80. package/dist/src/packlets/invitations/space-invitations-service.js +11 -169
  81. package/dist/src/packlets/invitations/space-invitations-service.js.map +1 -1
  82. package/dist/src/packlets/invitations/space-invitations-service.test.js +4 -4
  83. package/dist/src/packlets/invitations/space-invitations-service.test.js.map +1 -1
  84. package/dist/src/packlets/services/service-context.d.ts +2 -2
  85. package/dist/src/packlets/services/service-context.d.ts.map +1 -1
  86. package/dist/src/packlets/services/service-context.js +8 -8
  87. package/dist/src/packlets/services/service-context.js.map +1 -1
  88. package/dist/src/packlets/services/service-definitions.d.ts +5 -4
  89. package/dist/src/packlets/services/service-definitions.d.ts.map +1 -1
  90. package/dist/src/packlets/services/service-definitions.js +6 -4
  91. package/dist/src/packlets/services/service-definitions.js.map +1 -1
  92. package/dist/src/packlets/services/service-host.d.ts.map +1 -1
  93. package/dist/src/packlets/services/service-host.js +10 -8
  94. package/dist/src/packlets/services/service-host.js.map +1 -1
  95. package/dist/src/packlets/services/service-proxy.js +1 -1
  96. package/dist/src/packlets/services/service-registry.test.js +2 -2
  97. package/dist/src/packlets/services/service-registry.test.js.map +1 -1
  98. package/dist/src/packlets/vault/iframe-runtime.d.ts +5 -4
  99. package/dist/src/packlets/vault/iframe-runtime.d.ts.map +1 -1
  100. package/dist/src/packlets/vault/iframe-runtime.js +4 -12
  101. package/dist/src/packlets/vault/iframe-runtime.js.map +1 -1
  102. package/dist/src/packlets/vault/worker-runtime.d.ts +4 -3
  103. package/dist/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  104. package/dist/src/packlets/vault/worker-runtime.js +29 -24
  105. package/dist/src/packlets/vault/worker-runtime.js.map +1 -1
  106. package/dist/src/packlets/vault/worker-session.d.ts +5 -2
  107. package/dist/src/packlets/vault/worker-session.d.ts.map +1 -1
  108. package/dist/src/packlets/vault/worker-session.js +23 -10
  109. package/dist/src/packlets/vault/worker-session.js.map +1 -1
  110. package/package.json +26 -26
  111. package/src/packlets/deprecated/profile.ts +2 -84
  112. package/src/packlets/deprecated/system.ts +7 -0
  113. package/src/packlets/deprecated/tracing.ts +1 -1
  114. package/src/packlets/devtools/devtools.ts +3 -1
  115. package/src/packlets/identity/devices-service-impl.ts +0 -1
  116. package/src/packlets/identity/identity-manager.ts +50 -49
  117. package/src/packlets/identity/identity.ts +58 -1
  118. package/src/packlets/invitations/halo-invitations-handler.test.ts +79 -0
  119. package/src/packlets/invitations/halo-invitations-handler.ts +255 -0
  120. package/src/packlets/invitations/halo-invitations-proxy.ts +14 -0
  121. package/src/packlets/invitations/halo-invitations-service.test.ts +96 -0
  122. package/src/packlets/invitations/halo-invitations-service.ts +24 -0
  123. package/src/packlets/invitations/index.ts +5 -1
  124. package/src/packlets/invitations/invitations-handler.ts +59 -0
  125. package/src/packlets/invitations/invitations-proxy.ts +15 -13
  126. package/src/packlets/invitations/invitations-service.ts +184 -0
  127. package/src/packlets/invitations/invitations.ts +14 -46
  128. package/src/packlets/invitations/space-invitations-handler.test.ts +6 -6
  129. package/src/packlets/invitations/space-invitations-handler.ts +16 -16
  130. package/src/packlets/invitations/space-invitations-proxy.ts +2 -2
  131. package/src/packlets/invitations/space-invitations-service.test.ts +13 -13
  132. package/src/packlets/invitations/space-invitations-service.ts +16 -177
  133. package/src/packlets/services/service-context.ts +6 -6
  134. package/src/packlets/services/service-definitions.ts +20 -10
  135. package/src/packlets/services/service-host.ts +15 -7
  136. package/src/packlets/services/service-proxy.ts +1 -1
  137. package/src/packlets/services/service-registry.test.ts +5 -5
  138. package/src/packlets/vault/iframe-runtime.ts +8 -15
  139. package/src/packlets/vault/worker-runtime.ts +36 -30
  140. package/src/packlets/vault/worker-session.ts +20 -6
  141. package/dist/src/packlets/invitations/halo-invitations.d.ts +0 -23
  142. package/dist/src/packlets/invitations/halo-invitations.d.ts.map +0 -1
  143. package/dist/src/packlets/invitations/halo-invitations.js +0 -154
  144. package/dist/src/packlets/invitations/halo-invitations.js.map +0 -1
  145. package/dist/src/packlets/invitations/halo-invitations.test.d.ts +0 -2
  146. package/dist/src/packlets/invitations/halo-invitations.test.d.ts.map +0 -1
  147. package/dist/src/packlets/invitations/halo-invitations.test.js +0 -34
  148. package/dist/src/packlets/invitations/halo-invitations.test.js.map +0 -1
  149. package/dist/src/packlets/services/iframe-service-proxy.d.ts +0 -23
  150. package/dist/src/packlets/services/iframe-service-proxy.d.ts.map +0 -1
  151. package/dist/src/packlets/services/iframe-service-proxy.js +0 -53
  152. package/dist/src/packlets/services/iframe-service-proxy.js.map +0 -1
  153. package/dist/src/packlets/services/service-host.test.d.ts +0 -2
  154. package/dist/src/packlets/services/service-host.test.d.ts.map +0 -1
  155. package/dist/src/packlets/services/service-host.test.js +0 -68
  156. package/dist/src/packlets/services/service-host.test.js.map +0 -1
  157. package/src/packlets/invitations/halo-invitations.test.ts +0 -40
  158. package/src/packlets/invitations/halo-invitations.ts +0 -164
  159. package/src/packlets/services/iframe-service-proxy.ts +0 -71
  160. package/src/packlets/services/service-host.test.ts +0 -76
@@ -0,0 +1,184 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import assert from 'assert';
6
+
7
+ import { TimeoutError } from '@dxos/async';
8
+ import { Stream } from '@dxos/codec-protobuf';
9
+ import { log } from '@dxos/log';
10
+ import { AuthenticationRequest, Invitation } from '@dxos/protocols/proto/dxos/client/services';
11
+ import { Provider } from '@dxos/util';
12
+
13
+ import { IdentityManager } from '../identity';
14
+ import { AuthenticatingInvitationObservable, CancellableInvitationObservable, InvitationsService } from './invitations';
15
+ import { InvitationsHandler, InvitationsOptions } from './invitations-handler';
16
+
17
+ /**
18
+ * Adapts invitation service observable to client/service stream.
19
+ */
20
+ export abstract class AbstractInvitationsService<T = void> implements InvitationsService {
21
+ private readonly _createInvitations = new Map<string, CancellableInvitationObservable>();
22
+ private readonly _acceptInvitations = new Map<string, AuthenticatingInvitationObservable>();
23
+
24
+ // prettier-ignore
25
+ protected constructor (
26
+ private readonly _identityManager: IdentityManager,
27
+ private readonly _getInvitationsHandler: Provider<InvitationsHandler<T>>
28
+ ) {}
29
+
30
+ // TODO(burdon): Guest/host label.
31
+ getLoggingContext() {
32
+ return {
33
+ deviceKey: this._identityManager.identity?.deviceKey
34
+ };
35
+ }
36
+
37
+ abstract getContext(invitation: Invitation): T;
38
+
39
+ createInvitation(invitation: Invitation): Stream<Invitation> {
40
+ return new Stream<Invitation>(({ next, close }) => {
41
+ const invitationsHandler: InvitationsHandler<T> = this._getInvitationsHandler();
42
+ const context = this.getContext(invitation);
43
+ log('stream opened', this.getLoggingContext());
44
+
45
+ let invitationId: string;
46
+ const { type } = invitation;
47
+ const observable = invitationsHandler.createInvitation(context, { type });
48
+ observable.subscribe({
49
+ onConnecting: (invitation) => {
50
+ assert(invitation.invitationId);
51
+ invitationId = invitation.invitationId;
52
+ this._createInvitations.set(invitation.invitationId, observable);
53
+ invitation.state = Invitation.State.CONNECTING;
54
+ next(invitation);
55
+ },
56
+ onConnected: (invitation) => {
57
+ assert(invitation.invitationId);
58
+ invitation.state = Invitation.State.CONNECTED;
59
+ next(invitation);
60
+ },
61
+ onAuthenticating: (invitation) => {
62
+ assert(invitation.invitationId);
63
+ invitation.state = Invitation.State.AUTHENTICATING;
64
+ next(invitation);
65
+ },
66
+ onSuccess: (invitation) => {
67
+ assert(invitation.invitationId);
68
+ invitation.state = Invitation.State.SUCCESS;
69
+ next(invitation);
70
+ close();
71
+ },
72
+ onCancelled: () => {
73
+ assert(invitationId);
74
+ invitation.invitationId = invitationId;
75
+ invitation.state = Invitation.State.CANCELLED;
76
+ next(invitation);
77
+ close();
78
+ },
79
+ onTimeout: (err: TimeoutError) => {
80
+ invitation.state = Invitation.State.TIMEOUT;
81
+ close(err);
82
+ },
83
+ onError: (err: any) => {
84
+ invitation.state = Invitation.State.ERROR;
85
+ close(err);
86
+ }
87
+ });
88
+
89
+ return (err?: Error) => {
90
+ const context = this.getLoggingContext();
91
+ if (err) {
92
+ log.warn('stream closed', { ...context, err });
93
+ } else {
94
+ log('stream closed', context);
95
+ }
96
+
97
+ this._createInvitations.delete(invitation.invitationId!);
98
+ };
99
+ });
100
+ }
101
+
102
+ acceptInvitation(invitation: Invitation, options?: InvitationsOptions): Stream<Invitation> {
103
+ return new Stream<Invitation>(({ next, close }) => {
104
+ log('stream opened', this.getLoggingContext());
105
+ const invitationsHandler = this._getInvitationsHandler();
106
+
107
+ let invitationId: string;
108
+ const observable = invitationsHandler.acceptInvitation(invitation, options);
109
+ observable.subscribe({
110
+ onConnecting: (invitation) => {
111
+ assert(invitation.invitationId);
112
+ invitationId = invitation.invitationId;
113
+ this._acceptInvitations.set(invitation.invitationId, observable);
114
+ invitation.state = Invitation.State.CONNECTING;
115
+ next(invitation);
116
+ },
117
+ onConnected: (invitation) => {
118
+ assert(invitation.invitationId);
119
+ invitation.state = Invitation.State.CONNECTED;
120
+ next(invitation);
121
+ },
122
+ onAuthenticating: (invitation) => {
123
+ assert(invitation.invitationId);
124
+ invitation.state = Invitation.State.AUTHENTICATING;
125
+ next(invitation);
126
+ },
127
+ onSuccess: (invitation) => {
128
+ invitation.state = Invitation.State.SUCCESS;
129
+ next(invitation);
130
+ close();
131
+ },
132
+ onCancelled: () => {
133
+ assert(invitationId);
134
+ invitation.invitationId = invitationId;
135
+ invitation.state = Invitation.State.CANCELLED;
136
+ next(invitation);
137
+ close();
138
+ },
139
+ onTimeout: (err: TimeoutError) => {
140
+ invitation.state = Invitation.State.TIMEOUT;
141
+ close(err);
142
+ },
143
+ onError: (err: any) => {
144
+ invitation.state = Invitation.State.ERROR;
145
+ close(err);
146
+ }
147
+ });
148
+
149
+ return (err?: Error) => {
150
+ const context = this.getLoggingContext();
151
+ if (err) {
152
+ log.warn('stream closed', { ...context, err });
153
+ } else {
154
+ log('stream closed', context);
155
+ }
156
+
157
+ this._acceptInvitations.delete(invitation.invitationId!);
158
+ };
159
+ });
160
+ }
161
+
162
+ async authenticate({ invitationId, authenticationCode }: AuthenticationRequest): Promise<void> {
163
+ log('authenticating...');
164
+ assert(invitationId);
165
+ const observable = this._acceptInvitations.get(invitationId);
166
+ if (!observable) {
167
+ log.warn('invalid invitation', { invitationId });
168
+ } else {
169
+ await observable.authenticate(authenticationCode);
170
+ }
171
+ }
172
+
173
+ async cancelInvitation(invitation: Invitation): Promise<void> {
174
+ log('cancelling...');
175
+ assert(invitation.invitationId);
176
+ const observable =
177
+ this._createInvitations.get(invitation.invitationId) ?? this._acceptInvitations.get(invitation.invitationId);
178
+ if (!observable) {
179
+ log.warn('invalid invitation', { invitationId: invitation.invitationId });
180
+ } else {
181
+ await observable?.cancel();
182
+ }
183
+ }
184
+ }
@@ -11,14 +11,22 @@ import {
11
11
  Observable,
12
12
  CancellableObservableProvider
13
13
  } from '@dxos/async';
14
- import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
14
+ import type { Stream } from '@dxos/codec-protobuf';
15
+ import { AuthenticationRequest, CancelInvitationRequest, Invitation } from '@dxos/protocols/proto/dxos/client/services';
15
16
 
16
17
  export const AUTHENTICATION_CODE_LENGTH = 6;
17
18
 
18
- export type CreateInvitationsOptions = {
19
- type?: Invitation.Type;
20
- timeout?: number;
21
- };
19
+ export const INVITATION_TIMEOUT = 3 * 60_000; // 3 mins.
20
+
21
+ // TODO(burdon): Don't close until RPC has complete (bug).
22
+ export const ON_CLOSE_DELAY = 500;
23
+
24
+ export interface InvitationsService {
25
+ createInvitation(invitation: Invitation): Stream<Invitation>;
26
+ authenticate(request: AuthenticationRequest): Promise<void>;
27
+ acceptInvitation(invitation: Invitation): Stream<Invitation>;
28
+ cancelInvitation(request: CancelInvitationRequest): Promise<void>;
29
+ }
22
30
 
23
31
  /**
24
32
  * Common invitation events (callbacks) for creating and accepting invitations.
@@ -87,51 +95,11 @@ export class AuthenticatingInvitationProvider
87
95
  }
88
96
  }
89
97
 
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
98
  /**
131
99
  * Util to wrap observable with promise.
132
100
  * @deprecated
133
101
  */
134
- // TODO(burdon): Replace with InvitationObservableProvider.
102
+ // TODO(burdon): Replace with ObservableInvitationProvider.
135
103
  export const invitationObservable = async (observable: Observable<InvitationEvents>): Promise<Invitation> => {
136
104
  return new Promise((resolve, reject) => {
137
105
  const unsubscribe = observable.subscribe({
@@ -61,11 +61,11 @@ describe('services/space-invitations-handler', function () {
61
61
  onConnecting: async (invitation1: Invitation) => {
62
62
  const observable2 = guest.spaceInvitations!.acceptInvitation(invitation1);
63
63
  observable2.subscribe({
64
- onConnecting: async (invitation2: Invitation) => {},
64
+ onConnecting: async () => {},
65
65
  onConnected: async (invitation2: Invitation) => {
66
66
  expect(invitation1.swarmKey).to.eq(invitation2.swarmKey);
67
67
  },
68
- onAuthenticating: async (invitation2: Invitation) => {
68
+ onAuthenticating: async () => {
69
69
  await observable2.authenticate(await authenticationCode.wait());
70
70
  },
71
71
  onSuccess: (invitation: Invitation) => {
@@ -81,17 +81,17 @@ describe('services/space-invitations-handler', function () {
81
81
  authenticationCode.wake(invitation.authenticationCode);
82
82
  },
83
83
  onSuccess: (invitation: Invitation) => {
84
- complete1.wake(space1.key);
84
+ complete1.wake(invitation.spaceKey!);
85
85
  },
86
86
  onCancelled: () => raise(new Error()),
87
87
  onTimeout: (err: Error) => raise(new Error(err.message)),
88
88
  onError: (err: Error) => raise(new Error(err.message))
89
89
  });
90
90
 
91
- {
92
- const [spaceKey1, spaceKey2] = await Promise.all([complete1.wait(), complete2.wait()]);
93
- expect(spaceKey1).to.deep.eq(spaceKey2);
91
+ const [spaceKey1, spaceKey2] = await Promise.all([complete1.wait(), complete2.wait()]);
92
+ expect(spaceKey1).to.deep.eq(spaceKey2);
94
93
 
94
+ {
95
95
  const space1 = host.spaceManager!.spaces.get(spaceKey1)!;
96
96
  const space2 = guest.spaceManager!.spaces.get(spaceKey2)!;
97
97
  expect(space1).not.to.be.undefined;
@@ -18,39 +18,38 @@ import { createProtoRpcPeer } from '@dxos/rpc';
18
18
 
19
19
  import {
20
20
  AuthenticatingInvitationProvider,
21
- CreateInvitationsOptions,
22
- InvitationsHandler,
23
21
  InvitationObservable,
24
22
  InvitationObservableProvider,
25
- AUTHENTICATION_CODE_LENGTH
23
+ AUTHENTICATION_CODE_LENGTH,
24
+ INVITATION_TIMEOUT,
25
+ ON_CLOSE_DELAY
26
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.
27
+ import { AbstractInvitationsHandler, InvitationsOptions } from './invitations-handler';
32
28
 
33
29
  /**
34
30
  * Handles the life-cycle of Space invitations between peers.
35
31
  */
36
- export class SpaceInvitationsHandler implements InvitationsHandler<Space> {
32
+ // TODO(dmaretskyi): Split into Host and Guest parts.
33
+ export class SpaceInvitationsHandler extends AbstractInvitationsHandler<Space> {
37
34
  constructor(
35
+ networkManager: NetworkManager,
38
36
  private readonly _spaceManager: SpaceManager,
39
- private readonly _networkManager: NetworkManager,
40
37
  private readonly _signingContext: SigningContext
41
- ) {}
38
+ ) {
39
+ super(networkManager);
40
+ }
42
41
 
43
42
  /**
44
43
  * Creates an invitation and listens for a join request from the invited (guest) peer.
45
44
  */
46
- createInvitation(space: Space, options?: CreateInvitationsOptions): InvitationObservable {
45
+ createInvitation(space: Space, options?: InvitationsOptions): InvitationObservable {
47
46
  let swarmConnection: SwarmConnection | undefined;
48
47
  const { type, timeout = INVITATION_TIMEOUT } = options ?? {};
49
48
  assert(type !== Invitation.Type.OFFLINE);
49
+ assert(space);
50
50
 
51
51
  const invitation: Invitation = {
52
52
  type,
53
- // type: Invitation.Type.INTERACTIVE_TESTING,
54
53
  invitationId: PublicKey.random().toHex(),
55
54
  swarmKey: PublicKey.random(),
56
55
  spaceKey: space.key,
@@ -169,7 +168,8 @@ export class SpaceInvitationsHandler implements InvitationsHandler<Space> {
169
168
  * The local guest peer (invitee) then sends the local space invitation to the host,
170
169
  * which then writes the guest's credentials to the space.
171
170
  */
172
- acceptInvitation(invitation: Invitation): AuthenticatingInvitationProvider {
171
+ acceptInvitation(invitation: Invitation, options?: InvitationsOptions): AuthenticatingInvitationProvider {
172
+ const { timeout = INVITATION_TIMEOUT } = options ?? {};
173
173
  let swarmConnection: SwarmConnection | undefined;
174
174
 
175
175
  const authenticated = new Trigger<string>();
@@ -211,9 +211,9 @@ export class SpaceInvitationsHandler implements InvitationsHandler<Space> {
211
211
  // 2. Get authentication code.
212
212
  // TODO(burdon): Test timeout (options for timeouts at different steps).
213
213
  if (invitation.type === undefined || invitation.type === Invitation.Type.INTERACTIVE) {
214
- log('waiting for authentication code...');
214
+ log('guest waiting for authentication code...');
215
215
  observable.callback.onAuthenticating?.(invitation);
216
- const authenticationCode = await authenticated.wait({ timeout: INVITATION_TIMEOUT });
216
+ const authenticationCode = await authenticated.wait({ timeout });
217
217
  log('sending authentication request');
218
218
  await peer.rpc.SpaceHostService.authenticate({ authenticationCode });
219
219
  }
@@ -10,7 +10,7 @@ import { AbstractInvitationsProxy } from './invitations-proxy';
10
10
  * Adapts invitation service observable to client/service stream.
11
11
  */
12
12
  export class SpaceInvitationsProxy extends AbstractInvitationsProxy<PublicKey> {
13
- createInvitationObject(spaceKey: PublicKey) {
14
- return { spaceKey };
13
+ getInvitationOptions(context: PublicKey) {
14
+ return { spaceKey: context };
15
15
  }
16
16
  }
@@ -7,7 +7,7 @@ import { expect } from 'chai';
7
7
 
8
8
  import { asyncChain, Trigger } from '@dxos/async';
9
9
  import { raise } from '@dxos/debug';
10
- import { Invitation, InvitationsService } from '@dxos/protocols/proto/dxos/client/services';
10
+ import { Invitation, SpaceInvitationsService } from '@dxos/protocols/proto/dxos/client/services';
11
11
  import { afterTest } from '@dxos/testutils';
12
12
 
13
13
  import { ServiceContext } from '../services';
@@ -26,18 +26,18 @@ describe('services/space-invitation-service', function () {
26
26
 
27
27
  assert(host.spaceManager);
28
28
  assert(host.spaceInvitations);
29
- const service1: InvitationsService = new SpaceInvitationsServiceImpl(
29
+ const service1: SpaceInvitationsService = new SpaceInvitationsServiceImpl(
30
30
  host.identityManager,
31
- () => host.spaceManager!,
32
- () => host.spaceInvitations!
31
+ () => host.spaceInvitations!,
32
+ () => host.spaceManager!
33
33
  );
34
34
 
35
35
  assert(guest.spaceManager);
36
36
  assert(guest.spaceInvitations);
37
- const service2: InvitationsService = new SpaceInvitationsServiceImpl(
37
+ const service2: SpaceInvitationsService = new SpaceInvitationsServiceImpl(
38
38
  guest.identityManager,
39
- () => guest.spaceManager!,
40
- () => guest.spaceInvitations!
39
+ () => guest.spaceInvitations!,
40
+ () => guest.spaceManager!
41
41
  );
42
42
 
43
43
  const space1 = await host.spaceManager.createSpace();
@@ -87,18 +87,18 @@ describe('services/space-invitation-service', function () {
87
87
 
88
88
  assert(host.spaceManager);
89
89
  assert(host.spaceInvitations);
90
- const service1: InvitationsService = new SpaceInvitationsServiceImpl(
90
+ const service1: SpaceInvitationsService = new SpaceInvitationsServiceImpl(
91
91
  host.identityManager,
92
- () => host.spaceManager!,
93
- () => host.spaceInvitations!
92
+ () => host.spaceInvitations!,
93
+ () => host.spaceManager!
94
94
  );
95
95
 
96
96
  assert(guest.spaceManager);
97
97
  assert(guest.spaceInvitations);
98
- const service2: InvitationsService = new SpaceInvitationsServiceImpl(
98
+ const service2: SpaceInvitationsService = new SpaceInvitationsServiceImpl(
99
99
  guest.identityManager,
100
- () => guest.spaceManager!,
101
- () => guest.spaceInvitations!
100
+ () => guest.spaceInvitations!,
101
+ () => guest.spaceManager!
102
102
  );
103
103
 
104
104
  const space1 = await host.spaceManager.createSpace();
@@ -4,193 +4,32 @@
4
4
 
5
5
  import assert from 'assert';
6
6
 
7
- import { TimeoutError } from '@dxos/async';
8
- import { Stream } from '@dxos/codec-protobuf';
9
- import { SpaceManager } from '@dxos/echo-db';
10
- import { log } from '@dxos/log';
11
- import { AuthenticationRequest, Invitation, InvitationsService } from '@dxos/protocols/proto/dxos/client/services';
7
+ import { Space, SpaceManager } from '@dxos/echo-db';
8
+ import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
12
9
  import { Provider } from '@dxos/util';
13
10
 
14
11
  import { IdentityManager } from '../identity';
15
- import { AuthenticatingInvitationObservable, CancellableInvitationObservable } from './invitations';
16
- import { SpaceInvitationsHandler } from './space-invitations-handler';
12
+ import { InvitationsHandler } from './invitations-handler';
13
+ import { AbstractInvitationsService } from './invitations-service';
17
14
 
18
15
  /**
19
16
  * Adapts invitation service observable to client/service stream.
20
17
  */
21
- export class SpaceInvitationsServiceImpl implements InvitationsService {
22
- private readonly _createInvitations = new Map<string, CancellableInvitationObservable>();
23
- private readonly _acceptInvitations = new Map<string, AuthenticatingInvitationObservable>();
24
-
18
+ export class SpaceInvitationsServiceImpl extends AbstractInvitationsService<Space> {
25
19
  // prettier-ignore
26
20
  constructor (
27
- private readonly _identityManager: IdentityManager,
28
- private readonly _getSpaceManager: Provider<SpaceManager>,
29
- private readonly _getSpaceInvitations: Provider<SpaceInvitationsHandler>
30
- ) {}
31
-
32
- createInvitation(invitation: Invitation): Stream<Invitation> {
33
- return new Stream<Invitation>(({ next, close }) => {
34
- const spaceManager = this._getSpaceManager();
35
- const spaceInvitations = this._getSpaceInvitations();
36
-
37
- assert(invitation.spaceKey);
38
- log('stream opened', {
39
- host: this._identityManager.identity?.deviceKey,
40
- spaceKey: invitation.spaceKey
41
- });
42
- const space = spaceManager.spaces.get(invitation.spaceKey!);
43
- assert(space);
44
-
45
- let invitationId: string;
46
- const { type } = invitation;
47
- const observable = spaceInvitations.createInvitation(space, { type });
48
- observable.subscribe({
49
- onConnecting: (invitation) => {
50
- assert(invitation.invitationId);
51
- invitationId = invitation.invitationId;
52
- this._createInvitations.set(invitation.invitationId, observable);
53
- invitation.state = Invitation.State.CONNECTING;
54
- next(invitation);
55
- },
56
- onConnected: (invitation) => {
57
- assert(invitation.invitationId);
58
- invitation.state = Invitation.State.CONNECTED;
59
- next(invitation);
60
- },
61
- onAuthenticating: (invitation) => {
62
- assert(invitation.invitationId);
63
- invitation.state = Invitation.State.AUTHENTICATING;
64
- next(invitation);
65
- },
66
- onSuccess: (invitation) => {
67
- assert(invitation.invitationId);
68
- invitation.state = Invitation.State.SUCCESS;
69
- next(invitation);
70
- close();
71
- },
72
- onCancelled: () => {
73
- assert(invitationId);
74
- invitation.invitationId = invitationId;
75
- invitation.state = Invitation.State.CANCELLED;
76
- next(invitation);
77
- close();
78
- },
79
- onTimeout: (err: TimeoutError) => {
80
- invitation.state = Invitation.State.TIMEOUT;
81
- close(err);
82
- },
83
- onError: (err: any) => {
84
- invitation.state = Invitation.State.ERROR;
85
- close(err);
86
- }
87
- });
88
-
89
- return (err?: Error) => {
90
- const context = {
91
- guest: this._identityManager.identity?.deviceKey,
92
- spaceKey: invitation.spaceKey
93
- };
94
- if (err) {
95
- log.warn('stream closed', { ...context, err });
96
- } else {
97
- log('stream closed', context);
98
- }
99
-
100
- this._createInvitations.delete(invitation.invitationId!);
101
- };
102
- });
103
- }
104
-
105
- acceptInvitation(invitation: Invitation): Stream<Invitation> {
106
- return new Stream<Invitation>(({ next, close }) => {
107
- const spaceInvitations = this._getSpaceInvitations();
108
-
109
- assert(invitation.spaceKey);
110
- log('stream opened', {
111
- guest: this._identityManager.identity?.deviceKey,
112
- spaceKey: invitation.spaceKey
113
- });
114
-
115
- let invitationId: string;
116
- const observable = spaceInvitations.acceptInvitation(invitation);
117
- observable.subscribe({
118
- onConnecting: (invitation) => {
119
- assert(invitation.invitationId);
120
- invitationId = invitation.invitationId;
121
- this._acceptInvitations.set(invitation.invitationId, observable);
122
- invitation.state = Invitation.State.CONNECTING;
123
- next(invitation);
124
- },
125
- onConnected: (invitation) => {
126
- assert(invitation.invitationId);
127
- invitation.state = Invitation.State.CONNECTED;
128
- next(invitation);
129
- },
130
- onAuthenticating: (invitation) => {
131
- assert(invitation.invitationId);
132
- invitation.state = Invitation.State.AUTHENTICATING;
133
- next(invitation);
134
- },
135
- onSuccess: (invitation) => {
136
- assert(invitation.spaceKey);
137
- invitation.state = Invitation.State.SUCCESS;
138
- next(invitation);
139
- close();
140
- },
141
- onCancelled: () => {
142
- assert(invitationId);
143
- invitation.invitationId = invitationId;
144
- invitation.state = Invitation.State.CANCELLED;
145
- next(invitation);
146
- close();
147
- },
148
- onTimeout: (err: TimeoutError) => {
149
- invitation.state = Invitation.State.TIMEOUT;
150
- close(err);
151
- },
152
- onError: (err: any) => {
153
- invitation.state = Invitation.State.ERROR;
154
- close(err);
155
- }
156
- });
157
-
158
- return (err?: Error) => {
159
- const context = {
160
- guest: this._identityManager.identity?.deviceKey,
161
- spaceKey: invitation.spaceKey
162
- };
163
- if (err) {
164
- log.warn('stream closed', { ...context, err });
165
- } else {
166
- log('stream closed', context);
167
- }
168
-
169
- this._acceptInvitations.delete(invitation.invitationId!);
170
- };
171
- });
172
- }
173
-
174
- async authenticate({ invitationId, authenticationCode }: AuthenticationRequest): Promise<void> {
175
- log('authenticating...');
176
- assert(invitationId);
177
- const observable = this._acceptInvitations.get(invitationId);
178
- if (!observable) {
179
- log.warn('invalid invitation', { invitationId });
180
- } else {
181
- await observable.authenticate(authenticationCode);
182
- }
21
+ identityManager: IdentityManager,
22
+ private readonly invitationsHandler: Provider<InvitationsHandler<Space>>,
23
+ private readonly _getSpaceManager: Provider<SpaceManager>
24
+ ) {
25
+ super(identityManager, invitationsHandler);
183
26
  }
184
27
 
185
- async cancelInvitation(invitation: Invitation): Promise<void> {
186
- log('cancelling...');
187
- assert(invitation.invitationId);
188
- const observable =
189
- this._createInvitations.get(invitation.invitationId) ?? this._acceptInvitations.get(invitation.invitationId);
190
- if (!observable) {
191
- log.warn('invalid invitation', { invitationId: invitation.invitationId });
192
- } else {
193
- await observable?.cancel();
194
- }
28
+ override getContext(invitation: Invitation): Space {
29
+ assert(invitation.spaceKey);
30
+ const spaceManager = this._getSpaceManager();
31
+ const space = spaceManager.spaces.get(invitation.spaceKey!);
32
+ assert(space);
33
+ return space;
195
34
  }
196
35
  }