@comapeo/core 6.0.2 → 7.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.
Files changed (43) hide show
  1. package/dist/blob-store/downloader.d.ts +4 -9
  2. package/dist/blob-store/downloader.d.ts.map +1 -1
  3. package/dist/blob-store/hyperdrive-index.d.ts +4 -5
  4. package/dist/blob-store/hyperdrive-index.d.ts.map +1 -1
  5. package/dist/blob-store/index.d.ts +4 -5
  6. package/dist/blob-store/index.d.ts.map +1 -1
  7. package/dist/core-manager/index.d.ts +4 -23
  8. package/dist/core-manager/index.d.ts.map +1 -1
  9. package/dist/datastore/index.d.ts +1 -1
  10. package/dist/generated/extensions.d.ts +0 -30
  11. package/dist/generated/extensions.d.ts.map +1 -1
  12. package/dist/generated/rpc.d.ts +30 -0
  13. package/dist/generated/rpc.d.ts.map +1 -1
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/local-peers.d.ts +12 -0
  17. package/dist/local-peers.d.ts.map +1 -1
  18. package/dist/mapeo-manager.d.ts +41 -3
  19. package/dist/mapeo-manager.d.ts.map +1 -1
  20. package/dist/mapeo-project.d.ts +3 -76
  21. package/dist/mapeo-project.d.ts.map +1 -1
  22. package/dist/schema/project.d.ts +1 -1
  23. package/dist/sync/namespace-sync-state.d.ts +1 -1
  24. package/dist/sync/peer-sync-controller.d.ts +1 -1
  25. package/dist/sync/sync-api.d.ts.map +1 -1
  26. package/dist/utils.d.ts +3 -3
  27. package/dist/utils.d.ts.map +1 -1
  28. package/package.json +2 -1
  29. package/src/blob-store/downloader.js +12 -4
  30. package/src/blob-store/hyperdrive-index.js +22 -3
  31. package/src/blob-store/index.js +18 -7
  32. package/src/core-manager/index.js +16 -70
  33. package/src/generated/extensions.d.ts +0 -30
  34. package/src/generated/extensions.js +0 -165
  35. package/src/generated/extensions.ts +0 -204
  36. package/src/generated/rpc.d.ts +30 -0
  37. package/src/generated/rpc.js +191 -0
  38. package/src/generated/rpc.ts +236 -0
  39. package/src/index.js +2 -2
  40. package/src/local-peers.js +33 -0
  41. package/src/mapeo-manager.js +93 -35
  42. package/src/mapeo-project.js +18 -106
  43. package/src/utils.js +2 -2
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable */
2
+ import Long from "long";
2
3
  import _m0 from "protobufjs/minimal.js";
3
4
  import { EncryptionKeys } from "./keys.js";
4
5
 
@@ -181,6 +182,31 @@ export interface ProjectJoinDetailsAck {
181
182
  inviteId: Buffer;
182
183
  }
183
184
 
