@abraca/dabra 2.7.0 → 2.9.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/abracadabra-provider.cjs +85 -5
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +85 -6
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +37 -2
- package/package.json +2 -2
- package/src/AbracadabraWS.ts +72 -2
- package/src/DocUtils.ts +62 -3
- package/src/index.ts +1 -0
|
@@ -1203,6 +1203,8 @@ var AbracadabraWS = class extends EventEmitter {
|
|
|
1203
1203
|
this.identifier = 0;
|
|
1204
1204
|
this.intervals = { connectionChecker: null };
|
|
1205
1205
|
this.connectionAttempt = null;
|
|
1206
|
+
this.onlineListener = null;
|
|
1207
|
+
this.offlineListener = null;
|
|
1206
1208
|
this.receivedOnOpenPayload = void 0;
|
|
1207
1209
|
this.closeTries = 0;
|
|
1208
1210
|
this.setConfiguration(configuration);
|
|
@@ -1221,8 +1223,39 @@ var AbracadabraWS = class extends EventEmitter {
|
|
|
1221
1223
|
this.on("close", this.onClose.bind(this));
|
|
1222
1224
|
this.on("message", this.onMessage.bind(this));
|
|
1223
1225
|
this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.configuration.messageReconnectTimeout / 10);
|
|
1226
|
+
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
1227
|
+
this.onlineListener = this.handleOnline.bind(this);
|
|
1228
|
+
this.offlineListener = this.handleOffline.bind(this);
|
|
1229
|
+
window.addEventListener("online", this.onlineListener);
|
|
1230
|
+
window.addEventListener("offline", this.offlineListener);
|
|
1231
|
+
}
|
|
1224
1232
|
if (this.shouldConnect) this.connect();
|
|
1225
1233
|
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Whether the device currently believes it has network connectivity.
|
|
1236
|
+
*
|
|
1237
|
+
* Treats "unknown" as online: in Node and other non-browser environments
|
|
1238
|
+
* `navigator` (or `navigator.onLine`) is absent, and we must not gate
|
|
1239
|
+
* reconnection there — only the browser exposes a trustworthy signal.
|
|
1240
|
+
*/
|
|
1241
|
+
get isOnline() {
|
|
1242
|
+
return typeof navigator === "undefined" || navigator.onLine !== false;
|
|
1243
|
+
}
|
|
1244
|
+
handleOnline() {
|
|
1245
|
+
if (this.shouldConnect && this.status !== WebSocketStatus.Connected) this.connect();
|
|
1246
|
+
}
|
|
1247
|
+
handleOffline() {
|
|
1248
|
+
if (this.cancelWebsocketRetry) {
|
|
1249
|
+
this.cancelWebsocketRetry();
|
|
1250
|
+
this.cancelWebsocketRetry = void 0;
|
|
1251
|
+
}
|
|
1252
|
+
try {
|
|
1253
|
+
this.webSocket?.close();
|
|
1254
|
+
this.messageQueue = [];
|
|
1255
|
+
} catch (e) {
|
|
1256
|
+
console.error(e);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1226
1259
|
async onOpen(event) {
|
|
1227
1260
|
this.status = WebSocketStatus.Connected;
|
|
1228
1261
|
this.emit("status", { status: WebSocketStatus.Connected });
|
|
@@ -1252,6 +1285,10 @@ var AbracadabraWS = class extends EventEmitter {
|
|
|
1252
1285
|
}
|
|
1253
1286
|
async connect() {
|
|
1254
1287
|
if (this.status === WebSocketStatus.Connected) return;
|
|
1288
|
+
if (!this.isOnline) {
|
|
1289
|
+
this.shouldConnect = true;
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1255
1292
|
if (this.cancelWebsocketRetry) {
|
|
1256
1293
|
this.cancelWebsocketRetry();
|
|
1257
1294
|
this.cancelWebsocketRetry = void 0;
|
|
@@ -1416,7 +1453,7 @@ var AbracadabraWS = class extends EventEmitter {
|
|
|
1416
1453
|
const isRateLimited = event?.code === 4429 || event === 4429;
|
|
1417
1454
|
this.emit("disconnect", { event });
|
|
1418
1455
|
if (isRateLimited) this.emit("rateLimited");
|
|
1419
|
-
if (!this.cancelWebsocketRetry && this.shouldConnect) {
|
|
1456
|
+
if (!this.cancelWebsocketRetry && this.shouldConnect && this.isOnline) {
|
|
1420
1457
|
const delay = isRateLimited ? 6e4 : this.configuration.delay;
|
|
1421
1458
|
setTimeout(() => {
|
|
1422
1459
|
this.connect();
|
|
@@ -1426,6 +1463,12 @@ var AbracadabraWS = class extends EventEmitter {
|
|
|
1426
1463
|
destroy() {
|
|
1427
1464
|
this.shouldConnect = false;
|
|
1428
1465
|
this.emit("destroy");
|
|
1466
|
+
if (typeof window !== "undefined" && typeof window.removeEventListener === "function") {
|
|
1467
|
+
if (this.onlineListener) window.removeEventListener("online", this.onlineListener);
|
|
1468
|
+
if (this.offlineListener) window.removeEventListener("offline", this.offlineListener);
|
|
1469
|
+
}
|
|
1470
|
+
this.onlineListener = null;
|
|
1471
|
+
this.offlineListener = null;
|
|
1429
1472
|
clearInterval(this.intervals.connectionChecker);
|
|
1430
1473
|
this.stopConnectionAttempt();
|
|
1431
1474
|
this.disconnect();
|
|
@@ -15676,6 +15719,19 @@ function makeEntryMap(fields) {
|
|
|
15676
15719
|
return m;
|
|
15677
15720
|
}
|
|
15678
15721
|
/**
|
|
15722
|
+
* A label is a "placeholder" — i.e. carries no real user title — when it is
|
|
15723
|
+
* empty/whitespace, null/undefined, or the literal `"Untitled"` sentinel
|
|
15724
|
+
* (case-insensitive, so `"untitled"` from a `labelToFilename` round-trip is
|
|
15725
|
+
* caught too). The whole tree-label corruption class boils down to a
|
|
15726
|
+
* placeholder being allowed to overwrite a real title; `patchEntry` refuses
|
|
15727
|
+
* exactly that. Mirrors cou-sh's `isEmptyTreeLabel`.
|
|
15728
|
+
*/
|
|
15729
|
+
function isPlaceholderLabel(label) {
|
|
15730
|
+
if (typeof label !== "string") return label == null;
|
|
15731
|
+
const t = label.trim();
|
|
15732
|
+
return t === "" || t.toLowerCase() === "untitled";
|
|
15733
|
+
}
|
|
15734
|
+
/**
|
|
15679
15735
|
* Patch an EXISTING entry's fields per-key on its nested `Y.Map`, so a
|
|
15680
15736
|
* concurrent edit to a *different* field by a peer is preserved instead
|
|
15681
15737
|
* of being clobbered by a whole-entry write — the whole-entry-LWW fix
|
|
@@ -15690,21 +15746,44 @@ function makeEntryMap(fields) {
|
|
|
15690
15746
|
* Self-transacting: it batches its writes in one `Y.Doc` transaction
|
|
15691
15747
|
* (a safe reentrant no-op join when already inside one), so callers
|
|
15692
15748
|
* don't need to pass or own a transaction.
|
|
15749
|
+
*
|
|
15750
|
+
* ── NO-DESTROY LABEL INVARIANT ──────────────────────────────────────────
|
|
15751
|
+
* A `label` patch that is a placeholder (empty/whitespace/"Untitled") is
|
|
15752
|
+
* DROPPED when the entry already holds a real (non-placeholder) label —
|
|
15753
|
+
* regardless of which consumer (cou-sh title-sync, fs-sync rename
|
|
15754
|
+
* detection, MCP, table renderers, a stale snapshot) tried it. This is the
|
|
15755
|
+
* source-of-truth guard against the "card title silently becomes Untitled
|
|
15756
|
+
* / files renamed to untitled.md" corruption: a placeholder must never win
|
|
15757
|
+
* over a real title. Creating a brand-new entry with an empty label (e.g.
|
|
15758
|
+
* a fresh kanban card) is still allowed — the guard only fires when a real
|
|
15759
|
+
* label already exists. Pass `{ allowLabelClear: true }` to override (the
|
|
15760
|
+
* single legitimate "user explicitly cleared it" path).
|
|
15693
15761
|
*/
|
|
15694
|
-
function patchEntry(treeMap, id, patch, removeKeys = []) {
|
|
15762
|
+
function patchEntry(treeMap, id, patch, removeKeys = [], opts = {}) {
|
|
15695
15763
|
const apply = () => {
|
|
15696
15764
|
const raw = treeMap.get(id);
|
|
15765
|
+
let effectivePatch = patch;
|
|
15766
|
+
if (!opts.allowLabelClear && Object.hasOwn(patch, "label") && isPlaceholderLabel(patch.label)) {
|
|
15767
|
+
let existingLabel;
|
|
15768
|
+
if (raw != null && typeof raw.get === "function") existingLabel = raw.get("label");
|
|
15769
|
+
else if (raw != null && typeof raw.toJSON === "function") existingLabel = raw.toJSON()?.label;
|
|
15770
|
+
else if (raw != null && typeof raw === "object") existingLabel = raw.label;
|
|
15771
|
+
if (!isPlaceholderLabel(existingLabel)) {
|
|
15772
|
+
const { label: _dropped, ...rest } = patch;
|
|
15773
|
+
effectivePatch = rest;
|
|
15774
|
+
}
|
|
15775
|
+
}
|
|
15697
15776
|
if (raw instanceof Y.Map) {
|
|
15698
|
-
for (const [k, v] of Object.entries(
|
|
15777
|
+
for (const [k, v] of Object.entries(effectivePatch)) if (v === void 0) raw.delete(k);
|
|
15699
15778
|
else raw.set(k, v);
|
|
15700
15779
|
for (const k of removeKeys) raw.delete(k);
|
|
15701
15780
|
return;
|
|
15702
15781
|
}
|
|
15703
15782
|
const merged = {
|
|
15704
15783
|
...raw == null ? {} : toPlain(raw),
|
|
15705
|
-
...
|
|
15784
|
+
...effectivePatch
|
|
15706
15785
|
};
|
|
15707
|
-
for (const [k, v] of Object.entries(
|
|
15786
|
+
for (const [k, v] of Object.entries(effectivePatch)) if (v === void 0) delete merged[k];
|
|
15708
15787
|
for (const k of removeKeys) delete merged[k];
|
|
15709
15788
|
treeMap.set(id, makeEntryMap(merged));
|
|
15710
15789
|
};
|
|
@@ -20744,5 +20823,5 @@ var DocumentManager = class {
|
|
|
20744
20823
|
};
|
|
20745
20824
|
|
|
20746
20825
|
//#endregion
|
|
20747
|
-
export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AbracadabraWebRTC, AuthMessageType, AwarenessError, BackgroundSyncManager, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ChannelKeyResolver, ChatClient, ConnectionTimeout, ContentManager, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DevicePairingChannel, DeviceRegistrationService, DocKeyManager, DocumentCache, DocumentManager, E2EAbracadabraProvider, E2EEChannel, E2EOfflineStore, EncryptedChatClient, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, Forbidden, GEO_TYPE_META_SCHEMAS, HocuspocusProvider, HocuspocusProviderWebsocket, IdentityDocProvider, KEY_EXCHANGE_CHANNEL, Kind, LocalStorageDeviceSessionStorage, ManualSignaling, MessageTooBig, MessageType, MetaManager, MetaValidationError, NotificationsClient, OfflineStore, PAGE_TYPES, PeerConnection, QUERY_PREFIX, QueryClient, QueryError, RPC_PREFIX, ResetConnection, RpcClient, RpcError, SERVER_ROOT_ID, SearchIndex, SignalingSocket, SubdocMessage, TYPE_ALIASES, TokenManager, TreeManager, TypedDocTypeMismatchError, Unauthorized, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, wordlist as bip39Wordlist, buildBlockquoteElement, buildBlocksFromMarkdown, buildBulletListElement, buildCodeBlockElement, buildHeadingElement, buildHorizontalRuleElement, buildOrderedListElement, buildParagraphElement, buildTaskListElement, decryptChatContent, decryptField, deriveIdentityDocId, deriveSeedWrappingKey, encryptChatContent, encryptField, filenameToLabel, foldRecords, generateMnemonic, isEncryptedContent, makeEncryptedYMap, makeEncryptedYText, makeEntryMap, mnemonicToEd25519Seed, mnemonicToKeyPair, normalizeRootId, parseFrontmatter, patchEntry, populateYDocFromMarkdown, readAuthMessage, readBlocksFromFragment, recordFromYAny, resolvePageType, toPlain, unwrapSeed, validateMnemonic, waitForSync, withTimeout, wrapSeed, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest, yjsToMarkdown };
|
|
20826
|
+
export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AbracadabraWebRTC, AuthMessageType, AwarenessError, BackgroundSyncManager, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ChannelKeyResolver, ChatClient, ConnectionTimeout, ContentManager, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DevicePairingChannel, DeviceRegistrationService, DocKeyManager, DocumentCache, DocumentManager, E2EAbracadabraProvider, E2EEChannel, E2EOfflineStore, EncryptedChatClient, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, Forbidden, GEO_TYPE_META_SCHEMAS, HocuspocusProvider, HocuspocusProviderWebsocket, IdentityDocProvider, KEY_EXCHANGE_CHANNEL, Kind, LocalStorageDeviceSessionStorage, ManualSignaling, MessageTooBig, MessageType, MetaManager, MetaValidationError, NotificationsClient, OfflineStore, PAGE_TYPES, PeerConnection, QUERY_PREFIX, QueryClient, QueryError, RPC_PREFIX, ResetConnection, RpcClient, RpcError, SERVER_ROOT_ID, SearchIndex, SignalingSocket, SubdocMessage, TYPE_ALIASES, TokenManager, TreeManager, TypedDocTypeMismatchError, Unauthorized, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, wordlist as bip39Wordlist, buildBlockquoteElement, buildBlocksFromMarkdown, buildBulletListElement, buildCodeBlockElement, buildHeadingElement, buildHorizontalRuleElement, buildOrderedListElement, buildParagraphElement, buildTaskListElement, decryptChatContent, decryptField, deriveIdentityDocId, deriveSeedWrappingKey, encryptChatContent, encryptField, filenameToLabel, foldRecords, generateMnemonic, isEncryptedContent, isPlaceholderLabel, makeEncryptedYMap, makeEncryptedYText, makeEntryMap, mnemonicToEd25519Seed, mnemonicToKeyPair, normalizeRootId, parseFrontmatter, patchEntry, populateYDocFromMarkdown, readAuthMessage, readBlocksFromFragment, recordFromYAny, resolvePageType, toPlain, unwrapSeed, validateMnemonic, waitForSync, withTimeout, wrapSeed, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest, yjsToMarkdown };
|
|
20748
20827
|
//# sourceMappingURL=abracadabra-provider.esm.js.map
|