@abraca/dabra 1.0.21 → 1.0.22

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.
@@ -2822,7 +2822,7 @@ var AbracadabraProvider = class AbracadabraProvider extends AbracadabraBaseProvi
2822
2822
  this._client = client;
2823
2823
  this.abracadabraConfig = configuration;
2824
2824
  this.subdocLoading = configuration.subdocLoading ?? "lazy";
2825
- const serverOrigin = AbracadabraProvider.deriveServerOrigin(configuration, client);
2825
+ const serverOrigin = configuration.serverAgnostic ? void 0 : AbracadabraProvider.deriveServerOrigin(configuration, client);
2826
2826
  this.offlineStore = configuration.disableOfflineStore ? null : new OfflineStore(configuration.name, serverOrigin);
2827
2827
  this.on("subdocRegistered", configuration.onSubdocRegistered ?? (() => null));
2828
2828
  this.on("subdocLoaded", configuration.onSubdocLoaded ?? (() => null));
@@ -10747,6 +10747,305 @@ var BroadcastChannelSync = class BroadcastChannelSync extends EventEmitter {
10747
10747
  }
10748
10748
  };
10749
10749
 
10750
+ //#endregion
10751
+ //#region packages/provider/src/IdentityDoc.ts
10752
+ /**
10753
+ * Derives a deterministic UUID from an Ed25519 account-level public key.
10754
+ *
10755
+ * The result is a valid UUID v5-style string that any device sharing the
10756
+ * same identity can independently compute. The `abracadabra:identity:`
10757
+ * prefix prevents collisions with randomly generated doc UUIDs.
10758
+ *
10759
+ * @param publicKeyB64 Base64url-encoded Ed25519 public key (32 bytes).
10760
+ */
10761
+ function deriveIdentityDocId(publicKeyB64) {
10762
+ const hash = sha256(new TextEncoder().encode(`abracadabra:identity:${publicKeyB64}`));
10763
+ const bytes = new Uint8Array(hash.buffer, hash.byteOffset, 16);
10764
+ bytes[6] = bytes[6] & 15 | 80;
10765
+ bytes[8] = bytes[8] & 63 | 128;
10766
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
10767
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
10768
+ }
10769
+ /**
10770
+ * Manages a Y.Doc dedicated to user identity that syncs across trusted
10771
+ * targets only: a designated sync server, a local Tauri server, and/or
10772
+ * WebRTC P2P.
10773
+ *
10774
+ * The Y.Doc contains cross-device settings (profile, servers, spaces,
10775
+ * plugins, preferences). Each sync target gets its own
10776
+ * AbracadabraProvider sharing the same Y.Doc, with `serverAgnostic: true`
10777
+ * so they all use one IndexedDB store.
10778
+ */
10779
+ var IdentityDocProvider = class extends EventEmitter {
10780
+ constructor(configuration) {
10781
+ super();
10782
+ this.providers = /* @__PURE__ */ new Map();
10783
+ this.websockets = /* @__PURE__ */ new Map();
10784
+ this.webrtc = null;
10785
+ this._destroyed = false;
10786
+ this.config = configuration;
10787
+ this.docId = deriveIdentityDocId(configuration.publicKey);
10788
+ this.document = new yjs.Doc({ guid: this.docId });
10789
+ if (configuration.autoConnect !== false) this.connect();
10790
+ }
10791
+ connect() {
10792
+ if (this._destroyed) return;
10793
+ const targets = [];
10794
+ if (this.config.localServerUrl) targets.push({
10795
+ url: this.config.localServerUrl,
10796
+ key: "local"
10797
+ });
10798
+ if (this.config.syncServerUrl) targets.push({
10799
+ url: this.config.syncServerUrl,
10800
+ key: "sync"
10801
+ });
10802
+ for (const { url, key } of targets) {
10803
+ if (this.providers.has(key)) continue;
10804
+ this._connectToServer(key, url);
10805
+ }
10806
+ if (this.config.webrtc && !this.webrtc) this._connectWebRTC();
10807
+ }
10808
+ _connectToServer(key, serverUrl) {
10809
+ const token = this.config.tokens?.[serverUrl] ?? this.config.token ?? "";
10810
+ const ws = new AbracadabraWS({
10811
+ url: serverUrl.replace(/^http/, "ws").replace(/\/$/, "").concat("/ws"),
10812
+ WebSocketPolyfill: void 0
10813
+ });
10814
+ this.websockets.set(key, ws);
10815
+ const provider = new AbracadabraProvider({
10816
+ name: this.docId,
10817
+ document: this.document,
10818
+ websocketProvider: ws,
10819
+ token,
10820
+ serverAgnostic: true,
10821
+ disableOfflineStore: key !== "local" && key !== "sync" ? true : this.config.disableOfflineStore ?? false,
10822
+ ...this.config.providerDefaults
10823
+ });
10824
+ provider.on("synced", () => this.emit("synced", { server: key }));
10825
+ provider.on("status", (data) => this.emit("status", {
10826
+ server: key,
10827
+ ...data
10828
+ }));
10829
+ this.providers.set(key, provider);
10830
+ }
10831
+ _connectWebRTC() {
10832
+ const rtcConfig = this.config.webrtc;
10833
+ if (!rtcConfig) return;
10834
+ this.webrtc = new AbracadabraWebRTC({
10835
+ docId: this.docId,
10836
+ url: rtcConfig.signalingServerUrl,
10837
+ token: rtcConfig.token,
10838
+ document: this.document,
10839
+ enableDocSync: true,
10840
+ enableAwarenessSync: false,
10841
+ enableFileTransfer: false,
10842
+ e2ee: rtcConfig.e2ee,
10843
+ iceServers: rtcConfig.iceServers
10844
+ });
10845
+ }
10846
+ get profileMap() {
10847
+ return this.document.getMap("profile");
10848
+ }
10849
+ get serversMap() {
10850
+ return this.document.getMap("servers");
10851
+ }
10852
+ get spacesArray() {
10853
+ return this.document.getArray("spaces");
10854
+ }
10855
+ get pluginsMap() {
10856
+ return this.document.getMap("plugins");
10857
+ }
10858
+ get preferencesMap() {
10859
+ return this.document.getMap("preferences");
10860
+ }
10861
+ getProfile() {
10862
+ const m = this.profileMap;
10863
+ return {
10864
+ username: m.get("username"),
10865
+ displayName: m.get("displayName"),
10866
+ colorName: m.get("colorName"),
10867
+ neutralColorName: m.get("neutralColorName"),
10868
+ locale: m.get("locale"),
10869
+ avatarUrl: m.get("avatarUrl")
10870
+ };
10871
+ }
10872
+ setProfile(profile) {
10873
+ const m = this.profileMap;
10874
+ this.document.transact(() => {
10875
+ for (const [key, value] of Object.entries(profile)) if (value !== void 0) m.set(key, value);
10876
+ });
10877
+ }
10878
+ getServer(url) {
10879
+ const entry = this.serversMap.get(url);
10880
+ if (!entry) return void 0;
10881
+ return {
10882
+ label: entry.get("label") ?? url,
10883
+ hubDocId: entry.get("hubDocId"),
10884
+ entryDocId: entry.get("entryDocId"),
10885
+ defaultRole: entry.get("defaultRole"),
10886
+ spacesEnabled: entry.get("spacesEnabled"),
10887
+ addedAt: entry.get("addedAt") ?? 0
10888
+ };
10889
+ }
10890
+ setServer(url, entry) {
10891
+ const m = this.serversMap;
10892
+ this.document.transact(() => {
10893
+ let yEntry = m.get(url);
10894
+ if (!yEntry) {
10895
+ yEntry = new yjs.Map();
10896
+ m.set(url, yEntry);
10897
+ }
10898
+ for (const [key, value] of Object.entries(entry)) if (value !== void 0) yEntry.set(key, value);
10899
+ });
10900
+ }
10901
+ removeServer(url) {
10902
+ this.serversMap.delete(url);
10903
+ }
10904
+ getServers() {
10905
+ const result = /* @__PURE__ */ new Map();
10906
+ this.serversMap.forEach((entry, url) => {
10907
+ result.set(url, {
10908
+ label: entry.get("label") ?? url,
10909
+ hubDocId: entry.get("hubDocId"),
10910
+ entryDocId: entry.get("entryDocId"),
10911
+ defaultRole: entry.get("defaultRole"),
10912
+ spacesEnabled: entry.get("spacesEnabled"),
10913
+ addedAt: entry.get("addedAt") ?? 0
10914
+ });
10915
+ });
10916
+ return result;
10917
+ }
10918
+ getSpaces() {
10919
+ const result = [];
10920
+ this.spacesArray.forEach((yMap) => {
10921
+ result.push({
10922
+ id: yMap.get("id"),
10923
+ name: yMap.get("name"),
10924
+ type: yMap.get("type") ?? "remote",
10925
+ serverUrl: yMap.get("serverUrl") ?? null,
10926
+ docId: yMap.get("docId"),
10927
+ remoteSpaceId: yMap.get("remoteSpaceId"),
10928
+ visibility: yMap.get("visibility") ?? "private",
10929
+ isHub: yMap.get("isHub") ?? false,
10930
+ order: yMap.get("order") ?? 0,
10931
+ lastSyncedAt: yMap.get("lastSyncedAt"),
10932
+ createdAt: yMap.get("createdAt") ?? 0
10933
+ });
10934
+ });
10935
+ return result;
10936
+ }
10937
+ addSpace(space) {
10938
+ const yMap = new yjs.Map();
10939
+ this.document.transact(() => {
10940
+ for (const [key, value] of Object.entries(space)) if (value !== void 0) yMap.set(key, value);
10941
+ this.spacesArray.push([yMap]);
10942
+ });
10943
+ }
10944
+ removeSpace(spaceId) {
10945
+ const arr = this.spacesArray;
10946
+ for (let i = 0; i < arr.length; i++) if (arr.get(i).get("id") === spaceId) {
10947
+ arr.delete(i, 1);
10948
+ return;
10949
+ }
10950
+ }
10951
+ updateSpace(spaceId, updates) {
10952
+ const arr = this.spacesArray;
10953
+ for (let i = 0; i < arr.length; i++) {
10954
+ const yMap = arr.get(i);
10955
+ if (yMap.get("id") === spaceId) {
10956
+ this.document.transact(() => {
10957
+ for (const [key, value] of Object.entries(updates)) if (value !== void 0) yMap.set(key, value);
10958
+ });
10959
+ return;
10960
+ }
10961
+ }
10962
+ }
10963
+ getExternalPlugins() {
10964
+ let arr = this.pluginsMap.get("external");
10965
+ if (!arr) {
10966
+ arr = new yjs.Array();
10967
+ this.pluginsMap.set("external", arr);
10968
+ }
10969
+ return arr;
10970
+ }
10971
+ getDisabledBuiltins() {
10972
+ let arr = this.pluginsMap.get("disabledBuiltins");
10973
+ if (!arr) {
10974
+ arr = new yjs.Array();
10975
+ this.pluginsMap.set("disabledBuiltins", arr);
10976
+ }
10977
+ return arr;
10978
+ }
10979
+ getPreference(key) {
10980
+ return this.preferencesMap.get(key);
10981
+ }
10982
+ setPreference(key, value) {
10983
+ this.preferencesMap.set(key, value);
10984
+ }
10985
+ /**
10986
+ * Observe deep changes on a specific top-level map.
10987
+ * Returns an unsubscribe function.
10988
+ */
10989
+ observe(mapName, callback) {
10990
+ const map = this.document.getMap(mapName);
10991
+ map.observeDeep(callback);
10992
+ return () => map.unobserveDeep(callback);
10993
+ }
10994
+ /**
10995
+ * Observe changes to the spaces array.
10996
+ * Returns an unsubscribe function.
10997
+ */
10998
+ observeSpaces(callback) {
10999
+ const arr = this.spacesArray;
11000
+ arr.observe(callback);
11001
+ return () => arr.unobserve(callback);
11002
+ }
11003
+ /**
11004
+ * Returns true if the identity doc has no profile data yet (first use).
11005
+ * Call this to decide whether to run migration from localStorage.
11006
+ */
11007
+ isEmpty() {
11008
+ return this.profileMap.size === 0 && this.serversMap.size === 0;
11009
+ }
11010
+ /**
11011
+ * Update the sync server URL at runtime (e.g. when user changes their
11012
+ * designated sync server in settings).
11013
+ */
11014
+ setSyncServer(url) {
11015
+ const existing = this.providers.get("sync");
11016
+ if (existing) {
11017
+ existing.destroy();
11018
+ this.providers.delete("sync");
11019
+ }
11020
+ const existingWs = this.websockets.get("sync");
11021
+ if (existingWs) {
11022
+ existingWs.destroy();
11023
+ this.websockets.delete("sync");
11024
+ }
11025
+ if (url) {
11026
+ this.config = {
11027
+ ...this.config,
11028
+ syncServerUrl: url
11029
+ };
11030
+ this._connectToServer("sync", url);
11031
+ }
11032
+ }
11033
+ getProvider(key) {
11034
+ return this.providers.get(key);
11035
+ }
11036
+ destroy() {
11037
+ if (this._destroyed) return;
11038
+ this._destroyed = true;
11039
+ for (const [, provider] of this.providers) provider.destroy();
11040
+ for (const [, ws] of this.websockets) ws.destroy();
11041
+ if (this.webrtc) this.webrtc.disconnect?.();
11042
+ this.providers.clear();
11043
+ this.websockets.clear();
11044
+ this.webrtc = null;
11045
+ this.document.destroy();
11046
+ }
11047
+ };
11048
+
10750
11049
  //#endregion