185
+ export interface MapShareExtension {
186
+ /** URLs to map share */
187
+ mapShareUrls: string[];
188
+ /** ID of peer that can receive the map share (each map share is linked to a specific device ID) */
189
+ receiverDeviceKey: Buffer;
190
+ /** The ID of the map share */
191
+ shareId: string;
192
+ /** The name of the map being shared */
193
+ mapName: string;
194
+ /** The ID of the map being shared */
195
+ mapId: string;
196
+ /** When ths share was created */
197
+ mapShareCreatedAt: number;
198
+ /** When the map was created */
199
+ mapCreatedAt: number;
200
+ /** The bounding box of the map data being shared */
201
+ bounds: number[];
202
+ /** The minimum zoom level of the map data being shared */
203
+ minzoom: number;
204
+ /** The maximum zoom level of the map data being shared */
205
+ maxzoom: number;
206
+ /** Estimated size of the map data being shared in bytes */
207
+ estimatedSizeBytes: number;
208
+ }
209
+
184
210
  function createBaseInvite(): Invite {
185
211
  return {
186
212
  inviteId: Buffer.alloc(0),
@@ -761,6 +787,204 @@ export const ProjectJoinDetailsAck = {
761
787
  },
762
788
  };
763
789
 
790
+ function createBaseMapShareExtension(): MapShareExtension {
791
+ return {
792
+ mapShareUrls: [],
793
+ receiverDeviceKey: Buffer.alloc(0),
794
+ shareId: "",
795
+ mapName: "",
796
+ mapId: "",
797
+ mapShareCreatedAt: 0,
798
+ mapCreatedAt: 0,
799
+ bounds: [],
800
+ minzoom: 0,
801
+ maxzoom: 0,
802
+ estimatedSizeBytes: 0,
803
+ };
804
+ }
805
+
806
+ export const MapShareExtension = {
807
+ encode(message: MapShareExtension, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
808
+ for (const v of message.mapShareUrls) {
809
+ writer.uint32(10).string(v!);
810
+ }
811
+ if (message.receiverDeviceKey.length !== 0) {
812
+ writer.uint32(18).bytes(message.receiverDeviceKey);
813
+ }
814
+ if (message.shareId !== "") {
815
+ writer.uint32(26).string(message.shareId);
816
+ }
817
+ if (message.mapName !== "") {
818
+ writer.uint32(34).string(message.mapName);
819
+ }
820
+ if (message.mapId !== "") {
821
+ writer.uint32(42).string(message.mapId);
822
+ }
823
+ if (message.mapShareCreatedAt !== 0) {
824
+ writer.uint32(48).uint64(message.mapShareCreatedAt);
825
+ }
826
+ if (message.mapCreatedAt !== 0) {
827
+ writer.uint32(56).uint64(message.mapCreatedAt);
828
+ }
829
+ writer.uint32(66).fork();
830
+ for (const v of message.bounds) {
831
+ writer.double(v);
832
+ }
833
+ writer.ldelim();
834
+ if (message.minzoom !== 0) {
835
+ writer.uint32(72).int32(message.minzoom);
836
+ }
837
+ if (message.maxzoom !== 0) {
838
+ writer.uint32(80).int32(message.maxzoom);
839
+ }
840
+ if (message.estimatedSizeBytes !== 0) {
841
+ writer.uint32(88).uint64(message.estimatedSizeBytes);
842
+ }
843
+ return writer;
844
+ },
845
+
846
+ decode(input: _m0.Reader | Uint8Array, length?: number): MapShareExtension {
847
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
848
+ let end = length === undefined ? reader.len : reader.pos + length;
849
+ const message = createBaseMapShareExtension();
850
+ while (reader.pos < end) {
851
+ const tag = reader.uint32();
852
+ switch (tag >>> 3) {
853
+ case 1:
854
+ if (tag !== 10) {
855
+ break;
856
+ }
857
+
858
+ message.mapShareUrls.push(reader.string());
859
+ continue;
860
+ case 2:
861
+ if (tag !== 18) {
862
+ break;
863
+ }
864
+
865
+ message.receiverDeviceKey = reader.bytes() as Buffer;
866
+ continue;
867
+ case 3:
868
+ if (tag !== 26) {
869
+ break;
870
+ }
871
+
872
+ message.shareId = reader.string();
873
+ continue;
874
+ case 4:
875
+ if (tag !== 34) {
876
+ break;
877
+ }
878
+
879
+ message.mapName = reader.string();
880
+ continue;
881
+ case 5:
882
+ if (tag !== 42) {
883
+ break;
884
+ }
885
+
886
+ message.mapId = reader.string();
887
+ continue;
888
+ case 6:
889
+ if (tag !== 48) {
890
+ break;
891
+ }
892
+
893
+ message.mapShareCreatedAt = longToNumber(reader.uint64() as Long);
894
+ continue;
895
+ case 7:
896
+ if (tag !== 56) {
897
+ break;
898
+ }
899
+
900
+ message.mapCreatedAt = longToNumber(reader.uint64() as Long);
901
+ continue;
902
+ case 8:
903
+ if (tag === 65) {
904
+ message.bounds.push(reader.double());
905
+
906
+ continue;
907
+ }
908
+
909
+ if (tag === 66) {
910
+ const end2 = reader.uint32() + reader.pos;
911
+ while (reader.pos < end2) {
912
+ message.bounds.push(reader.double());
913
+ }
914
+
915
+ continue;
916
+ }
917
+
918
+ break;
919
+ case 9:
920
+ if (tag !== 72) {
921
+ break;
922
+ }
923
+
924
+ message.minzoom = reader.int32();
925
+ continue;
926
+ case 10:
927
+ if (tag !== 80) {
928
+ break;
929
+ }
930
+
931
+ message.maxzoom = reader.int32();
932
+ continue;
933
+ case 11:
934
+ if (tag !== 88) {
935
+ break;
936
+ }
937
+
938
+ message.estimatedSizeBytes = longToNumber(reader.uint64() as Long);
939
+ continue;
940
+ }
941
+ if ((tag & 7) === 4 || tag === 0) {
942
+ break;
943
+ }
944
+ reader.skipType(tag & 7);
945
+ }
946
+ return message;
947
+ },
948
+
949
+ create<I extends Exact<DeepPartial<MapShareExtension>, I>>(base?: I): MapShareExtension {
950
+ return MapShareExtension.fromPartial(base ?? ({} as any));
951
+ },
952
+ fromPartial<I extends Exact<DeepPartial<MapShareExtension>, I>>(object: I): MapShareExtension {
953
+ const message = createBaseMapShareExtension();
954
+ message.mapShareUrls = object.mapShareUrls?.map((e) => e) || [];
955
+ message.receiverDeviceKey = object.receiverDeviceKey ?? Buffer.alloc(0);
956
+ message.shareId = object.shareId ?? "";
957
+ message.mapName = object.mapName ?? "";
958
+ message.mapId = object.mapId ?? "";
959
+ message.mapShareCreatedAt = object.mapShareCreatedAt ?? 0;
960
+ message.mapCreatedAt = object.mapCreatedAt ?? 0;
961
+ message.bounds = object.bounds?.map((e) => e) || [];
962
+ message.minzoom = object.minzoom ?? 0;
963
+ message.maxzoom = object.maxzoom ?? 0;
964
+ message.estimatedSizeBytes = object.estimatedSizeBytes ?? 0;
965
+ return message;
966
+ },
967
+ };
968
+
969
+ declare const self: any | undefined;
970
+ declare const window: any | undefined;
971
+ declare const global: any | undefined;
972
+ const tsProtoGlobalThis: any = (() => {
973
+ if (typeof globalThis !== "undefined") {
974
+ return globalThis;
975
+ }
976
+ if (typeof self !== "undefined") {
977
+ return self;
978
+ }
979
+ if (typeof window !== "undefined") {
980
+ return window;
981
+ }
982
+ if (typeof global !== "undefined") {
983
+ return global;
984
+ }
985
+ throw "Unable to locate global object";
986
+ })();
987
+
764
988
  type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
765
989
 
766
990
  type DeepPartial<T> = T extends Builtin ? T
@@ -771,3 +995,15 @@ type DeepPartial<T> = T extends Builtin ? T
771
995
  type KeysOfUnion<T> = T extends T ? keyof T : never;
772
996
  type Exact<P, I extends P> = P extends Builtin ? P
773
997
  : P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };
