@comapeo/core 2.0.1 → 2.2.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/blob-store/downloader.d.ts +43 -0
- package/dist/blob-store/downloader.d.ts.map +1 -0
- package/dist/blob-store/entries-stream.d.ts +13 -0
- package/dist/blob-store/entries-stream.d.ts.map +1 -0
- package/dist/blob-store/hyperdrive-index.d.ts +20 -0
- package/dist/blob-store/hyperdrive-index.d.ts.map +1 -0
- package/dist/blob-store/index.d.ts +34 -29
- package/dist/blob-store/index.d.ts.map +1 -1
- package/dist/blob-store/utils.d.ts +27 -0
- package/dist/blob-store/utils.d.ts.map +1 -0
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/core-manager/index.d.ts +11 -1
- package/dist/core-manager/index.d.ts.map +1 -1
- package/dist/core-ownership.d.ts.map +1 -1
- package/dist/datastore/index.d.ts +5 -4
- package/dist/datastore/index.d.ts.map +1 -1
- package/dist/datatype/index.d.ts +5 -1
- package/dist/discovery/local-discovery.d.ts.map +1 -1
- package/dist/errors.d.ts +6 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/fastify-plugins/blobs.d.ts.map +1 -1
- package/dist/fastify-plugins/maps.d.ts.map +1 -1
- package/dist/generated/extensions.d.ts +31 -0
- package/dist/generated/extensions.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/drizzle-helpers.d.ts +6 -0
- package/dist/lib/drizzle-helpers.d.ts.map +1 -0
- package/dist/lib/error.d.ts +51 -0
- package/dist/lib/error.d.ts.map +1 -0
- package/dist/lib/get-own.d.ts +9 -0
- package/dist/lib/get-own.d.ts.map +1 -0
- package/dist/lib/is-hostname-ip-address.d.ts +17 -0
- package/dist/lib/is-hostname-ip-address.d.ts.map +1 -0
- package/dist/lib/ws-core-replicator.d.ts +11 -0
- package/dist/lib/ws-core-replicator.d.ts.map +1 -0
- package/dist/mapeo-manager.d.ts +18 -22
- package/dist/mapeo-manager.d.ts.map +1 -1
- package/dist/mapeo-project.d.ts +459 -26
- package/dist/mapeo-project.d.ts.map +1 -1
- package/dist/member-api.d.ts +44 -1
- package/dist/member-api.d.ts.map +1 -1
- package/dist/roles.d.ts.map +1 -1
- package/dist/schema/client.d.ts +17 -5
- package/dist/schema/client.d.ts.map +1 -1
- package/dist/schema/project.d.ts +212 -2
- package/dist/schema/project.d.ts.map +1 -1
- package/dist/sync/core-sync-state.d.ts +20 -15
- package/dist/sync/core-sync-state.d.ts.map +1 -1
- package/dist/sync/namespace-sync-state.d.ts +13 -1
- package/dist/sync/namespace-sync-state.d.ts.map +1 -1
- package/dist/sync/peer-sync-controller.d.ts +1 -1
- package/dist/sync/peer-sync-controller.d.ts.map +1 -1
- package/dist/sync/sync-api.d.ts +47 -2
- package/dist/sync/sync-api.d.ts.map +1 -1
- package/dist/sync/sync-state.d.ts +12 -0
- package/dist/sync/sync-state.d.ts.map +1 -1
- package/dist/translation-api.d.ts +2 -2
- package/dist/translation-api.d.ts.map +1 -1
- package/dist/types.d.ts +10 -2
- package/dist/types.d.ts.map +1 -1
- package/drizzle/client/0001_chubby_cargill.sql +12 -0
- package/drizzle/client/meta/0001_snapshot.json +208 -0
- package/drizzle/client/meta/_journal.json +7 -0
- package/drizzle/project/0001_medical_wendell_rand.sql +22 -0
- package/drizzle/project/meta/0001_snapshot.json +1267 -0
- package/drizzle/project/meta/_journal.json +7 -0
- package/package.json +14 -5
- package/src/blob-store/downloader.js +130 -0
- package/src/blob-store/entries-stream.js +81 -0
- package/src/blob-store/hyperdrive-index.js +122 -0
- package/src/blob-store/index.js +59 -117
- package/src/blob-store/utils.js +54 -0
- package/src/constants.js +4 -1
- package/src/core-manager/index.js +60 -3
- package/src/core-ownership.js +2 -4
- package/src/datastore/README.md +1 -2
- package/src/datastore/index.js +8 -8
- package/src/datatype/index.d.ts +5 -1
- package/src/datatype/index.js +22 -9
- package/src/discovery/local-discovery.js +2 -1
- package/src/errors.js +11 -2
- package/src/fastify-plugins/blobs.js +17 -1
- package/src/fastify-plugins/maps.js +2 -1
- package/src/generated/extensions.d.ts +31 -0
- package/src/generated/extensions.js +150 -0
- package/src/generated/extensions.ts +181 -0
- package/src/index.js +10 -0
- package/src/invite-api.js +1 -1
- package/src/lib/drizzle-helpers.js +79 -0
- package/src/lib/error.js +71 -0
- package/src/lib/get-own.js +10 -0
- package/src/lib/is-hostname-ip-address.js +26 -0
- package/src/lib/ws-core-replicator.js +47 -0
- package/src/mapeo-manager.js +74 -45
- package/src/mapeo-project.js +238 -58
- package/src/member-api.js +295 -2
- package/src/roles.js +38 -32
- package/src/schema/client.js +4 -3
- package/src/schema/project.js +7 -0
- package/src/sync/core-sync-state.js +39 -23
- package/src/sync/namespace-sync-state.js +22 -0
- package/src/sync/peer-sync-controller.js +1 -0
- package/src/sync/sync-api.js +197 -3
- package/src/sync/sync-state.js +18 -0
- package/src/translation-api.js +5 -9
- package/src/types.ts +12 -3
- package/dist/blob-store/live-download.d.ts +0 -107
- package/dist/blob-store/live-download.d.ts.map +0 -1
- package/dist/lib/timing-safe-equal.d.ts +0 -15
- package/dist/lib/timing-safe-equal.d.ts.map +0 -1
- package/src/blob-store/live-download.js +0 -373
- package/src/lib/timing-safe-equal.js +0 -34
package/src/mapeo-project.js
CHANGED
|
@@ -2,7 +2,6 @@ import path from 'path'
|
|
|
2
2
|
import Database from 'better-sqlite3'
|
|
3
3
|
import { decodeBlockPrefix, decode, parseVersionId } from '@comapeo/schema'
|
|
4
4
|
import { drizzle } from 'drizzle-orm/better-sqlite3'
|
|
5
|
-
import { migrate } from 'drizzle-orm/better-sqlite3/migrator'
|
|
6
5
|
import { discoveryKey } from 'hypercore-crypto'
|
|
7
6
|
import { TypedEmitter } from 'tiny-typed-emitter'
|
|
8
7
|
|
|
@@ -24,6 +23,7 @@ import {
|
|
|
24
23
|
roleTable,
|
|
25
24
|
iconTable,
|
|
26
25
|
translationTable,
|
|
26
|
+
remoteDetectionAlertTable,
|
|
27
27
|
} from './schema/project.js'
|
|
28
28
|
import {
|
|
29
29
|
CoreOwnership,
|
|
@@ -38,20 +38,31 @@ import {
|
|
|
38
38
|
} from './roles.js'
|
|
39
39
|
import {
|
|
40
40
|
assert,
|
|
41
|
+
ExhaustivenessError,
|
|
41
42
|
getDeviceId,
|
|
42
43
|
projectKeyToId,
|
|
43
44
|
projectKeyToPublicId,
|
|
44
45
|
valueOf,
|
|
45
46
|
} from './utils.js'
|
|
47
|
+
import { migrate } from './lib/drizzle-helpers.js'
|
|
46
48
|
import { omit } from './lib/omit.js'
|
|
47
49
|
import { MemberApi } from './member-api.js'
|
|
48
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
SyncApi,
|
|
52
|
+
kAddBlobWantRange,
|
|
53
|
+
kClearBlobWantRanges,
|
|
54
|
+
kHandleDiscoveryKey,
|
|
55
|
+
kSetBlobDownloadFilter,
|
|
56
|
+
kWaitForInitialSyncWithPeer,
|
|
57
|
+
} from './sync/sync-api.js'
|
|
49
58
|
import { Logger } from './logger.js'
|
|
50
59
|
import { IconApi } from './icon-api.js'
|
|
51
60
|
import { readConfig } from './config-import.js'
|
|
52
61
|
import TranslationApi from './translation-api.js'
|
|
62
|
+
import { NotFoundError, nullIfNotFound } from './errors.js'
|
|
63
|
+
import { getErrorCode, getErrorMessage } from './lib/error.js'
|
|
53
64
|
/** @import { ProjectSettingsValue } from '@comapeo/schema' */
|
|
54
|
-
/** @import { CoreStorage, KeyPair, Namespace } from './types.js' */
|
|
65
|
+
/** @import { CoreStorage, BlobFilter, BlobStoreEntriesStream, KeyPair, Namespace, ReplicationStream } from './types.js' */
|
|
55
66
|
|
|
56
67
|
/** @typedef {Omit<ProjectSettingsValue, 'schemaName'>} EditableProjectSettings */
|
|
57
68
|
/** @typedef {ProjectSettingsValue['configMetadata']} ConfigMetadata */
|
|
@@ -66,15 +77,25 @@ export const kProjectReplicate = Symbol('replicate project')
|
|
|
66
77
|
export const kDataTypes = Symbol('dataTypes')
|
|
67
78
|
export const kProjectLeave = Symbol('leave project')
|
|
68
79
|
export const kClearDataIfLeft = Symbol('clear data if left project')
|
|
80
|
+
export const kSetIsArchiveDevice = Symbol('set isArchiveDevice')
|
|
81
|
+
export const kIsArchiveDevice = Symbol('isArchiveDevice (temp - test only)')
|
|
69
82
|
|
|
70
83
|
const EMPTY_PROJECT_SETTINGS = Object.freeze({})
|
|
71
84
|
|
|
85
|
+
/** @type {import('./types.js').BlobFilter} */
|
|
86
|
+
const NON_ARCHIVE_DEVICE_DOWNLOAD_FILTER = {
|
|
87
|
+
photo: ['preview', 'thumbnail'],
|
|
88
|
+
// Don't download any audio of video files, since previews and
|
|
89
|
+
// thumbnails aren't supported yet.
|
|
90
|
+
}
|
|
91
|
+
|
|
72
92
|
/**
|
|
73
93
|
* @extends {TypedEmitter<{ close: () => void }>}
|
|
74
94
|
*/
|
|
75
95
|
export class MapeoProject extends TypedEmitter {
|
|
76
|
-
#
|
|
96
|
+
#projectKey
|
|
77
97
|
#deviceId
|
|
98
|
+
#identityKeypair
|
|
78
99
|
#coreManager
|
|
79
100
|
#indexWriter
|
|
80
101
|
#dataStores
|
|
@@ -91,6 +112,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
91
112
|
#l
|
|
92
113
|
/** @type {Boolean} this avoids loading multiple configs in parallel */
|
|
93
114
|
#loadingConfig
|
|
115
|
+
#isArchiveDevice
|
|
94
116
|
|
|
95
117
|
static EMPTY_PROJECT_SETTINGS = EMPTY_PROJECT_SETTINGS
|
|
96
118
|
|
|
@@ -107,6 +129,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
107
129
|
* @param {CoreStorage} opts.coreStorage Folder to store all hypercore data
|
|
108
130
|
* @param {(mediaType: 'blobs' | 'icons') => Promise<string>} opts.getMediaBaseUrl
|
|
109
131
|
* @param {import('./local-peers.js').LocalPeers} opts.localPeers
|
|
132
|
+
* @param {boolean} opts.isArchiveDevice Whether this device is an archive device
|
|
110
133
|
* @param {Logger} [opts.logger]
|
|
111
134
|
*
|
|
112
135
|
*/
|
|
@@ -123,20 +146,60 @@ export class MapeoProject extends TypedEmitter {
|
|
|
123
146
|
getMediaBaseUrl,
|
|
124
147
|
localPeers,
|
|
125
148
|
logger,
|
|
149
|
+
isArchiveDevice,
|
|
126
150
|
}) {
|
|
127
151
|
super()
|
|
128
152
|
|
|
129
153
|
this.#l = Logger.create('project', logger)
|
|
130
154
|
this.#deviceId = getDeviceId(keyManager)
|
|
131
|
-
this.#
|
|
155
|
+
this.#projectKey = projectKey
|
|
132
156
|
this.#loadingConfig = false
|
|
157
|
+
this.#isArchiveDevice = isArchiveDevice
|
|
158
|
+
|
|
159
|
+
const getReplicationStream = this[kProjectReplicate].bind(this, true)
|
|
160
|
+
|
|
161
|
+
const blobDownloadFilter = getBlobDownloadFilter(isArchiveDevice)
|
|
133
162
|
|
|
134
163
|
///////// 1. Setup database
|
|
164
|
+
|
|
135
165
|
this.#sqlite = new Database(dbPath)
|
|
136
166
|
const db = drizzle(this.#sqlite)
|
|
137
|
-
migrate(db, {
|
|
167
|
+
const migrationResult = migrate(db, {
|
|
168
|
+
migrationsFolder: projectMigrationsFolder,
|
|
169
|
+
})
|
|
170
|
+
let reindex
|
|
171
|
+
switch (migrationResult) {
|
|
172
|
+
case 'initialized database':
|
|
173
|
+
case 'no migration':
|
|
174
|
+
reindex = false
|
|
175
|
+
break
|
|
176
|
+
case 'migrated':
|
|
177
|
+
reindex = true
|
|
178
|
+
break
|
|
179
|
+
default:
|
|
180
|
+
throw new ExhaustivenessError(migrationResult)
|
|
181
|
+
}
|
|
138
182
|
|
|
139
|
-
|
|
183
|
+
const indexedTables = [
|
|
184
|
+
observationTable,
|
|
185
|
+
trackTable,
|
|
186
|
+
presetTable,
|
|
187
|
+
fieldTable,
|
|
188
|
+
coreOwnershipTable,
|
|
189
|
+
roleTable,
|
|
190
|
+
deviceInfoTable,
|
|
191
|
+
iconTable,
|
|
192
|
+
translationTable,
|
|
193
|
+
remoteDetectionAlertTable,
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
///////// 2. Wipe data if we need to re-index
|
|
197
|
+
|
|
198
|
+
if (reindex) {
|
|
199
|
+
for (const table of indexedTables) db.delete(table).run()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
///////// 3. Setup random-access-storage functions
|
|
140
203
|
|
|
141
204
|
/** @type {ConstructorParameters<typeof CoreManager>[0]['storage']} */
|
|
142
205
|
const coreManagerStorage = (name) =>
|
|
@@ -146,7 +209,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
146
209
|
const indexerStorage = (name) =>
|
|
147
210
|
coreStorage(path.join(INDEXER_STORAGE_FOLDER_NAME, name))
|
|
148
211
|
|
|
149
|
-
/////////
|
|
212
|
+
///////// 4. Create instances
|
|
150
213
|
|
|
151
214
|
this.#coreManager = new CoreManager({
|
|
152
215
|
projectSecretKey,
|
|
@@ -159,17 +222,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
159
222
|
})
|
|
160
223
|
|
|
161
224
|
this.#indexWriter = new IndexWriter({
|
|
162
|
-
tables:
|
|
163
|
-
observationTable,
|
|
164
|
-
trackTable,
|
|
165
|
-
presetTable,
|
|
166
|
-
fieldTable,
|
|
167
|
-
coreOwnershipTable,
|
|
168
|
-
roleTable,
|
|
169
|
-
deviceInfoTable,
|
|
170
|
-
iconTable,
|
|
171
|
-
translationTable,
|
|
172
|
-
],
|
|
225
|
+
tables: indexedTables,
|
|
173
226
|
sqlite: this.#sqlite,
|
|
174
227
|
getWinner,
|
|
175
228
|
mapDoc: (doc, version) => {
|
|
@@ -191,6 +244,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
191
244
|
namespace: 'auth',
|
|
192
245
|
batch: (entries) => this.#indexWriter.batch(entries),
|
|
193
246
|
storage: indexerStorage,
|
|
247
|
+
reindex,
|
|
194
248
|
}),
|
|
195
249
|
config: new DataStore({
|
|
196
250
|
coreManager: this.#coreManager,
|
|
@@ -201,12 +255,14 @@ export class MapeoProject extends TypedEmitter {
|
|
|
201
255
|
sharedIndexWriter,
|
|
202
256
|
}),
|
|
203
257
|
storage: indexerStorage,
|
|
258
|
+
reindex,
|
|
204
259
|
}),
|
|
205
260
|
data: new DataStore({
|
|
206
261
|
coreManager: this.#coreManager,
|
|
207
262
|
namespace: 'data',
|
|
208
263
|
batch: (entries) => this.#indexWriter.batch(entries),
|
|
209
264
|
storage: indexerStorage,
|
|
265
|
+
reindex,
|
|
210
266
|
}),
|
|
211
267
|
}
|
|
212
268
|
|
|
@@ -225,6 +281,12 @@ export class MapeoProject extends TypedEmitter {
|
|
|
225
281
|
db,
|
|
226
282
|
getTranslations,
|
|
227
283
|
}),
|
|
284
|
+
remoteDetectionAlert: new DataType({
|
|
285
|
+
dataStore: this.#dataStores.data,
|
|
286
|
+
table: remoteDetectionAlertTable,
|
|
287
|
+
db,
|
|
288
|
+
getTranslations,
|
|
289
|
+
}),
|
|
228
290
|
preset: new DataType({
|
|
229
291
|
dataStore: this.#dataStores.config,
|
|
230
292
|
table: presetTable,
|
|
@@ -276,7 +338,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
276
338
|
},
|
|
277
339
|
}),
|
|
278
340
|
}
|
|
279
|
-
|
|
341
|
+
this.#identityKeypair = keyManager.getIdentityKeypair()
|
|
280
342
|
const coreKeypairs = getCoreKeypairs({
|
|
281
343
|
projectKey,
|
|
282
344
|
projectSecretKey,
|
|
@@ -285,14 +347,14 @@ export class MapeoProject extends TypedEmitter {
|
|
|
285
347
|
this.#coreOwnership = new CoreOwnership({
|
|
286
348
|
dataType: this.#dataTypes.coreOwnership,
|
|
287
349
|
coreKeypairs,
|
|
288
|
-
identityKeypair,
|
|
350
|
+
identityKeypair: this.#identityKeypair,
|
|
289
351
|
})
|
|
290
352
|
this.#roles = new Roles({
|
|
291
353
|
dataType: this.#dataTypes.role,
|
|
292
354
|
coreOwnership: this.#coreOwnership,
|
|
293
355
|
coreManager: this.#coreManager,
|
|
294
356
|
projectKey: projectKey,
|
|
295
|
-
deviceKey:
|
|
357
|
+
deviceKey: this.#identityKeypair.publicKey,
|
|
296
358
|
})
|
|
297
359
|
|
|
298
360
|
this.#memberApi = new MemberApi({
|
|
@@ -300,18 +362,27 @@ export class MapeoProject extends TypedEmitter {
|
|
|
300
362
|
roles: this.#roles,
|
|
301
363
|
coreOwnership: this.#coreOwnership,
|
|
302
364
|
encryptionKeys,
|
|
365
|
+
getProjectName: this.#getProjectName.bind(this),
|
|
303
366
|
projectKey,
|
|
304
367
|
rpc: localPeers,
|
|
368
|
+
getReplicationStream,
|
|
369
|
+
waitForInitialSyncWithPeer: (deviceId, abortSignal) =>
|
|
370
|
+
this.$sync[kWaitForInitialSyncWithPeer](deviceId, abortSignal),
|
|
305
371
|
dataTypes: {
|
|
306
372
|
deviceInfo: this.#dataTypes.deviceInfo,
|
|
307
373
|
project: this.#dataTypes.projectSettings,
|
|
308
374
|
},
|
|
309
375
|
})
|
|
310
376
|
|
|
311
|
-
const projectPublicId = projectKeyToPublicId(projectKey)
|
|
312
|
-
|
|
313
377
|
this.#blobStore = new BlobStore({
|
|
314
378
|
coreManager: this.#coreManager,
|
|
379
|
+
downloadFilter: blobDownloadFilter,
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
this.#blobStore.on('error', (err) => {
|
|
383
|
+
// TODO: Handle this error in some way - this error will come from an
|
|
384
|
+
// unexpected error with background blob downloads
|
|
385
|
+
console.error('BlobStore error', err)
|
|
315
386
|
})
|
|
316
387
|
|
|
317
388
|
this.$blobs = new BlobApi({
|
|
@@ -321,7 +392,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
321
392
|
if (!base.endsWith('/')) {
|
|
322
393
|
base += '/'
|
|
323
394
|
}
|
|
324
|
-
return base + projectPublicId
|
|
395
|
+
return base + this.#projectPublicId
|
|
325
396
|
},
|
|
326
397
|
})
|
|
327
398
|
|
|
@@ -333,7 +404,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
333
404
|
if (!base.endsWith('/')) {
|
|
334
405
|
base += '/'
|
|
335
406
|
}
|
|
336
|
-
return base + projectPublicId
|
|
407
|
+
return base + this.#projectPublicId
|
|
337
408
|
},
|
|
338
409
|
})
|
|
339
410
|
|
|
@@ -341,14 +412,75 @@ export class MapeoProject extends TypedEmitter {
|
|
|
341
412
|
coreManager: this.#coreManager,
|
|
342
413
|
coreOwnership: this.#coreOwnership,
|
|
343
414
|
roles: this.#roles,
|
|
415
|
+
blobDownloadFilter,
|
|
344
416
|
logger: this.#l,
|
|
417
|
+
getServerWebsocketUrls: async () => {
|
|
418
|
+
const members = await this.#memberApi.getMany()
|
|
419
|
+
/** @type {string[]} */
|
|
420
|
+
const serverWebsocketUrls = []
|
|
421
|
+
for (const member of members) {
|
|
422
|
+
if (
|
|
423
|
+
member.deviceType === 'selfHostedServer' &&
|
|
424
|
+
member.selfHostedServerDetails
|
|
425
|
+
) {
|
|
426
|
+
const { baseUrl } = member.selfHostedServerDetails
|
|
427
|
+
const wsUrl = new URL(`/sync/${this.#projectPublicId}`, baseUrl)
|
|
428
|
+
wsUrl.protocol = wsUrl.protocol === 'http:' ? 'ws:' : 'wss:'
|
|
429
|
+
serverWebsocketUrls.push(wsUrl.href)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return serverWebsocketUrls
|
|
433
|
+
},
|
|
434
|
+
getReplicationStream,
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
/** @type {Map<string, BlobStoreEntriesStream>} */
|
|
438
|
+
const entriesReadStreams = new Map()
|
|
439
|
+
|
|
440
|
+
this.#coreManager.on('peer-download-intent', async (filter, peerId) => {
|
|
441
|
+
entriesReadStreams.get(peerId)?.destroy()
|
|
442
|
+
|
|
443
|
+
const entriesReadStream = this.#blobStore.createEntriesReadStream({
|
|
444
|
+
live: true,
|
|
445
|
+
filter,
|
|
446
|
+
})
|
|
447
|
+
entriesReadStreams.set(peerId, entriesReadStream)
|
|
448
|
+
|
|
449
|
+
entriesReadStream.once('close', () => {
|
|
450
|
+
if (entriesReadStreams.get(peerId) === entriesReadStream) {
|
|
451
|
+
entriesReadStreams.delete(peerId)
|
|
452
|
+
}
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
this.#syncApi[kClearBlobWantRanges](peerId)
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
for await (const entry of entriesReadStream) {
|
|
459
|
+
const { blockOffset, blockLength } = entry.value.blob
|
|
460
|
+
this.#syncApi[kAddBlobWantRange](peerId, blockOffset, blockLength)
|
|
461
|
+
}
|
|
462
|
+
} catch (err) {
|
|
463
|
+
if (getErrorCode(err) === 'ERR_STREAM_PREMATURE_CLOSE') return
|
|
464
|
+
this.#l.log(
|
|
465
|
+
'Error getting blob entries stream for peer %h: %s',
|
|
466
|
+
peerId,
|
|
467
|
+
getErrorMessage(err)
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
this.#coreManager.creatorCore.on('peer-remove', (peer) => {
|
|
473
|
+
const peerKey = peer.protomux.stream.remotePublicKey
|
|
474
|
+
const peerId = peerKey.toString('hex')
|
|
475
|
+
entriesReadStreams.get(peerId)?.destroy()
|
|
476
|
+
entriesReadStreams.delete(peerId)
|
|
345
477
|
})
|
|
346
478
|
|
|
347
479
|
this.#translationApi = new TranslationApi({
|
|
348
480
|
dataType: this.#dataTypes.translation,
|
|
349
481
|
})
|
|
350
482
|
|
|
351
|
-
/////////
|
|
483
|
+
///////// 5. Replicate local peers automatically
|
|
352
484
|
|
|
353
485
|
// Replicate already connected local peers
|
|
354
486
|
for (const peer of localPeers.peers) {
|
|
@@ -416,6 +548,14 @@ export class MapeoProject extends TypedEmitter {
|
|
|
416
548
|
return this.#deviceId
|
|
417
549
|
}
|
|
418
550
|
|
|
551
|
+
get #projectId() {
|
|
552
|
+
return projectKeyToId(this.#projectKey)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
get #projectPublicId() {
|
|
556
|
+
return projectKeyToPublicId(this.#projectKey)
|
|
557
|
+
}
|
|
558
|
+
|
|
419
559
|
/**
|
|
420
560
|
* Resolves when hypercores have all loaded
|
|
421
561
|
*
|
|
@@ -429,6 +569,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
429
569
|
*/
|
|
430
570
|
async close() {
|
|
431
571
|
this.#l.log('closing project %h', this.#projectId)
|
|
572
|
+
this.#blobStore.close()
|
|
432
573
|
const dataStorePromises = []
|
|
433
574
|
for (const dataStore of Object.values(this.#dataStores)) {
|
|
434
575
|
dataStorePromises.push(dataStore.close())
|
|
@@ -499,6 +640,10 @@ export class MapeoProject extends TypedEmitter {
|
|
|
499
640
|
return this.#dataTypes.field
|
|
500
641
|
}
|
|
501
642
|
|
|
643
|
+
get remoteDetectionAlert() {
|
|
644
|
+
return this.#dataTypes.remoteDetectionAlert
|
|
645
|
+
}
|
|
646
|
+
|
|
502
647
|
get $member() {
|
|
503
648
|
return this.#memberApi
|
|
504
649
|
}
|
|
@@ -518,14 +663,9 @@ export class MapeoProject extends TypedEmitter {
|
|
|
518
663
|
async $setProjectSettings(settings) {
|
|
519
664
|
const { projectSettings } = this.#dataTypes
|
|
520
665
|
|
|
521
|
-
// We only want to catch the error to the getByDocId call
|
|
522
|
-
// Using try/catch for this is a little verbose when dealing with TS types
|
|
523
666
|
const existing = await projectSettings
|
|
524
667
|
.getByDocId(this.#projectId)
|
|
525
|
-
.catch(
|
|
526
|
-
// project does not exist so return null
|
|
527
|
-
return null
|
|
528
|
-
})
|
|
668
|
+
.catch(nullIfNotFound)
|
|
529
669
|
|
|
530
670
|
if (existing) {
|
|
531
671
|
return extractEditableProjectSettings(
|
|
@@ -557,6 +697,13 @@ export class MapeoProject extends TypedEmitter {
|
|
|
557
697
|
}
|
|
558
698
|
}
|
|
559
699
|
|
|
700
|
+
/**
|
|
701
|
+
* @returns {Promise<undefined | string>}
|
|
702
|
+
*/
|
|
703
|
+
async #getProjectName() {
|
|
704
|
+
return (await this.$getProjectSettings()).name
|
|
705
|
+
}
|
|
706
|
+
|
|
560
707
|
async $getOwnRole() {
|
|
561
708
|
return this.#roles.getRole(this.#deviceId)
|
|
562
709
|
}
|
|
@@ -571,35 +718,45 @@ export class MapeoProject extends TypedEmitter {
|
|
|
571
718
|
const coreId = this.#coreManager
|
|
572
719
|
.getCoreByDiscoveryKey(coreDiscoveryKey)
|
|
573
720
|
?.key.toString('hex')
|
|
574
|
-
if (!coreId) throw new
|
|
721
|
+
if (!coreId) throw new NotFoundError()
|
|
575
722
|
return this.#coreOwnership.getOwner(coreId)
|
|
576
723
|
}
|
|
577
724
|
|
|
578
725
|
/**
|
|
579
726
|
* Replicate a project to a @hyperswarm/secret-stream. Invites will not
|
|
580
727
|
* function because the RPC channel is not connected for project replication,
|
|
581
|
-
* and only this project will replicate
|
|
582
|
-
* need to replicate the manager instance via manager[kManagerReplicate])
|
|
728
|
+
* and only this project will replicate.
|
|
583
729
|
*
|
|
584
|
-
* @param {
|
|
730
|
+
* @param {(
|
|
731
|
+
* boolean |
|
|
732
|
+
* import('stream').Duplex |
|
|
733
|
+
* import('streamx').Duplex
|
|
734
|
+
* )} isInitiatorOrStream
|
|
735
|
+
* @returns {ReplicationStream}
|
|
585
736
|
*/
|
|
586
|
-
[kProjectReplicate](
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
737
|
+
[kProjectReplicate](isInitiatorOrStream) {
|
|
738
|
+
const replicationStream = this.#coreManager.creatorCore.replicate(
|
|
739
|
+
isInitiatorOrStream,
|
|
740
|
+
/**
|
|
741
|
+
* Hypercore types need updating.
|
|
742
|
+
* @type {any}
|
|
743
|
+
*/ ({
|
|
744
|
+
keyPair: this.#identityKeypair,
|
|
745
|
+
/** @param {Buffer} discoveryKey */
|
|
746
|
+
ondiscoverykey: async (discoveryKey) => {
|
|
747
|
+
const protomux =
|
|
748
|
+
/** @type {import('protomux')<import('@hyperswarm/secret-stream')>} */ (
|
|
749
|
+
replicationStream.noiseStream.userData
|
|
750
|
+
)
|
|
751
|
+
this.#syncApi[kHandleDiscoveryKey](discoveryKey, protomux)
|
|
752
|
+
},
|
|
753
|
+
})
|
|
754
|
+
)
|
|
598
755
|
return replicationStream
|
|
599
756
|
}
|
|
600
757
|
|
|
601
758
|
/**
|
|
602
|
-
* @param {Pick<import('@comapeo/schema').DeviceInfoValue, 'name' | 'deviceType'>} value
|
|
759
|
+
* @param {Pick<import('@comapeo/schema').DeviceInfoValue, 'name' | 'deviceType' | 'selfHostedServerDetails'>} value
|
|
603
760
|
* @returns {Promise<import('@comapeo/schema').DeviceInfo>}
|
|
604
761
|
*/
|
|
605
762
|
async [kSetOwnDeviceInfo](value) {
|
|
@@ -612,17 +769,32 @@ export class MapeoProject extends TypedEmitter {
|
|
|
612
769
|
const doc = {
|
|
613
770
|
name: value.name,
|
|
614
771
|
deviceType: value.deviceType,
|
|
772
|
+
selfHostedServerDetails: value.selfHostedServerDetails,
|
|
615
773
|
schemaName: /** @type {const} */ ('deviceInfo'),
|
|
616
774
|
}
|
|
617
775
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
776
|
+
const existingDoc = await deviceInfo
|
|
777
|
+
.getByDocId(configCoreId)
|
|
778
|
+
.catch(nullIfNotFound)
|
|
779
|
+
if (existingDoc) {
|
|
780
|
+
return await deviceInfo.update(existingDoc.versionId, doc)
|
|
781
|
+
} else {
|
|
622
782
|
return await deviceInfo[kCreateWithDocId](configCoreId, doc)
|
|
623
783
|
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/** @param {boolean} isArchiveDevice */
|
|
787
|
+
async [kSetIsArchiveDevice](isArchiveDevice) {
|
|
788
|
+
if (this.#isArchiveDevice === isArchiveDevice) return
|
|
789
|
+
const blobDownloadFilter = getBlobDownloadFilter(isArchiveDevice)
|
|
790
|
+
this.#blobStore.setDownloadFilter(blobDownloadFilter)
|
|
791
|
+
this.#syncApi[kSetBlobDownloadFilter](blobDownloadFilter)
|
|
792
|
+
this.#isArchiveDevice = isArchiveDevice
|
|
793
|
+
}
|
|
624
794
|
|
|
625
|
-
|
|
795
|
+
/** @returns {boolean} */
|
|
796
|
+
get [kIsArchiveDevice]() {
|
|
797
|
+
return this.#isArchiveDevice
|
|
626
798
|
}
|
|
627
799
|
|
|
628
800
|
/**
|
|
@@ -769,7 +941,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
769
941
|
const fieldRefs = fieldNames.map((fieldName) => {
|
|
770
942
|
const fieldRef = fieldNameToRef.get(fieldName)
|
|
771
943
|
if (!fieldRef) {
|
|
772
|
-
throw new
|
|
944
|
+
throw new NotFoundError(
|
|
773
945
|
`field ${fieldName} not found (referenced by preset ${value.name})})`
|
|
774
946
|
)
|
|
775
947
|
}
|
|
@@ -781,7 +953,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
781
953
|
}
|
|
782
954
|
const iconRef = iconNameToRef.get(iconName)
|
|
783
955
|
if (!iconRef) {
|
|
784
|
-
throw new
|
|
956
|
+
throw new NotFoundError(
|
|
785
957
|
`icon ${iconName} not found (referenced by preset ${value.name})`
|
|
786
958
|
)
|
|
787
959
|
}
|
|
@@ -828,7 +1000,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
828
1000
|
})
|
|
829
1001
|
)
|
|
830
1002
|
} else {
|
|
831
|
-
throw new
|
|
1003
|
+
throw new NotFoundError(
|
|
832
1004
|
`docRef for ${value.docRefType} with name ${name} not found`
|
|
833
1005
|
)
|
|
834
1006
|
}
|
|
@@ -883,6 +1055,14 @@ export class MapeoProject extends TypedEmitter {
|
|
|
883
1055
|
}
|
|
884
1056
|
}
|
|
885
1057
|
|
|
1058
|
+
/**
|
|
1059
|
+
* @param {boolean} isArchiveDevice
|
|
1060
|
+
* @returns {null | BlobFilter}
|
|
1061
|
+
*/
|
|
1062
|
+
function getBlobDownloadFilter(isArchiveDevice) {
|
|
1063
|
+
return isArchiveDevice ? null : NON_ARCHIVE_DEVICE_DOWNLOAD_FILTER
|
|
1064
|
+
}
|
|
1065
|
+
|
|
886
1066
|
/**
|
|
887
1067
|
* @param {import("@comapeo/schema").ProjectSettings & { forks: string[] }} projectDoc
|
|
888
1068
|
* @returns {EditableProjectSettings}
|