@comapeo/core 2.3.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/invite/invite-api.d.ts +112 -0
- package/dist/invite/invite-api.d.ts.map +1 -0
- package/dist/invite/invite-state-machine.d.ts +510 -0
- package/dist/invite/invite-state-machine.d.ts.map +1 -0
- package/dist/local-peers.d.ts.map +1 -1
- package/dist/mapeo-manager.d.ts +1 -1
- package/dist/mapeo-manager.d.ts.map +1 -1
- package/dist/member-api.d.ts.map +1 -1
- package/dist/sync/sync-api.d.ts.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/errors.js +33 -0
- package/src/invite/StateDiagram.md +47 -0
- package/src/invite/invite-api.js +387 -0
- package/src/invite/invite-state-machine.js +208 -0
- package/src/local-peers.js +76 -30
- package/src/mapeo-manager.js +1 -1
- package/src/member-api.js +5 -4
- package/src/types.ts +6 -0
- package/src/utils.js +8 -3
- package/dist/invite-api.d.ts +0 -70
- package/dist/invite-api.d.ts.map +0 -1
- package/src/invite-api.js +0 -450
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { setup, assign, fromPromise, assertEvent, raise } from 'xstate'
|
|
2
|
+
import { omit } from '../lib/omit.js'
|
|
3
|
+
import { InviteResponse_Decision } from '../generated/rpc.js'
|
|
4
|
+
import ensureError from 'ensure-error'
|
|
5
|
+
import { TimeoutError } from '../errors.js'
|
|
6
|
+
|
|
7
|
+
const RECEIVE_PROJECT_DETAILS_TIMEOUT_MS = 10_000
|
|
8
|
+
const ADD_PROJECT_TIMEOUT_MS = 10_000
|
|
9
|
+
|
|
10
|
+
/** @import { StringToTaggedUnion } from '../types.js' */
|
|
11
|
+
/** @import { ProjectJoinDetails } from '../generated/rpc.js' */
|
|
12
|
+
/**
|
|
13
|
+
* @internal
|
|
14
|
+
* @typedef {object} Context
|
|
15
|
+
* @property {null | Error} error
|
|
16
|
+
* @property {null | string} projectPublicId
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} MachineSetupTypes
|
|
20
|
+
* @property {Context} context
|
|
21
|
+
* @property {{ projectPublicId: string | null }} output
|
|
22
|
+
* @property {StringToTaggedUnion<'ACCEPT_INVITE' | 'CANCEL_INVITE' | 'REJECT_INVITE' | 'ALREADY_IN_PROJECT' | 'ADDED_PROJECT' | 'PEER_DISCONNECTED'> | ({ type: 'RECEIVE_PROJECT_DETAILS' } & ProjectJoinDetails)} events
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export const inviteStateMachine = setup({
|
|
26
|
+
types: /** @type {MachineSetupTypes} */ ({}),
|
|
27
|
+
actors: {
|
|
28
|
+
sendInviteResponse: fromPromise(
|
|
29
|
+
/**
|
|
30
|
+
* @param {{ input: { decision: InviteResponse_Decision }}} _opts
|
|
31
|
+
*/
|
|
32
|
+
async (_opts) => {}
|
|
33
|
+
),
|
|
34
|
+
addProject: fromPromise(
|
|
35
|
+
/**
|
|
36
|
+
* @param {{ input: ProjectJoinDetails }} _opts
|
|
37
|
+
* @returns {Promise<string>} The project public ID
|
|
38
|
+
*/
|
|
39
|
+
async (_opts) => ''
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
guards: {
|
|
43
|
+
isNotAlreadyJoiningOrInProject: () => true,
|
|
44
|
+
},
|
|
45
|
+
delays: {
|
|
46
|
+
receiveTimeout: RECEIVE_PROJECT_DETAILS_TIMEOUT_MS,
|
|
47
|
+
addProjectTimeout: ADD_PROJECT_TIMEOUT_MS,
|
|
48
|
+
},
|
|
49
|
+
}).createMachine({
|
|
50
|
+
id: 'invite',
|
|
51
|
+
context: {
|
|
52
|
+
error: null,
|
|
53
|
+
projectPublicId: null,
|
|
54
|
+
},
|
|
55
|
+
initial: 'pending',
|
|
56
|
+
states: {
|
|
57
|
+
pending: {
|
|
58
|
+
description: 'Pending invite awaiting response',
|
|
59
|
+
on: {
|
|
60
|
+
CANCEL_INVITE: { target: 'canceled' },
|
|
61
|
+
ACCEPT_INVITE: [
|
|
62
|
+
{
|
|
63
|
+
target: 'responding.accept',
|
|
64
|
+
guard: { type: 'isNotAlreadyJoiningOrInProject' },
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
actions: raise({ type: 'ALREADY_IN_PROJECT' }),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
ALREADY_IN_PROJECT: {
|
|
71
|
+
target: 'responding.already',
|
|
72
|
+
},
|
|
73
|
+
REJECT_INVITE: {
|
|
74
|
+
target: 'responding.reject',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
responding: {
|
|
79
|
+
description: 'Responding to invite',
|
|
80
|
+
initial: 'default',
|
|
81
|
+
on: {
|
|
82
|
+
CANCEL_INVITE: { target: '#invite.canceled' },
|
|
83
|
+
},
|
|
84
|
+
states: {
|
|
85
|
+
default: {
|
|
86
|
+
always: {
|
|
87
|
+
target: '#invite.error',
|
|
88
|
+
actions: assign({ error: () => new TypeError('InvalidState') }),
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
accept: {
|
|
92
|
+
description: 'Sending accept response',
|
|
93
|
+
invoke: {
|
|
94
|
+
src: 'sendInviteResponse',
|
|
95
|
+
input: { decision: InviteResponse_Decision.ACCEPT },
|
|
96
|
+
onDone: '#invite.joining',
|
|
97
|
+
onError: '#invite.error',
|
|
98
|
+
},
|
|
99
|
+
on: {
|
|
100
|
+
// It's possible project details could be received before the send
|
|
101
|
+
// response promise resolves (e.g. somehow the peer receives the
|
|
102
|
+
// response and sends the project details before the response is
|
|
103
|
+
// confirmed as sent), so we accept project details at this point
|
|
104
|
+
RECEIVE_PROJECT_DETAILS: {
|
|
105
|
+
target: '#invite.joining.addingProject',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
reject: {
|
|
110
|
+
description: 'Sending reject response',
|
|
111
|
+
invoke: {
|
|
112
|
+
src: 'sendInviteResponse',
|
|
113
|
+
input: { decision: InviteResponse_Decision.REJECT },
|
|
114
|
+
onDone: '#invite.rejected',
|
|
115
|
+
onError: '#invite.error',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
already: {
|
|
119
|
+
description: 'Sending already response',
|
|
120
|
+
invoke: {
|
|
121
|
+
src: 'sendInviteResponse',
|
|
122
|
+
input: { decision: InviteResponse_Decision.ALREADY },
|
|
123
|
+
onDone: '#invite.respondedAlready',
|
|
124
|
+
onError: '#invite.error',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
joining: {
|
|
130
|
+
initial: 'awaitingDetails',
|
|
131
|
+
description: 'Joining project from invite',
|
|
132
|
+
states: {
|
|
133
|
+
awaitingDetails: {
|
|
134
|
+
description: 'Waiting for project details',
|
|
135
|
+
on: {
|
|
136
|
+
RECEIVE_PROJECT_DETAILS: { target: 'addingProject' },
|
|
137
|
+
CANCEL_INVITE: { target: '#invite.canceled' },
|
|
138
|
+
},
|
|
139
|
+
after: {
|
|
140
|
+
receiveTimeout: {
|
|
141
|
+
target: '#invite.error',
|
|
142
|
+
actions: assign({
|
|
143
|
+
error: () =>
|
|
144
|
+
new TimeoutError('Timed out waiting for project details'),
|
|
145
|
+
}),
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
addingProject: {
|
|
150
|
+
description: 'Adding project from invite',
|
|
151
|
+
invoke: {
|
|
152
|
+
src: 'addProject',
|
|
153
|
+
input: ({ event }) => {
|
|
154
|
+
assertEvent(event, 'RECEIVE_PROJECT_DETAILS')
|
|
155
|
+
return omit(event, ['type'])
|
|
156
|
+
},
|
|
157
|
+
onDone: {
|
|
158
|
+
target: '#invite.joined',
|
|
159
|
+
actions: assign({
|
|
160
|
+
projectPublicId: ({ event }) => event.output,
|
|
161
|
+
}),
|
|
162
|
+
},
|
|
163
|
+
onError: '#invite.error',
|
|
164
|
+
},
|
|
165
|
+
after: {
|
|
166
|
+
addProjectTimeout: {
|
|
167
|
+
target: '#invite.error',
|
|
168
|
+
actions: assign({
|
|
169
|
+
error: () => new TimeoutError('Timed out adding project'),
|
|
170
|
+
}),
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
canceled: {
|
|
177
|
+
description: 'The invite has been canceled',
|
|
178
|
+
type: 'final',
|
|
179
|
+
},
|
|
180
|
+
rejected: {
|
|
181
|
+
description: 'Rejected invite',
|
|
182
|
+
type: 'final',
|
|
183
|
+
},
|
|
184
|
+
respondedAlready: {
|
|
185
|
+
description: 'Responded that already in project',
|
|
186
|
+
type: 'final',
|
|
187
|
+
},
|
|
188
|
+
joined: {
|
|
189
|
+
description: 'Successfully joined project',
|
|
190
|
+
type: 'final',
|
|
191
|
+
},
|
|
192
|
+
error: {
|
|
193
|
+
entry: assign({
|
|
194
|
+
error: ({ event, context }) =>
|
|
195
|
+
context.error ||
|
|
196
|
+
ensureError(
|
|
197
|
+
// @ts-expect-error - xstate types are incorrect, for internal events
|
|
198
|
+
// the error property can exist, and ensureError handles event.error
|
|
199
|
+
// being undefined.
|
|
200
|
+
event.error
|
|
201
|
+
),
|
|
202
|
+
}),
|
|
203
|
+
type: 'final',
|
|
204
|
+
description: 'Error joining project',
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
output: ({ context }) => ({ projectPublicId: context.projectPublicId }),
|
|
208
|
+
})
|
package/src/local-peers.js
CHANGED
|
@@ -64,6 +64,7 @@ class Peer {
|
|
|
64
64
|
#deviceType
|
|
65
65
|
#connectedAt = 0
|
|
66
66
|
#disconnectedAt = 0
|
|
67
|
+
#drainedListeners = new Set()
|
|
67
68
|
#protomux
|
|
68
69
|
#log
|
|
69
70
|
|
|
@@ -157,53 +158,94 @@ class Peer {
|
|
|
157
158
|
this.#disconnectedAt = Date.now()
|
|
158
159
|
// This promise should have already resolved, but if the peer never connected then we reject here
|
|
159
160
|
this.#connected.reject(new PeerFailedConnectionError())
|
|
161
|
+
for (const listener of this.#drainedListeners) {
|
|
162
|
+
listener.reject(new Error('RPC Disconnected before sending'))
|
|
163
|
+
}
|
|
164
|
+
this.#drainedListeners.clear()
|
|
160
165
|
this.#log('disconnected')
|
|
161
166
|
}
|
|
167
|
+
|
|
168
|
+
// Call this when the stream has drained all data to the network
|
|
169
|
+
drained() {
|
|
170
|
+
for (const listener of this.#drainedListeners) {
|
|
171
|
+
listener.resolve()
|
|
172
|
+
}
|
|
173
|
+
this.#drainedListeners.clear()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {boolean} didWrite
|
|
178
|
+
* @returns {Promise<void>}
|
|
179
|
+
*/
|
|
180
|
+
async #waitForDrain(didWrite) {
|
|
181
|
+
if (didWrite) return
|
|
182
|
+
const onDrain = pDefer()
|
|
183
|
+
|
|
184
|
+
this.#drainedListeners.add(onDrain)
|
|
185
|
+
|
|
186
|
+
await onDrain.promise
|
|
187
|
+
}
|
|
188
|
+
|
|
162
189
|
/**
|
|
163
190
|
* @param {Buffer} buf
|
|
191
|
+
* @returns {Promise<void>}
|
|
164
192
|
*/
|
|
165
|
-
[kTestOnlySendRawInvite](buf) {
|
|
193
|
+
async [kTestOnlySendRawInvite](buf) {
|
|
166
194
|
this.#assertConnected()
|
|
167
195
|
const messageType = MESSAGE_TYPES.Invite
|
|
168
|
-
this.#channel.messages[messageType].send(buf)
|
|
196
|
+
await this.#waitForDrain(this.#channel.messages[messageType].send(buf))
|
|
169
197
|
}
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
-
|
|
198
|
+
/**
|
|
199
|
+
* @param {Invite} invite
|
|
200
|
+
* @returns {Promise<void>}
|
|
201
|
+
*/
|
|
202
|
+
async sendInvite(invite) {
|
|
203
|
+
this.#assertConnected('Peer disconnected before sending invite')
|
|
173
204
|
const buf = Buffer.from(Invite.encode(invite).finish())
|
|
174
205
|
const messageType = MESSAGE_TYPES.Invite
|
|
175
|
-
this.#channel.messages[messageType].send(buf)
|
|
206
|
+
await this.#waitForDrain(this.#channel.messages[messageType].send(buf))
|
|
176
207
|
this.#log('sent invite %h', invite.inviteId)
|
|
177
208
|
}
|
|
178
|
-
/**
|
|
179
|
-
|
|
180
|
-
|
|
209
|
+
/**
|
|
210
|
+
* @param {InviteCancel} inviteCancel
|
|
211
|
+
* @returns {Promise<void>}
|
|
212
|
+
*/
|
|
213
|
+
async sendInviteCancel(inviteCancel) {
|
|
214
|
+
this.#assertConnected('Peer disconnected before sending invite cancel')
|
|
181
215
|
const buf = Buffer.from(InviteCancel.encode(inviteCancel).finish())
|
|
182
216
|
const messageType = MESSAGE_TYPES.InviteCancel
|
|
183
|
-
this.#channel.messages[messageType].send(buf)
|
|
217
|
+
await this.#waitForDrain(this.#channel.messages[messageType].send(buf))
|
|
184
218
|
this.#log('sent invite cancel %h', inviteCancel.inviteId)
|
|
185
219
|
}
|
|
186
|
-
/**
|
|
187
|
-
|
|
188
|
-
|
|
220
|
+
/**
|
|
221
|
+
* @param {InviteResponse} response
|
|
222
|
+
* @returns {Promise<void>}
|
|
223
|
+
*/
|
|
224
|
+
async sendInviteResponse(response) {
|
|
225
|
+
this.#assertConnected('Peer disconnected before sending invite response')
|
|
189
226
|
const buf = Buffer.from(InviteResponse.encode(response).finish())
|
|
190
227
|
const messageType = MESSAGE_TYPES.InviteResponse
|
|
191
|
-
this.#channel.messages[messageType].send(buf)
|
|
228
|
+
await this.#waitForDrain(this.#channel.messages[messageType].send(buf))
|
|
192
229
|
this.#log('sent response for %h: %s', response.inviteId, response.decision)
|
|
193
230
|
}
|
|
194
231
|
/** @param {ProjectJoinDetails} details */
|
|
195
|
-
sendProjectJoinDetails(details) {
|
|
196
|
-
this.#assertConnected(
|
|
232
|
+
async sendProjectJoinDetails(details) {
|
|
233
|
+
this.#assertConnected(
|
|
234
|
+
'Peer disconnected before sending project join details'
|
|
235
|
+
)
|
|
197
236
|
const buf = Buffer.from(ProjectJoinDetails.encode(details).finish())
|
|
198
237
|
const messageType = MESSAGE_TYPES.ProjectJoinDetails
|
|
199
|
-
this.#channel.messages[messageType].send(buf)
|
|
238
|
+
await this.#waitForDrain(this.#channel.messages[messageType].send(buf))
|
|
200
239
|
this.#log('sent project join details for %h', details.projectKey)
|
|
201
240
|
}
|
|
202
|
-
/**
|
|
203
|
-
|
|
241
|
+
/**
|
|
242
|
+
* @param {DeviceInfo} deviceInfo
|
|
243
|
+
* @returns {Promise<void>}
|
|
244
|
+
*/
|
|
245
|
+
async sendDeviceInfo(deviceInfo) {
|
|
204
246
|
const buf = Buffer.from(DeviceInfo.encode(deviceInfo).finish())
|
|
205
247
|
const messageType = MESSAGE_TYPES.DeviceInfo
|
|
206
|
-
this.#channel.messages[messageType].send(buf)
|
|
248
|
+
await this.#waitForDrain(this.#channel.messages[messageType].send(buf))
|
|
207
249
|
this.#log('sent deviceInfo %o', deviceInfo)
|
|
208
250
|
}
|
|
209
251
|
/** @param {DeviceInfo} deviceInfo */
|
|
@@ -212,10 +254,11 @@ class Peer {
|
|
|
212
254
|
this.#deviceType = deviceInfo.deviceType
|
|
213
255
|
this.#log('received deviceInfo %o', deviceInfo)
|
|
214
256
|
}
|
|
215
|
-
|
|
257
|
+
/** @param {string} [message] */
|
|
258
|
+
#assertConnected(message) {
|
|
216
259
|
if (this.#state === 'connected' && !this.#channel.closed) return
|
|
217
260
|
/* c8 ignore next */
|
|
218
|
-
throw new PeerDisconnectedError() // TODO: report error - this should not happen
|
|
261
|
+
throw new PeerDisconnectedError(message) // TODO: report error - this should not happen
|
|
219
262
|
}
|
|
220
263
|
}
|
|
221
264
|
|
|
@@ -270,7 +313,7 @@ export class LocalPeers extends TypedEmitter {
|
|
|
270
313
|
async sendInvite(deviceId, invite) {
|
|
271
314
|
await this.#waitForPendingConnections()
|
|
272
315
|
const peer = await this.#getPeerByDeviceId(deviceId)
|
|
273
|
-
peer.sendInvite(invite)
|
|
316
|
+
await peer.sendInvite(invite)
|
|
274
317
|
}
|
|
275
318
|
|
|
276
319
|
/**
|
|
@@ -281,7 +324,7 @@ export class LocalPeers extends TypedEmitter {
|
|
|
281
324
|
async sendInviteCancel(deviceId, inviteCancel) {
|
|
282
325
|
await this.#waitForPendingConnections()
|
|
283
326
|
const peer = await this.#getPeerByDeviceId(deviceId)
|
|
284
|
-
peer.sendInviteCancel(inviteCancel)
|
|
327
|
+
await peer.sendInviteCancel(inviteCancel)
|
|
285
328
|
}
|
|
286
329
|
|
|
287
330
|
/**
|
|
@@ -293,7 +336,7 @@ export class LocalPeers extends TypedEmitter {
|
|
|
293
336
|
async sendInviteResponse(deviceId, inviteResponse) {
|
|
294
337
|
await this.#waitForPendingConnections()
|
|
295
338
|
const peer = await this.#getPeerByDeviceId(deviceId)
|
|
296
|
-
peer.sendInviteResponse(inviteResponse)
|
|
339
|
+
await peer.sendInviteResponse(inviteResponse)
|
|
297
340
|
}
|
|
298
341
|
|
|
299
342
|
/**
|
|
@@ -303,7 +346,7 @@ export class LocalPeers extends TypedEmitter {
|
|
|
303
346
|
async sendProjectJoinDetails(deviceId, details) {
|
|
304
347
|
await this.#waitForPendingConnections()
|
|
305
348
|
const peer = await this.#getPeerByDeviceId(deviceId)
|
|
306
|
-
peer.sendProjectJoinDetails(details)
|
|
349
|
+
await peer.sendProjectJoinDetails(details)
|
|
307
350
|
}
|
|
308
351
|
|
|
309
352
|
/**
|
|
@@ -314,7 +357,7 @@ export class LocalPeers extends TypedEmitter {
|
|
|
314
357
|
async sendDeviceInfo(deviceId, deviceInfo) {
|
|
315
358
|
await this.#waitForPendingConnections()
|
|
316
359
|
const peer = await this.#getPeerByDeviceId(deviceId)
|
|
317
|
-
peer.sendDeviceInfo(deviceInfo)
|
|
360
|
+
await peer.sendDeviceInfo(deviceInfo)
|
|
318
361
|
}
|
|
319
362
|
|
|
320
363
|
/**
|
|
@@ -446,6 +489,9 @@ export class LocalPeers extends TypedEmitter {
|
|
|
446
489
|
this.#emitPeers()
|
|
447
490
|
done()
|
|
448
491
|
},
|
|
492
|
+
ondrain: () => {
|
|
493
|
+
peer.drained()
|
|
494
|
+
},
|
|
449
495
|
})
|
|
450
496
|
channel.open()
|
|
451
497
|
|
|
@@ -614,7 +660,7 @@ export { TimeoutError }
|
|
|
614
660
|
|
|
615
661
|
export class UnknownPeerError extends Error {
|
|
616
662
|
/** @param {string} [message] */
|
|
617
|
-
constructor(message) {
|
|
663
|
+
constructor(message = 'UnknownPeerError') {
|
|
618
664
|
super(message)
|
|
619
665
|
this.name = 'UnknownPeerError'
|
|
620
666
|
}
|
|
@@ -622,7 +668,7 @@ export class UnknownPeerError extends Error {
|
|
|
622
668
|
|
|
623
669
|
export class PeerDisconnectedError extends Error {
|
|
624
670
|
/** @param {string} [message] */
|
|
625
|
-
constructor(message) {
|
|
671
|
+
constructor(message = 'Peer disconnected') {
|
|
626
672
|
super(message)
|
|
627
673
|
this.name = 'PeerDisconnectedError'
|
|
628
674
|
}
|
|
@@ -630,7 +676,7 @@ export class PeerDisconnectedError extends Error {
|
|
|
630
676
|
|
|
631
677
|
export class PeerFailedConnectionError extends Error {
|
|
632
678
|
/** @param {string} [message] */
|
|
633
|
-
constructor(message) {
|
|
679
|
+
constructor(message = 'PeerFailedConnectionError') {
|
|
634
680
|
super(message)
|
|
635
681
|
this.name = 'PeerFailedConnectionError'
|
|
636
682
|
}
|
package/src/mapeo-manager.js
CHANGED
|
@@ -42,7 +42,7 @@ import IconServerPlugin from './fastify-plugins/icons.js'
|
|
|
42
42
|
import { plugin as MapServerPlugin } from './fastify-plugins/maps.js'
|
|
43
43
|
import { getFastifyServerAddress } from './fastify-plugins/utils.js'
|
|
44
44
|
import { LocalPeers } from './local-peers.js'
|
|
45
|
-
import { InviteApi } from './invite-api.js'
|
|
45
|
+
import { InviteApi } from './invite/invite-api.js'
|
|
46
46
|
import { LocalDiscovery } from './discovery/local-discovery.js'
|
|
47
47
|
import { Roles } from './roles.js'
|
|
48
48
|
import { Logger } from './logger.js'
|
package/src/member-api.js
CHANGED
|
@@ -17,6 +17,7 @@ import { abortSignalAny } from './lib/ponyfills.js'
|
|
|
17
17
|
import timingSafeEqual from 'string-timing-safe-equal'
|
|
18
18
|
import { isHostnameIpAddress } from './lib/is-hostname-ip-address.js'
|
|
19
19
|
import { ErrorWithCode, getErrorMessage } from './lib/error.js'
|
|
20
|
+
import { InviteAbortedError } from './errors.js'
|
|
20
21
|
import { wsCoreReplicator } from './lib/ws-core-replicator.js'
|
|
21
22
|
import { MEMBER_ROLE_ID, ROLES, isRoleIdForNewInvite } from './roles.js'
|
|
22
23
|
/**
|
|
@@ -212,9 +213,9 @@ export class MemberApi extends TypedEmitter {
|
|
|
212
213
|
* @param {AbortSignal} signal
|
|
213
214
|
*/
|
|
214
215
|
async #sendInviteAndGetResponse(deviceId, invite, signal) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
if (signal.aborted) {
|
|
217
|
+
throw new InviteAbortedError()
|
|
218
|
+
}
|
|
218
219
|
|
|
219
220
|
const abortController = new AbortController()
|
|
220
221
|
|
|
@@ -246,7 +247,7 @@ export class MemberApi extends TypedEmitter {
|
|
|
246
247
|
return await responsePromise
|
|
247
248
|
} catch (err) {
|
|
248
249
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
249
|
-
throw
|
|
250
|
+
throw new InviteAbortedError()
|
|
250
251
|
} else {
|
|
251
252
|
throw err
|
|
252
253
|
}
|
package/src/types.ts
CHANGED
package/src/utils.js
CHANGED
|
@@ -34,11 +34,16 @@ export function noop() {}
|
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* @param {unknown} condition
|
|
37
|
-
* @param {string}
|
|
37
|
+
* @param {string | Error} messageOrError
|
|
38
38
|
* @returns {asserts condition}
|
|
39
39
|
*/
|
|
40
|
-
export function assert(condition,
|
|
41
|
-
if (
|
|
40
|
+
export function assert(condition, messageOrError) {
|
|
41
|
+
if (condition) return
|
|
42
|
+
if (typeof messageOrError === 'string') {
|
|
43
|
+
throw new Error(messageOrError)
|
|
44
|
+
} else {
|
|
45
|
+
throw messageOrError
|
|
46
|
+
}
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
/**
|
package/dist/invite-api.d.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {Object} InviteApiEvents
|
|
3
|
-
* @property {(invite: Invite) => void} invite-received
|
|
4
|
-
* @property {(invite: Invite, removalReason: InviteRemovalReason) => void} invite-removed
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* @extends {TypedEmitter<InviteApiEvents>}
|
|
8
|
-
*/
|
|
9
|
-
export class InviteApi extends TypedEmitter<InviteApiEvents> {
|
|
10
|
-
/**
|
|
11
|
-
* @param {Object} options
|
|
12
|
-
* @param {import('./local-peers.js').LocalPeers} options.rpc
|
|
13
|
-
* @param {object} options.queries
|
|
14
|
-
* @param {(projectInviteId: Readonly<Buffer>) => undefined | { projectPublicId: string }} options.queries.getProjectByInviteId
|
|
15
|
-
* @param {(projectDetails: Pick<ProjectJoinDetails, 'projectKey' | 'encryptionKeys'> & { projectName: string }) => Promise<string>} options.queries.addProject
|
|
16
|
-
* @param {Logger} [options.logger]
|
|
17
|
-
*/
|
|
18
|
-
constructor({ rpc, queries, logger }: {
|
|
19
|
-
rpc: import("./local-peers.js").LocalPeers;
|
|
20
|
-
queries: {
|
|
21
|
-
getProjectByInviteId: (projectInviteId: Readonly<Buffer>) => undefined | {
|
|
22
|
-
projectPublicId: string;
|
|
23
|
-
};
|
|
24
|
-
addProject: (projectDetails: Pick<ProjectJoinDetails, "projectKey" | "encryptionKeys"> & {
|
|
25
|
-
projectName: string;
|
|
26
|
-
}) => Promise<string>;
|
|
27
|
-
};
|
|
28
|
-
logger?: Logger | undefined;
|
|
29
|
-
});
|
|
30
|
-
rpc: import("./local-peers.js").LocalPeers;
|
|
31
|
-
/**
|
|
32
|
-
* @returns {Array<Invite>}
|
|
33
|
-
*/
|
|
34
|
-
getPending(): Array<Invite>;
|
|
35
|
-
/**
|
|
36
|
-
* Attempt to accept the invite.
|
|
37
|
-
*
|
|
38
|
-
* This can fail if the invitor has canceled the invite or if you cannot
|
|
39
|
-
* connect to the invitor's device.
|
|
40
|
-
*
|
|
41
|
-
* If the invite is accepted and you had other invites to the same project,
|
|
42
|
-
* those invites are removed, and the invitors are told that you're already
|
|
43
|
-
* part of this project.
|
|
44
|
-
*
|
|
45
|
-
* @param {Pick<Invite, 'inviteId'>} invite
|
|
46
|
-
* @returns {Promise<string>}
|
|
47
|
-
*/
|
|
48
|
-
accept({ inviteId: inviteIdString }: Pick<Invite, "inviteId">): Promise<string>;
|
|
49
|
-
/**
|
|
50
|
-
* @param {Pick<Invite, 'inviteId'>} invite
|
|
51
|
-
* @returns {void}
|
|
52
|
-
*/
|
|
53
|
-
reject({ inviteId: inviteIdString }: Pick<Invite, "inviteId">): void;
|
|
54
|
-
#private;
|
|
55
|
-
}
|
|
56
|
-
export type InviteInternal = InviteRpcMessage & {
|
|
57
|
-
receivedAt: number;
|
|
58
|
-
};
|
|
59
|
-
export type Invite = MapBuffers<InviteInternal>;
|
|
60
|
-
export type InviteRemovalReason = ("accepted" | "rejected" | "canceled" | "accepted other" | "connection error" | "internal error");
|
|
61
|
-
export type InviteApiEvents = {
|
|
62
|
-
"invite-received": (invite: Invite) => void;
|
|
63
|
-
"invite-removed": (invite: Invite, removalReason: InviteRemovalReason) => void;
|
|
64
|
-
};
|
|
65
|
-
import { TypedEmitter } from 'tiny-typed-emitter';
|
|
66
|
-
import type { ProjectJoinDetails } from './generated/rpc.js';
|
|
67
|
-
import { Logger } from './logger.js';
|
|
68
|
-
import type { Invite as InviteRpcMessage } from './generated/rpc.js';
|
|
69
|
-
import type { MapBuffers } from './types.js';
|
|
70
|
-
//# sourceMappingURL=invite-api.d.ts.map
|
package/dist/invite-api.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invite-api.d.ts","sourceRoot":"","sources":["../src/invite-api.js"],"names":[],"mappings":"AAiJA;;;;GAIG;AAEH;;GAEG;AACH;IAME;;;;;;;OAOG;IACH,sCANG;QAAuD,GAAG,EAAlD,OAAO,kBAAkB,EAAE,UAAU;QACrB,OAAO,EAC/B;YAAwG,oBAAoB,EAApH,CAAC,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,SAAS,GAAG;gBAAE,eAAe,EAAE,MAAM,CAAA;aAAE;YAC4D,UAAU,EAApJ,CAAC,cAAc,EAAE,IAAI,CAAC,kBAAkB,EAAE,YAAY,GAAG,gBAAgB,CAAC,GAAG;gBAAE,WAAW,EAAE,MAAM,CAAA;aAAE,KAAK,OAAO,CAAC,MAAM,CAAC;SAChI;QAAyB,MAAM;KAAC,EA0BlC;IAnBC,2CAAc;IAsFhB;;OAEG;IACH,cAFa,KAAK,CAAC,MAAM,CAAC,CAMzB;IAED;;;;;;;;;;;;OAYG;IACH,qCAHW,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,GACtB,OAAO,CAAC,MAAM,CAAC,CAkI3B;IAED;;;OAGG;IACH,qCAHW,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,GACtB,IAAI,CAuBhB;;CACF;6BA7ZY,gBAAgB,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE;qBAGxC,WAAW,cAAc,CAAC;kCAG3B,CACZ,UAAa,GACb,UAAa,GACb,UAAa,GACb,gBAAmB,GACnB,kBAAqB,GACrB,gBAAmB,CAChB;;uBA8GU,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI;sBACxB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,mBAAmB,KAAK,IAAI;;6BApJ7C,oBAAoB;wCAavC,oBAAoB;uBAPP,aAAa;gDAO1B,oBAAoB;gCANE,YAAY"}
|