998
+
999
+ function longToNumber(long: Long): number {
1000
+ if (long.gt(Number.MAX_SAFE_INTEGER)) {
1001
+ throw new tsProtoGlobalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
1002
+ }
1003
+ return long.toNumber();
1004
+ }
1005
+
1006
+ if (_m0.util.Long !== Long) {
1007
+ _m0.util.Long = Long as any;
1008
+ _m0.configure();
1009
+ }
package/src/index.js CHANGED
@@ -9,8 +9,8 @@ export { FastifyController } from './fastify-controller.js'
9
9
  export { MapeoManager } from './mapeo-manager.js'
10
10
 
11
11
  // Type exports
12
- /** @typedef {import('./mapeo-project.js').MapShare} MapShare */
13
- /** @typedef {import('./generated/extensions.js').MapShareExtension} MapShareExtension */
12
+ /** @typedef {import('./mapeo-manager.js').MapShare} MapShare */
13
+ /** @typedef {import('./generated/rpc.js').MapShareExtension} MapShareExtension */
14
14
  /** @typedef {import('./mapeo-project.js').MapeoProject} MapeoProject */
15
15
  /** @typedef {import('./mapeo-project.js').RoleChangeEvent} RoleChangeEvent */
16
16
  /** @typedef {import('./mapeo-manager.js').PublicPeerInfo} PublicPeerInfo */
@@ -15,6 +15,7 @@ import {
15
15
  ProjectJoinDetails,
16
16
  ProjectJoinDetailsAck,
17
17
  DeviceInfo_RPCFeatures,
18
+ MapShareExtension,
18
19
  } from './generated/rpc.js'
19
20
  import pDefer from 'p-defer'
20
21
  import { Logger } from './logger.js'
@@ -69,6 +70,7 @@ const MESSAGE_TYPES = {
69
70
  InviteCancelAck: 6,
70
71
  InviteResponseAck: 7,
71
72
  ProjectJoinDetailsAck: 8,
73
+ MapShareExtension: 9,
72
74
  }
