@comapeo/core 2.0.0 → 2.1.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/index.d.ts +23 -49
- package/dist/blob-store/index.d.ts.map +1 -1
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/core-manager/index.d.ts +10 -0
- 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/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 +37 -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/omit.d.ts +17 -0
- package/dist/lib/omit.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 +454 -37
- package/dist/mapeo-project.d.ts.map +1 -1
- package/dist/member-api.d.ts +40 -1
- package/dist/member-api.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 +211 -1
- package/dist/schema/project.d.ts.map +1 -1
- package/dist/sync/peer-sync-controller.d.ts.map +1 -1
- package/dist/sync/sync-api.d.ts +28 -2
- package/dist/sync/sync-api.d.ts.map +1 -1
- package/dist/translation-api.d.ts.map +1 -1
- package/dist/types.d.ts +3 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.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 +10 -6
- package/src/blob-store/index.js +20 -4
- package/src/config-import.js +0 -1
- package/src/constants.js +4 -1
- package/src/core-manager/index.js +58 -2
- package/src/core-ownership.js +5 -2
- package/src/datastore/README.md +1 -2
- package/src/datastore/index.js +4 -5
- package/src/fastify-plugins/blobs.js +1 -0
- package/src/fastify-plugins/maps.js +11 -3
- 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 +47 -0
- package/src/lib/get-own.js +10 -0
- package/src/lib/is-hostname-ip-address.js +26 -0
- package/src/lib/omit.js +28 -0
- package/src/lib/ws-core-replicator.js +47 -0
- package/src/mapeo-manager.js +76 -53
- package/src/mapeo-project.js +155 -46
- package/src/member-api.js +253 -2
- package/src/schema/client.js +4 -3
- package/src/schema/project.js +7 -0
- package/src/sync/peer-sync-controller.js +1 -0
- package/src/sync/sync-api.js +171 -3
- package/src/translation-api.js +2 -2
- package/src/types.ts +4 -3
- package/src/utils.js +11 -14
- package/dist/lib/timing-safe-equal.d.ts +0 -15
- package/dist/lib/timing-safe-equal.d.ts.map +0 -1
- package/src/lib/timing-safe-equal.js +0 -34
package/src/mapeo-manager.js
CHANGED
|
@@ -16,10 +16,11 @@ import {
|
|
|
16
16
|
kBlobStore,
|
|
17
17
|
kClearDataIfLeft,
|
|
18
18
|
kProjectLeave,
|
|
19
|
+
kSetIsArchiveDevice,
|
|
19
20
|
kSetOwnDeviceInfo,
|
|
20
21
|
} from './mapeo-project.js'
|
|
21
22
|
import {
|
|
22
|
-
|
|
23
|
+
deviceSettingsTable,
|
|
23
24
|
projectKeysTable,
|
|
24
25
|
projectSettingsTable,
|
|
25
26
|
} from './schema/client.js'
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
projectKeyToPublicId,
|
|
35
36
|
} from './utils.js'
|
|
36
37
|
import { openedNoiseSecretStream } from './lib/noise-secret-stream-helpers.js'
|
|
38
|
+
import { omit } from './lib/omit.js'
|
|
37
39
|
import { RandomAccessFilePool } from './core-manager/random-access-file-pool.js'
|
|
38
40
|
import BlobServerPlugin from './fastify-plugins/blobs.js'
|
|
39
41
|
import IconServerPlugin from './fastify-plugins/icons.js'
|
|
@@ -43,7 +45,6 @@ import { LocalPeers } from './local-peers.js'
|
|
|
43
45
|
import { InviteApi } from './invite-api.js'
|
|
44
46
|
import { LocalDiscovery } from './discovery/local-discovery.js'
|
|
45
47
|
import { Roles } from './roles.js'
|
|
46
|
-
import NoiseSecretStream from '@hyperswarm/secret-stream'
|
|
47
48
|
import { Logger } from './logger.js'
|
|
48
49
|
import {
|
|
49
50
|
kSyncState,
|
|
@@ -51,6 +52,7 @@ import {
|
|
|
51
52
|
kRescindFullStopRequest,
|
|
52
53
|
} from './sync/sync-api.js'
|
|
53
54
|
/** @import { ProjectSettingsValue as ProjectValue } from '@comapeo/schema' */
|
|
55
|
+
/** @import NoiseSecretStream from '@hyperswarm/secret-stream' */
|
|
54
56
|
/** @import { SetNonNullable } from 'type-fest' */
|
|
55
57
|
/** @import { CoreStorage, Namespace } from './types.js' */
|
|
56
58
|
/** @import { DeviceInfoParam } from './schema/client.js' */
|
|
@@ -78,9 +80,6 @@ export const DEFAULT_FALLBACK_MAP_FILE_PATH = require.resolve(
|
|
|
78
80
|
export const DEFAULT_ONLINE_STYLE_URL =
|
|
79
81
|
'https://demotiles.maplibre.org/style.json'
|
|
80
82
|
|
|
81
|
-
export const kRPC = Symbol('rpc')
|
|
82
|
-
export const kManagerReplicate = Symbol('replicate manager')
|
|
83
|
-
|
|
84
83
|
/**
|
|
85
84
|
* @typedef {Omit<import('./local-peers.js').PeerInfo, 'protomux'>} PublicPeerInfo
|
|
86
85
|
*/
|
|
@@ -220,33 +219,10 @@ export class MapeoManager extends TypedEmitter {
|
|
|
220
219
|
this.#localDiscovery.on('connection', this.#replicate.bind(this))
|
|
221
220
|
}
|
|
222
221
|
|
|
223
|
-
/**
|
|
224
|
-
* MapeoRPC instance, used for tests
|
|
225
|
-
*/
|
|
226
|
-
get [kRPC]() {
|
|
227
|
-
return this.#localPeers
|
|
228
|
-
}
|
|
229
|
-
|
|
230
222
|
get deviceId() {
|
|
231
223
|
return this.#deviceId
|
|
232
224
|
}
|
|
233
225
|
|
|
234
|
-
/**
|
|
235
|
-
* Create a Mapeo replication stream. This replication connects the Mapeo RPC
|
|
236
|
-
* channel and allows invites. All active projects will sync automatically to
|
|
237
|
-
* this replication stream. Only use for local (trusted) connections, because
|
|
238
|
-
* the RPC channel key is public. To sync a specific project without
|
|
239
|
-
* connecting RPC, use project[kProjectReplication].
|
|
240
|
-
*
|
|
241
|
-
* @param {boolean} isInitiator
|
|
242
|
-
*/
|
|
243
|
-
[kManagerReplicate](isInitiator) {
|
|
244
|
-
const noiseStream = new NoiseSecretStream(isInitiator, undefined, {
|
|
245
|
-
keyPair: this.#keyManager.getIdentityKeypair(),
|
|
246
|
-
})
|
|
247
|
-
return this.#replicate(noiseStream)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
226
|
/**
|
|
251
227
|
* @param {'blobs' | 'icons' | 'maps'} mediaType
|
|
252
228
|
* @returns {Promise<string>}
|
|
@@ -442,9 +418,10 @@ export class MapeoManager extends TypedEmitter {
|
|
|
442
418
|
|
|
443
419
|
// 7. Load config, if relevant
|
|
444
420
|
// TODO: see how to expose warnings to frontend
|
|
445
|
-
|
|
421
|
+
// eslint-disable-next-line no-unused-vars
|
|
446
422
|
let warnings
|
|
447
423
|
if (configPath) {
|
|
424
|
+
// eslint-disable-next-line no-unused-vars
|
|
448
425
|
warnings = await project.importConfig({ configPath })
|
|
449
426
|
}
|
|
450
427
|
|
|
@@ -505,6 +482,7 @@ export class MapeoManager extends TypedEmitter {
|
|
|
505
482
|
async #createProjectInstance(projectKeys) {
|
|
506
483
|
validateProjectKeys(projectKeys)
|
|
507
484
|
const projectId = keyToId(projectKeys.projectKey)
|
|
485
|
+
const isArchiveDevice = this.getIsArchiveDevice()
|
|
508
486
|
const project = new MapeoProject({
|
|
509
487
|
...this.#projectStorage(projectId),
|
|
510
488
|
...projectKeys,
|
|
@@ -515,6 +493,7 @@ export class MapeoManager extends TypedEmitter {
|
|
|
515
493
|
localPeers: this.#localPeers,
|
|
516
494
|
logger: this.#loggerBase,
|
|
517
495
|
getMediaBaseUrl: this.#getMediaBaseUrl.bind(this),
|
|
496
|
+
isArchiveDevice,
|
|
518
497
|
})
|
|
519
498
|
await project[kClearDataIfLeft]()
|
|
520
499
|
return project
|
|
@@ -577,7 +556,7 @@ export class MapeoManager extends TypedEmitter {
|
|
|
577
556
|
* downloaded their proof of project membership and the project config.
|
|
578
557
|
*
|
|
579
558
|
* @param {Pick<import('./generated/rpc.js').ProjectJoinDetails, 'projectKey' | 'encryptionKeys'> & { projectName: string }} projectJoinDetails
|
|
580
|
-
* @param {{ waitForSync?: boolean }} [opts]
|
|
559
|
+
* @param {{ waitForSync?: boolean }} [opts] Set opts.waitForSync = false to not wait for sync during addProject()
|
|
581
560
|
* @returns {Promise<string>}
|
|
582
561
|
*/
|
|
583
562
|
addProject = async (
|
|
@@ -731,9 +710,7 @@ export class MapeoManager extends TypedEmitter {
|
|
|
731
710
|
}
|
|
732
711
|
|
|
733
712
|
/**
|
|
734
|
-
* @typedef {
|
|
735
|
-
* import('./schema/client.js').DeviceInfoParam['deviceType'],
|
|
736
|
-
* 'selfHostedServer'>} RPCDeviceType
|
|
713
|
+
* @typedef {import('./schema/client.js').DeviceInfoParam['deviceType']} RPCDeviceType
|
|
737
714
|
*/
|
|
738
715
|
|
|
739
716
|
/**
|
|
@@ -744,10 +721,10 @@ export class MapeoManager extends TypedEmitter {
|
|
|
744
721
|
async setDeviceInfo(deviceInfo) {
|
|
745
722
|
const values = { deviceId: this.#deviceId, deviceInfo }
|
|
746
723
|
this.#db
|
|
747
|
-
.insert(
|
|
724
|
+
.insert(deviceSettingsTable)
|
|
748
725
|
.values(values)
|
|
749
726
|
.onConflictDoUpdate({
|
|
750
|
-
target:
|
|
727
|
+
target: deviceSettingsTable.deviceId,
|
|
751
728
|
set: values,
|
|
752
729
|
})
|
|
753
730
|
.run()
|
|
@@ -760,13 +737,22 @@ export class MapeoManager extends TypedEmitter {
|
|
|
760
737
|
})
|
|
761
738
|
)
|
|
762
739
|
|
|
763
|
-
|
|
764
|
-
this
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
740
|
+
if (deviceInfo.deviceType !== 'selfHostedServer') {
|
|
741
|
+
// We have to make a copy of this because TypeScript can't guarantee that
|
|
742
|
+
// `deviceInfo` won't be mutated by the time it gets to the
|
|
743
|
+
// `sendDeviceInfo` call below.
|
|
744
|
+
const deviceInfoToSend = {
|
|
745
|
+
...deviceInfo,
|
|
746
|
+
deviceType: deviceInfo.deviceType,
|
|
747
|
+
}
|
|
748
|
+
await Promise.all(
|
|
749
|
+
this.#localPeers.peers
|
|
750
|
+
.filter(({ status }) => status === 'connected')
|
|
751
|
+
.map((peer) =>
|
|
752
|
+
this.#localPeers.sendDeviceInfo(peer.deviceId, deviceInfoToSend)
|
|
753
|
+
)
|
|
754
|
+
)
|
|
755
|
+
}
|
|
770
756
|
|
|
771
757
|
this.#l.log('set device info %o', deviceInfo)
|
|
772
758
|
}
|
|
@@ -782,8 +768,8 @@ export class MapeoManager extends TypedEmitter {
|
|
|
782
768
|
getDeviceInfo() {
|
|
783
769
|
const row = this.#db
|
|
784
770
|
.select()
|
|
785
|
-
.from(
|
|
786
|
-
.where(eq(
|
|
771
|
+
.from(deviceSettingsTable)
|
|
772
|
+
.where(eq(deviceSettingsTable.deviceId, this.#deviceId))
|
|
787
773
|
.get()
|
|
788
774
|
return {
|
|
789
775
|
deviceId: this.#deviceId,
|
|
@@ -792,6 +778,50 @@ export class MapeoManager extends TypedEmitter {
|
|
|
792
778
|
}
|
|
793
779
|
}
|
|
794
780
|
|
|
781
|
+
/**
|
|
782
|
+
* Set whether this device is an archive device. Archive devices will download
|
|
783
|
+
* all media during sync, where-as non-archive devices will not download media
|
|
784
|
+
* original variants, and only download preview and thumbnail variants.
|
|
785
|
+
* @param {boolean} isArchiveDevice
|
|
786
|
+
* @returns {void}
|
|
787
|
+
*/
|
|
788
|
+
setIsArchiveDevice(isArchiveDevice) {
|
|
789
|
+
const values = { deviceId: this.#deviceId, isArchiveDevice }
|
|
790
|
+
const result = this.#db
|
|
791
|
+
.insert(deviceSettingsTable)
|
|
792
|
+
.values(values)
|
|
793
|
+
.onConflictDoUpdate({
|
|
794
|
+
target: deviceSettingsTable.deviceId,
|
|
795
|
+
set: values,
|
|
796
|
+
})
|
|
797
|
+
.run()
|
|
798
|
+
if (!result || result.changes === 0) {
|
|
799
|
+
throw new Error('Failed to set isArchiveDevice')
|
|
800
|
+
}
|
|
801
|
+
for (const project of this.#activeProjects.values()) {
|
|
802
|
+
project[kSetIsArchiveDevice](isArchiveDevice)
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Get whether this device is an archive device. Archive devices will download
|
|
808
|
+
* all media during sync, where-as non-archive devices will not download media
|
|
809
|
+
* original variants, and only download preview and thumbnail variants.
|
|
810
|
+
* @returns {boolean} isArchiveDevice
|
|
811
|
+
*/
|
|
812
|
+
getIsArchiveDevice() {
|
|
813
|
+
const row = this.#db
|
|
814
|
+
.select()
|
|
815
|
+
.from(deviceSettingsTable)
|
|
816
|
+
.where(eq(deviceSettingsTable.deviceId, this.#deviceId))
|
|
817
|
+
.get()
|
|
818
|
+
if (typeof row?.isArchiveDevice === 'boolean') {
|
|
819
|
+
return row.isArchiveDevice
|
|
820
|
+
} else {
|
|
821
|
+
return true
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
795
825
|
/**
|
|
796
826
|
* @returns {InviteApi}
|
|
797
827
|
*/
|
|
@@ -917,15 +947,8 @@ export class MapeoManager extends TypedEmitter {
|
|
|
917
947
|
* @returns {PublicPeerInfo[]}
|
|
918
948
|
*/
|
|
919
949
|
function omitPeerProtomux(peers) {
|
|
920
|
-
return peers.map(
|
|
921
|
-
(
|
|
922
|
-
// @ts-ignore
|
|
923
|
-
// eslint-disable-next-line no-unused-vars
|
|
924
|
-
protomux,
|
|
925
|
-
...publicPeerInfo
|
|
926
|
-
}) => {
|
|
927
|
-
return publicPeerInfo
|
|
928
|
-
}
|
|
950
|
+
return peers.map((peer) =>
|
|
951
|
+
'protomux' in peer ? omit(peer, ['protomux']) : peer
|
|
929
952
|
)
|
|
930
953
|
}
|
|
931
954
|
|
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,19 +38,26 @@ 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'
|
|
48
|
+
import { omit } from './lib/omit.js'
|
|
46
49
|
import { MemberApi } from './member-api.js'
|
|
47
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
SyncApi,
|
|
52
|
+
kHandleDiscoveryKey,
|
|
53
|
+
kWaitForInitialSyncWithPeer,
|
|
54
|
+
} from './sync/sync-api.js'
|
|
48
55
|
import { Logger } from './logger.js'
|
|
49
56
|
import { IconApi } from './icon-api.js'
|
|
50
57
|
import { readConfig } from './config-import.js'
|
|
51
58
|
import TranslationApi from './translation-api.js'
|
|
52
59
|
/** @import { ProjectSettingsValue } from '@comapeo/schema' */
|
|
53
|
-
/** @import { CoreStorage, KeyPair, Namespace } from './types.js' */
|
|
60
|
+
/** @import { CoreStorage, KeyPair, Namespace, ReplicationStream } from './types.js' */
|
|
54
61
|
|
|
55
62
|
/** @typedef {Omit<ProjectSettingsValue, 'schemaName'>} EditableProjectSettings */
|
|
56
63
|
/** @typedef {ProjectSettingsValue['configMetadata']} ConfigMetadata */
|
|
@@ -65,6 +72,8 @@ export const kProjectReplicate = Symbol('replicate project')
|
|
|
65
72
|
export const kDataTypes = Symbol('dataTypes')
|
|
66
73
|
export const kProjectLeave = Symbol('leave project')
|
|
67
74
|
export const kClearDataIfLeft = Symbol('clear data if left project')
|
|
75
|
+
export const kSetIsArchiveDevice = Symbol('set isArchiveDevice')
|
|
76
|
+
export const kIsArchiveDevice = Symbol('isArchiveDevice (temp - test only)')
|
|
68
77
|
|
|
69
78
|
const EMPTY_PROJECT_SETTINGS = Object.freeze({})
|
|
70
79
|
|
|
@@ -72,8 +81,9 @@ const EMPTY_PROJECT_SETTINGS = Object.freeze({})
|
|
|
72
81
|
* @extends {TypedEmitter<{ close: () => void }>}
|
|
73
82
|
*/
|
|
74
83
|
export class MapeoProject extends TypedEmitter {
|
|
75
|
-
#
|
|
84
|
+
#projectKey
|
|
76
85
|
#deviceId
|
|
86
|
+
#identityKeypair
|
|
77
87
|
#coreManager
|
|
78
88
|
#indexWriter
|
|
79
89
|
#dataStores
|
|
@@ -90,6 +100,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
90
100
|
#l
|
|
91
101
|
/** @type {Boolean} this avoids loading multiple configs in parallel */
|
|
92
102
|
#loadingConfig
|
|
103
|
+
#isArchiveDevice
|
|
93
104
|
|
|
94
105
|
static EMPTY_PROJECT_SETTINGS = EMPTY_PROJECT_SETTINGS
|
|
95
106
|
|
|
@@ -106,6 +117,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
106
117
|
* @param {CoreStorage} opts.coreStorage Folder to store all hypercore data
|
|
107
118
|
* @param {(mediaType: 'blobs' | 'icons') => Promise<string>} opts.getMediaBaseUrl
|
|
108
119
|
* @param {import('./local-peers.js').LocalPeers} opts.localPeers
|
|
120
|
+
* @param {boolean} opts.isArchiveDevice Whether this device is an archive device
|
|
109
121
|
* @param {Logger} [opts.logger]
|
|
110
122
|
*
|
|
111
123
|
*/
|
|
@@ -122,20 +134,58 @@ export class MapeoProject extends TypedEmitter {
|
|
|
122
134
|
getMediaBaseUrl,
|
|
123
135
|
localPeers,
|
|
124
136
|
logger,
|
|
137
|
+
isArchiveDevice,
|
|
125
138
|
}) {
|
|
126
139
|
super()
|
|
127
140
|
|
|
128
141
|
this.#l = Logger.create('project', logger)
|
|
129
142
|
this.#deviceId = getDeviceId(keyManager)
|
|
130
|
-
this.#
|
|
143
|
+
this.#projectKey = projectKey
|
|
131
144
|
this.#loadingConfig = false
|
|
145
|
+
this.#isArchiveDevice = isArchiveDevice
|
|
146
|
+
|
|
147
|
+
const getReplicationStream = this[kProjectReplicate].bind(this, true)
|
|
132
148
|
|
|
133
149
|
///////// 1. Setup database
|
|
150
|
+
|
|
134
151
|
this.#sqlite = new Database(dbPath)
|
|
135
152
|
const db = drizzle(this.#sqlite)
|
|
136
|
-
migrate(db, {
|
|
153
|
+
const migrationResult = migrate(db, {
|
|
154
|
+
migrationsFolder: projectMigrationsFolder,
|
|
155
|
+
})
|
|
156
|
+
let reindex
|
|
157
|
+
switch (migrationResult) {
|
|
158
|
+
case 'initialized database':
|
|
159
|
+
case 'no migration':
|
|
160
|
+
reindex = false
|
|
161
|
+
break
|
|
162
|
+
case 'migrated':
|
|
163
|
+
reindex = true
|
|
164
|
+
break
|
|
165
|
+
default:
|
|
166
|
+
throw new ExhaustivenessError(migrationResult)
|
|
167
|
+
}
|
|
137
168
|
|
|
138
|
-
|
|
169
|
+
const indexedTables = [
|
|
170
|
+
observationTable,
|
|
171
|
+
trackTable,
|
|
172
|
+
presetTable,
|
|
173
|
+
fieldTable,
|
|
174
|
+
coreOwnershipTable,
|
|
175
|
+
roleTable,
|
|
176
|
+
deviceInfoTable,
|
|
177
|
+
iconTable,
|
|
178
|
+
translationTable,
|
|
179
|
+
remoteDetectionAlertTable,
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
///////// 2. Wipe data if we need to re-index
|
|
183
|
+
|
|
184
|
+
if (reindex) {
|
|
185
|
+
for (const table of indexedTables) db.delete(table).run()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
///////// 3. Setup random-access-storage functions
|
|
139
189
|
|
|
140
190
|
/** @type {ConstructorParameters<typeof CoreManager>[0]['storage']} */
|
|
141
191
|
const coreManagerStorage = (name) =>
|
|
@@ -145,7 +195,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
145
195
|
const indexerStorage = (name) =>
|
|
146
196
|
coreStorage(path.join(INDEXER_STORAGE_FOLDER_NAME, name))
|
|
147
197
|
|
|
148
|
-
/////////
|
|
198
|
+
///////// 4. Create instances
|
|
149
199
|
|
|
150
200
|
this.#coreManager = new CoreManager({
|
|
151
201
|
projectSecretKey,
|
|
@@ -158,17 +208,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
158
208
|
})
|
|
159
209
|
|
|
160
210
|
this.#indexWriter = new IndexWriter({
|
|
161
|
-
tables:
|
|
162
|
-
observationTable,
|
|
163
|
-
trackTable,
|
|
164
|
-
presetTable,
|
|
165
|
-
fieldTable,
|
|
166
|
-
coreOwnershipTable,
|
|
167
|
-
roleTable,
|
|
168
|
-
deviceInfoTable,
|
|
169
|
-
iconTable,
|
|
170
|
-
translationTable,
|
|
171
|
-
],
|
|
211
|
+
tables: indexedTables,
|
|
172
212
|
sqlite: this.#sqlite,
|
|
173
213
|
getWinner,
|
|
174
214
|
mapDoc: (doc, version) => {
|
|
@@ -190,6 +230,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
190
230
|
namespace: 'auth',
|
|
191
231
|
batch: (entries) => this.#indexWriter.batch(entries),
|
|
192
232
|
storage: indexerStorage,
|
|
233
|
+
reindex,
|
|
193
234
|
}),
|
|
194
235
|
config: new DataStore({
|
|
195
236
|
coreManager: this.#coreManager,
|
|
@@ -200,12 +241,14 @@ export class MapeoProject extends TypedEmitter {
|
|
|
200
241
|
sharedIndexWriter,
|
|
201
242
|
}),
|
|
202
243
|
storage: indexerStorage,
|
|
244
|
+
reindex,
|
|
203
245
|
}),
|
|
204
246
|
data: new DataStore({
|
|
205
247
|
coreManager: this.#coreManager,
|
|
206
248
|
namespace: 'data',
|
|
207
249
|
batch: (entries) => this.#indexWriter.batch(entries),
|
|
208
250
|
storage: indexerStorage,
|
|
251
|
+
reindex,
|
|
209
252
|
}),
|
|
210
253
|
}
|
|
211
254
|
|
|
@@ -224,6 +267,12 @@ export class MapeoProject extends TypedEmitter {
|
|
|
224
267
|
db,
|
|
225
268
|
getTranslations,
|
|
226
269
|
}),
|
|
270
|
+
remoteDetectionAlert: new DataType({
|
|
271
|
+
dataStore: this.#dataStores.data,
|
|
272
|
+
table: remoteDetectionAlertTable,
|
|
273
|
+
db,
|
|
274
|
+
getTranslations,
|
|
275
|
+
}),
|
|
227
276
|
preset: new DataType({
|
|
228
277
|
dataStore: this.#dataStores.config,
|
|
229
278
|
table: presetTable,
|
|
@@ -275,7 +324,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
275
324
|
},
|
|
276
325
|
}),
|
|
277
326
|
}
|
|
278
|
-
|
|
327
|
+
this.#identityKeypair = keyManager.getIdentityKeypair()
|
|
279
328
|
const coreKeypairs = getCoreKeypairs({
|
|
280
329
|
projectKey,
|
|
281
330
|
projectSecretKey,
|
|
@@ -284,14 +333,14 @@ export class MapeoProject extends TypedEmitter {
|
|
|
284
333
|
this.#coreOwnership = new CoreOwnership({
|
|
285
334
|
dataType: this.#dataTypes.coreOwnership,
|
|
286
335
|
coreKeypairs,
|
|
287
|
-
identityKeypair,
|
|
336
|
+
identityKeypair: this.#identityKeypair,
|
|
288
337
|
})
|
|
289
338
|
this.#roles = new Roles({
|
|
290
339
|
dataType: this.#dataTypes.role,
|
|
291
340
|
coreOwnership: this.#coreOwnership,
|
|
292
341
|
coreManager: this.#coreManager,
|
|
293
342
|
projectKey: projectKey,
|
|
294
|
-
deviceKey:
|
|
343
|
+
deviceKey: this.#identityKeypair.publicKey,
|
|
295
344
|
})
|
|
296
345
|
|
|
297
346
|
this.#memberApi = new MemberApi({
|
|
@@ -299,16 +348,18 @@ export class MapeoProject extends TypedEmitter {
|
|
|
299
348
|
roles: this.#roles,
|
|
300
349
|
coreOwnership: this.#coreOwnership,
|
|
301
350
|
encryptionKeys,
|
|
351
|
+
getProjectName: this.#getProjectName.bind(this),
|
|
302
352
|
projectKey,
|
|
303
353
|
rpc: localPeers,
|
|
354
|
+
getReplicationStream,
|
|
355
|
+
waitForInitialSyncWithPeer: (deviceId, abortSignal) =>
|
|
356
|
+
this.$sync[kWaitForInitialSyncWithPeer](deviceId, abortSignal),
|
|
304
357
|
dataTypes: {
|
|
305
358
|
deviceInfo: this.#dataTypes.deviceInfo,
|
|
306
359
|
project: this.#dataTypes.projectSettings,
|
|
307
360
|
},
|
|
308
361
|
})
|
|
309
362
|
|
|
310
|
-
const projectPublicId = projectKeyToPublicId(projectKey)
|
|
311
|
-
|
|
312
363
|
this.#blobStore = new BlobStore({
|
|
313
364
|
coreManager: this.#coreManager,
|
|
314
365
|
})
|
|
@@ -320,7 +371,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
320
371
|
if (!base.endsWith('/')) {
|
|
321
372
|
base += '/'
|
|
322
373
|
}
|
|
323
|
-
return base + projectPublicId
|
|
374
|
+
return base + this.#projectPublicId
|
|
324
375
|
},
|
|
325
376
|
})
|
|
326
377
|
|
|
@@ -332,7 +383,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
332
383
|
if (!base.endsWith('/')) {
|
|
333
384
|
base += '/'
|
|
334
385
|
}
|
|
335
|
-
return base + projectPublicId
|
|
386
|
+
return base + this.#projectPublicId
|
|
336
387
|
},
|
|
337
388
|
})
|
|
338
389
|
|
|
@@ -340,14 +391,33 @@ export class MapeoProject extends TypedEmitter {
|
|
|
340
391
|
coreManager: this.#coreManager,
|
|
341
392
|
coreOwnership: this.#coreOwnership,
|
|
342
393
|
roles: this.#roles,
|
|
394
|
+
blobDownloadFilter: null,
|
|
343
395
|
logger: this.#l,
|
|
396
|
+
getServerWebsocketUrls: async () => {
|
|
397
|
+
const members = await this.#memberApi.getMany()
|
|
398
|
+
/** @type {string[]} */
|
|
399
|
+
const serverWebsocketUrls = []
|
|
400
|
+
for (const member of members) {
|
|
401
|
+
if (
|
|
402
|
+
member.deviceType === 'selfHostedServer' &&
|
|
403
|
+
member.selfHostedServerDetails
|
|
404
|
+
) {
|
|
405
|
+
const { baseUrl } = member.selfHostedServerDetails
|
|
406
|
+
const wsUrl = new URL(`/sync/${this.#projectPublicId}`, baseUrl)
|
|
407
|
+
wsUrl.protocol = wsUrl.protocol === 'http:' ? 'ws:' : 'wss:'
|
|
408
|
+
serverWebsocketUrls.push(wsUrl.href)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return serverWebsocketUrls
|
|
412
|
+
},
|
|
413
|
+
getReplicationStream,
|
|
344
414
|
})
|
|
345
415
|
|
|
346
416
|
this.#translationApi = new TranslationApi({
|
|
347
417
|
dataType: this.#dataTypes.translation,
|
|
348
418
|
})
|
|
349
419
|
|
|
350
|
-
/////////
|
|
420
|
+
///////// 5. Replicate local peers automatically
|
|
351
421
|
|
|
352
422
|
// Replicate already connected local peers
|
|
353
423
|
for (const peer of localPeers.peers) {
|
|
@@ -415,6 +485,14 @@ export class MapeoProject extends TypedEmitter {
|
|
|
415
485
|
return this.#deviceId
|
|
416
486
|
}
|
|
417
487
|
|
|
488
|
+
get #projectId() {
|
|
489
|
+
return projectKeyToId(this.#projectKey)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
get #projectPublicId() {
|
|
493
|
+
return projectKeyToPublicId(this.#projectKey)
|
|
494
|
+
}
|
|
495
|
+
|
|
418
496
|
/**
|
|
419
497
|
* Resolves when hypercores have all loaded
|
|
420
498
|
*
|
|
@@ -498,6 +576,10 @@ export class MapeoProject extends TypedEmitter {
|
|
|
498
576
|
return this.#dataTypes.field
|
|
499
577
|
}
|
|
500
578
|
|
|
579
|
+
get remoteDetectionAlert() {
|
|
580
|
+
return this.#dataTypes.remoteDetectionAlert
|
|
581
|
+
}
|
|
582
|
+
|
|
501
583
|
get $member() {
|
|
502
584
|
return this.#memberApi
|
|
503
585
|
}
|
|
@@ -556,6 +638,13 @@ export class MapeoProject extends TypedEmitter {
|
|
|
556
638
|
}
|
|
557
639
|
}
|
|
558
640
|
|
|
641
|
+
/**
|
|
642
|
+
* @returns {Promise<undefined | string>}
|
|
643
|
+
*/
|
|
644
|
+
async #getProjectName() {
|
|
645
|
+
return (await this.$getProjectSettings()).name
|
|
646
|
+
}
|
|
647
|
+
|
|
559
648
|
async $getOwnRole() {
|
|
560
649
|
return this.#roles.getRole(this.#deviceId)
|
|
561
650
|
}
|
|
@@ -577,28 +666,38 @@ export class MapeoProject extends TypedEmitter {
|
|
|
577
666
|
/**
|
|
578
667
|
* Replicate a project to a @hyperswarm/secret-stream. Invites will not
|
|
579
668
|
* function because the RPC channel is not connected for project replication,
|
|
580
|
-
* and only this project will replicate
|
|
581
|
-
* need to replicate the manager instance via manager[kManagerReplicate])
|
|
669
|
+
* and only this project will replicate.
|
|
582
670
|
*
|
|
583
|
-
* @param {
|
|
671
|
+
* @param {(
|
|
672
|
+
* boolean |
|
|
673
|
+
* import('stream').Duplex |
|
|
674
|
+
* import('streamx').Duplex
|
|
675
|
+
* )} isInitiatorOrStream
|
|
676
|
+
* @returns {ReplicationStream}
|
|
584
677
|
*/
|
|
585
|
-
[kProjectReplicate](
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
678
|
+
[kProjectReplicate](isInitiatorOrStream) {
|
|
679
|
+
const replicationStream = this.#coreManager.creatorCore.replicate(
|
|
680
|
+
isInitiatorOrStream,
|
|
681
|
+
/**
|
|
682
|
+
* Hypercore types need updating.
|
|
683
|
+
* @type {any}
|
|
684
|
+
*/ ({
|
|
685
|
+
keyPair: this.#identityKeypair,
|
|
686
|
+
/** @param {Buffer} discoveryKey */
|
|
687
|
+
ondiscoverykey: async (discoveryKey) => {
|
|
688
|
+
const protomux =
|
|
689
|
+
/** @type {import('protomux')<import('@hyperswarm/secret-stream')>} */ (
|
|
690
|
+
replicationStream.noiseStream.userData
|
|
691
|
+
)
|
|
692
|
+
this.#syncApi[kHandleDiscoveryKey](discoveryKey, protomux)
|
|
693
|
+
},
|
|
694
|
+
})
|
|
695
|
+
)
|
|
597
696
|
return replicationStream
|
|
598
697
|
}
|
|
599
698
|
|
|
600
699
|
/**
|
|
601
|
-
* @param {Pick<import('@comapeo/schema').DeviceInfoValue, 'name' | 'deviceType'>} value
|
|
700
|
+
* @param {Pick<import('@comapeo/schema').DeviceInfoValue, 'name' | 'deviceType' | 'selfHostedServerDetails'>} value
|
|
602
701
|
* @returns {Promise<import('@comapeo/schema').DeviceInfo>}
|
|
603
702
|
*/
|
|
604
703
|
async [kSetOwnDeviceInfo](value) {
|
|
@@ -611,6 +710,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
611
710
|
const doc = {
|
|
612
711
|
name: value.name,
|
|
613
712
|
deviceType: value.deviceType,
|
|
713
|
+
selfHostedServerDetails: value.selfHostedServerDetails,
|
|
614
714
|
schemaName: /** @type {const} */ ('deviceInfo'),
|
|
615
715
|
}
|
|
616
716
|
|
|
@@ -624,6 +724,17 @@ export class MapeoProject extends TypedEmitter {
|
|
|
624
724
|
return deviceInfo.update(existingDoc.versionId, doc)
|
|
625
725
|
}
|
|
626
726
|
|
|
727
|
+
/** @param {boolean} isArchiveDevice */
|
|
728
|
+
async [kSetIsArchiveDevice](isArchiveDevice) {
|
|
729
|
+
this.#isArchiveDevice = isArchiveDevice
|
|
730
|
+
// TODO: call this.#syncApi[kSetBlobDownloadFilter]()
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/** @returns {boolean} */
|
|
734
|
+
get [kIsArchiveDevice]() {
|
|
735
|
+
return this.#isArchiveDevice
|
|
736
|
+
}
|
|
737
|
+
|
|
627
738
|
/**
|
|
628
739
|
* @returns {import('./icon-api.js').IconApi}
|
|
629
740
|
*/
|
|
@@ -887,9 +998,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
887
998
|
* @returns {EditableProjectSettings}
|
|
888
999
|
*/
|
|
889
1000
|
function extractEditableProjectSettings(projectDoc) {
|
|
890
|
-
|
|
891
|
-
const { schemaName, ...result } = valueOf(projectDoc)
|
|
892
|
-
return result
|
|
1001
|
+
return omit(valueOf(projectDoc), ['schemaName'])
|
|
893
1002
|
}
|
|
894
1003
|
|
|
895
1004
|
/**
|