10751
11050
  exports.AbracadabraBaseProvider = AbracadabraBaseProvider;
10752
11051
  exports.AbracadabraClient = AbracadabraClient;
@@ -10778,6 +11077,7 @@ exports.FileTransferHandle = FileTransferHandle;
10778
11077
  exports.Forbidden = Forbidden;
10779
11078
  exports.HocuspocusProvider = HocuspocusProvider;
10780
11079
  exports.HocuspocusProviderWebsocket = HocuspocusProviderWebsocket;
11080
+ exports.IdentityDocProvider = IdentityDocProvider;
10781
11081
  exports.KEY_EXCHANGE_CHANNEL = KEY_EXCHANGE_CHANNEL;
10782
11082
  exports.ManualSignaling = ManualSignaling;
10783
11083
  exports.MessageTooBig = MessageTooBig;
@@ -10795,6 +11095,7 @@ exports.YjsDataChannel = YjsDataChannel;
10795
11095
  exports.attachUpdatedAtObserver = attachUpdatedAtObserver;
10796
11096
  exports.awarenessStatesToArray = awarenessStatesToArray;
10797
11097
  exports.decryptField = decryptField;
11098
+ exports.deriveIdentityDocId = deriveIdentityDocId;
10798
11099
  exports.encryptField = encryptField;
10799
11100
  exports.makeEncryptedYMap = makeEncryptedYMap;
10800
11101
  exports.makeEncryptedYText = makeEncryptedYText;