73
75
  const MESSAGES_MAX_ID = Math.max.apply(null, [...Object.values(MESSAGE_TYPES)])
74
76
 
@@ -289,6 +291,19 @@ class Peer {
289
291
  const messageType = MESSAGE_TYPES.Invite
290
292
  await this.#waitForDrain(this.#channel.messages[messageType].send(buf))
291
293
  }
294
+
295
+ /**
296
+ * @param {MapShareExtension} mapShare
297
+ * @returns {Promise<void>}
298
+ */
299
+ async sendMapShare(mapShare) {
300
+ this.#assertConnected('Peer disconnected before sending map share')
301
+ const buf = Buffer.from(MapShareExtension.encode(mapShare).finish())
302
+ const messageType = MESSAGE_TYPES.MapShareExtension
303
+ await this.#waitForDrain(this.#channel.messages[messageType].send(buf))
304
+ this.#log('sent map share %s', mapShare.shareId)
305
+ }
306
+
292
307
  /**
293
308
  * @param {Invite} invite
294
309
  * @returns {Promise<void>}
@@ -433,6 +448,7 @@ class Peer {
433
448
  * @property {(peerId: string, inviteResponse: InviteResponseAck) => void} invite-response-ack Emitted when an invite response acknowledgement is received
434
449
  * @property {(peerId: string, details: ProjectJoinDetails) => void} got-project-details Emitted when project details are received
435
450
  * @property {(peerId: string, details: ProjectJoinDetailsAck) => void} got-project-details-ack Emitted when project details are acknowledged as received
451
+ * @property {(sender: PeerInfo, details: MapShareExtension) => void} map-share Emitted when a MapShare request is received
436
452
  * @property {(discoveryKey: Buffer, protomux: Protomux<import('@hyperswarm/secret-stream')>) => void} discovery-key Emitted when a new hypercore is replicated (by a peer) to a peer protomux instance (passed as the second parameter)
437
453
  * @property {(messageType: string, errorMessage?: string) => void} failed-to-handle-message Emitted when we received a message we couldn't handle for some reason. Primarily useful for testing
438
454
  */
@@ -468,6 +484,17 @@ export class LocalPeers extends TypedEmitter {
468
484
  return connectedPeerInfos
469
485
  }
470
486
 
