@abraca/dabra 1.0.2 → 1.0.4
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 +1196 -10
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +1187 -11
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +363 -1
- package/package.json +1 -1
- package/src/AbracadabraClient.ts +18 -0
- package/src/AbracadabraWS.ts +3 -1
- package/src/index.ts +1 -0
- package/src/types.ts +2 -0
- package/src/webrtc/AbracadabraWebRTC.ts +540 -0
- package/src/webrtc/DataChannelRouter.ts +110 -0
- package/src/webrtc/FileTransferChannel.ts +359 -0
- package/src/webrtc/PeerConnection.ts +133 -0
- package/src/webrtc/SignalingSocket.ts +366 -0
- package/src/webrtc/YjsDataChannel.ts +195 -0
- package/src/webrtc/index.ts +20 -0
- package/src/webrtc/types.ts +159 -0
|
@@ -1878,7 +1878,7 @@ var AbracadabraWS = class extends EventEmitter {
|
|
|
1878
1878
|
if (this.connectionAttempt) this.rejectConnectionAttempt();
|
|
1879
1879
|
this.status = WebSocketStatus.Disconnected;
|
|
1880
1880
|
this.emit("status", { status: WebSocketStatus.Disconnected });
|
|
1881
|
-
const isRateLimited = event?.code === 4429;
|
|
1881
|
+
const isRateLimited = event?.code === 4429 || event === 4429;
|
|
1882
1882
|
this.emit("disconnect", { event });
|
|
1883
1883
|
if (isRateLimited) this.emit("rateLimited");
|
|
1884
1884
|
if (!this.cancelWebsocketRetry && this.shouldConnect) {
|
|
@@ -3335,6 +3335,18 @@ var AbracadabraClient = class {
|
|
|
3335
3335
|
async serverInfo() {
|
|
3336
3336
|
return this.request("GET", "/info", { auth: false });
|
|
3337
3337
|
}
|
|
3338
|
+
/**
|
|
3339
|
+
* Fetch ICE server configuration for WebRTC peer connections.
|
|
3340
|
+
* Falls back to default Google STUN server if the endpoint is unavailable.
|
|
3341
|
+
* No auth required.
|
|
3342
|
+
*/
|
|
3343
|
+
async getIceServers() {
|
|
3344
|
+
try {
|
|
3345
|
+
return (await this.request("GET", "/ice-servers", { auth: false })).iceServers;
|
|
3346
|
+
} catch {
|
|
3347
|
+
return [{ urls: "stun:stun.l.google.com:19302" }];
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3338
3350
|
async request(method, path, opts) {
|
|
3339
3351
|
const auth = opts?.auth ?? true;
|
|
3340
3352
|
const headers = {};
|
|
@@ -4133,7 +4145,7 @@ const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(1
|
|
|
4133
4145
|
* Convert byte array to hex string. Uses built-in function, when available.
|
|
4134
4146
|
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
|
4135
4147
|
*/
|
|
4136
|
-
function bytesToHex(bytes) {
|
|
4148
|
+
function bytesToHex$1(bytes) {
|
|
4137
4149
|
abytes(bytes);
|
|
4138
4150
|
if (hasHexBuiltin) return bytes.toHex();
|
|
4139
4151
|
let hex = "";
|
|
@@ -4157,7 +4169,7 @@ function asciiToBase16(ch) {
|
|
|
4157
4169
|
* Convert hex string to byte array. Uses built-in function, when available.
|
|
4158
4170
|
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
|
|
4159
4171
|
*/
|
|
4160
|
-
function hexToBytes(hex) {
|
|
4172
|
+
function hexToBytes$1(hex) {
|
|
4161
4173
|
if (typeof hex !== "string") throw new Error("hex string expected, got " + typeof hex);
|
|
4162
4174
|
if (hasHexBuiltin) return Uint8Array.fromHex(hex);
|
|
4163
4175
|
const hl = hex.length;
|
|
@@ -4652,15 +4664,15 @@ function hexToNumber(hex) {
|
|
|
4652
4664
|
return hex === "" ? _0n$5 : BigInt("0x" + hex);
|
|
4653
4665
|
}
|
|
4654
4666
|
function bytesToNumberBE(bytes) {
|
|
4655
|
-
return hexToNumber(bytesToHex(bytes));
|
|
4667
|
+
return hexToNumber(bytesToHex$1(bytes));
|
|
4656
4668
|
}
|
|
4657
4669
|
function bytesToNumberLE(bytes) {
|
|
4658
|
-
return hexToNumber(bytesToHex(copyBytes(abytes(bytes)).reverse()));
|
|
4670
|
+
return hexToNumber(bytesToHex$1(copyBytes(abytes(bytes)).reverse()));
|
|
4659
4671
|
}
|
|
4660
4672
|
function numberToBytesBE(n, len) {
|
|
4661
4673
|
anumber(len);
|
|
4662
4674
|
n = abignumber(n);
|
|
4663
|
-
const res = hexToBytes(n.toString(16).padStart(len * 2, "0"));
|
|
4675
|
+
const res = hexToBytes$1(n.toString(16).padStart(len * 2, "0"));
|
|
4664
4676
|
if (res.length !== len) throw new Error("number too large");
|
|
4665
4677
|
return res;
|
|
4666
4678
|
}
|
|
@@ -5622,7 +5634,7 @@ function edwards(params, extraOpts = {}) {
|
|
|
5622
5634
|
});
|
|
5623
5635
|
}
|
|
5624
5636
|
static fromHex(hex, zip215 = false) {
|
|
5625
|
-
return Point.fromBytes(hexToBytes(hex), zip215);
|
|
5637
|
+
return Point.fromBytes(hexToBytes$1(hex), zip215);
|
|
5626
5638
|
}
|
|
5627
5639
|
get x() {
|
|
5628
5640
|
return this.toAffine().x;
|
|
@@ -5723,7 +5735,7 @@ function edwards(params, extraOpts = {}) {
|
|
|
5723
5735
|
return bytes;
|
|
5724
5736
|
}
|
|
5725
5737
|
toHex() {
|
|
5726
|
-
return bytesToHex(this.toBytes());
|
|
5738
|
+
return bytesToHex$1(this.toBytes());
|
|
5727
5739
|
}
|
|
5728
5740
|
toString() {
|
|
5729
5741
|
return `<Point ${this.is0() ? "ZERO" : this.toHex()}>`;
|
|
@@ -5769,7 +5781,7 @@ var PrimeEdwardsPoint = class {
|
|
|
5769
5781
|
return this.ep.toAffine(invertedZ);
|
|
5770
5782
|
}
|
|
5771
5783
|
toHex() {
|
|
5772
|
-
return bytesToHex(this.toBytes());
|
|
5784
|
+
return bytesToHex$1(this.toBytes());
|
|
5773
5785
|
}
|
|
5774
5786
|
toString() {
|
|
5775
5787
|
return this.toHex();
|
|
@@ -6802,7 +6814,7 @@ var _RistrettoPoint = class _RistrettoPoint extends PrimeEdwardsPoint {
|
|
|
6802
6814
|
* @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
|
|
6803
6815
|
*/
|
|
6804
6816
|
static fromHex(hex) {
|
|
6805
|
-
return _RistrettoPoint.fromBytes(hexToBytes(hex));
|
|
6817
|
+
return _RistrettoPoint.fromBytes(hexToBytes$1(hex));
|
|
6806
6818
|
}
|
|
6807
6819
|
/**
|
|
6808
6820
|
* Encodes ristretto point to Uint8Array.
|
|
@@ -8396,5 +8408,1169 @@ var BackgroundSyncManager = class extends EventEmitter {
|
|
|
8396
8408
|
};
|
|
8397
8409
|
|
|
8398
8410
|
//#endregion
|
|
8399
|
-
|
|
8411
|
+
//#region packages/provider/src/webrtc/SignalingSocket.ts
|
|
8412
|
+
var SignalingSocket = class extends EventEmitter {
|
|
8413
|
+
constructor(configuration) {
|
|
8414
|
+
super();
|
|
8415
|
+
this.ws = null;
|
|
8416
|
+
this.wsHandlers = {};
|
|
8417
|
+
this.shouldConnect = true;
|
|
8418
|
+
this.connectionAttempt = null;
|
|
8419
|
+
this.localPeerId = null;
|
|
8420
|
+
this.isConnected = false;
|
|
8421
|
+
this.config = {
|
|
8422
|
+
url: configuration.url,
|
|
8423
|
+
token: configuration.token,
|
|
8424
|
+
delay: configuration.delay ?? 1e3,
|
|
8425
|
+
factor: configuration.factor ?? 2,
|
|
8426
|
+
minDelay: configuration.minDelay ?? 1e3,
|
|
8427
|
+
maxDelay: configuration.maxDelay ?? 3e4,
|
|
8428
|
+
jitter: configuration.jitter ?? true,
|
|
8429
|
+
maxAttempts: configuration.maxAttempts ?? 0,
|
|
8430
|
+
WebSocketPolyfill: configuration.WebSocketPolyfill ?? WebSocket
|
|
8431
|
+
};
|
|
8432
|
+
if (configuration.autoConnect !== false) this.connect();
|
|
8433
|
+
}
|
|
8434
|
+
async getToken() {
|
|
8435
|
+
if (typeof this.config.token === "function") return await this.config.token();
|
|
8436
|
+
return this.config.token;
|
|
8437
|
+
}
|
|
8438
|
+
async connect() {
|
|
8439
|
+
if (this.isConnected) return;
|
|
8440
|
+
if (this.cancelRetry) {
|
|
8441
|
+
this.cancelRetry();
|
|
8442
|
+
this.cancelRetry = void 0;
|
|
8443
|
+
}
|
|
8444
|
+
this.shouldConnect = true;
|
|
8445
|
+
let cancelAttempt = false;
|
|
8446
|
+
const retryPromise = retry(() => this.createConnection(), {
|
|
8447
|
+
delay: this.config.delay,
|
|
8448
|
+
initialDelay: 0,
|
|
8449
|
+
factor: this.config.factor,
|
|
8450
|
+
maxAttempts: this.config.maxAttempts,
|
|
8451
|
+
minDelay: this.config.minDelay,
|
|
8452
|
+
maxDelay: this.config.maxDelay,
|
|
8453
|
+
jitter: this.config.jitter,
|
|
8454
|
+
timeout: 0,
|
|
8455
|
+
beforeAttempt: (context) => {
|
|
8456
|
+
if (!this.shouldConnect || cancelAttempt) context.abort();
|
|
8457
|
+
}
|
|
8458
|
+
}).catch((error) => {
|
|
8459
|
+
if (error && error.code !== "ATTEMPT_ABORTED") throw error;
|
|
8460
|
+
});
|
|
8461
|
+
this.cancelRetry = () => {
|
|
8462
|
+
cancelAttempt = true;
|
|
8463
|
+
};
|
|
8464
|
+
return retryPromise;
|
|
8465
|
+
}
|
|
8466
|
+
async createConnection() {
|
|
8467
|
+
this.cleanup();
|
|
8468
|
+
const token = await this.getToken();
|
|
8469
|
+
const separator = this.config.url.includes("?") ? "&" : "?";
|
|
8470
|
+
const url = `${this.config.url}${separator}token=${encodeURIComponent(token)}`;
|
|
8471
|
+
const ws = new this.config.WebSocketPolyfill(url);
|
|
8472
|
+
return new Promise((resolve, reject) => {
|
|
8473
|
+
const onOpen = () => {
|
|
8474
|
+
this.isConnected = true;
|
|
8475
|
+
this.sendRaw({ type: "join" });
|
|
8476
|
+
};
|
|
8477
|
+
const onMessage = (event) => {
|
|
8478
|
+
const data = typeof event === "string" ? event : typeof event.data === "string" ? event.data : null;
|
|
8479
|
+
if (!data) return;
|
|
8480
|
+
let msg;
|
|
8481
|
+
try {
|
|
8482
|
+
msg = JSON.parse(data);
|
|
8483
|
+
} catch {
|
|
8484
|
+
return;
|
|
8485
|
+
}
|
|
8486
|
+
this.handleMessage(msg, resolve);
|
|
8487
|
+
};
|
|
8488
|
+
const onClose = (event) => {
|
|
8489
|
+
const wasConnected = this.isConnected;
|
|
8490
|
+
this.isConnected = false;
|
|
8491
|
+
this.localPeerId = null;
|
|
8492
|
+
if (this.connectionAttempt) {
|
|
8493
|
+
this.connectionAttempt = null;
|
|
8494
|
+
reject(/* @__PURE__ */ new Error(`Signaling WebSocket closed: ${event?.code}`));
|
|
8495
|
+
}
|
|
8496
|
+
this.emit("disconnected");
|
|
8497
|
+
if (!wasConnected) return;
|
|
8498
|
+
if (this.shouldConnect && !this.cancelRetry) setTimeout(() => this.connect(), this.config.delay);
|
|
8499
|
+
};
|
|
8500
|
+
const onError = (err) => {
|
|
8501
|
+
if (this.connectionAttempt) {
|
|
8502
|
+
this.connectionAttempt = null;
|
|
8503
|
+
reject(err);
|
|
8504
|
+
}
|
|
8505
|
+
};
|
|
8506
|
+
this.wsHandlers = {
|
|
8507
|
+
open: onOpen,
|
|
8508
|
+
message: onMessage,
|
|
8509
|
+
close: onClose,
|
|
8510
|
+
error: onError
|
|
8511
|
+
};
|
|
8512
|
+
for (const [name, handler] of Object.entries(this.wsHandlers)) ws.addEventListener(name, handler);
|
|
8513
|
+
this.ws = ws;
|
|
8514
|
+
this.connectionAttempt = {
|
|
8515
|
+
resolve,
|
|
8516
|
+
reject
|
|
8517
|
+
};
|
|
8518
|
+
});
|
|
8519
|
+
}
|
|
8520
|
+
handleMessage(msg, resolveConnection) {
|
|
8521
|
+
switch (msg.type) {
|
|
8522
|
+
case "welcome":
|
|
8523
|
+
this.localPeerId = msg.peer_id;
|
|
8524
|
+
if (this.connectionAttempt) {
|
|
8525
|
+
this.connectionAttempt = null;
|
|
8526
|
+
resolveConnection?.();
|
|
8527
|
+
}
|
|
8528
|
+
this.emit("welcome", {
|
|
8529
|
+
peerId: msg.peer_id,
|
|
8530
|
+
peers: msg.peers
|
|
8531
|
+
});
|
|
8532
|
+
break;
|
|
8533
|
+
case "joined":
|
|
8534
|
+
this.emit("joined", {
|
|
8535
|
+
peerId: msg.peer_id,
|
|
8536
|
+
userId: msg.user_id,
|
|
8537
|
+
muted: msg.muted,
|
|
8538
|
+
video: msg.video,
|
|
8539
|
+
screen: msg.screen,
|
|
8540
|
+
name: msg.name,
|
|
8541
|
+
color: msg.color
|
|
8542
|
+
});
|
|
8543
|
+
break;
|
|
8544
|
+
case "left":
|
|
8545
|
+
this.emit("left", { peerId: msg.peer_id });
|
|
8546
|
+
break;
|
|
8547
|
+
case "offer":
|
|
8548
|
+
this.emit("offer", {
|
|
8549
|
+
from: msg.from,
|
|
8550
|
+
sdp: msg.sdp
|
|
8551
|
+
});
|
|
8552
|
+
break;
|
|
8553
|
+
case "answer":
|
|
8554
|
+
this.emit("answer", {
|
|
8555
|
+
from: msg.from,
|
|
8556
|
+
sdp: msg.sdp
|
|
8557
|
+
});
|
|
8558
|
+
break;
|
|
8559
|
+
case "ice":
|
|
8560
|
+
this.emit("ice", {
|
|
8561
|
+
from: msg.from,
|
|
8562
|
+
candidate: msg.candidate
|
|
8563
|
+
});
|
|
8564
|
+
break;
|
|
8565
|
+
case "mute":
|
|
8566
|
+
this.emit("mute", {
|
|
8567
|
+
peerId: msg.peer_id,
|
|
8568
|
+
muted: msg.muted
|
|
8569
|
+
});
|
|
8570
|
+
break;
|
|
8571
|
+
case "media-state":
|
|
8572
|
+
this.emit("media-state", {
|
|
8573
|
+
peerId: msg.peer_id,
|
|
8574
|
+
video: msg.video,
|
|
8575
|
+
screen: msg.screen
|
|
8576
|
+
});
|
|
8577
|
+
break;
|
|
8578
|
+
case "profile":
|
|
8579
|
+
this.emit("profile", {
|
|
8580
|
+
peerId: msg.peer_id,
|
|
8581
|
+
name: msg.name,
|
|
8582
|
+
color: msg.color
|
|
8583
|
+
});
|
|
8584
|
+
break;
|
|
8585
|
+
case "ping":
|
|
8586
|
+
this.sendRaw({ type: "pong" });
|
|
8587
|
+
break;
|
|
8588
|
+
case "error":
|
|
8589
|
+
this.emit("error", {
|
|
8590
|
+
code: msg.code,
|
|
8591
|
+
message: msg.message
|
|
8592
|
+
});
|
|
8593
|
+
break;
|
|
8594
|
+
}
|
|
8595
|
+
}
|
|
8596
|
+
sendRaw(msg) {
|
|
8597
|
+
if (this.ws?.readyState === 1) this.ws.send(JSON.stringify(msg));
|
|
8598
|
+
}
|
|
8599
|
+
sendOffer(to, sdp) {
|
|
8600
|
+
this.sendRaw({
|
|
8601
|
+
type: "offer",
|
|
8602
|
+
to,
|
|
8603
|
+
sdp
|
|
8604
|
+
});
|
|
8605
|
+
}
|
|
8606
|
+
sendAnswer(to, sdp) {
|
|
8607
|
+
this.sendRaw({
|
|
8608
|
+
type: "answer",
|
|
8609
|
+
to,
|
|
8610
|
+
sdp
|
|
8611
|
+
});
|
|
8612
|
+
}
|
|
8613
|
+
sendIce(to, candidate) {
|
|
8614
|
+
this.sendRaw({
|
|
8615
|
+
type: "ice",
|
|
8616
|
+
to,
|
|
8617
|
+
candidate
|
|
8618
|
+
});
|
|
8619
|
+
}
|
|
8620
|
+
sendMute(muted) {
|
|
8621
|
+
this.sendRaw({
|
|
8622
|
+
type: "mute",
|
|
8623
|
+
muted
|
|
8624
|
+
});
|
|
8625
|
+
}
|
|
8626
|
+
sendMediaState(video, screen) {
|
|
8627
|
+
this.sendRaw({
|
|
8628
|
+
type: "media-state",
|
|
8629
|
+
video,
|
|
8630
|
+
screen
|
|
8631
|
+
});
|
|
8632
|
+
}
|
|
8633
|
+
sendProfile(name, color) {
|
|
8634
|
+
this.sendRaw({
|
|
8635
|
+
type: "profile",
|
|
8636
|
+
name,
|
|
8637
|
+
color
|
|
8638
|
+
});
|
|
8639
|
+
}
|
|
8640
|
+
sendLeave() {
|
|
8641
|
+
this.sendRaw({ type: "leave" });
|
|
8642
|
+
}
|
|
8643
|
+
disconnect() {
|
|
8644
|
+
this.shouldConnect = false;
|
|
8645
|
+
this.sendLeave();
|
|
8646
|
+
if (this.cancelRetry) {
|
|
8647
|
+
this.cancelRetry();
|
|
8648
|
+
this.cancelRetry = void 0;
|
|
8649
|
+
}
|
|
8650
|
+
this.cleanup();
|
|
8651
|
+
}
|
|
8652
|
+
destroy() {
|
|
8653
|
+
this.disconnect();
|
|
8654
|
+
this.removeAllListeners();
|
|
8655
|
+
}
|
|
8656
|
+
cleanup() {
|
|
8657
|
+
if (!this.ws) return;
|
|
8658
|
+
for (const [name, handler] of Object.entries(this.wsHandlers)) this.ws.removeEventListener(name, handler);
|
|
8659
|
+
this.wsHandlers = {};
|
|
8660
|
+
try {
|
|
8661
|
+
if (this.ws.readyState !== 3) this.ws.close();
|
|
8662
|
+
} catch {}
|
|
8663
|
+
this.ws = null;
|
|
8664
|
+
this.isConnected = false;
|
|
8665
|
+
this.localPeerId = null;
|
|
8666
|
+
}
|
|
8667
|
+
};
|
|
8668
|
+
|
|
8669
|
+
//#endregion
|
|
8670
|
+
//#region packages/provider/src/webrtc/types.ts
|
|
8671
|
+
/** Data channel file transfer message type discriminators (first byte). */
|
|
8672
|
+
const FILE_MSG = {
|
|
8673
|
+
START: 1,
|
|
8674
|
+
CHUNK: 2,
|
|
8675
|
+
COMPLETE: 3,
|
|
8676
|
+
CANCEL: 4
|
|
8677
|
+
};
|
|
8678
|
+
/** Data channel Y.js message type discriminators (first byte). */
|
|
8679
|
+
const YJS_MSG = {
|
|
8680
|
+
SYNC: 0,
|
|
8681
|
+
UPDATE: 1
|
|
8682
|
+
};
|
|
8683
|
+
const CHANNEL_NAMES = {
|
|
8684
|
+
YJS_SYNC: "yjs-sync",
|
|
8685
|
+
AWARENESS: "awareness",
|
|
8686
|
+
FILE_TRANSFER: "file-transfer",
|
|
8687
|
+
CUSTOM: "custom"
|
|
8688
|
+
};
|
|
8689
|
+
const DEFAULT_ICE_SERVERS = [{ urls: "stun:stun.l.google.com:19302" }];
|
|
8690
|
+
const DEFAULT_FILE_CHUNK_SIZE = 16384;
|
|
8691
|
+
/** UUID v4 transfer ID length when encoded as raw bytes. */
|
|
8692
|
+
const TRANSFER_ID_BYTES = 16;
|
|
8693
|
+
/** SHA-256 hash length in bytes. */
|
|
8694
|
+
const SHA256_BYTES = 32;
|
|
8695
|
+
|
|
8696
|
+
//#endregion
|
|
8697
|
+
//#region packages/provider/src/webrtc/DataChannelRouter.ts
|
|
8698
|
+
var DataChannelRouter = class extends EventEmitter {
|
|
8699
|
+
constructor(connection) {
|
|
8700
|
+
super();
|
|
8701
|
+
this.connection = connection;
|
|
8702
|
+
this.channels = /* @__PURE__ */ new Map();
|
|
8703
|
+
this.connection.ondatachannel = (event) => {
|
|
8704
|
+
this.registerChannel(event.channel);
|
|
8705
|
+
};
|
|
8706
|
+
}
|
|
8707
|
+
/** Create a named data channel (initiator side). */
|
|
8708
|
+
createChannel(name, options) {
|
|
8709
|
+
const channel = this.connection.createDataChannel(name, options);
|
|
8710
|
+
this.registerChannel(channel);
|
|
8711
|
+
return channel;
|
|
8712
|
+
}
|
|
8713
|
+
/** Create the standard set of channels for Abracadabra WebRTC. */
|
|
8714
|
+
createDefaultChannels(opts) {
|
|
8715
|
+
if (opts.enableDocSync) this.createChannel(CHANNEL_NAMES.YJS_SYNC, { ordered: true });
|
|
8716
|
+
if (opts.enableAwareness) this.createChannel(CHANNEL_NAMES.AWARENESS, {
|
|
8717
|
+
ordered: false,
|
|
8718
|
+
maxRetransmits: 0
|
|
8719
|
+
});
|
|
8720
|
+
if (opts.enableFileTransfer) this.createChannel(CHANNEL_NAMES.FILE_TRANSFER, { ordered: true });
|
|
8721
|
+
}
|
|
8722
|
+
getChannel(name) {
|
|
8723
|
+
return this.channels.get(name) ?? null;
|
|
8724
|
+
}
|
|
8725
|
+
isOpen(name) {
|
|
8726
|
+
return this.channels.get(name)?.readyState === "open";
|
|
8727
|
+
}
|
|
8728
|
+
registerChannel(channel) {
|
|
8729
|
+
channel.binaryType = "arraybuffer";
|
|
8730
|
+
this.channels.set(channel.label, channel);
|
|
8731
|
+
channel.onopen = () => {
|
|
8732
|
+
this.emit("channelOpen", {
|
|
8733
|
+
name: channel.label,
|
|
8734
|
+
channel
|
|
8735
|
+
});
|
|
8736
|
+
};
|
|
8737
|
+
channel.onclose = () => {
|
|
8738
|
+
this.emit("channelClose", { name: channel.label });
|
|
8739
|
+
this.channels.delete(channel.label);
|
|
8740
|
+
};
|
|
8741
|
+
channel.onmessage = (event) => {
|
|
8742
|
+
this.emit("channelMessage", {
|
|
8743
|
+
name: channel.label,
|
|
8744
|
+
data: event.data
|
|
8745
|
+
});
|
|
8746
|
+
};
|
|
8747
|
+
channel.onerror = (event) => {
|
|
8748
|
+
this.emit("channelError", {
|
|
8749
|
+
name: channel.label,
|
|
8750
|
+
error: event
|
|
8751
|
+
});
|
|
8752
|
+
};
|
|
8753
|
+
if (channel.readyState === "open") this.emit("channelOpen", {
|
|
8754
|
+
name: channel.label,
|
|
8755
|
+
channel
|
|
8756
|
+
});
|
|
8757
|
+
}
|
|
8758
|
+
close() {
|
|
8759
|
+
for (const channel of this.channels.values()) try {
|
|
8760
|
+
channel.close();
|
|
8761
|
+
} catch {}
|
|
8762
|
+
this.channels.clear();
|
|
8763
|
+
}
|
|
8764
|
+
destroy() {
|
|
8765
|
+
this.close();
|
|
8766
|
+
this.connection.ondatachannel = null;
|
|
8767
|
+
this.removeAllListeners();
|
|
8768
|
+
}
|
|
8769
|
+
};
|
|
8770
|
+
|
|
8771
|
+
//#endregion
|
|
8772
|
+
//#region packages/provider/src/webrtc/PeerConnection.ts
|
|
8773
|
+
var PeerConnection = class extends EventEmitter {
|
|
8774
|
+
constructor(peerId, iceServers) {
|
|
8775
|
+
super();
|
|
8776
|
+
this.pendingCandidates = [];
|
|
8777
|
+
this.hasRemoteDescription = false;
|
|
8778
|
+
this.peerId = peerId;
|
|
8779
|
+
this.connection = new RTCPeerConnection({ iceServers });
|
|
8780
|
+
this.router = new DataChannelRouter(this.connection);
|
|
8781
|
+
this.connection.onicecandidate = (event) => {
|
|
8782
|
+
if (event.candidate) this.emit("iceCandidate", {
|
|
8783
|
+
peerId: this.peerId,
|
|
8784
|
+
candidate: JSON.stringify(event.candidate.toJSON())
|
|
8785
|
+
});
|
|
8786
|
+
};
|
|
8787
|
+
this.connection.oniceconnectionstatechange = () => {
|
|
8788
|
+
const state = this.connection.iceConnectionState;
|
|
8789
|
+
this.emit("iceStateChange", {
|
|
8790
|
+
peerId: this.peerId,
|
|
8791
|
+
state
|
|
8792
|
+
});
|
|
8793
|
+
if (state === "failed") this.emit("iceFailed", { peerId: this.peerId });
|
|
8794
|
+
};
|
|
8795
|
+
this.connection.onconnectionstatechange = () => {
|
|
8796
|
+
this.emit("connectionStateChange", {
|
|
8797
|
+
peerId: this.peerId,
|
|
8798
|
+
state: this.connection.connectionState
|
|
8799
|
+
});
|
|
8800
|
+
};
|
|
8801
|
+
}
|
|
8802
|
+
get connectionState() {
|
|
8803
|
+
return this.connection.connectionState;
|
|
8804
|
+
}
|
|
8805
|
+
get iceConnectionState() {
|
|
8806
|
+
return this.connection.iceConnectionState;
|
|
8807
|
+
}
|
|
8808
|
+
/** Create an SDP offer (initiator side). */
|
|
8809
|
+
async createOffer(iceRestart = false) {
|
|
8810
|
+
const offer = await this.connection.createOffer(iceRestart ? { iceRestart: true } : void 0);
|
|
8811
|
+
await this.connection.setLocalDescription(offer);
|
|
8812
|
+
return JSON.stringify(this.connection.localDescription?.toJSON());
|
|
8813
|
+
}
|
|
8814
|
+
/** Set a remote offer and create an answer (receiver side). Returns the SDP answer. */
|
|
8815
|
+
async setRemoteOffer(sdp) {
|
|
8816
|
+
const offer = JSON.parse(sdp);
|
|
8817
|
+
await this.connection.setRemoteDescription(new RTCSessionDescription(offer));
|
|
8818
|
+
this.hasRemoteDescription = true;
|
|
8819
|
+
await this.flushPendingCandidates();
|
|
8820
|
+
const answer = await this.connection.createAnswer();
|
|
8821
|
+
await this.connection.setLocalDescription(answer);
|
|
8822
|
+
return JSON.stringify(this.connection.localDescription?.toJSON());
|
|
8823
|
+
}
|
|
8824
|
+
/** Set the remote answer (initiator side). */
|
|
8825
|
+
async setRemoteAnswer(sdp) {
|
|
8826
|
+
const answer = JSON.parse(sdp);
|
|
8827
|
+
await this.connection.setRemoteDescription(new RTCSessionDescription(answer));
|
|
8828
|
+
this.hasRemoteDescription = true;
|
|
8829
|
+
await this.flushPendingCandidates();
|
|
8830
|
+
}
|
|
8831
|
+
/** Add a remote ICE candidate. Queues if remote description not yet set. */
|
|
8832
|
+
async addIceCandidate(candidateJson) {
|
|
8833
|
+
const candidate = JSON.parse(candidateJson);
|
|
8834
|
+
if (!this.hasRemoteDescription) {
|
|
8835
|
+
this.pendingCandidates.push(candidate);
|
|
8836
|
+
return;
|
|
8837
|
+
}
|
|
8838
|
+
await this.connection.addIceCandidate(new RTCIceCandidate(candidate));
|
|
8839
|
+
}
|
|
8840
|
+
async flushPendingCandidates() {
|
|
8841
|
+
for (const candidate of this.pendingCandidates) await this.connection.addIceCandidate(new RTCIceCandidate(candidate));
|
|
8842
|
+
this.pendingCandidates = [];
|
|
8843
|
+
}
|
|
8844
|
+
close() {
|
|
8845
|
+
this.router.close();
|
|
8846
|
+
try {
|
|
8847
|
+
this.connection.close();
|
|
8848
|
+
} catch {}
|
|
8849
|
+
}
|
|
8850
|
+
destroy() {
|
|
8851
|
+
this.router.destroy();
|
|
8852
|
+
this.connection.onicecandidate = null;
|
|
8853
|
+
this.connection.oniceconnectionstatechange = null;
|
|
8854
|
+
this.connection.onconnectionstatechange = null;
|
|
8855
|
+
try {
|
|
8856
|
+
this.connection.close();
|
|
8857
|
+
} catch {}
|
|
8858
|
+
this.removeAllListeners();
|
|
8859
|
+
}
|
|
8860
|
+
};
|
|
8861
|
+
|
|
8862
|
+
//#endregion
|
|
8863
|
+
//#region packages/provider/src/webrtc/YjsDataChannel.ts
|
|
8864
|
+
/**
|
|
8865
|
+
* Handles Y.js document sync and awareness over WebRTC data channels.
|
|
8866
|
+
*
|
|
8867
|
+
* Uses the same y-protocols/sync encoding as the WebSocket provider but
|
|
8868
|
+
* transported over RTCDataChannel instead. A unique origin is used to
|
|
8869
|
+
* prevent echo loops with the server-based provider.
|
|
8870
|
+
*/
|
|
8871
|
+
var YjsDataChannel = class {
|
|
8872
|
+
constructor(document, awareness, router) {
|
|
8873
|
+
this.document = document;
|
|
8874
|
+
this.awareness = awareness;
|
|
8875
|
+
this.router = router;
|
|
8876
|
+
this.docUpdateHandler = null;
|
|
8877
|
+
this.awarenessUpdateHandler = null;
|
|
8878
|
+
this.channelOpenHandler = null;
|
|
8879
|
+
this.channelMessageHandler = null;
|
|
8880
|
+
}
|
|
8881
|
+
/** Start listening for Y.js updates and data channel messages. */
|
|
8882
|
+
attach() {
|
|
8883
|
+
this.docUpdateHandler = (update, origin) => {
|
|
8884
|
+
if (origin === this) return;
|
|
8885
|
+
const channel = this.router.getChannel(CHANNEL_NAMES.YJS_SYNC);
|
|
8886
|
+
if (!channel || channel.readyState !== "open") return;
|
|
8887
|
+
const encoder = createEncoder();
|
|
8888
|
+
writeVarUint(encoder, YJS_MSG.UPDATE);
|
|
8889
|
+
writeVarUint8Array(encoder, update);
|
|
8890
|
+
channel.send(toUint8Array(encoder));
|
|
8891
|
+
};
|
|
8892
|
+
this.document.on("update", this.docUpdateHandler);
|
|
8893
|
+
if (this.awareness) {
|
|
8894
|
+
this.awarenessUpdateHandler = ({ added, updated, removed }, _origin) => {
|
|
8895
|
+
const channel = this.router.getChannel(CHANNEL_NAMES.AWARENESS);
|
|
8896
|
+
if (!channel || channel.readyState !== "open") return;
|
|
8897
|
+
const changedClients = added.concat(updated).concat(removed);
|
|
8898
|
+
const update = encodeAwarenessUpdate(this.awareness, changedClients);
|
|
8899
|
+
channel.send(update);
|
|
8900
|
+
};
|
|
8901
|
+
this.awareness.on("update", this.awarenessUpdateHandler);
|
|
8902
|
+
}
|
|
8903
|
+
this.channelMessageHandler = ({ name, data }) => {
|
|
8904
|
+
if (name === CHANNEL_NAMES.YJS_SYNC) this.handleSyncMessage(data);
|
|
8905
|
+
else if (name === CHANNEL_NAMES.AWARENESS) this.handleAwarenessMessage(data);
|
|
8906
|
+
};
|
|
8907
|
+
this.router.on("channelMessage", this.channelMessageHandler);
|
|
8908
|
+
this.channelOpenHandler = ({ name }) => {
|
|
8909
|
+
if (name === CHANNEL_NAMES.YJS_SYNC) this.sendSyncStep1();
|
|
8910
|
+
else if (name === CHANNEL_NAMES.AWARENESS && this.awareness) {
|
|
8911
|
+
const channel = this.router.getChannel(CHANNEL_NAMES.AWARENESS);
|
|
8912
|
+
if (channel?.readyState === "open") {
|
|
8913
|
+
const update = encodeAwarenessUpdate(this.awareness, Array.from(this.awareness.getStates().keys()));
|
|
8914
|
+
channel.send(update);
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
};
|
|
8918
|
+
this.router.on("channelOpen", this.channelOpenHandler);
|
|
8919
|
+
if (this.router.isOpen(CHANNEL_NAMES.YJS_SYNC)) this.sendSyncStep1();
|
|
8920
|
+
if (this.awareness && this.router.isOpen(CHANNEL_NAMES.AWARENESS)) {
|
|
8921
|
+
const channel = this.router.getChannel(CHANNEL_NAMES.AWARENESS);
|
|
8922
|
+
if (channel?.readyState === "open") {
|
|
8923
|
+
const update = encodeAwarenessUpdate(this.awareness, Array.from(this.awareness.getStates().keys()));
|
|
8924
|
+
channel.send(update);
|
|
8925
|
+
}
|
|
8926
|
+
}
|
|
8927
|
+
}
|
|
8928
|
+
/** Stop listening and clean up handlers. */
|
|
8929
|
+
detach() {
|
|
8930
|
+
if (this.docUpdateHandler) {
|
|
8931
|
+
this.document.off("update", this.docUpdateHandler);
|
|
8932
|
+
this.docUpdateHandler = null;
|
|
8933
|
+
}
|
|
8934
|
+
if (this.awarenessUpdateHandler && this.awareness) {
|
|
8935
|
+
this.awareness.off("update", this.awarenessUpdateHandler);
|
|
8936
|
+
this.awarenessUpdateHandler = null;
|
|
8937
|
+
}
|
|
8938
|
+
if (this.channelMessageHandler) {
|
|
8939
|
+
this.router.off("channelMessage", this.channelMessageHandler);
|
|
8940
|
+
this.channelMessageHandler = null;
|
|
8941
|
+
}
|
|
8942
|
+
if (this.channelOpenHandler) {
|
|
8943
|
+
this.router.off("channelOpen", this.channelOpenHandler);
|
|
8944
|
+
this.channelOpenHandler = null;
|
|
8945
|
+
}
|
|
8946
|
+
this.isSynced = false;
|
|
8947
|
+
}
|
|
8948
|
+
destroy() {
|
|
8949
|
+
this.detach();
|
|
8950
|
+
}
|
|
8951
|
+
sendSyncStep1() {
|
|
8952
|
+
const channel = this.router.getChannel(CHANNEL_NAMES.YJS_SYNC);
|
|
8953
|
+
if (!channel || channel.readyState !== "open") return;
|
|
8954
|
+
const encoder = createEncoder();
|
|
8955
|
+
writeVarUint(encoder, YJS_MSG.SYNC);
|
|
8956
|
+
writeSyncStep1(encoder, this.document);
|
|
8957
|
+
channel.send(toUint8Array(encoder));
|
|
8958
|
+
}
|
|
8959
|
+
handleSyncMessage(data) {
|
|
8960
|
+
const buf = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
8961
|
+
const decoder = createDecoder(buf);
|
|
8962
|
+
const msgType = readVarUint(decoder);
|
|
8963
|
+
if (msgType === YJS_MSG.SYNC) {
|
|
8964
|
+
const encoder = createEncoder();
|
|
8965
|
+
const syncMessageType = readSyncMessage(decoder, encoder, this.document, this);
|
|
8966
|
+
if (length(encoder) > 0) {
|
|
8967
|
+
const responseEncoder = createEncoder();
|
|
8968
|
+
writeVarUint(responseEncoder, YJS_MSG.SYNC);
|
|
8969
|
+
writeUint8Array(responseEncoder, toUint8Array(encoder));
|
|
8970
|
+
const channel = this.router.getChannel(CHANNEL_NAMES.YJS_SYNC);
|
|
8971
|
+
if (channel?.readyState === "open") channel.send(toUint8Array(responseEncoder));
|
|
8972
|
+
}
|
|
8973
|
+
if (syncMessageType === messageYjsSyncStep2) this.isSynced = true;
|
|
8974
|
+
} else if (msgType === YJS_MSG.UPDATE) {
|
|
8975
|
+
const update = readVarUint8Array(decoder);
|
|
8976
|
+
Y.applyUpdate(this.document, update, this);
|
|
8977
|
+
}
|
|
8978
|
+
}
|
|
8979
|
+
handleAwarenessMessage(data) {
|
|
8980
|
+
if (!this.awareness) return;
|
|
8981
|
+
const buf = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
8982
|
+
applyAwarenessUpdate(this.awareness, buf, this);
|
|
8983
|
+
}
|
|
8984
|
+
};
|
|
8985
|
+
|
|
8986
|
+
//#endregion
|
|
8987
|
+
//#region packages/provider/src/webrtc/FileTransferChannel.ts
|
|
8988
|
+
/**
|
|
8989
|
+
* Handle for tracking a file transfer in progress.
|
|
8990
|
+
*/
|
|
8991
|
+
var FileTransferHandle = class extends EventEmitter {
|
|
8992
|
+
constructor(transferId) {
|
|
8993
|
+
super();
|
|
8994
|
+
this.progress = 0;
|
|
8995
|
+
this.status = "pending";
|
|
8996
|
+
this.abortController = new AbortController();
|
|
8997
|
+
this.transferId = transferId;
|
|
8998
|
+
}
|
|
8999
|
+
cancel() {
|
|
9000
|
+
this.status = "cancelled";
|
|
9001
|
+
this.abortController.abort();
|
|
9002
|
+
this.emit("cancelled");
|
|
9003
|
+
}
|
|
9004
|
+
get signal() {
|
|
9005
|
+
return this.abortController.signal;
|
|
9006
|
+
}
|
|
9007
|
+
/** @internal */
|
|
9008
|
+
_setProgress(p) {
|
|
9009
|
+
this.progress = p;
|
|
9010
|
+
this.emit("progress", p);
|
|
9011
|
+
}
|
|
9012
|
+
/** @internal */
|
|
9013
|
+
_setStatus(s) {
|
|
9014
|
+
this.status = s;
|
|
9015
|
+
}
|
|
9016
|
+
};
|
|
9017
|
+
/**
|
|
9018
|
+
* Chunked binary file transfer over a dedicated WebRTC data channel.
|
|
9019
|
+
*/
|
|
9020
|
+
var FileTransferChannel = class extends EventEmitter {
|
|
9021
|
+
constructor(router, chunkSize) {
|
|
9022
|
+
super();
|
|
9023
|
+
this.router = router;
|
|
9024
|
+
this.receives = /* @__PURE__ */ new Map();
|
|
9025
|
+
this.channelMessageHandler = null;
|
|
9026
|
+
this.chunkSize = chunkSize ?? DEFAULT_FILE_CHUNK_SIZE;
|
|
9027
|
+
this.channelMessageHandler = ({ name, data }) => {
|
|
9028
|
+
if (name === CHANNEL_NAMES.FILE_TRANSFER) this.handleMessage(data);
|
|
9029
|
+
};
|
|
9030
|
+
this.router.on("channelMessage", this.channelMessageHandler);
|
|
9031
|
+
}
|
|
9032
|
+
/** Send a file to a peer. Returns a handle for tracking progress. */
|
|
9033
|
+
async send(file, filename) {
|
|
9034
|
+
const transferId = generateTransferId();
|
|
9035
|
+
const handle = new FileTransferHandle(transferId);
|
|
9036
|
+
const transferIdBytes = hexToBytes(transferId);
|
|
9037
|
+
const totalSize = file.size;
|
|
9038
|
+
const totalChunks = Math.ceil(totalSize / this.chunkSize);
|
|
9039
|
+
const meta = {
|
|
9040
|
+
transferId,
|
|
9041
|
+
filename,
|
|
9042
|
+
mimeType: file instanceof File ? file.type : "application/octet-stream",
|
|
9043
|
+
totalSize,
|
|
9044
|
+
chunkSize: this.chunkSize,
|
|
9045
|
+
totalChunks
|
|
9046
|
+
};
|
|
9047
|
+
const channel = this.router.getChannel(CHANNEL_NAMES.FILE_TRANSFER);
|
|
9048
|
+
if (!channel || channel.readyState !== "open") {
|
|
9049
|
+
handle._setStatus("error");
|
|
9050
|
+
handle.emit("error", /* @__PURE__ */ new Error("File transfer channel not open"));
|
|
9051
|
+
return handle;
|
|
9052
|
+
}
|
|
9053
|
+
const startMsg = new Uint8Array(1 + new TextEncoder().encode(JSON.stringify(meta)).length);
|
|
9054
|
+
startMsg[0] = FILE_MSG.START;
|
|
9055
|
+
startMsg.set(new TextEncoder().encode(JSON.stringify(meta)), 1);
|
|
9056
|
+
channel.send(startMsg);
|
|
9057
|
+
handle._setStatus("sending");
|
|
9058
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
9059
|
+
const fileBytes = new Uint8Array(arrayBuffer);
|
|
9060
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", fileBytes);
|
|
9061
|
+
const hashBytes = new Uint8Array(hashBuffer);
|
|
9062
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
9063
|
+
if (handle.signal.aborted) {
|
|
9064
|
+
const cancelMsg = new Uint8Array(1 + TRANSFER_ID_BYTES);
|
|
9065
|
+
cancelMsg[0] = FILE_MSG.CANCEL;
|
|
9066
|
+
cancelMsg.set(transferIdBytes, 1);
|
|
9067
|
+
channel.send(cancelMsg);
|
|
9068
|
+
return handle;
|
|
9069
|
+
}
|
|
9070
|
+
const offset = i * this.chunkSize;
|
|
9071
|
+
const chunk = fileBytes.slice(offset, Math.min(offset + this.chunkSize, totalSize));
|
|
9072
|
+
const msg = new Uint8Array(1 + TRANSFER_ID_BYTES + 4 + chunk.length);
|
|
9073
|
+
msg[0] = FILE_MSG.CHUNK;
|
|
9074
|
+
msg.set(transferIdBytes, 1);
|
|
9075
|
+
new DataView(msg.buffer).setUint32(1 + TRANSFER_ID_BYTES, i, false);
|
|
9076
|
+
msg.set(chunk, 1 + TRANSFER_ID_BYTES + 4);
|
|
9077
|
+
while (channel.bufferedAmount > this.chunkSize * 4) {
|
|
9078
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
9079
|
+
if (handle.signal.aborted) return handle;
|
|
9080
|
+
}
|
|
9081
|
+
channel.send(msg);
|
|
9082
|
+
handle._setProgress((i + 1) / totalChunks);
|
|
9083
|
+
}
|
|
9084
|
+
const completeMsg = new Uint8Array(1 + TRANSFER_ID_BYTES + SHA256_BYTES);
|
|
9085
|
+
completeMsg[0] = FILE_MSG.COMPLETE;
|
|
9086
|
+
completeMsg.set(transferIdBytes, 1);
|
|
9087
|
+
completeMsg.set(hashBytes, 1 + TRANSFER_ID_BYTES);
|
|
9088
|
+
channel.send(completeMsg);
|
|
9089
|
+
handle._setStatus("complete");
|
|
9090
|
+
handle.emit("complete");
|
|
9091
|
+
return handle;
|
|
9092
|
+
}
|
|
9093
|
+
handleMessage(data) {
|
|
9094
|
+
const buf = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
9095
|
+
if (buf.length < 1) return;
|
|
9096
|
+
switch (buf[0]) {
|
|
9097
|
+
case FILE_MSG.START:
|
|
9098
|
+
this.handleStart(buf);
|
|
9099
|
+
break;
|
|
9100
|
+
case FILE_MSG.CHUNK:
|
|
9101
|
+
this.handleChunk(buf);
|
|
9102
|
+
break;
|
|
9103
|
+
case FILE_MSG.COMPLETE:
|
|
9104
|
+
this.handleComplete(buf);
|
|
9105
|
+
break;
|
|
9106
|
+
case FILE_MSG.CANCEL:
|
|
9107
|
+
this.handleCancel(buf);
|
|
9108
|
+
break;
|
|
9109
|
+
}
|
|
9110
|
+
}
|
|
9111
|
+
handleStart(buf) {
|
|
9112
|
+
const json = new TextDecoder().decode(buf.slice(1));
|
|
9113
|
+
let meta;
|
|
9114
|
+
try {
|
|
9115
|
+
meta = JSON.parse(json);
|
|
9116
|
+
} catch {
|
|
9117
|
+
return;
|
|
9118
|
+
}
|
|
9119
|
+
this.receives.set(meta.transferId, {
|
|
9120
|
+
meta,
|
|
9121
|
+
chunks: new Array(meta.totalChunks).fill(null),
|
|
9122
|
+
receivedCount: 0
|
|
9123
|
+
});
|
|
9124
|
+
this.emit("receiveStart", meta);
|
|
9125
|
+
}
|
|
9126
|
+
handleChunk(buf) {
|
|
9127
|
+
if (buf.length < 1 + TRANSFER_ID_BYTES + 4) return;
|
|
9128
|
+
const transferId = bytesToHex(buf.slice(1, 1 + TRANSFER_ID_BYTES));
|
|
9129
|
+
const chunkIndex = new DataView(buf.buffer, buf.byteOffset).getUint32(1 + TRANSFER_ID_BYTES, false);
|
|
9130
|
+
const chunkData = buf.slice(1 + TRANSFER_ID_BYTES + 4);
|
|
9131
|
+
const state = this.receives.get(transferId);
|
|
9132
|
+
if (!state) return;
|
|
9133
|
+
if (chunkIndex < state.chunks.length && !state.chunks[chunkIndex]) {
|
|
9134
|
+
state.chunks[chunkIndex] = chunkData;
|
|
9135
|
+
state.receivedCount++;
|
|
9136
|
+
const progress = state.receivedCount / state.meta.totalChunks;
|
|
9137
|
+
this.emit("receiveProgress", {
|
|
9138
|
+
transferId,
|
|
9139
|
+
received: state.receivedCount,
|
|
9140
|
+
total: state.meta.totalChunks,
|
|
9141
|
+
progress
|
|
9142
|
+
});
|
|
9143
|
+
}
|
|
9144
|
+
}
|
|
9145
|
+
async handleComplete(buf) {
|
|
9146
|
+
if (buf.length < 1 + TRANSFER_ID_BYTES + SHA256_BYTES) return;
|
|
9147
|
+
const transferId = bytesToHex(buf.slice(1, 1 + TRANSFER_ID_BYTES));
|
|
9148
|
+
const expectedHash = buf.slice(1 + TRANSFER_ID_BYTES, 1 + TRANSFER_ID_BYTES + SHA256_BYTES);
|
|
9149
|
+
const state = this.receives.get(transferId);
|
|
9150
|
+
if (!state) return;
|
|
9151
|
+
const totalSize = state.meta.totalSize;
|
|
9152
|
+
const assembled = new Uint8Array(totalSize);
|
|
9153
|
+
let offset = 0;
|
|
9154
|
+
for (let i = 0; i < state.chunks.length; i++) {
|
|
9155
|
+
const chunk = state.chunks[i];
|
|
9156
|
+
if (!chunk) {
|
|
9157
|
+
this.emit("receiveError", {
|
|
9158
|
+
transferId,
|
|
9159
|
+
error: `Missing chunk ${i}`
|
|
9160
|
+
});
|
|
9161
|
+
this.receives.delete(transferId);
|
|
9162
|
+
return;
|
|
9163
|
+
}
|
|
9164
|
+
assembled.set(chunk, offset);
|
|
9165
|
+
offset += chunk.length;
|
|
9166
|
+
}
|
|
9167
|
+
const actualHashBuffer = await crypto.subtle.digest("SHA-256", assembled);
|
|
9168
|
+
if (!constantTimeEqual(expectedHash, new Uint8Array(actualHashBuffer))) {
|
|
9169
|
+
this.emit("receiveError", {
|
|
9170
|
+
transferId,
|
|
9171
|
+
error: "SHA-256 integrity check failed"
|
|
9172
|
+
});
|
|
9173
|
+
this.receives.delete(transferId);
|
|
9174
|
+
return;
|
|
9175
|
+
}
|
|
9176
|
+
const blob = new Blob([assembled], { type: state.meta.mimeType });
|
|
9177
|
+
this.emit("receiveComplete", {
|
|
9178
|
+
transferId,
|
|
9179
|
+
blob,
|
|
9180
|
+
filename: state.meta.filename,
|
|
9181
|
+
mimeType: state.meta.mimeType,
|
|
9182
|
+
size: state.meta.totalSize
|
|
9183
|
+
});
|
|
9184
|
+
this.receives.delete(transferId);
|
|
9185
|
+
}
|
|
9186
|
+
handleCancel(buf) {
|
|
9187
|
+
if (buf.length < 1 + TRANSFER_ID_BYTES) return;
|
|
9188
|
+
const transferId = bytesToHex(buf.slice(1, 1 + TRANSFER_ID_BYTES));
|
|
9189
|
+
this.receives.delete(transferId);
|
|
9190
|
+
this.emit("receiveCancelled", { transferId });
|
|
9191
|
+
}
|
|
9192
|
+
destroy() {
|
|
9193
|
+
if (this.channelMessageHandler) {
|
|
9194
|
+
this.router.off("channelMessage", this.channelMessageHandler);
|
|
9195
|
+
this.channelMessageHandler = null;
|
|
9196
|
+
}
|
|
9197
|
+
this.receives.clear();
|
|
9198
|
+
this.removeAllListeners();
|
|
9199
|
+
}
|
|
9200
|
+
};
|
|
9201
|
+
function generateTransferId() {
|
|
9202
|
+
const bytes = new Uint8Array(TRANSFER_ID_BYTES);
|
|
9203
|
+
crypto.getRandomValues(bytes);
|
|
9204
|
+
return bytesToHex(bytes);
|
|
9205
|
+
}
|
|
9206
|
+
function bytesToHex(bytes) {
|
|
9207
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
9208
|
+
}
|
|
9209
|
+
function hexToBytes(hex) {
|
|
9210
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
9211
|
+
for (let i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
9212
|
+
return bytes;
|
|
9213
|
+
}
|
|
9214
|
+
function constantTimeEqual(a, b) {
|
|
9215
|
+
if (a.length !== b.length) return false;
|
|
9216
|
+
let result = 0;
|
|
9217
|
+
for (let i = 0; i < a.length; i++) result |= a[i] ^ b[i];
|
|
9218
|
+
return result === 0;
|
|
9219
|
+
}
|
|
9220
|
+
|
|
9221
|
+
//#endregion
|
|
9222
|
+
//#region packages/provider/src/webrtc/AbracadabraWebRTC.ts
|
|
9223
|
+
const HAS_RTC = typeof globalThis.RTCPeerConnection !== "undefined";
|
|
9224
|
+
/**
|
|
9225
|
+
* Optional WebRTC provider for peer-to-peer Y.js sync, awareness, and file transfer.
|
|
9226
|
+
*
|
|
9227
|
+
* Uses the server's signaling endpoint (`/ws/:doc_id/signaling`) for connection
|
|
9228
|
+
* negotiation, then establishes direct data channels between peers. Designed to
|
|
9229
|
+
* work alongside `AbracadabraProvider` — the server remains the persistence layer,
|
|
9230
|
+
* while WebRTC provides low-latency P2P sync.
|
|
9231
|
+
*
|
|
9232
|
+
* Falls back to a no-op when `RTCPeerConnection` is unavailable (e.g. Node.js).
|
|
9233
|
+
*/
|
|
9234
|
+
var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
|
|
9235
|
+
constructor(configuration) {
|
|
9236
|
+
super();
|
|
9237
|
+
this.signaling = null;
|
|
9238
|
+
this.peerConnections = /* @__PURE__ */ new Map();
|
|
9239
|
+
this.yjsChannels = /* @__PURE__ */ new Map();
|
|
9240
|
+
this.fileChannels = /* @__PURE__ */ new Map();
|
|
9241
|
+
this.peers = /* @__PURE__ */ new Map();
|
|
9242
|
+
this.localPeerId = null;
|
|
9243
|
+
this.isConnected = false;
|
|
9244
|
+
const doc = configuration.document ?? null;
|
|
9245
|
+
const awareness = configuration.awareness ?? null;
|
|
9246
|
+
this.config = {
|
|
9247
|
+
docId: configuration.docId,
|
|
9248
|
+
url: configuration.url,
|
|
9249
|
+
token: configuration.token,
|
|
9250
|
+
document: doc,
|
|
9251
|
+
awareness,
|
|
9252
|
+
iceServers: configuration.iceServers ?? DEFAULT_ICE_SERVERS,
|
|
9253
|
+
displayName: configuration.displayName ?? null,
|
|
9254
|
+
color: configuration.color ?? null,
|
|
9255
|
+
enableDocSync: configuration.enableDocSync ?? !!doc,
|
|
9256
|
+
enableAwarenessSync: configuration.enableAwarenessSync ?? !!awareness,
|
|
9257
|
+
enableFileTransfer: configuration.enableFileTransfer ?? false,
|
|
9258
|
+
fileChunkSize: configuration.fileChunkSize ?? 16384,
|
|
9259
|
+
WebSocketPolyfill: configuration.WebSocketPolyfill
|
|
9260
|
+
};
|
|
9261
|
+
if (configuration.autoConnect !== false && HAS_RTC) this.connect();
|
|
9262
|
+
}
|
|
9263
|
+
/**
|
|
9264
|
+
* Create an AbracadabraWebRTC instance from an existing provider,
|
|
9265
|
+
* reusing its document, awareness, URL, and token.
|
|
9266
|
+
*/
|
|
9267
|
+
static fromProvider(provider, options) {
|
|
9268
|
+
const config = provider.configuration;
|
|
9269
|
+
const httpUrl = (config.websocketProvider?.url ?? config.url ?? "").replace(/^wss:/, "https:").replace(/^ws:/, "http:");
|
|
9270
|
+
return new AbracadabraWebRTC({
|
|
9271
|
+
docId: config.name,
|
|
9272
|
+
url: httpUrl,
|
|
9273
|
+
token: config.token,
|
|
9274
|
+
document: provider.document,
|
|
9275
|
+
awareness: provider.awareness,
|
|
9276
|
+
...options
|
|
9277
|
+
});
|
|
9278
|
+
}
|
|
9279
|
+
async connect() {
|
|
9280
|
+
if (!HAS_RTC) return;
|
|
9281
|
+
if (this.isConnected) return;
|
|
9282
|
+
this.signaling = new SignalingSocket({
|
|
9283
|
+
url: this.buildSignalingUrl(),
|
|
9284
|
+
token: this.config.token,
|
|
9285
|
+
autoConnect: false,
|
|
9286
|
+
WebSocketPolyfill: this.config.WebSocketPolyfill
|
|
9287
|
+
});
|
|
9288
|
+
this.signaling.on("welcome", (data) => {
|
|
9289
|
+
this.localPeerId = data.peerId;
|
|
9290
|
+
this.isConnected = true;
|
|
9291
|
+
this.emit("connected");
|
|
9292
|
+
if (this.config.displayName && this.config.color) this.signaling.sendProfile(this.config.displayName, this.config.color);
|
|
9293
|
+
for (const peer of data.peers) {
|
|
9294
|
+
this.addPeer(peer);
|
|
9295
|
+
if (this.localPeerId < peer.peer_id) this.initiateConnection(peer.peer_id);
|
|
9296
|
+
}
|
|
9297
|
+
});
|
|
9298
|
+
this.signaling.on("joined", (peer) => {
|
|
9299
|
+
this.addPeer(peer);
|
|
9300
|
+
this.emit("peerJoined", peer);
|
|
9301
|
+
if (this.localPeerId < peer.peer_id) this.initiateConnection(peer.peer_id);
|
|
9302
|
+
});
|
|
9303
|
+
this.signaling.on("left", ({ peerId }) => {
|
|
9304
|
+
this.removePeer(peerId);
|
|
9305
|
+
this.emit("peerLeft", { peerId });
|
|
9306
|
+
});
|
|
9307
|
+
this.signaling.on("offer", async ({ from, sdp }) => {
|
|
9308
|
+
await this.handleOffer(from, sdp);
|
|
9309
|
+
});
|
|
9310
|
+
this.signaling.on("answer", async ({ from, sdp }) => {
|
|
9311
|
+
const pc = this.peerConnections.get(from);
|
|
9312
|
+
if (pc) await pc.setRemoteAnswer(sdp);
|
|
9313
|
+
});
|
|
9314
|
+
this.signaling.on("ice", async ({ from, candidate }) => {
|
|
9315
|
+
const pc = this.peerConnections.get(from);
|
|
9316
|
+
if (pc) await pc.addIceCandidate(candidate);
|
|
9317
|
+
});
|
|
9318
|
+
this.signaling.on("mute", ({ peerId, muted }) => {
|
|
9319
|
+
const peer = this.peers.get(peerId);
|
|
9320
|
+
if (peer) {
|
|
9321
|
+
peer.muted = muted;
|
|
9322
|
+
this.emit("peerMuted", {
|
|
9323
|
+
peerId,
|
|
9324
|
+
muted
|
|
9325
|
+
});
|
|
9326
|
+
}
|
|
9327
|
+
});
|
|
9328
|
+
this.signaling.on("media-state", ({ peerId, video, screen }) => {
|
|
9329
|
+
const peer = this.peers.get(peerId);
|
|
9330
|
+
if (peer) {
|
|
9331
|
+
peer.video = video;
|
|
9332
|
+
peer.screen = screen;
|
|
9333
|
+
this.emit("peerMediaState", {
|
|
9334
|
+
peerId,
|
|
9335
|
+
video,
|
|
9336
|
+
screen
|
|
9337
|
+
});
|
|
9338
|
+
}
|
|
9339
|
+
});
|
|
9340
|
+
this.signaling.on("profile", ({ peerId, name, color }) => {
|
|
9341
|
+
const peer = this.peers.get(peerId);
|
|
9342
|
+
if (peer) {
|
|
9343
|
+
peer.name = name;
|
|
9344
|
+
peer.color = color;
|
|
9345
|
+
this.emit("peerProfile", {
|
|
9346
|
+
peerId,
|
|
9347
|
+
name,
|
|
9348
|
+
color
|
|
9349
|
+
});
|
|
9350
|
+
}
|
|
9351
|
+
});
|
|
9352
|
+
this.signaling.on("disconnected", () => {
|
|
9353
|
+
this.isConnected = false;
|
|
9354
|
+
this.localPeerId = null;
|
|
9355
|
+
this.removeAllPeers();
|
|
9356
|
+
this.emit("disconnected");
|
|
9357
|
+
});
|
|
9358
|
+
this.signaling.on("error", (err) => {
|
|
9359
|
+
this.emit("signalingError", err);
|
|
9360
|
+
});
|
|
9361
|
+
await this.signaling.connect();
|
|
9362
|
+
}
|
|
9363
|
+
disconnect() {
|
|
9364
|
+
if (!HAS_RTC) return;
|
|
9365
|
+
this.removeAllPeers();
|
|
9366
|
+
if (this.signaling) {
|
|
9367
|
+
this.signaling.disconnect();
|
|
9368
|
+
this.signaling = null;
|
|
9369
|
+
}
|
|
9370
|
+
this.isConnected = false;
|
|
9371
|
+
this.localPeerId = null;
|
|
9372
|
+
}
|
|
9373
|
+
destroy() {
|
|
9374
|
+
this.disconnect();
|
|
9375
|
+
if (this.signaling) {
|
|
9376
|
+
this.signaling.destroy();
|
|
9377
|
+
this.signaling = null;
|
|
9378
|
+
}
|
|
9379
|
+
this.removeAllListeners();
|
|
9380
|
+
}
|
|
9381
|
+
setMuted(muted) {
|
|
9382
|
+
this.signaling?.sendMute(muted);
|
|
9383
|
+
}
|
|
9384
|
+
setMediaState(video, screen) {
|
|
9385
|
+
this.signaling?.sendMediaState(video, screen);
|
|
9386
|
+
}
|
|
9387
|
+
setProfile(name, color) {
|
|
9388
|
+
this.signaling?.sendProfile(name, color);
|
|
9389
|
+
}
|
|
9390
|
+
/**
|
|
9391
|
+
* Send a file to a specific peer. Returns a handle for tracking progress.
|
|
9392
|
+
*/
|
|
9393
|
+
async sendFile(peerId, file, filename) {
|
|
9394
|
+
const fc = this.fileChannels.get(peerId);
|
|
9395
|
+
if (!fc) return null;
|
|
9396
|
+
return fc.send(file, filename);
|
|
9397
|
+
}
|
|
9398
|
+
/**
|
|
9399
|
+
* Send a file to all connected peers. Returns an array of handles.
|
|
9400
|
+
*/
|
|
9401
|
+
async broadcastFile(file, filename) {
|
|
9402
|
+
const handles = [];
|
|
9403
|
+
for (const [peerId, fc] of this.fileChannels) {
|
|
9404
|
+
const handle = await fc.send(file, filename);
|
|
9405
|
+
handles.push(handle);
|
|
9406
|
+
}
|
|
9407
|
+
return handles;
|
|
9408
|
+
}
|
|
9409
|
+
/**
|
|
9410
|
+
* Send a custom string message to a specific peer via a data channel.
|
|
9411
|
+
*/
|
|
9412
|
+
sendCustomMessage(peerId, payload) {
|
|
9413
|
+
const pc = this.peerConnections.get(peerId);
|
|
9414
|
+
if (!pc) return;
|
|
9415
|
+
let channel = pc.router.getChannel("custom");
|
|
9416
|
+
if (!channel || channel.readyState !== "open") {
|
|
9417
|
+
channel = pc.router.createChannel("custom", { ordered: true });
|
|
9418
|
+
channel.onopen = () => {
|
|
9419
|
+
channel.send(payload);
|
|
9420
|
+
};
|
|
9421
|
+
return;
|
|
9422
|
+
}
|
|
9423
|
+
channel.send(payload);
|
|
9424
|
+
}
|
|
9425
|
+
/**
|
|
9426
|
+
* Send a custom string message to all connected peers.
|
|
9427
|
+
*/
|
|
9428
|
+
broadcastCustomMessage(payload) {
|
|
9429
|
+
for (const peerId of this.peerConnections.keys()) this.sendCustomMessage(peerId, payload);
|
|
9430
|
+
}
|
|
9431
|
+
addPeer(info) {
|
|
9432
|
+
this.peers.set(info.peer_id, {
|
|
9433
|
+
...info,
|
|
9434
|
+
connectionState: "new"
|
|
9435
|
+
});
|
|
9436
|
+
}
|
|
9437
|
+
removePeer(peerId) {
|
|
9438
|
+
this.peers.delete(peerId);
|
|
9439
|
+
const yjs = this.yjsChannels.get(peerId);
|
|
9440
|
+
if (yjs) {
|
|
9441
|
+
yjs.destroy();
|
|
9442
|
+
this.yjsChannels.delete(peerId);
|
|
9443
|
+
}
|
|
9444
|
+
const fc = this.fileChannels.get(peerId);
|
|
9445
|
+
if (fc) {
|
|
9446
|
+
fc.destroy();
|
|
9447
|
+
this.fileChannels.delete(peerId);
|
|
9448
|
+
}
|
|
9449
|
+
const pc = this.peerConnections.get(peerId);
|
|
9450
|
+
if (pc) {
|
|
9451
|
+
pc.destroy();
|
|
9452
|
+
this.peerConnections.delete(peerId);
|
|
9453
|
+
}
|
|
9454
|
+
}
|
|
9455
|
+
removeAllPeers() {
|
|
9456
|
+
for (const peerId of [...this.peers.keys()]) this.removePeer(peerId);
|
|
9457
|
+
}
|
|
9458
|
+
createPeerConnection(peerId) {
|
|
9459
|
+
const pc = new PeerConnection(peerId, this.config.iceServers);
|
|
9460
|
+
pc.on("iceCandidate", ({ peerId, candidate }) => {
|
|
9461
|
+
this.signaling?.sendIce(peerId, candidate);
|
|
9462
|
+
});
|
|
9463
|
+
pc.on("iceFailed", async ({ peerId }) => {
|
|
9464
|
+
try {
|
|
9465
|
+
const sdp = await pc.createOffer(true);
|
|
9466
|
+
this.signaling?.sendOffer(peerId, sdp);
|
|
9467
|
+
} catch {
|
|
9468
|
+
this.removePeer(peerId);
|
|
9469
|
+
}
|
|
9470
|
+
});
|
|
9471
|
+
pc.on("connectionStateChange", ({ peerId, state }) => {
|
|
9472
|
+
const peer = this.peers.get(peerId);
|
|
9473
|
+
if (peer) peer.connectionState = state;
|
|
9474
|
+
if (state === "disconnected" || state === "closed") {}
|
|
9475
|
+
this.emit("peerConnectionState", {
|
|
9476
|
+
peerId,
|
|
9477
|
+
state
|
|
9478
|
+
});
|
|
9479
|
+
});
|
|
9480
|
+
pc.router.on("channelMessage", ({ name, data }) => {
|
|
9481
|
+
if (name === "custom") {
|
|
9482
|
+
const payload = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
9483
|
+
this.emit("customMessage", {
|
|
9484
|
+
peerId,
|
|
9485
|
+
payload
|
|
9486
|
+
});
|
|
9487
|
+
}
|
|
9488
|
+
});
|
|
9489
|
+
this.peerConnections.set(peerId, pc);
|
|
9490
|
+
this.attachDataHandlers(peerId, pc);
|
|
9491
|
+
return pc;
|
|
9492
|
+
}
|
|
9493
|
+
attachDataHandlers(peerId, pc) {
|
|
9494
|
+
if (this.config.document && this.config.enableDocSync) {
|
|
9495
|
+
const yjs = new YjsDataChannel(this.config.document, this.config.enableAwarenessSync ? this.config.awareness : null, pc.router);
|
|
9496
|
+
yjs.attach();
|
|
9497
|
+
this.yjsChannels.set(peerId, yjs);
|
|
9498
|
+
}
|
|
9499
|
+
if (this.config.enableFileTransfer) {
|
|
9500
|
+
const fc = new FileTransferChannel(pc.router, this.config.fileChunkSize);
|
|
9501
|
+
fc.on("receiveStart", (meta) => {
|
|
9502
|
+
this.emit("fileReceiveStart", {
|
|
9503
|
+
peerId,
|
|
9504
|
+
...meta
|
|
9505
|
+
});
|
|
9506
|
+
});
|
|
9507
|
+
fc.on("receiveProgress", (data) => {
|
|
9508
|
+
this.emit("fileReceiveProgress", {
|
|
9509
|
+
peerId,
|
|
9510
|
+
...data
|
|
9511
|
+
});
|
|
9512
|
+
});
|
|
9513
|
+
fc.on("receiveComplete", (data) => {
|
|
9514
|
+
this.emit("fileReceiveComplete", {
|
|
9515
|
+
peerId,
|
|
9516
|
+
...data
|
|
9517
|
+
});
|
|
9518
|
+
});
|
|
9519
|
+
fc.on("receiveError", (data) => {
|
|
9520
|
+
this.emit("fileReceiveError", {
|
|
9521
|
+
peerId,
|
|
9522
|
+
...data
|
|
9523
|
+
});
|
|
9524
|
+
});
|
|
9525
|
+
fc.on("receiveCancelled", (data) => {
|
|
9526
|
+
this.emit("fileReceiveCancelled", {
|
|
9527
|
+
peerId,
|
|
9528
|
+
...data
|
|
9529
|
+
});
|
|
9530
|
+
});
|
|
9531
|
+
this.fileChannels.set(peerId, fc);
|
|
9532
|
+
}
|
|
9533
|
+
}
|
|
9534
|
+
async initiateConnection(peerId) {
|
|
9535
|
+
const pc = this.createPeerConnection(peerId);
|
|
9536
|
+
pc.router.createDefaultChannels({
|
|
9537
|
+
enableDocSync: this.config.enableDocSync,
|
|
9538
|
+
enableAwareness: this.config.enableAwarenessSync,
|
|
9539
|
+
enableFileTransfer: this.config.enableFileTransfer
|
|
9540
|
+
});
|
|
9541
|
+
try {
|
|
9542
|
+
const sdp = await pc.createOffer();
|
|
9543
|
+
this.signaling?.sendOffer(peerId, sdp);
|
|
9544
|
+
} catch {
|
|
9545
|
+
this.removePeer(peerId);
|
|
9546
|
+
}
|
|
9547
|
+
}
|
|
9548
|
+
async handleOffer(from, sdp) {
|
|
9549
|
+
const existing = this.peerConnections.get(from);
|
|
9550
|
+
if (existing) {
|
|
9551
|
+
existing.destroy();
|
|
9552
|
+
this.yjsChannels.get(from)?.destroy();
|
|
9553
|
+
this.yjsChannels.delete(from);
|
|
9554
|
+
this.fileChannels.get(from)?.destroy();
|
|
9555
|
+
this.fileChannels.delete(from);
|
|
9556
|
+
this.peerConnections.delete(from);
|
|
9557
|
+
}
|
|
9558
|
+
const pc = this.createPeerConnection(from);
|
|
9559
|
+
try {
|
|
9560
|
+
const answerSdp = await pc.setRemoteOffer(sdp);
|
|
9561
|
+
this.signaling?.sendAnswer(from, answerSdp);
|
|
9562
|
+
} catch {
|
|
9563
|
+
this.removePeer(from);
|
|
9564
|
+
}
|
|
9565
|
+
}
|
|
9566
|
+
buildSignalingUrl() {
|
|
9567
|
+
let base = this.config.url;
|
|
9568
|
+
while (base.endsWith("/")) base = base.slice(0, -1);
|
|
9569
|
+
base = base.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
|
|
9570
|
+
return `${base}/ws/${encodeURIComponent(this.config.docId)}/signaling`;
|
|
9571
|
+
}
|
|
9572
|
+
};
|
|
9573
|
+
|
|
9574
|
+
//#endregion
|
|
9575
|
+
export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AbracadabraWebRTC, AuthMessageType, AwarenessError, BackgroundSyncManager, BackgroundSyncPersistence, CHANNEL_NAMES, ConnectionTimeout, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DocKeyManager, DocumentCache, E2EAbracadabraProvider, E2EOfflineStore, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, Forbidden, HocuspocusProvider, HocuspocusProviderWebsocket, MessageTooBig, MessageType, OfflineStore, PeerConnection, ResetConnection, SearchIndex, SignalingSocket, SubdocMessage, Unauthorized, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, decryptField, encryptField, makeEncryptedYMap, makeEncryptedYText, readAuthMessage, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest };
|
|
8400
9576
|
//# sourceMappingURL=abracadabra-provider.esm.js.map
|