@comapeo/core 5.5.0 → 6.0.1
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-api.d.ts.map +1 -1
- package/dist/blob-store/downloader.d.ts.map +1 -1
- package/dist/blob-store/hyperdrive-index.d.ts.map +1 -1
- package/dist/blob-store/index.d.ts.map +1 -1
- package/dist/core-manager/bitfield-rle.d.ts.map +1 -1
- package/dist/core-manager/core-index.d.ts.map +1 -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.map +1 -1
- package/dist/datatype/index.d.ts +7 -0
- package/dist/datatype/index.d.ts.map +1 -1
- package/dist/discovery/local-discovery.d.ts.map +1 -1
- package/dist/errors.d.ts +437 -35
- package/dist/errors.d.ts.map +1 -1
- package/dist/fastify-plugins/blobs.d.ts.map +1 -1
- package/dist/fastify-plugins/icons.d.ts.map +1 -1
- package/dist/fastify-plugins/maps.d.ts.map +1 -1
- package/dist/generated/rpc.d.ts +1 -0
- package/dist/generated/rpc.d.ts.map +1 -1
- package/dist/icon-api.d.ts +0 -1
- package/dist/icon-api.d.ts.map +1 -1
- package/dist/import-categories.d.ts.map +1 -1
- package/dist/index-writer/index.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/intl/parse-bcp-47.d.ts.map +1 -1
- package/dist/invite/invite-api.d.ts.map +1 -1
- package/dist/lib/drizzle-helpers.d.ts.map +1 -1
- package/dist/lib/hypercore-helpers.d.ts.map +1 -1
- package/dist/lib/key-by.d.ts.map +1 -1
- package/dist/local-peers.d.ts +0 -14
- package/dist/local-peers.d.ts.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/mapeo-manager.d.ts +2 -1
- package/dist/mapeo-manager.d.ts.map +1 -1
- package/dist/mapeo-project.d.ts +1 -3
- package/dist/mapeo-project.d.ts.map +1 -1
- package/dist/member-api.d.ts +42 -7
- package/dist/member-api.d.ts.map +1 -1
- package/dist/roles.d.ts.map +1 -1
- package/dist/schema/json-schema-to-drizzle.d.ts.map +1 -1
- package/dist/schema.d.ts +2 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/sync/core-sync-state.d.ts.map +1 -1
- package/dist/sync/peer-sync-controller.d.ts.map +1 -1
- package/dist/sync/sync-api.d.ts.map +1 -1
- package/dist/utils.d.ts +8 -10
- package/dist/utils.d.ts.map +1 -1
- package/package.json +19 -3
- package/src/blob-api.js +24 -4
- package/src/blob-store/downloader.js +7 -6
- package/src/blob-store/entries-stream.js +1 -1
- package/src/blob-store/hyperdrive-index.js +3 -5
- package/src/blob-store/index.js +15 -20
- package/src/core-manager/bitfield-rle.js +2 -1
- package/src/core-manager/core-index.js +2 -1
- package/src/core-manager/index.js +9 -10
- package/src/core-ownership.js +7 -3
- package/src/datastore/index.js +13 -9
- package/src/datatype/index.js +28 -5
- package/src/discovery/local-discovery.js +8 -7
- package/src/errors.js +530 -62
- package/src/fastify-controller.js +3 -3
- package/src/fastify-plugins/blobs.js +21 -14
- package/src/fastify-plugins/icons.js +18 -9
- package/src/fastify-plugins/maps.js +6 -5
- package/src/generated/rpc.d.ts +1 -0
- package/src/generated/rpc.js +12 -1
- package/src/generated/rpc.ts +13 -0
- package/src/icon-api.js +15 -7
- package/src/import-categories.js +6 -7
- package/src/index-writer/index.js +3 -2
- package/src/index.js +1 -0
- package/src/intl/parse-bcp-47.js +2 -1
- package/src/invite/invite-api.js +26 -20
- package/src/lib/drizzle-helpers.js +54 -39
- package/src/lib/hypercore-helpers.js +4 -2
- package/src/lib/key-by.js +3 -1
- package/src/local-peers.js +39 -46
- package/src/logger.js +2 -1
- package/src/mapeo-manager.js +36 -23
- package/src/mapeo-project.js +68 -61
- package/src/member-api.js +177 -96
- package/src/roles.js +11 -10
- package/src/schema/json-schema-to-drizzle.js +13 -4
- package/src/schema.js +1 -0
- package/src/sync/core-sync-state.js +2 -1
- package/src/sync/peer-sync-controller.js +4 -3
- package/src/sync/sync-api.js +9 -9
- package/src/translation-api.js +2 -2
- package/src/utils.js +56 -41
- package/dist/lib/error.d.ts +0 -51
- package/dist/lib/error.d.ts.map +0 -1
- package/src/lib/error.js +0 -71
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { sql } from 'drizzle-orm'
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
-
import { assert } from '../utils.js'
|
|
5
4
|
import { migrate as drizzleMigrate } from 'drizzle-orm/better-sqlite3/migrator'
|
|
6
5
|
import { DRIZZLE_MIGRATIONS_TABLE } from '../constants.js'
|
|
6
|
+
import {
|
|
7
|
+
InvalidDrizzleJournalError,
|
|
8
|
+
InvalidDrizzleQueryResultError,
|
|
9
|
+
MigrationError,
|
|
10
|
+
} from '../errors.js'
|
|
7
11
|
/** @import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3' */
|
|
8
12
|
|
|
9
13
|
/**
|
|
@@ -11,13 +15,16 @@ import { DRIZZLE_MIGRATIONS_TABLE } from '../constants.js'
|
|
|
11
15
|
* @returns {number}
|
|
12
16
|
*/
|
|
13
17
|
const getNumberResult = (queryResult) => {
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
if (
|
|
19
|
+
!(
|
|
20
|
+
queryResult &&
|
|
16
21
|
typeof queryResult === 'object' &&
|
|
17
22
|
'result' in queryResult &&
|
|
18
|
-
typeof queryResult.result === 'number'
|
|
19
|
-
|
|
20
|
-
)
|
|
23
|
+
typeof queryResult.result === 'number'
|
|
24
|
+
)
|
|
25
|
+
) {
|
|
26
|
+
throw new InvalidDrizzleQueryResultError()
|
|
27
|
+
}
|
|
21
28
|
return queryResult.result
|
|
22
29
|
}
|
|
23
30
|
|
|
@@ -68,37 +75,40 @@ const safeGetLatestMigrationMillis = (db) =>
|
|
|
68
75
|
* @returns {MigrationResult}
|
|
69
76
|
*/
|
|
70
77
|
export function migrate(db, { migrationsFolder, migrationFns = {} }) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
try {
|
|
79
|
+
const journal = /** @type {unknown} */ (
|
|
80
|
+
JSON.parse(
|
|
81
|
+
fs.readFileSync(
|
|
82
|
+
path.join(migrationsFolder, 'meta/_journal.json'),
|
|
83
|
+
'utf-8'
|
|
84
|
+
)
|
|
76
85
|
)
|
|
77
86
|
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
assertValidJournal(journal)
|
|
82
|
-
|
|
83
|
-
const prevMigrationMillis = safeGetLatestMigrationMillis(db)
|
|
87
|
+
// Drizzle _could_ decide to change the journal format in the future, but this
|
|
88
|
+
// assertion will ensure that tests fail if they do.
|
|
89
|
+
assertValidJournal(journal)
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
migrationsFolder,
|
|
87
|
-
migrationsTable: DRIZZLE_MIGRATIONS_TABLE,
|
|
88
|
-
})
|
|
91
|
+
const prevMigrationMillis = safeGetLatestMigrationMillis(db)
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
93
|
+
drizzleMigrate(db, {
|
|
94
|
+
migrationsFolder,
|
|
95
|
+
migrationsTable: DRIZZLE_MIGRATIONS_TABLE,
|
|
96
|
+
})
|
|
95
97
|
|
|
96
|
-
|
|
98
|
+
for (const entry of journal.entries) {
|
|
99
|
+
if (entry.when <= prevMigrationMillis) continue
|
|
100
|
+
const fn = migrationFns[entry.tag]
|
|
101
|
+
if (fn) fn(db)
|
|
102
|
+
}
|
|
97
103
|
|
|
98
|
-
|
|
104
|
+
const lastMigrationMillis = safeGetLatestMigrationMillis(db)
|
|
99
105
|
|
|
100
|
-
|
|
106
|
+
if (lastMigrationMillis === prevMigrationMillis) return 'no migration'
|
|
101
107
|
|
|
108
|
+
if (prevMigrationMillis === 0) return 'initialized database'
|
|
109
|
+
} catch (e) {
|
|
110
|
+
throw new MigrationError({ cause: e })
|
|
111
|
+
}
|
|
102
112
|
return 'migrated'
|
|
103
113
|
}
|
|
104
114
|
|
|
@@ -108,13 +118,16 @@ export function migrate(db, { migrationsFolder, migrationFns = {} }) {
|
|
|
108
118
|
* @returns {asserts journal is { version: '5', entries: { tag: string, when: number }[] }}
|
|
109
119
|
*/
|
|
110
120
|
function assertValidJournal(journal) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
if (!(journal && typeof journal === 'object')) {
|
|
122
|
+
throw new InvalidDrizzleJournalError()
|
|
123
|
+
}
|
|
124
|
+
if (!('version' in journal && journal.version === '5')) {
|
|
125
|
+
throw new InvalidDrizzleJournalError('unexpected journal version')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (
|
|
129
|
+
!(
|
|
130
|
+
'entries' in journal &&
|
|
118
131
|
Array.isArray(journal.entries) &&
|
|
119
132
|
journal.entries.every(
|
|
120
133
|
/** @param {unknown} m */
|
|
@@ -125,7 +138,9 @@ function assertValidJournal(journal) {
|
|
|
125
138
|
typeof m.tag === 'string' &&
|
|
126
139
|
'when' in m &&
|
|
127
140
|
typeof m.when === 'number'
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
)
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
) {
|
|
144
|
+
throw new InvalidDrizzleJournalError('invalid entries in journal')
|
|
145
|
+
}
|
|
131
146
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MissingDiscoveryKeyError } from '../errors.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @param {import('hypercore')<'binary', any>} core Core to unreplicate. Must be ready.
|
|
5
5
|
* @param {import('protomux')} protomux
|
|
6
6
|
*/
|
|
7
7
|
export function unreplicate(core, protomux) {
|
|
8
|
-
|
|
8
|
+
if (!core.discoveryKey) {
|
|
9
|
+
throw new MissingDiscoveryKeyError()
|
|
10
|
+
}
|
|
9
11
|
protomux.unpair({
|
|
10
12
|
protocol: 'hypercore/alpha',
|
|
11
13
|
id: core.discoveryKey,
|
package/src/lib/key-by.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DuplicateKeyError } from '../errors.js'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Like [`Map.groupBy`][0], but the result's values aren't arrays.
|
|
3
5
|
*
|
|
@@ -16,7 +18,7 @@ export function keyBy(items, callbackFn) {
|
|
|
16
18
|
for (const item of items) {
|
|
17
19
|
const key = callbackFn(item)
|
|
18
20
|
if (result.has(key)) {
|
|
19
|
-
throw new
|
|
21
|
+
throw new DuplicateKeyError({ key: `${key}` })
|
|
20
22
|
}
|
|
21
23
|
result.set(key, item)
|
|
22
24
|
}
|
package/src/local-peers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TypedEmitter } from 'tiny-typed-emitter'
|
|
2
2
|
import Protomux from 'protomux'
|
|
3
3
|
import timingSafeEqual from 'string-timing-safe-equal'
|
|
4
|
-
import {
|
|
4
|
+
import { keyToId, noop, timeoutPromise } from './utils.js'
|
|
5
5
|
import { isBlank } from './lib/string.js'
|
|
6
6
|
import cenc from 'compact-encoding'
|
|
7
7
|
import {
|
|
@@ -18,10 +18,15 @@ import {
|
|
|
18
18
|
} from './generated/rpc.js'
|
|
19
19
|
import pDefer from 'p-defer'
|
|
20
20
|
import { Logger } from './logger.js'
|
|
21
|
-
import pTimeout, { TimeoutError } from 'p-timeout'
|
|
22
21
|
import {
|
|
22
|
+
PeerDisconnectedError,
|
|
23
|
+
PeerFailedConnectionError,
|
|
23
24
|
RPCDisconnectBeforeAckError,
|
|
24
25
|
RPCDisconnectBeforeSendingError,
|
|
26
|
+
UnknownPeerError,
|
|
27
|
+
ExhaustivenessError,
|
|
28
|
+
InvalidInviteError,
|
|
29
|
+
InvalidProjectJoinDetailsError,
|
|
25
30
|
} from './errors.js'
|
|
26
31
|
/** @import NoiseStream from '@hyperswarm/secret-stream' */
|
|
27
32
|
/** @import { OpenedNoiseStream } from './lib/noise-secret-stream-helpers.js' */
|
|
@@ -157,7 +162,7 @@ class Peer {
|
|
|
157
162
|
}
|
|
158
163
|
/* c8 ignore next 2 */
|
|
159
164
|
default:
|
|
160
|
-
throw new ExhaustivenessError(this.#state)
|
|
165
|
+
throw new ExhaustivenessError({ value: this.#state })
|
|
161
166
|
}
|
|
162
167
|
}
|
|
163
168
|
/**
|
|
@@ -280,7 +285,7 @@ class Peer {
|
|
|
280
285
|
* @returns {Promise<void>}
|
|
281
286
|
*/
|
|
282
287
|
async [kTestOnlySendRawInvite](buf) {
|
|
283
|
-
this.#assertConnected()
|
|
288
|
+
this.#assertConnected('Peer not connected for test')
|
|
284
289
|
const messageType = MESSAGE_TYPES.Invite
|
|
285
290
|
await this.#waitForDrain(this.#channel.messages[messageType].send(buf))
|
|
286
291
|
}
|
|
@@ -408,7 +413,7 @@ class Peer {
|
|
|
408
413
|
this.#features = deviceInfo.features
|
|
409
414
|
this.#log('received deviceInfo %o', deviceInfo)
|
|
410
415
|
}
|
|
411
|
-
/** @param {string}
|
|
416
|
+
/** @param {string} message */
|
|
412
417
|
#assertConnected(message) {
|
|
413
418
|
if (this.#state === 'connected' && !this.#channel.closed) return
|
|
414
419
|
/* c8 ignore next */
|
|
@@ -536,7 +541,6 @@ export class LocalPeers extends TypedEmitter {
|
|
|
536
541
|
*/
|
|
537
542
|
connect(stream) {
|
|
538
543
|
const noiseStream = stream.noiseStream
|
|
539
|
-
if (!noiseStream) throw new Error('Invalid stream')
|
|
540
544
|
const outerStream = noiseStream.rawStream
|
|
541
545
|
const protomux =
|
|
542
546
|
noiseStream.userData && Protomux.isProtomux(noiseStream.userData)
|
|
@@ -617,8 +621,8 @@ export class LocalPeers extends TypedEmitter {
|
|
|
617
621
|
/** @type {keyof typeof MESSAGE_TYPES} */ (type),
|
|
618
622
|
message
|
|
619
623
|
)
|
|
620
|
-
} catch (
|
|
621
|
-
const errorMessage = String(
|
|
624
|
+
} catch (e) {
|
|
625
|
+
const errorMessage = String(e)
|
|
622
626
|
this.emit('failed-to-handle-message', type, errorMessage)
|
|
623
627
|
this.#l.log(`Error handling ${type} message: ${errorMessage}`)
|
|
624
628
|
}
|
|
@@ -817,7 +821,9 @@ export class LocalPeers extends TypedEmitter {
|
|
|
817
821
|
* Wait for any connections that are currently opening
|
|
818
822
|
*/
|
|
819
823
|
#waitForPendingConnections() {
|
|
820
|
-
return
|
|
824
|
+
return timeoutPromise(Promise.all(this.#opening), {
|
|
825
|
+
milliseconds: SEND_TIMEOUT,
|
|
826
|
+
})
|
|
821
827
|
}
|
|
822
828
|
|
|
823
829
|
/**
|
|
@@ -830,14 +836,14 @@ export class LocalPeers extends TypedEmitter {
|
|
|
830
836
|
async #getPeerByDeviceId(deviceId) {
|
|
831
837
|
const devicePeers = this.#peers.get(deviceId)
|
|
832
838
|
if (!devicePeers || devicePeers.size === 0) {
|
|
833
|
-
throw new UnknownPeerError(
|
|
839
|
+
throw new UnknownPeerError({ deviceId: deviceId.slice(0, 7) })
|
|
834
840
|
}
|
|
835
841
|
const peer = chooseDevicePeer(devicePeers)
|
|
836
842
|
if (peer) return peer
|
|
837
843
|
return new Promise((resolve, reject) => {
|
|
838
844
|
const timeoutId = setTimeout(() => {
|
|
839
845
|
this.off('peers', onPeers)
|
|
840
|
-
reject(new UnknownPeerError(
|
|
846
|
+
reject(new UnknownPeerError({ deviceId: deviceId.slice(0, 7) }))
|
|
841
847
|
}, DEDUPE_TIMEOUT)
|
|
842
848
|
|
|
843
849
|
const onPeers = () => {
|
|
@@ -854,38 +860,14 @@ export class LocalPeers extends TypedEmitter {
|
|
|
854
860
|
}
|
|
855
861
|
}
|
|
856
862
|
|
|
857
|
-
export { TimeoutError }
|
|
858
|
-
|
|
859
|
-
export class UnknownPeerError extends Error {
|
|
860
|
-
/** @param {string} [message] */
|
|
861
|
-
constructor(message = 'UnknownPeerError') {
|
|
862
|
-
super(message)
|
|
863
|
-
this.name = 'UnknownPeerError'
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
export class PeerDisconnectedError extends Error {
|
|
868
|
-
/** @param {string} [message] */
|
|
869
|
-
constructor(message = 'Peer disconnected') {
|
|
870
|
-
super(message)
|
|
871
|
-
this.name = 'PeerDisconnectedError'
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
export class PeerFailedConnectionError extends Error {
|
|
876
|
-
/** @param {string} [message] */
|
|
877
|
-
constructor(message = 'PeerFailedConnectionError') {
|
|
878
|
-
super(message)
|
|
879
|
-
this.name = 'PeerFailedConnectionError'
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
863
|
/**
|
|
884
864
|
* @param {Readonly<Uint8Array>} id
|
|
885
865
|
* @throws if the invite ID is too short
|
|
886
866
|
*/
|
|
887
867
|
function assertInviteIdIsValid(id) {
|
|
888
|
-
|
|
868
|
+
if (id.byteLength < 32) {
|
|
869
|
+
throw new InvalidInviteError('Invite ID must be >= 32 bytes')
|
|
870
|
+
}
|
|
889
871
|
}
|
|
890
872
|
|
|
891
873
|
/**
|
|
@@ -896,9 +878,15 @@ function assertInviteIdIsValid(id) {
|
|
|
896
878
|
function parseInvite(data) {
|
|
897
879
|
const result = Invite.decode(data)
|
|
898
880
|
assertInviteIdIsValid(result.inviteId)
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
881
|
+
if (!result.projectInviteId.length) {
|
|
882
|
+
throw new InvalidInviteError('Invite must have project invite ID')
|
|
883
|
+
}
|
|
884
|
+
if (isBlank(result.projectName)) {
|
|
885
|
+
throw new InvalidInviteError('Invite project name cannot be blank')
|
|
886
|
+
}
|
|
887
|
+
if (isBlank(result.invitorName)) {
|
|
888
|
+
throw new InvalidInviteError('Invite invitor name cannot be blank')
|
|
889
|
+
}
|
|
902
890
|
return result
|
|
903
891
|
}
|
|
904
892
|
|
|
@@ -932,11 +920,16 @@ function parseInviteResponse(data) {
|
|
|
932
920
|
function parseProjectJoinDetails(data) {
|
|
933
921
|
const result = ProjectJoinDetails.decode(data)
|
|
934
922
|
assertInviteIdIsValid(result.inviteId)
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
923
|
+
if (!result.projectKey.length) {
|
|
924
|
+
throw new InvalidProjectJoinDetailsError(
|
|
925
|
+
'Project join details must have project key'
|
|
926
|
+
)
|
|
927
|
+
}
|
|
928
|
+
if (!result.encryptionKeys?.auth?.byteLength) {
|
|
929
|
+
throw new InvalidProjectJoinDetailsError(
|
|
930
|
+
'Project join details must have auth encryption keys'
|
|
931
|
+
)
|
|
932
|
+
}
|
|
940
933
|
return result
|
|
941
934
|
}
|
|
942
935
|
|
package/src/logger.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import createDebug from 'debug'
|
|
2
|
+
import ensureError from 'ensure-error'
|
|
2
3
|
import { discoveryKey } from 'hypercore-crypto'
|
|
3
4
|
import mapObject from 'map-obj'
|
|
4
5
|
import util from 'util'
|
|
@@ -69,7 +70,7 @@ createDebug.formatters.X = function (v) {
|
|
|
69
70
|
breakLength: 90,
|
|
70
71
|
})
|
|
71
72
|
} catch (e) {
|
|
72
|
-
return `[ERROR: $(e.message
|
|
73
|
+
return `[ERROR: ${ensureError(e).message}]`
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
|
package/src/mapeo-manager.js
CHANGED
|
@@ -6,7 +6,6 @@ import { eq, and } from 'drizzle-orm'
|
|
|
6
6
|
import { drizzle } from 'drizzle-orm/better-sqlite3'
|
|
7
7
|
import Hypercore from 'hypercore'
|
|
8
8
|
import { TypedEmitter } from 'tiny-typed-emitter'
|
|
9
|
-
import pTimeout from 'p-timeout'
|
|
10
9
|
import { createRequire } from 'module'
|
|
11
10
|
|
|
12
11
|
import { IndexWriter } from './index-writer/index.js'
|
|
@@ -36,6 +35,7 @@ import {
|
|
|
36
35
|
projectKeyToId,
|
|
37
36
|
projectKeyToProjectInviteId,
|
|
38
37
|
projectKeyToPublicId,
|
|
38
|
+
timeoutPromise,
|
|
39
39
|
} from './utils.js'
|
|
40
40
|
import { openedNoiseSecretStream } from './lib/noise-secret-stream-helpers.js'
|
|
41
41
|
import { omit } from './lib/omit.js'
|
|
@@ -49,7 +49,14 @@ import { InviteApi } from './invite/invite-api.js'
|
|
|
49
49
|
import { LocalDiscovery } from './discovery/local-discovery.js'
|
|
50
50
|
import { Logger } from './logger.js'
|
|
51
51
|
import { kRequestFullStop, kRescindFullStopRequest } from './sync/sync-api.js'
|
|
52
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
EncryptionKeysNotFoundError,
|
|
54
|
+
ensureKnownError,
|
|
55
|
+
ExhaustivenessError,
|
|
56
|
+
FailedToSetIsArchiveDeviceError,
|
|
57
|
+
NotFoundError,
|
|
58
|
+
ProjectExistsError,
|
|
59
|
+
} from './errors.js'
|
|
53
60
|
import { WebSocket } from 'ws'
|
|
54
61
|
import { excludeKeys } from 'filter-obj'
|
|
55
62
|
import { migrate } from './lib/drizzle-helpers.js'
|
|
@@ -63,7 +70,7 @@ import { migrate } from './lib/drizzle-helpers.js'
|
|
|
63
70
|
/** @import { ProjectSettings, ProjectSettingsValue } from '@comapeo/schema' */
|
|
64
71
|
|
|
65
72
|
/** @typedef {SetNonNullable<ProjectKeys, 'encryptionKeys'>} ValidatedProjectKeys */
|
|
66
|
-
/** @typedef {Pick<ProjectJoinDetails, 'projectKey' | 'encryptionKeys'> & { projectName: string, projectColor?: string, projectDescription?: string, sendStats?: boolean }} ProjectToAddDetails */
|
|
73
|
+
/** @typedef {Pick<ProjectJoinDetails, 'projectKey' | 'encryptionKeys'> & { projectName: string, projectColor?: string, projectDescription?: string, sendStats?: boolean, invitorWroteDeviceInfo? : boolean }} ProjectToAddDetails */
|
|
67
74
|
/** @typedef {Pick<ProjectSettings, 'createdAt' | 'updatedAt' | 'name' | 'projectColor' | 'projectDescription' | 'sendStats'>} ListedProjectSettings */
|
|
68
75
|
/** @typedef {ListedProjectSettings & { status: 'joined', projectId: string } | ProjectInfo & { status: 'joining' | 'left', projectId: string }} ListedProject */
|
|
69
76
|
|
|
@@ -277,7 +284,7 @@ export class MapeoManager extends TypedEmitter {
|
|
|
277
284
|
break
|
|
278
285
|
}
|
|
279
286
|
default: {
|
|
280
|
-
throw new
|
|
287
|
+
throw new ExhaustivenessError({ value: mediaType })
|
|
281
288
|
}
|
|
282
289
|
}
|
|
283
290
|
|
|
@@ -587,11 +594,11 @@ export class MapeoManager extends TypedEmitter {
|
|
|
587
594
|
|
|
588
595
|
/**
|
|
589
596
|
*
|
|
590
|
-
* @param {Error}
|
|
597
|
+
* @param {Error} e
|
|
591
598
|
* @param {MapShareExtension} mapShareExtension
|
|
592
599
|
*/
|
|
593
|
-
const onMapShareError = (
|
|
594
|
-
this.emit('map-share-error',
|
|
600
|
+
const onMapShareError = (e, mapShareExtension) => {
|
|
601
|
+
this.emit('map-share-error', e, mapShareExtension)
|
|
595
602
|
}
|
|
596
603
|
|
|
597
604
|
project.once('close', () => {
|
|
@@ -696,6 +703,7 @@ export class MapeoManager extends TypedEmitter {
|
|
|
696
703
|
projectColor,
|
|
697
704
|
projectDescription,
|
|
698
705
|
sendStats = false,
|
|
706
|
+
invitorWroteDeviceInfo = false,
|
|
699
707
|
},
|
|
700
708
|
{ waitForSync = true } = {}
|
|
701
709
|
) => {
|
|
@@ -715,7 +723,7 @@ export class MapeoManager extends TypedEmitter {
|
|
|
715
723
|
.get()
|
|
716
724
|
|
|
717
725
|
if (existingProject && existingProject.hasLeftProject !== true) {
|
|
718
|
-
throw new
|
|
726
|
+
throw new ProjectExistsError({ projectPublicId })
|
|
719
727
|
}
|
|
720
728
|
|
|
721
729
|
// 2. Check for an active project
|
|
@@ -770,7 +778,7 @@ export class MapeoManager extends TypedEmitter {
|
|
|
770
778
|
.delete(projectKeysTable)
|
|
771
779
|
.where(eq(projectKeysTable.projectId, projectId))
|
|
772
780
|
.run()
|
|
773
|
-
throw e
|
|
781
|
+
throw ensureKnownError(e)
|
|
774
782
|
}
|
|
775
783
|
|
|
776
784
|
// Make sure to clean up when closed
|
|
@@ -778,18 +786,21 @@ export class MapeoManager extends TypedEmitter {
|
|
|
778
786
|
this.#activeProjects.delete(projectPublicId)
|
|
779
787
|
})
|
|
780
788
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
789
|
+
// Only write info on invite if configured
|
|
790
|
+
if (!invitorWroteDeviceInfo) {
|
|
791
|
+
try {
|
|
792
|
+
const deviceInfo = this.getDeviceInfo()
|
|
793
|
+
if (hasSavedDeviceInfo(deviceInfo)) {
|
|
794
|
+
await project[kSetOwnDeviceInfo](deviceInfo)
|
|
795
|
+
}
|
|
796
|
+
} catch (e) {
|
|
797
|
+
// Can ignore an error trying to write device info
|
|
798
|
+
this.#l.log(
|
|
799
|
+
'ERROR: failed to write project %h deviceInfo %o',
|
|
800
|
+
projectKey,
|
|
801
|
+
e
|
|
802
|
+
)
|
|
785
803
|
}
|
|
786
|
-
} catch (e) {
|
|
787
|
-
// Can ignore an error trying to write device info
|
|
788
|
-
this.#l.log(
|
|
789
|
-
'ERROR: failed to write project %h deviceInfo %o',
|
|
790
|
-
projectKey,
|
|
791
|
-
e
|
|
792
|
-
)
|
|
793
804
|
}
|
|
794
805
|
|
|
795
806
|
// 5. Wait for initial project sync
|
|
@@ -899,7 +910,7 @@ export class MapeoManager extends TypedEmitter {
|
|
|
899
910
|
})
|
|
900
911
|
.run()
|
|
901
912
|
if (!result || result.changes === 0) {
|
|
902
|
-
throw new
|
|
913
|
+
throw new FailedToSetIsArchiveDeviceError()
|
|
903
914
|
}
|
|
904
915
|
for (const project of this.#activeProjects.values()) {
|
|
905
916
|
project[kSetIsArchiveDevice](isArchiveDevice)
|
|
@@ -1036,7 +1047,9 @@ export class MapeoManager extends TypedEmitter {
|
|
|
1036
1047
|
}
|
|
1037
1048
|
|
|
1038
1049
|
async getMapStyleJsonUrl() {
|
|
1039
|
-
await
|
|
1050
|
+
await timeoutPromise(Promise.resolve(this.#fastify.ready()), {
|
|
1051
|
+
milliseconds: 1000,
|
|
1052
|
+
})
|
|
1040
1053
|
return (await this.#getMediaBaseUrl('maps')) + '/style.json'
|
|
1041
1054
|
}
|
|
1042
1055
|
|
|
@@ -1078,7 +1091,7 @@ function omitPeerProtomux(peers) {
|
|
|
1078
1091
|
*/
|
|
1079
1092
|
function validateProjectKeys(projectKeys) {
|
|
1080
1093
|
if (!projectKeys.encryptionKeys) {
|
|
1081
|
-
throw new
|
|
1094
|
+
throw new EncryptionKeysNotFoundError()
|
|
1082
1095
|
}
|
|
1083
1096
|
}
|
|
1084
1097
|
|