487
+ /**
488
+ * @param {string} deviceId
489
+ * @param {MapShareExtension} mapShare
490
+ * @returns {Promise<void>}
491
+ */
492
+ async sendMapShare(deviceId, mapShare) {
493
+ await this.#waitForPendingConnections()
494
+ const peer = await this.#getPeerByDeviceId(deviceId)
495
+ await peer.sendMapShare(mapShare)
496
+ }
497
+
471
498
  /**
472
499
  * @param {string} deviceId
473
500
  * @param {Invite} invite
@@ -783,6 +810,12 @@ export class LocalPeers extends TypedEmitter {
783
810
  this.#emitPeers()
784
811
  break
785
812
  }
813
+ case 'MapShareExtension': {
814
+ const mapShare = MapShareExtension.decode(value)
815
+ const info = /** @type {PeerInfo} */ (peer.info)
816
+ this.emit('map-share', info, mapShare)
817
+ break
818
+ }
786
819
  case 'InviteAck': {
787
820
  const ack = InviteAck.decode(value)
788
821
  peer.receiveAck('InviteAck', ack)
@@ -7,6 +7,7 @@ import { drizzle } from 'drizzle-orm/better-sqlite3'
7
7
  import Hypercore from 'hypercore'
8
8
  import { TypedEmitter } from 'tiny-typed-emitter'
9
9
  import { createRequire } from 'module'
10
+ import ensureError from 'ensure-error'
10
11
 
11
12
  import { IndexWriter } from './index-writer/index.js'
12
13
  import {
@@ -36,6 +37,7 @@ import {
36
37
  projectKeyToProjectInviteId,
37
38
  projectKeyToPublicId,
38
39
  timeoutPromise,
40
+ validateMapShareExtension,
39
41
  } from './utils.js'
40
42
  import { openedNoiseSecretStream } from './lib/noise-secret-stream-helpers.js'
41
43
  import { omit } from './lib/omit.js'
@@ -61,7 +63,7 @@ import { WebSocket } from 'ws'
61
63
  import { excludeKeys } from 'filter-obj'
62
64
  import { migrate } from './lib/drizzle-helpers.js'
63
65
 
64
- /** @import { MapShareExtension } from './generated/extensions.js' */
66
+ /** @import { MapShareExtension } from './generated/rpc.js' */
65
67
  /** @import NoiseSecretStream from '@hyperswarm/secret-stream' */
66
68
  /** @import { SetNonNullable } from 'type-fest' */
67
69
  /** @import { ProjectJoinDetails, } from './generated/rpc.js' */
@@ -74,6 +76,24 @@ import { migrate } from './lib/drizzle-helpers.js'
74
76
  /** @typedef {Pick<ProjectSettings, 'createdAt' | 'updatedAt' | 'name' | 'projectColor' | 'projectDescription' | 'sendStats'>} ListedProjectSettings */
75
77
  /** @typedef {ListedProjectSettings & { status: 'joined', projectId: string } | ProjectInfo & { status: 'joining' | 'left', projectId: string }} ListedProject */
76
78
 
79
+ /**
80
+ * @typedef {object} AugmentedMapShareProperties
81
+ * @property {readonly [number, number, number, number]} bounds - Bounding box of the shared map [W, S, E, N].
82
+ * @property {readonly [string, ...string[]]} mapShareUrls - URLs associated with the map share.
83
+ * @property {number} mapShareReceivedAt - Timestamp when the map share was received.
84
+ * @property {string} senderDeviceId - The ID of the device that sent the map share.
85
+ * @property {string} [senderDeviceName] - The name of the device that sent the map share.
86
+ * @property {string} receiverDeviceId - The deviceId of the peer the map share was sent to
87
+ */
88
+
89
+ /**
90
+ * @typedef {Omit<MapShareExtension, 'bounds' | 'mapShareUrls' | 'receiverDeviceKey'> & AugmentedMapShareProperties} MapShare
91
+ */
92
+
93
+ /**
94
+ * @typedef {Omit<MapShare, 'mapShareReceivedAt' | 'senderDeviceId' | 'senderDeviceName'>} MapShareSend
95
+ */
96
+
77
97
  const CLIENT_SQLITE_FILE_NAME = 'client.db'
78
98
 
79
99
  // Max file descriptors that RandomAccessFile should use for hypercore storage
@@ -107,7 +127,7 @@ export const DEFAULT_IS_ARCHIVE_DEVICE = true
107
127
  /**
108
128
  * @typedef {object} MapeoManagerEvents
109
129
  * @property {(peers: PublicPeerInfo[]) => void} local-peers Emitted when the list of connected peers changes (new ones added, or connection status changes)
110
- * @property {(mapShare: import('./mapeo-project.js').MapShare) => void} map-share Emitted when a project has recieved a map share request
130
+ * @property {(mapShare: MapShare) => void} map-share Emitted when a project has recieved a map share request
111
131
  * @property {(e: Error, mapShare: MapShareExtension) => void} map-share-error - Emitted when an incoming map share fails to be recieved due to formatting issues
112
132
  */
113
133
 
@@ -202,6 +222,15 @@ export class MapeoManager extends TypedEmitter {
202
222
  this.#l.log('Received dk %h but no active projects', dk)
203
223
  }
204
224
  })
225
+ this.#localPeers.on('map-share', (deviceId, mapShareBase) =>
226
+ this.#handleMapShare(deviceId, mapShareBase).catch((e) => {
227
+ this.emit('map-share-error', e, mapShareBase)
228
+ this.#l.log(
229
+ 'Error: Unable to handle incoming Map Share',
230
+ ensureError(e)
231
+ )
232
+ })
233
+ )
205
234
 
206
235
  this.#projectSettingsIndexWriter = new IndexWriter({
207
236
  tables: [projectSettingsTable],
@@ -491,6 +520,11 @@ export class MapeoManager extends TypedEmitter {
491
520
  // TODO: Close the project instance instead of keeping it around
492
521
  this.#activeProjects.set(projectPublicId, project)
493
522
 
523
+ // Make sure to clean up when closed
524
+ project.once('close', () => {
525
+ this.#activeProjects.delete(projectPublicId)
526
+ })
527
+
494
528
  // 7. Load config, if relevant
495
529
  // TODO: see how to expose warnings to frontend
496
530
  // eslint-disable-next-line no-unused-vars
@@ -554,6 +588,11 @@ export class MapeoManager extends TypedEmitter {
554
588
  // 3. Keep track of project instance as we know it's a properly existing project
555
589
  this.#activeProjects.set(projectPublicId, project)
556
590
 
591
+ // Make sure to clean up when closed
592
+ project.once('close', () => {
593
+ this.#activeProjects.delete(projectPublicId)
594
+ })
595
+
557
596
  return project
558
597
  }
559
598
 
@@ -583,34 +622,6 @@ export class MapeoManager extends TypedEmitter {
583
622
  },
584
623
  })
