@comapeo/core 1.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/LICENSE.md +9 -0
- package/README.md +31 -0
- package/dist/blob-api.d.ts +92 -0
- package/dist/blob-api.d.ts.map +1 -0
- package/dist/blob-store/index.d.ts +163 -0
- package/dist/blob-store/index.d.ts.map +1 -0
- package/dist/blob-store/live-download.d.ts +107 -0
- package/dist/blob-store/live-download.d.ts.map +1 -0
- package/dist/config-import.d.ts +74 -0
- package/dist/config-import.d.ts.map +1 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/core-manager/bitfield-rle.d.ts +25 -0
- package/dist/core-manager/bitfield-rle.d.ts.map +1 -0
- package/dist/core-manager/core-index.d.ts +56 -0
- package/dist/core-manager/core-index.d.ts.map +1 -0
- package/dist/core-manager/index.d.ts +125 -0
- package/dist/core-manager/index.d.ts.map +1 -0
- package/dist/core-manager/random-access-file-pool.d.ts +17 -0
- package/dist/core-manager/random-access-file-pool.d.ts.map +1 -0
- package/dist/core-manager/remote-bitfield.d.ts +146 -0
- package/dist/core-manager/remote-bitfield.d.ts.map +1 -0
- package/dist/core-ownership.d.ts +112 -0
- package/dist/core-ownership.d.ts.map +1 -0
- package/dist/datastore/index.d.ts +91 -0
- package/dist/datastore/index.d.ts.map +1 -0
- package/dist/datatype/index.d.ts +108 -0
- package/dist/discovery/local-discovery.d.ts +64 -0
- package/dist/discovery/local-discovery.d.ts.map +1 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/fastify-controller.d.ts +27 -0
- package/dist/fastify-controller.d.ts.map +1 -0
- package/dist/fastify-plugins/blobs.d.ts +6 -0
- package/dist/fastify-plugins/blobs.d.ts.map +1 -0
- package/dist/fastify-plugins/constants.d.ts +3 -0
- package/dist/fastify-plugins/constants.d.ts.map +1 -0
- package/dist/fastify-plugins/icons.d.ts +6 -0
- package/dist/fastify-plugins/icons.d.ts.map +1 -0
- package/dist/fastify-plugins/maps/index.d.ts +11 -0
- package/dist/fastify-plugins/maps/index.d.ts.map +1 -0
- package/dist/fastify-plugins/maps/offline-fallback-map.d.ts +12 -0
- package/dist/fastify-plugins/maps/offline-fallback-map.d.ts.map +1 -0
- package/dist/fastify-plugins/maps/static-maps.d.ts +11 -0
- package/dist/fastify-plugins/maps/static-maps.d.ts.map +1 -0
- package/dist/fastify-plugins/utils.d.ts +23 -0
- package/dist/fastify-plugins/utils.d.ts.map +1 -0
- package/dist/generated/extensions.d.ts +44 -0
- package/dist/generated/extensions.d.ts.map +1 -0
- package/dist/generated/keys.d.ts +36 -0
- package/dist/generated/keys.d.ts.map +1 -0
- package/dist/generated/rpc.d.ts +87 -0
- package/dist/generated/rpc.d.ts.map +1 -0
- package/dist/icon-api.d.ts +109 -0
- package/dist/icon-api.d.ts.map +1 -0
- package/dist/index-writer/index.d.ts +51 -0
- package/dist/index-writer/index.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/invite-api.d.ts +70 -0
- package/dist/invite-api.d.ts.map +1 -0
- package/dist/lib/hashmap.d.ts +62 -0
- package/dist/lib/hashmap.d.ts.map +1 -0
- package/dist/lib/hypercore-helpers.d.ts +6 -0
- package/dist/lib/hypercore-helpers.d.ts.map +1 -0
- package/dist/lib/noise-secret-stream-helpers.d.ts +45 -0
- package/dist/lib/noise-secret-stream-helpers.d.ts.map +1 -0
- package/dist/lib/ponyfills.d.ts +10 -0
- package/dist/lib/ponyfills.d.ts.map +1 -0
- package/dist/lib/string.d.ts +2 -0
- package/dist/lib/string.d.ts.map +1 -0
- package/dist/lib/timing-safe-equal.d.ts +15 -0
- package/dist/lib/timing-safe-equal.d.ts.map +1 -0
- package/dist/local-peers.d.ts +151 -0
- package/dist/local-peers.d.ts.map +1 -0
- package/dist/logger.d.ts +32 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/mapeo-manager.d.ts +178 -0
- package/dist/mapeo-manager.d.ts.map +1 -0
- package/dist/mapeo-project.d.ts +3233 -0
- package/dist/mapeo-project.d.ts.map +1 -0
- package/dist/member-api.d.ts +114 -0
- package/dist/member-api.d.ts.map +1 -0
- package/dist/roles.d.ts +157 -0
- package/dist/roles.d.ts.map +1 -0
- package/dist/schema/client.d.ts +284 -0
- package/dist/schema/client.d.ts.map +1 -0
- package/dist/schema/project.d.ts +1812 -0
- package/dist/schema/project.d.ts.map +1 -0
- package/dist/schema/schema-to-drizzle.d.ts +20 -0
- package/dist/schema/schema-to-drizzle.d.ts.map +1 -0
- package/dist/schema/types.d.ts +98 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/utils.d.ts +55 -0
- package/dist/schema/utils.d.ts.map +1 -0
- package/dist/sync/core-sync-state.d.ts +252 -0
- package/dist/sync/core-sync-state.d.ts.map +1 -0
- package/dist/sync/namespace-sync-state.d.ts +47 -0
- package/dist/sync/namespace-sync-state.d.ts.map +1 -0
- package/dist/sync/peer-sync-controller.d.ts +44 -0
- package/dist/sync/peer-sync-controller.d.ts.map +1 -0
- package/dist/sync/sync-api.d.ts +158 -0
- package/dist/sync/sync-api.d.ts.map +1 -0
- package/dist/sync/sync-state.d.ts +40 -0
- package/dist/sync/sync-state.d.ts.map +1 -0
- package/dist/translation-api.d.ts +288 -0
- package/dist/translation-api.d.ts.map +1 -0
- package/dist/types.d.ts +115 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +115 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils_types.d.ts +14 -0
- package/drizzle/client/0000_bumpy_carnage.sql +33 -0
- package/drizzle/client/meta/0000_snapshot.json +199 -0
- package/drizzle/client/meta/_journal.json +13 -0
- package/drizzle/project/0000_spooky_lady_ursula.sql +192 -0
- package/drizzle/project/meta/0000_snapshot.json +1137 -0
- package/drizzle/project/meta/_journal.json +13 -0
- package/package.json +202 -0
- package/src/blob-api.js +139 -0
- package/src/blob-store/index.js +325 -0
- package/src/blob-store/live-download.js +373 -0
- package/src/config-import.js +604 -0
- package/src/constants.js +34 -0
- package/src/core-manager/bitfield-rle.js +235 -0
- package/src/core-manager/core-index.js +87 -0
- package/src/core-manager/index.js +504 -0
- package/src/core-manager/random-access-file-pool.js +30 -0
- package/src/core-manager/remote-bitfield.js +416 -0
- package/src/core-ownership.js +235 -0
- package/src/datastore/README.md +46 -0
- package/src/datastore/index.js +234 -0
- package/src/datatype/README.md +33 -0
- package/src/datatype/index.d.ts +108 -0
- package/src/datatype/index.js +358 -0
- package/src/discovery/local-discovery.js +303 -0
- package/src/errors.js +5 -0
- package/src/fastify-controller.js +84 -0
- package/src/fastify-plugins/blobs.js +139 -0
- package/src/fastify-plugins/constants.js +5 -0
- package/src/fastify-plugins/icons.js +158 -0
- package/src/fastify-plugins/maps/index.js +173 -0
- package/src/fastify-plugins/maps/offline-fallback-map.js +114 -0
- package/src/fastify-plugins/maps/static-maps.js +271 -0
- package/src/fastify-plugins/utils.js +52 -0
- package/src/generated/README.md +3 -0
- package/src/generated/extensions.d.ts +44 -0
- package/src/generated/extensions.js +196 -0
- package/src/generated/extensions.ts +237 -0
- package/src/generated/keys.d.ts +36 -0
- package/src/generated/keys.js +148 -0
- package/src/generated/keys.ts +185 -0
- package/src/generated/rpc.d.ts +87 -0
- package/src/generated/rpc.js +389 -0
- package/src/generated/rpc.ts +463 -0
- package/src/icon-api.js +282 -0
- package/src/index-writer/README.md +38 -0
- package/src/index-writer/index.js +124 -0
- package/src/index.js +16 -0
- package/src/invite-api.js +450 -0
- package/src/lib/hashmap.js +91 -0
- package/src/lib/hypercore-helpers.js +18 -0
- package/src/lib/noise-secret-stream-helpers.js +37 -0
- package/src/lib/ponyfills.js +25 -0
- package/src/lib/string.js +7 -0
- package/src/lib/timing-safe-equal.js +34 -0
- package/src/local-peers.js +737 -0
- package/src/logger.js +99 -0
- package/src/mapeo-manager.js +914 -0
- package/src/mapeo-project.js +980 -0
- package/src/member-api.js +319 -0
- package/src/roles.js +412 -0
- package/src/schema/client.js +55 -0
- package/src/schema/project.js +44 -0
- package/src/schema/schema-to-drizzle.js +118 -0
- package/src/schema/types.ts +153 -0
- package/src/schema/utils.js +51 -0
- package/src/sync/core-sync-state.js +440 -0
- package/src/sync/namespace-sync-state.js +193 -0
- package/src/sync/peer-sync-controller.js +332 -0
- package/src/sync/sync-api.js +588 -0
- package/src/sync/sync-state.js +63 -0
- package/src/translation-api.js +141 -0
- package/src/types.ts +149 -0
- package/src/utils.js +210 -0
- package/src/utils_types.d.ts +14 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto'
|
|
2
|
+
import { TypedEmitter } from 'tiny-typed-emitter'
|
|
3
|
+
import { pEvent } from 'p-event'
|
|
4
|
+
import { InviteResponse_Decision } from './generated/rpc.js'
|
|
5
|
+
import {
|
|
6
|
+
assert,
|
|
7
|
+
noop,
|
|
8
|
+
ExhaustivenessError,
|
|
9
|
+
projectKeyToId,
|
|
10
|
+
projectKeyToProjectInviteId,
|
|
11
|
+
} from './utils.js'
|
|
12
|
+
import { abortSignalAny } from './lib/ponyfills.js'
|
|
13
|
+
import timingSafeEqual from './lib/timing-safe-equal.js'
|
|
14
|
+
import { ROLES, isRoleIdForNewInvite } from './roles.js'
|
|
15
|
+
/**
|
|
16
|
+
* @import {
|
|
17
|
+
* DeviceInfo,
|
|
18
|
+
* DeviceInfoValue,
|
|
19
|
+
* ProjectSettings,
|
|
20
|
+
* ProjectSettingsValue
|
|
21
|
+
* } from '@comapeo/schema'
|
|
22
|
+
*/
|
|
23
|
+
/** @import { Invite, InviteResponse } from './generated/rpc.js' */
|
|
24
|
+
/** @import { DataType } from './datatype/index.js' */
|
|
25
|
+
/** @import { DataStore } from './datastore/index.js' */
|
|
26
|
+
/** @import { deviceInfoTable } from './schema/project.js' */
|
|
27
|
+
/** @import { projectSettingsTable } from './schema/client.js' */
|
|
28
|
+
|
|
29
|
+
/** @typedef {DataType<DataStore<'config'>, typeof deviceInfoTable, "deviceInfo", DeviceInfo, DeviceInfoValue>} DeviceInfoDataType */
|
|
30
|
+
/** @typedef {DataType<DataStore<'config'>, typeof projectSettingsTable, "projectSettings", ProjectSettings, ProjectSettingsValue>} ProjectDataType */
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {object} MemberInfo
|
|
33
|
+
* @prop {string} deviceId
|
|
34
|
+
* @prop {import('./roles.js').Role} role
|
|
35
|
+
* @prop {DeviceInfo['name']} [name]
|
|
36
|
+
* @prop {DeviceInfo['deviceType']} [deviceType]
|
|
37
|
+
* @prop {DeviceInfo['createdAt']} [joinedAt]
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
export class MemberApi extends TypedEmitter {
|
|
41
|
+
#ownDeviceId
|
|
42
|
+
#roles
|
|
43
|
+
#coreOwnership
|
|
44
|
+
#encryptionKeys
|
|
45
|
+
#projectKey
|
|
46
|
+
#rpc
|
|
47
|
+
#dataTypes
|
|
48
|
+
|
|
49
|
+
/** @type {Map<string, { abortController: AbortController }>} */
|
|
50
|
+
#outboundInvitesByDevice = new Map()
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Object} opts
|
|
54
|
+
* @param {string} opts.deviceId public key of this device as hex string
|
|
55
|
+
* @param {import('./roles.js').Roles} opts.roles
|
|
56
|
+
* @param {import('./core-ownership.js').CoreOwnership} opts.coreOwnership
|
|
57
|
+
* @param {import('./generated/keys.js').EncryptionKeys} opts.encryptionKeys
|
|
58
|
+
* @param {Buffer} opts.projectKey
|
|
59
|
+
* @param {import('./local-peers.js').LocalPeers} opts.rpc
|
|
60
|
+
* @param {Object} opts.dataTypes
|
|
61
|
+
* @param {Pick<DeviceInfoDataType, 'getByDocId' | 'getMany'>} opts.dataTypes.deviceInfo
|
|
62
|
+
* @param {Pick<ProjectDataType, 'getByDocId'>} opts.dataTypes.project
|
|
63
|
+
*/
|
|
64
|
+
constructor({
|
|
65
|
+
deviceId,
|
|
66
|
+
roles,
|
|
67
|
+
coreOwnership,
|
|
68
|
+
encryptionKeys,
|
|
69
|
+
projectKey,
|
|
70
|
+
rpc,
|
|
71
|
+
dataTypes,
|
|
72
|
+
}) {
|
|
73
|
+
super()
|
|
74
|
+
this.#ownDeviceId = deviceId
|
|
75
|
+
this.#roles = roles
|
|
76
|
+
this.#coreOwnership = coreOwnership
|
|
77
|
+
this.#encryptionKeys = encryptionKeys
|
|
78
|
+
this.#projectKey = projectKey
|
|
79
|
+
this.#rpc = rpc
|
|
80
|
+
this.#dataTypes = dataTypes
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Send an invite. Resolves when receiving a response. Rejects if the invite
|
|
85
|
+
* is canceled, or if something else goes wrong.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} deviceId
|
|
88
|
+
* @param {Object} opts
|
|
89
|
+
* @param {import('./roles.js').RoleIdForNewInvite} opts.roleId
|
|
90
|
+
* @param {string} [opts.roleName]
|
|
91
|
+
* @param {string} [opts.roleDescription]
|
|
92
|
+
* @returns {Promise<(
|
|
93
|
+
* typeof InviteResponse_Decision.ACCEPT |
|
|
94
|
+
* typeof InviteResponse_Decision.REJECT |
|
|
95
|
+
* typeof InviteResponse_Decision.ALREADY
|
|
96
|
+
* )>}
|
|
97
|
+
*/
|
|
98
|
+
async invite(
|
|
99
|
+
deviceId,
|
|
100
|
+
{ roleId, roleName = ROLES[roleId]?.name, roleDescription }
|
|
101
|
+
) {
|
|
102
|
+
assert(isRoleIdForNewInvite(roleId), 'Invalid role ID for new invite')
|
|
103
|
+
assert(
|
|
104
|
+
!this.#outboundInvitesByDevice.has(deviceId),
|
|
105
|
+
'Already inviting this device ID'
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const abortController = new AbortController()
|
|
109
|
+
const abortSignal = abortController.signal
|
|
110
|
+
this.#outboundInvitesByDevice.set(deviceId, { abortController })
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const { name: invitorName } = await this.getById(this.#ownDeviceId)
|
|
114
|
+
// since we are always getting #ownDeviceId,
|
|
115
|
+
// this should never throw (see comment on getById), but it pleases ts
|
|
116
|
+
assert(
|
|
117
|
+
invitorName,
|
|
118
|
+
'Internal error trying to read own device name for this invite'
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
abortSignal.throwIfAborted()
|
|
122
|
+
|
|
123
|
+
const inviteId = crypto.randomBytes(32)
|
|
124
|
+
const projectId = projectKeyToId(this.#projectKey)
|
|
125
|
+
const projectInviteId = projectKeyToProjectInviteId(this.#projectKey)
|
|
126
|
+
const project = await this.#dataTypes.project.getByDocId(projectId)
|
|
127
|
+
const projectName = project.name
|
|
128
|
+
assert(projectName, 'Project must have a name to invite people')
|
|
129
|
+
|
|
130
|
+
abortSignal.throwIfAborted()
|
|
131
|
+
|
|
132
|
+
const invite = {
|
|
133
|
+
inviteId,
|
|
134
|
+
projectInviteId,
|
|
135
|
+
projectName,
|
|
136
|
+
roleName,
|
|
137
|
+
roleDescription,
|
|
138
|
+
invitorName,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const inviteResponse = await this.#sendInviteAndGetResponse(
|
|
142
|
+
deviceId,
|
|
143
|
+
invite,
|
|
144
|
+
abortSignal
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
// Though the invite is still arguably outgoing here, it can no longer
|
|
148
|
+
// be canceled.
|
|
149
|
+
this.#outboundInvitesByDevice.delete(deviceId)
|
|
150
|
+
|
|
151
|
+
switch (inviteResponse.decision) {
|
|
152
|
+
case InviteResponse_Decision.ALREADY:
|
|
153
|
+
case InviteResponse_Decision.REJECT:
|
|
154
|
+
return inviteResponse.decision
|
|
155
|
+
case InviteResponse_Decision.UNRECOGNIZED:
|
|
156
|
+
case InviteResponse_Decision.DECISION_UNSPECIFIED:
|
|
157
|
+
return InviteResponse_Decision.REJECT
|
|
158
|
+
case InviteResponse_Decision.ACCEPT:
|
|
159
|
+
// We should assign the role locally *before* sharing the project details
|
|
160
|
+
// so that they're part of the project even if they don't receive the
|
|
161
|
+
// project details message.
|
|
162
|
+
|
|
163
|
+
await this.#roles.assignRole(deviceId, roleId)
|
|
164
|
+
|
|
165
|
+
await this.#rpc.sendProjectJoinDetails(deviceId, {
|
|
166
|
+
inviteId,
|
|
167
|
+
projectKey: this.#projectKey,
|
|
168
|
+
encryptionKeys: this.#encryptionKeys,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
return inviteResponse.decision
|
|
172
|
+
default:
|
|
173
|
+
throw new ExhaustivenessError(inviteResponse.decision)
|
|
174
|
+
}
|
|
175
|
+
} finally {
|
|
176
|
+
this.#outboundInvitesByDevice.delete(deviceId)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {string} deviceId
|
|
182
|
+
* @param {Invite} invite
|
|
183
|
+
* @param {AbortSignal} signal
|
|
184
|
+
*/
|
|
185
|
+
async #sendInviteAndGetResponse(deviceId, invite, signal) {
|
|
186
|
+
const inviteAbortedError = new Error('Invite aborted')
|
|
187
|
+
|
|
188
|
+
if (signal.aborted) throw inviteAbortedError
|
|
189
|
+
|
|
190
|
+
const abortController = new AbortController()
|
|
191
|
+
|
|
192
|
+
const responsePromise =
|
|
193
|
+
/** @type {typeof pEvent<'invite-response', [string, InviteResponse]>} */ (
|
|
194
|
+
pEvent
|
|
195
|
+
)(this.#rpc, 'invite-response', {
|
|
196
|
+
multiArgs: true,
|
|
197
|
+
filter: ([peerId, inviteResponse]) =>
|
|
198
|
+
timingSafeEqual(peerId, deviceId) &&
|
|
199
|
+
timingSafeEqual(invite.inviteId, inviteResponse.inviteId),
|
|
200
|
+
signal: abortSignalAny([abortController.signal, signal]),
|
|
201
|
+
}).then((args) => args?.[1])
|
|
202
|
+
|
|
203
|
+
responsePromise.catch(noop)
|
|
204
|
+
|
|
205
|
+
signal.addEventListener(
|
|
206
|
+
'abort',
|
|
207
|
+
() => {
|
|
208
|
+
this.#rpc
|
|
209
|
+
.sendInviteCancel(deviceId, { inviteId: invite.inviteId })
|
|
210
|
+
.catch(noop)
|
|
211
|
+
},
|
|
212
|
+
{ once: true }
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
await this.#rpc.sendInvite(deviceId, invite)
|
|
217
|
+
return await responsePromise
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
220
|
+
throw inviteAbortedError
|
|
221
|
+
} else {
|
|
222
|
+
throw err
|
|
223
|
+
}
|
|
224
|
+
} finally {
|
|
225
|
+
abortController.abort()
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Attempt to cancel an outbound invite, if it exists.
|
|
231
|
+
*
|
|
232
|
+
* No-op if we weren't inviting this device.
|
|
233
|
+
*
|
|
234
|
+
* @param {string} deviceId
|
|
235
|
+
* @returns {void}
|
|
236
|
+
*/
|
|
237
|
+
requestCancelInvite(deviceId) {
|
|
238
|
+
this.#outboundInvitesByDevice.get(deviceId)?.abortController.abort()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @param {string} deviceId
|
|
243
|
+
* @returns {Promise<MemberInfo>}
|
|
244
|
+
*/
|
|
245
|
+
async getById(deviceId) {
|
|
246
|
+
const role = await this.#roles.getRole(deviceId)
|
|
247
|
+
|
|
248
|
+
/** @type {MemberInfo} */
|
|
249
|
+
const result = { deviceId, role }
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const configCoreId = await this.#coreOwnership.getCoreId(
|
|
253
|
+
deviceId,
|
|
254
|
+
'config'
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
const deviceInfo = await this.#dataTypes.deviceInfo.getByDocId(
|
|
258
|
+
configCoreId
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
result.name = deviceInfo.name
|
|
262
|
+
result.deviceType = deviceInfo.deviceType
|
|
263
|
+
result.joinedAt = deviceInfo.createdAt
|
|
264
|
+
} catch (err) {
|
|
265
|
+
// Attempting to get someone else may throw because sync hasn't occurred or completed
|
|
266
|
+
// Only throw if attempting to get themself since the relevant information should be available
|
|
267
|
+
if (deviceId === this.#ownDeviceId) throw err
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return result
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @returns {Promise<Array<MemberInfo>>}
|
|
275
|
+
*/
|
|
276
|
+
async getMany() {
|
|
277
|
+
const [allRoles, allDeviceInfo] = await Promise.all([
|
|
278
|
+
this.#roles.getAll(),
|
|
279
|
+
this.#dataTypes.deviceInfo.getMany(),
|
|
280
|
+
])
|
|
281
|
+
|
|
282
|
+
return Promise.all(
|
|
283
|
+
[...allRoles.entries()].map(async ([deviceId, role]) => {
|
|
284
|
+
/** @type {MemberInfo} */
|
|
285
|
+
const memberInfo = { deviceId, role }
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const configCoreId = await this.#coreOwnership.getCoreId(
|
|
289
|
+
deviceId,
|
|
290
|
+
'config'
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
const deviceInfo = allDeviceInfo.find(
|
|
294
|
+
({ docId }) => docId === configCoreId
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
memberInfo.name = deviceInfo?.name
|
|
298
|
+
memberInfo.deviceType = deviceInfo?.deviceType
|
|
299
|
+
memberInfo.joinedAt = deviceInfo?.createdAt
|
|
300
|
+
} catch (err) {
|
|
301
|
+
// Attempting to get someone else may throw because sync hasn't occurred or completed
|
|
302
|
+
// Only throw if attempting to get themself since the relevant information should be available
|
|
303
|
+
if (deviceId === this.#ownDeviceId) throw err
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return memberInfo
|
|
307
|
+
})
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @param {string} deviceId
|
|
313
|
+
* @param {import('./roles.js').RoleIdAssignableToOthers} roleId
|
|
314
|
+
* @returns {Promise<void>}
|
|
315
|
+
*/
|
|
316
|
+
async assignRole(deviceId, roleId) {
|
|
317
|
+
return this.#roles.assignRole(deviceId, roleId)
|
|
318
|
+
}
|
|
319
|
+
}
|