585
624
 
586
- const projectPublicId = projectKeyToPublicId(projectKeys.projectKey)
587
-
588
- /**
589
- * @param {import('./mapeo-project.js').MapShare} mapShare
590
- */
591
- const onMapShare = (mapShare) => {
592
- this.emit('map-share', mapShare)
593
- }
594
-
595
- /**
596
- *
597
- * @param {Error} e
598
- * @param {MapShareExtension} mapShareExtension
599
- */
600
- const onMapShareError = (e, mapShareExtension) => {
601
- this.emit('map-share-error', e, mapShareExtension)
602
- }
603
-
604
- project.once('close', () => {
605
- this.#activeProjects.delete(projectPublicId)
606
- project.removeListener('map-share', onMapShare)
607
- project.removeListener('map-share-error', onMapShareError)
608
- })
609
-
610
- project.on('map-share', onMapShare)
611
-
612
- project.on('map-share-error', onMapShareError)
613
-
614
625
  return project
615
626
  }
616
627
 
@@ -781,11 +792,6 @@ export class MapeoManager extends TypedEmitter {
781
792
  throw ensureKnownError(e)
782
793
  }
783
794
 
784
- // Make sure to clean up when closed
785
- project.once('close', () => {
786
- this.#activeProjects.delete(projectPublicId)
787
- })
788
-
789
795
  // Only write info on invite if configured
790
796
  if (!invitorWroteDeviceInfo) {
791
797
  try {
@@ -1046,6 +1052,58 @@ export class MapeoManager extends TypedEmitter {
1046
1052
  })
1047
1053
  }
1048
1054
 
1055
+ /**
1056
+ * Send a map share offer to the peer with device ID `mapShare.receiverDeviceId`
1057
+ *
1058
+ * @param {MapShareSend} mapShare
1059
+ * @param {object} [options]
1060
+ * @param {boolean} [options.__testOnlyBypassValidation=false] Warning: Do not use!
1061
+ */
1062
+ async sendMapShare(mapShare, { __testOnlyBypassValidation = false } = {}) {
1063
+ const { receiverDeviceId, ...mapShareData } = mapShare
1064
+ const receiverDeviceKey = Buffer.from(receiverDeviceId, 'hex')
1065
+
1066
+ /** @type {MapShareExtension} */
1067
+ // @ts-expect-error readonly fields being assigned as mutable
1068
+ const shareExtension = {
1069
+ ...mapShareData,
1070
+ receiverDeviceKey,
1071
+ }
1072
+ if (!__testOnlyBypassValidation) {
1073
+ validateMapShareExtension(shareExtension)
1074
+ }
1075
+ await this.#localPeers.sendMapShare(receiverDeviceId, shareExtension)
1076
+ }
1077
+
1078
+ /**
1079
+ * @param {import('./local-peers.js').PeerInfo} sender
1080
+ * @param {MapShareExtension} mapShareBase
1081
+ */
1082
+ async #handleMapShare(sender, mapShareBase) {
1083
+ const mapShareReceivedAt = Date.now()
1084
+
1085
+ validateMapShareExtension(mapShareBase)
1086
+
1087
+ const { receiverDeviceKey, ...mapShareData } = mapShareBase
1088
+
1089
+ const receiverDeviceId = receiverDeviceKey.toString('hex')
1090
+
1091
+ if (receiverDeviceId !== this.#deviceId) {
1092
+ throw new Error('Got map share intended for a different peer')
1093
+ }
1094
+
1095
+ /** @type {MapShare} */
1096
+ const mapShare = {
1097
+ ...mapShareData,
1098
+ senderDeviceId: sender.deviceId,
1099
+ senderDeviceName: sender.name,
1100
+ mapShareReceivedAt,
1101
+ receiverDeviceId,
1102
+ }
1103
+
1104
+ this.emit('map-share', mapShare)
1105
+ }
1106
+
1049
1107
  async getMapStyleJsonUrl() {
1050
1108
  await timeoutPromise(Promise.resolve(this.#fastify.ready()), {
1051
1109
  milliseconds: 1000,