@takosjp/yurucommu-api 3.2.1 → 3.4.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/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +805 -0
- package/dist/lib/realtime-client.d.ts +37 -0
- package/dist/lib/realtime-client.d.ts.map +1 -0
- package/dist/lib/rtc-client.d.ts +84 -0
- package/dist/lib/rtc-client.d.ts.map +1 -0
- package/dist/types/call.d.ts +199 -0
- package/dist/types/call.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/realtime.d.ts +86 -0
- package/dist/types/realtime.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1355,6 +1355,707 @@ async function reportContent(input) {
|
|
|
1355
1355
|
});
|
|
1356
1356
|
await assertOk(res, "Failed to submit report");
|
|
1357
1357
|
}
|
|
1358
|
+
// src/lib/rtc-client.ts
|
|
1359
|
+
var CANDIDATE_FLUSH_MS = 200;
|
|
1360
|
+
|
|
1361
|
+
class CallClient {
|
|
1362
|
+
options;
|
|
1363
|
+
ws = null;
|
|
1364
|
+
wantConnected = false;
|
|
1365
|
+
backoff = 500;
|
|
1366
|
+
reconnectTimer = null;
|
|
1367
|
+
listeners = new Map;
|
|
1368
|
+
call = null;
|
|
1369
|
+
localStream = null;
|
|
1370
|
+
state = "idle";
|
|
1371
|
+
constructor(options = {}) {
|
|
1372
|
+
this.options = options;
|
|
1373
|
+
}
|
|
1374
|
+
on(event, listener) {
|
|
1375
|
+
let set = this.listeners.get(event);
|
|
1376
|
+
if (!set) {
|
|
1377
|
+
set = new Set;
|
|
1378
|
+
this.listeners.set(event, set);
|
|
1379
|
+
}
|
|
1380
|
+
set.add(listener);
|
|
1381
|
+
return () => set?.delete(listener);
|
|
1382
|
+
}
|
|
1383
|
+
emit(event, ...args) {
|
|
1384
|
+
for (const l of this.listeners.get(event) ?? []) {
|
|
1385
|
+
l(...args);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
setState(state) {
|
|
1389
|
+
if (this.state === state)
|
|
1390
|
+
return;
|
|
1391
|
+
this.state = state;
|
|
1392
|
+
this.emit("state", state);
|
|
1393
|
+
}
|
|
1394
|
+
getState() {
|
|
1395
|
+
return this.state;
|
|
1396
|
+
}
|
|
1397
|
+
origin() {
|
|
1398
|
+
return this.options.origin ?? (typeof location !== "undefined" ? location.origin : "");
|
|
1399
|
+
}
|
|
1400
|
+
socketUrl() {
|
|
1401
|
+
const o = this.origin();
|
|
1402
|
+
return `${o.replace(/^http/, "ws")}/api/rtc/socket`;
|
|
1403
|
+
}
|
|
1404
|
+
connect() {
|
|
1405
|
+
this.wantConnected = true;
|
|
1406
|
+
this.openSocket();
|
|
1407
|
+
}
|
|
1408
|
+
disconnect() {
|
|
1409
|
+
this.wantConnected = false;
|
|
1410
|
+
if (this.reconnectTimer)
|
|
1411
|
+
clearTimeout(this.reconnectTimer);
|
|
1412
|
+
this.reconnectTimer = null;
|
|
1413
|
+
this.ws?.close();
|
|
1414
|
+
this.ws = null;
|
|
1415
|
+
}
|
|
1416
|
+
openSocket() {
|
|
1417
|
+
if (this.ws && this.ws.readyState <= WebSocket.OPEN)
|
|
1418
|
+
return;
|
|
1419
|
+
let socket;
|
|
1420
|
+
try {
|
|
1421
|
+
socket = new WebSocket(this.socketUrl());
|
|
1422
|
+
} catch (err) {
|
|
1423
|
+
this.emit("error", "socket_open_failed", String(err));
|
|
1424
|
+
this.scheduleReconnect();
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
this.ws = socket;
|
|
1428
|
+
socket.onopen = () => {
|
|
1429
|
+
this.backoff = 500;
|
|
1430
|
+
this.send({ t: "hello" });
|
|
1431
|
+
if (this.call)
|
|
1432
|
+
this.send({ t: "resume", callId: this.call.callId });
|
|
1433
|
+
};
|
|
1434
|
+
socket.onmessage = (ev) => {
|
|
1435
|
+
this.onFrame(ev.data);
|
|
1436
|
+
};
|
|
1437
|
+
socket.onclose = () => {
|
|
1438
|
+
if (this.ws === socket)
|
|
1439
|
+
this.ws = null;
|
|
1440
|
+
if (this.wantConnected)
|
|
1441
|
+
this.scheduleReconnect();
|
|
1442
|
+
};
|
|
1443
|
+
socket.onerror = () => {
|
|
1444
|
+
socket.close();
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
scheduleReconnect() {
|
|
1448
|
+
if (!this.wantConnected || this.reconnectTimer)
|
|
1449
|
+
return;
|
|
1450
|
+
const max = this.options.maxBackoffMs ?? 15000;
|
|
1451
|
+
const delay = Math.min(this.backoff, max);
|
|
1452
|
+
this.backoff = Math.min(this.backoff * 2, max);
|
|
1453
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1454
|
+
this.reconnectTimer = null;
|
|
1455
|
+
this.openSocket();
|
|
1456
|
+
}, delay);
|
|
1457
|
+
}
|
|
1458
|
+
send(frame) {
|
|
1459
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1460
|
+
this.ws.send(JSON.stringify(frame));
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
async startCall(peer, media) {
|
|
1464
|
+
if (this.call)
|
|
1465
|
+
throw new Error("already in a call");
|
|
1466
|
+
const res = await fetch(`${this.origin()}/api/rtc/calls`, {
|
|
1467
|
+
method: "POST",
|
|
1468
|
+
credentials: "include",
|
|
1469
|
+
headers: { "Content-Type": "application/json" },
|
|
1470
|
+
body: JSON.stringify({ to: peer, media })
|
|
1471
|
+
});
|
|
1472
|
+
if (!res.ok) {
|
|
1473
|
+
const code = res.status === 403 ? "blocked" : "start_failed";
|
|
1474
|
+
this.emit("error", code);
|
|
1475
|
+
throw new Error(code);
|
|
1476
|
+
}
|
|
1477
|
+
const data = await res.json();
|
|
1478
|
+
this.call = this.newCall(data.callId, peer, media, "caller", data.iceServers);
|
|
1479
|
+
this.setState("calling");
|
|
1480
|
+
this.connect();
|
|
1481
|
+
this.send({ t: "invite", callId: data.callId, to: peer, media });
|
|
1482
|
+
await this.setupMedia(this.call);
|
|
1483
|
+
await this.makeOffer(this.call);
|
|
1484
|
+
}
|
|
1485
|
+
async accept() {
|
|
1486
|
+
const call = this.call;
|
|
1487
|
+
if (!call || call.role !== "callee")
|
|
1488
|
+
return;
|
|
1489
|
+
this.setState("connecting");
|
|
1490
|
+
this.send({ t: "accept", callId: call.callId });
|
|
1491
|
+
await this.setupMedia(call);
|
|
1492
|
+
if (call.pc && call.remoteDescriptionSet) {
|
|
1493
|
+
const answer = await call.pc.createAnswer();
|
|
1494
|
+
await call.pc.setLocalDescription(answer);
|
|
1495
|
+
this.send({ t: "answer", callId: call.callId, sdp: answer.sdp ?? "" });
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
reject(reason = "declined") {
|
|
1499
|
+
const call = this.call;
|
|
1500
|
+
if (!call)
|
|
1501
|
+
return;
|
|
1502
|
+
this.send({ t: "reject", callId: call.callId, reason });
|
|
1503
|
+
this.teardown("ended");
|
|
1504
|
+
}
|
|
1505
|
+
hangup(reason = "hangup") {
|
|
1506
|
+
const call = this.call;
|
|
1507
|
+
if (!call)
|
|
1508
|
+
return;
|
|
1509
|
+
this.send({ t: "hangup", callId: call.callId, reason });
|
|
1510
|
+
this.teardown("ended");
|
|
1511
|
+
}
|
|
1512
|
+
setMuted(muted) {
|
|
1513
|
+
for (const track of this.localStream?.getAudioTracks() ?? []) {
|
|
1514
|
+
track.enabled = !muted;
|
|
1515
|
+
}
|
|
1516
|
+
this.emit("muted", muted);
|
|
1517
|
+
}
|
|
1518
|
+
setCameraEnabled(enabled) {
|
|
1519
|
+
for (const track of this.localStream?.getVideoTracks() ?? []) {
|
|
1520
|
+
track.enabled = enabled;
|
|
1521
|
+
}
|
|
1522
|
+
this.emit("cameraoff", !enabled);
|
|
1523
|
+
}
|
|
1524
|
+
async onFrame(raw) {
|
|
1525
|
+
if (typeof raw !== "string")
|
|
1526
|
+
return;
|
|
1527
|
+
let frame;
|
|
1528
|
+
try {
|
|
1529
|
+
frame = JSON.parse(raw);
|
|
1530
|
+
} catch {
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
switch (frame.t) {
|
|
1534
|
+
case "ready":
|
|
1535
|
+
case "pong":
|
|
1536
|
+
return;
|
|
1537
|
+
case "ringing":
|
|
1538
|
+
this.onRinging(frame);
|
|
1539
|
+
return;
|
|
1540
|
+
case "ice-servers":
|
|
1541
|
+
if (this.call && this.call.callId === frame.callId) {
|
|
1542
|
+
this.call.iceServers = frame.iceServers;
|
|
1543
|
+
}
|
|
1544
|
+
return;
|
|
1545
|
+
case "offer":
|
|
1546
|
+
await this.onRemoteOffer(frame);
|
|
1547
|
+
return;
|
|
1548
|
+
case "answer":
|
|
1549
|
+
await this.onRemoteAnswer(frame);
|
|
1550
|
+
return;
|
|
1551
|
+
case "candidates":
|
|
1552
|
+
await this.onRemoteCandidates(frame);
|
|
1553
|
+
return;
|
|
1554
|
+
case "peer-accepted":
|
|
1555
|
+
if (this.state === "calling")
|
|
1556
|
+
this.setState("connecting");
|
|
1557
|
+
return;
|
|
1558
|
+
case "peer-rejected":
|
|
1559
|
+
this.emit("error", "declined", frame.reason);
|
|
1560
|
+
this.teardown("ended");
|
|
1561
|
+
return;
|
|
1562
|
+
case "peer-hangup":
|
|
1563
|
+
this.teardown("ended");
|
|
1564
|
+
return;
|
|
1565
|
+
case "call-state":
|
|
1566
|
+
this.onCallState(frame);
|
|
1567
|
+
return;
|
|
1568
|
+
case "error":
|
|
1569
|
+
this.emit("error", frame.code, frame.message);
|
|
1570
|
+
if (frame.code === "peer_unreachable")
|
|
1571
|
+
this.teardown("ended");
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
onRinging(frame) {
|
|
1576
|
+
if (this.call) {
|
|
1577
|
+
this.send({ t: "reject", callId: frame.callId, reason: "busy" });
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
this.call = this.newCall(frame.callId, frame.from, frame.media, "callee", []);
|
|
1581
|
+
this.setState("incoming");
|
|
1582
|
+
this.emit("incoming", {
|
|
1583
|
+
callId: frame.callId,
|
|
1584
|
+
from: frame.from,
|
|
1585
|
+
media: frame.media
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
onCallState(frame) {
|
|
1589
|
+
if (!this.call || this.call.callId !== frame.callId)
|
|
1590
|
+
return;
|
|
1591
|
+
if (frame.state === "missed" || frame.state === "cancelled" || frame.state === "rejected" || frame.state === "failed" || frame.state === "ended") {
|
|
1592
|
+
this.teardown("ended");
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
async onRemoteOffer(frame) {
|
|
1596
|
+
let call = this.call;
|
|
1597
|
+
if (!call || call.callId !== frame.callId) {
|
|
1598
|
+
call = this.newCall(frame.callId, "unknown", frame.media ?? { audio: true, video: false }, "callee", []);
|
|
1599
|
+
this.call = call;
|
|
1600
|
+
this.setState("incoming");
|
|
1601
|
+
}
|
|
1602
|
+
if (!call.pc)
|
|
1603
|
+
this.buildPeerConnection(call);
|
|
1604
|
+
await call.pc?.setRemoteDescription({ type: "offer", sdp: frame.sdp });
|
|
1605
|
+
call.remoteDescriptionSet = true;
|
|
1606
|
+
await this.drainRemoteCandidates(call);
|
|
1607
|
+
}
|
|
1608
|
+
async onRemoteAnswer(frame) {
|
|
1609
|
+
const call = this.call;
|
|
1610
|
+
if (!call || call.callId !== frame.callId || !call.pc)
|
|
1611
|
+
return;
|
|
1612
|
+
await call.pc.setRemoteDescription({ type: "answer", sdp: frame.sdp });
|
|
1613
|
+
call.remoteDescriptionSet = true;
|
|
1614
|
+
await this.drainRemoteCandidates(call);
|
|
1615
|
+
}
|
|
1616
|
+
async onRemoteCandidates(frame) {
|
|
1617
|
+
const call = this.call;
|
|
1618
|
+
if (!call || call.callId !== frame.callId)
|
|
1619
|
+
return;
|
|
1620
|
+
for (const cand of frame.candidates) {
|
|
1621
|
+
if (call.pc && call.remoteDescriptionSet) {
|
|
1622
|
+
try {
|
|
1623
|
+
await call.pc.addIceCandidate(cand);
|
|
1624
|
+
} catch {}
|
|
1625
|
+
} else {
|
|
1626
|
+
call.pendingRemoteCandidates.push(cand);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
async drainRemoteCandidates(call) {
|
|
1631
|
+
if (!call.pc)
|
|
1632
|
+
return;
|
|
1633
|
+
const pending = call.pendingRemoteCandidates.splice(0);
|
|
1634
|
+
for (const cand of pending) {
|
|
1635
|
+
try {
|
|
1636
|
+
await call.pc.addIceCandidate(cand);
|
|
1637
|
+
} catch {}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
async setupMedia(call) {
|
|
1641
|
+
if (!this.localStream) {
|
|
1642
|
+
try {
|
|
1643
|
+
this.localStream = await navigator.mediaDevices.getUserMedia({
|
|
1644
|
+
audio: call.media.audio,
|
|
1645
|
+
video: call.media.video
|
|
1646
|
+
});
|
|
1647
|
+
} catch (err) {
|
|
1648
|
+
this.emit("error", "media_denied", String(err));
|
|
1649
|
+
this.hangup("media_denied");
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
this.emit("localstream", this.localStream);
|
|
1653
|
+
}
|
|
1654
|
+
if (!call.pc)
|
|
1655
|
+
this.buildPeerConnection(call);
|
|
1656
|
+
for (const track of this.localStream.getTracks()) {
|
|
1657
|
+
call.pc?.addTrack(track, this.localStream);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
buildPeerConnection(call) {
|
|
1661
|
+
const pc = new RTCPeerConnection({
|
|
1662
|
+
iceServers: call.iceServers.map((s) => ({
|
|
1663
|
+
urls: s.urls,
|
|
1664
|
+
username: s.username,
|
|
1665
|
+
credential: s.credential
|
|
1666
|
+
}))
|
|
1667
|
+
});
|
|
1668
|
+
pc.onicecandidate = (ev) => {
|
|
1669
|
+
if (ev.candidate)
|
|
1670
|
+
this.bufferCandidate(call, ev.candidate.toJSON());
|
|
1671
|
+
else
|
|
1672
|
+
this.flushCandidates(call);
|
|
1673
|
+
};
|
|
1674
|
+
pc.ontrack = (ev) => {
|
|
1675
|
+
this.emit("remotestream", ev.streams[0] ?? null);
|
|
1676
|
+
};
|
|
1677
|
+
pc.onconnectionstatechange = () => {
|
|
1678
|
+
if (pc.connectionState === "connected")
|
|
1679
|
+
this.setState("connected");
|
|
1680
|
+
else if (pc.connectionState === "failed" || pc.connectionState === "closed") {
|
|
1681
|
+
this.teardown("ended");
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
call.pc = pc;
|
|
1685
|
+
}
|
|
1686
|
+
async makeOffer(call) {
|
|
1687
|
+
if (!call.pc)
|
|
1688
|
+
this.buildPeerConnection(call);
|
|
1689
|
+
const offer = await call.pc.createOffer();
|
|
1690
|
+
await call.pc.setLocalDescription(offer);
|
|
1691
|
+
this.send({ t: "offer", callId: call.callId, sdp: offer.sdp ?? "" });
|
|
1692
|
+
}
|
|
1693
|
+
bufferCandidate(call, cand) {
|
|
1694
|
+
call.candidateBuffer.push(cand);
|
|
1695
|
+
if (call.flushTimer)
|
|
1696
|
+
return;
|
|
1697
|
+
call.flushTimer = setTimeout(() => this.flushCandidates(call), CANDIDATE_FLUSH_MS);
|
|
1698
|
+
}
|
|
1699
|
+
flushCandidates(call) {
|
|
1700
|
+
if (call.flushTimer) {
|
|
1701
|
+
clearTimeout(call.flushTimer);
|
|
1702
|
+
call.flushTimer = null;
|
|
1703
|
+
}
|
|
1704
|
+
if (call.candidateBuffer.length === 0)
|
|
1705
|
+
return;
|
|
1706
|
+
const candidates = call.candidateBuffer.splice(0).map((c) => ({
|
|
1707
|
+
candidate: c.candidate ?? "",
|
|
1708
|
+
sdpMid: c.sdpMid ?? null,
|
|
1709
|
+
sdpMLineIndex: c.sdpMLineIndex ?? null,
|
|
1710
|
+
usernameFragment: c.usernameFragment ?? null
|
|
1711
|
+
}));
|
|
1712
|
+
this.send({ t: "candidates", callId: call.callId, candidates });
|
|
1713
|
+
}
|
|
1714
|
+
newCall(callId, peer, media, role, iceServers) {
|
|
1715
|
+
return {
|
|
1716
|
+
callId,
|
|
1717
|
+
peer,
|
|
1718
|
+
media,
|
|
1719
|
+
role,
|
|
1720
|
+
pc: null,
|
|
1721
|
+
iceServers,
|
|
1722
|
+
pendingRemoteCandidates: [],
|
|
1723
|
+
candidateBuffer: [],
|
|
1724
|
+
flushTimer: null,
|
|
1725
|
+
remoteDescriptionSet: false
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
teardown(finalState) {
|
|
1729
|
+
const call = this.call;
|
|
1730
|
+
if (call) {
|
|
1731
|
+
if (call.flushTimer)
|
|
1732
|
+
clearTimeout(call.flushTimer);
|
|
1733
|
+
call.pc?.getSenders().forEach((s) => s.track?.stop());
|
|
1734
|
+
try {
|
|
1735
|
+
call.pc?.close();
|
|
1736
|
+
} catch {}
|
|
1737
|
+
}
|
|
1738
|
+
if (this.localStream) {
|
|
1739
|
+
this.localStream.getTracks().forEach((t) => t.stop());
|
|
1740
|
+
this.localStream = null;
|
|
1741
|
+
this.emit("localstream", null);
|
|
1742
|
+
}
|
|
1743
|
+
this.emit("remotestream", null);
|
|
1744
|
+
this.call = null;
|
|
1745
|
+
this.setState(finalState);
|
|
1746
|
+
if (finalState === "ended") {
|
|
1747
|
+
setTimeout(() => {
|
|
1748
|
+
if (!this.call)
|
|
1749
|
+
this.setState("idle");
|
|
1750
|
+
}, 400);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
// src/types/realtime.ts
|
|
1755
|
+
function parseRealtimeClientFrame(raw) {
|
|
1756
|
+
if (!raw || typeof raw !== "object")
|
|
1757
|
+
return null;
|
|
1758
|
+
const frame = raw;
|
|
1759
|
+
if (frame.t === "ping" || frame.t === "pong")
|
|
1760
|
+
return { t: frame.t };
|
|
1761
|
+
if (frame.t === "hello") {
|
|
1762
|
+
const lastEventId = typeof frame.lastEventId === "number" && Number.isFinite(frame.lastEventId) && frame.lastEventId >= 0 ? Math.floor(frame.lastEventId) : undefined;
|
|
1763
|
+
return { t: "hello", lastEventId };
|
|
1764
|
+
}
|
|
1765
|
+
return null;
|
|
1766
|
+
}
|
|
1767
|
+
function parseRealtimeServerFrame(raw) {
|
|
1768
|
+
if (!raw || typeof raw !== "object")
|
|
1769
|
+
return null;
|
|
1770
|
+
const frame = raw;
|
|
1771
|
+
if (frame.t === "ping" || frame.t === "pong" || frame.t === "resync") {
|
|
1772
|
+
return { t: frame.t };
|
|
1773
|
+
}
|
|
1774
|
+
if (frame.t === "hello_ok" && typeof frame.lastEventId === "number") {
|
|
1775
|
+
return { t: "hello_ok", lastEventId: frame.lastEventId };
|
|
1776
|
+
}
|
|
1777
|
+
if (frame.t === "event" && frame.event && typeof frame.event === "object") {
|
|
1778
|
+
const event = frame.event;
|
|
1779
|
+
if (typeof event.id === "number" && typeof event.type === "string") {
|
|
1780
|
+
return {
|
|
1781
|
+
t: "event",
|
|
1782
|
+
event: {
|
|
1783
|
+
id: event.id,
|
|
1784
|
+
type: event.type,
|
|
1785
|
+
data: event.data && typeof event.data === "object" ? event.data : {}
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
return null;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// src/lib/realtime-client.ts
|
|
1794
|
+
var BACKOFF_BASE_MS = 1000;
|
|
1795
|
+
var BACKOFF_CAP_MS = 30000;
|
|
1796
|
+
var PING_INTERVAL_MS = 25000;
|
|
1797
|
+
var IDLE_TIMEOUT_MS = 60000;
|
|
1798
|
+
function resolveSocketUrl(actorApId, ticket) {
|
|
1799
|
+
if (typeof location === "undefined")
|
|
1800
|
+
return null;
|
|
1801
|
+
const transport = getYurucommuApiTransport();
|
|
1802
|
+
const resolved = transport.resolveUrl("/api/realtime/socket");
|
|
1803
|
+
let url;
|
|
1804
|
+
try {
|
|
1805
|
+
url = new URL(resolved, location.origin);
|
|
1806
|
+
} catch {
|
|
1807
|
+
return null;
|
|
1808
|
+
}
|
|
1809
|
+
url.protocol = url.protocol === "http:" ? "ws:" : "wss:";
|
|
1810
|
+
url.searchParams.set("actor", actorApId);
|
|
1811
|
+
url.searchParams.set("ticket", ticket);
|
|
1812
|
+
return url.toString();
|
|
1813
|
+
}
|
|
1814
|
+
function createRealtimeClient() {
|
|
1815
|
+
const handlers = new Map;
|
|
1816
|
+
const resyncHandlers = new Set;
|
|
1817
|
+
const statusHandlers = new Set;
|
|
1818
|
+
let currentStatus = "idle";
|
|
1819
|
+
let socket = null;
|
|
1820
|
+
let started = false;
|
|
1821
|
+
let attempts = 0;
|
|
1822
|
+
let lastEventId;
|
|
1823
|
+
let lastFrameAt = 0;
|
|
1824
|
+
let pingTimer = null;
|
|
1825
|
+
let reconnectTimer = null;
|
|
1826
|
+
let generation = 0;
|
|
1827
|
+
function setStatus(next) {
|
|
1828
|
+
if (currentStatus === next)
|
|
1829
|
+
return;
|
|
1830
|
+
currentStatus = next;
|
|
1831
|
+
for (const handler of statusHandlers) {
|
|
1832
|
+
try {
|
|
1833
|
+
handler(next);
|
|
1834
|
+
} catch {}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
function dispatch(event) {
|
|
1838
|
+
for (const key of [event.type, "*"]) {
|
|
1839
|
+
const set = handlers.get(key);
|
|
1840
|
+
if (!set)
|
|
1841
|
+
continue;
|
|
1842
|
+
for (const handler of set) {
|
|
1843
|
+
try {
|
|
1844
|
+
handler(event);
|
|
1845
|
+
} catch {}
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
function dispatchResync() {
|
|
1850
|
+
for (const handler of resyncHandlers) {
|
|
1851
|
+
try {
|
|
1852
|
+
handler();
|
|
1853
|
+
} catch {}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
function clearTimers() {
|
|
1857
|
+
if (pingTimer !== null) {
|
|
1858
|
+
clearInterval(pingTimer);
|
|
1859
|
+
pingTimer = null;
|
|
1860
|
+
}
|
|
1861
|
+
if (reconnectTimer !== null) {
|
|
1862
|
+
clearTimeout(reconnectTimer);
|
|
1863
|
+
reconnectTimer = null;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
function closeSocket() {
|
|
1867
|
+
if (!socket)
|
|
1868
|
+
return;
|
|
1869
|
+
const s = socket;
|
|
1870
|
+
socket = null;
|
|
1871
|
+
try {
|
|
1872
|
+
s.close();
|
|
1873
|
+
} catch {}
|
|
1874
|
+
}
|
|
1875
|
+
function scheduleReconnect() {
|
|
1876
|
+
if (!started)
|
|
1877
|
+
return;
|
|
1878
|
+
setStatus("reconnecting");
|
|
1879
|
+
attempts += 1;
|
|
1880
|
+
const backoff = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** Math.min(attempts - 1, 5));
|
|
1881
|
+
const jitter = backoff * (0.5 + Math.random() * 0.5);
|
|
1882
|
+
reconnectTimer = setTimeout(() => {
|
|
1883
|
+
reconnectTimer = null;
|
|
1884
|
+
connect();
|
|
1885
|
+
}, jitter);
|
|
1886
|
+
}
|
|
1887
|
+
async function connect() {
|
|
1888
|
+
if (!started || typeof WebSocket === "undefined")
|
|
1889
|
+
return;
|
|
1890
|
+
const myGeneration = ++generation;
|
|
1891
|
+
setStatus(attempts === 0 ? "connecting" : "reconnecting");
|
|
1892
|
+
let available;
|
|
1893
|
+
try {
|
|
1894
|
+
const res = await apiFetch("/api/realtime/config");
|
|
1895
|
+
if (!res.ok)
|
|
1896
|
+
throw new Error(`config ${res.status}`);
|
|
1897
|
+
const body = await res.json();
|
|
1898
|
+
available = body.available === true;
|
|
1899
|
+
} catch {
|
|
1900
|
+
if (!started || myGeneration !== generation)
|
|
1901
|
+
return;
|
|
1902
|
+
scheduleReconnect();
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
if (!started || myGeneration !== generation)
|
|
1906
|
+
return;
|
|
1907
|
+
if (!available) {
|
|
1908
|
+
setStatus("unavailable");
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
let ticket;
|
|
1912
|
+
let actorApId;
|
|
1913
|
+
try {
|
|
1914
|
+
const res = await apiPost("/api/realtime/ticket");
|
|
1915
|
+
if (res.status === 401) {
|
|
1916
|
+
setStatus("unavailable");
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
if (!res.ok)
|
|
1920
|
+
throw new Error(`ticket ${res.status}`);
|
|
1921
|
+
const body = await res.json();
|
|
1922
|
+
if (!body.ticket || !body.actor_ap_id)
|
|
1923
|
+
throw new Error("bad ticket");
|
|
1924
|
+
ticket = body.ticket;
|
|
1925
|
+
actorApId = body.actor_ap_id;
|
|
1926
|
+
} catch {
|
|
1927
|
+
if (!started || myGeneration !== generation)
|
|
1928
|
+
return;
|
|
1929
|
+
scheduleReconnect();
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
if (!started || myGeneration !== generation)
|
|
1933
|
+
return;
|
|
1934
|
+
const url = resolveSocketUrl(actorApId, ticket);
|
|
1935
|
+
if (!url) {
|
|
1936
|
+
setStatus("unavailable");
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1939
|
+
let ws;
|
|
1940
|
+
try {
|
|
1941
|
+
ws = new WebSocket(url);
|
|
1942
|
+
} catch {
|
|
1943
|
+
scheduleReconnect();
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
socket = ws;
|
|
1947
|
+
lastFrameAt = Date.now();
|
|
1948
|
+
ws.onopen = () => {
|
|
1949
|
+
if (socket !== ws)
|
|
1950
|
+
return;
|
|
1951
|
+
ws.send(JSON.stringify({ t: "hello", lastEventId }));
|
|
1952
|
+
};
|
|
1953
|
+
ws.onmessage = (message) => {
|
|
1954
|
+
if (socket !== ws)
|
|
1955
|
+
return;
|
|
1956
|
+
lastFrameAt = Date.now();
|
|
1957
|
+
let parsed;
|
|
1958
|
+
try {
|
|
1959
|
+
parsed = JSON.parse(String(message.data));
|
|
1960
|
+
} catch {
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
const frame = parseRealtimeServerFrame(parsed);
|
|
1964
|
+
if (!frame)
|
|
1965
|
+
return;
|
|
1966
|
+
switch (frame.t) {
|
|
1967
|
+
case "hello_ok":
|
|
1968
|
+
attempts = 0;
|
|
1969
|
+
if (lastEventId === undefined)
|
|
1970
|
+
lastEventId = frame.lastEventId;
|
|
1971
|
+
else
|
|
1972
|
+
lastEventId = Math.max(lastEventId, frame.lastEventId);
|
|
1973
|
+
setStatus("connected");
|
|
1974
|
+
return;
|
|
1975
|
+
case "event":
|
|
1976
|
+
lastEventId = Math.max(lastEventId ?? 0, frame.event.id);
|
|
1977
|
+
dispatch(frame.event);
|
|
1978
|
+
return;
|
|
1979
|
+
case "resync":
|
|
1980
|
+
dispatchResync();
|
|
1981
|
+
return;
|
|
1982
|
+
case "ping":
|
|
1983
|
+
ws.send(JSON.stringify({ t: "pong" }));
|
|
1984
|
+
return;
|
|
1985
|
+
case "pong":
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
const onGone = () => {
|
|
1990
|
+
if (socket !== ws)
|
|
1991
|
+
return;
|
|
1992
|
+
socket = null;
|
|
1993
|
+
scheduleReconnect();
|
|
1994
|
+
};
|
|
1995
|
+
ws.onclose = onGone;
|
|
1996
|
+
ws.onerror = onGone;
|
|
1997
|
+
if (pingTimer === null) {
|
|
1998
|
+
pingTimer = setInterval(() => {
|
|
1999
|
+
if (!socket || socket.readyState !== WebSocket.OPEN)
|
|
2000
|
+
return;
|
|
2001
|
+
if (Date.now() - lastFrameAt > IDLE_TIMEOUT_MS) {
|
|
2002
|
+
closeSocket();
|
|
2003
|
+
scheduleReconnect();
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
try {
|
|
2007
|
+
socket.send(JSON.stringify({ t: "ping" }));
|
|
2008
|
+
} catch {}
|
|
2009
|
+
}, PING_INTERVAL_MS);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return {
|
|
2013
|
+
start() {
|
|
2014
|
+
if (started)
|
|
2015
|
+
return;
|
|
2016
|
+
started = true;
|
|
2017
|
+
attempts = 0;
|
|
2018
|
+
connect();
|
|
2019
|
+
},
|
|
2020
|
+
stop() {
|
|
2021
|
+
started = false;
|
|
2022
|
+
generation += 1;
|
|
2023
|
+
clearTimers();
|
|
2024
|
+
closeSocket();
|
|
2025
|
+
lastEventId = undefined;
|
|
2026
|
+
setStatus("idle");
|
|
2027
|
+
},
|
|
2028
|
+
status() {
|
|
2029
|
+
return currentStatus;
|
|
2030
|
+
},
|
|
2031
|
+
isConnected() {
|
|
2032
|
+
return currentStatus === "connected";
|
|
2033
|
+
},
|
|
2034
|
+
on(type, handler) {
|
|
2035
|
+
let set = handlers.get(type);
|
|
2036
|
+
if (!set) {
|
|
2037
|
+
set = new Set;
|
|
2038
|
+
handlers.set(type, set);
|
|
2039
|
+
}
|
|
2040
|
+
set.add(handler);
|
|
2041
|
+
return () => {
|
|
2042
|
+
set.delete(handler);
|
|
2043
|
+
};
|
|
2044
|
+
},
|
|
2045
|
+
onResync(handler) {
|
|
2046
|
+
resyncHandlers.add(handler);
|
|
2047
|
+
return () => {
|
|
2048
|
+
resyncHandlers.delete(handler);
|
|
2049
|
+
};
|
|
2050
|
+
},
|
|
2051
|
+
onStatus(handler) {
|
|
2052
|
+
statusHandlers.add(handler);
|
|
2053
|
+
return () => {
|
|
2054
|
+
statusHandlers.delete(handler);
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
1358
2059
|
// src/social-server.ts
|
|
1359
2060
|
async function fetchCurrentActor() {
|
|
1360
2061
|
const res = await apiFetch("/api/auth/me");
|
|
@@ -1369,6 +2070,101 @@ async function fetchSocialServerDiscovery() {
|
|
|
1369
2070
|
await assertOk(res, "Failed to load social server discovery");
|
|
1370
2071
|
return await res.json();
|
|
1371
2072
|
}
|
|
2073
|
+
// src/types/call.ts
|
|
2074
|
+
var RTC_SIGNAL_ENVELOPE_VERSION = 1;
|
|
2075
|
+
var TERMINAL_CALL_STATES = [
|
|
2076
|
+
"ended",
|
|
2077
|
+
"rejected",
|
|
2078
|
+
"missed",
|
|
2079
|
+
"failed",
|
|
2080
|
+
"cancelled"
|
|
2081
|
+
];
|
|
2082
|
+
function isTerminalCallState(state) {
|
|
2083
|
+
return TERMINAL_CALL_STATES.includes(state);
|
|
2084
|
+
}
|
|
2085
|
+
var SIGNAL_TYPES = [
|
|
2086
|
+
"offer",
|
|
2087
|
+
"answer",
|
|
2088
|
+
"candidate",
|
|
2089
|
+
"accept",
|
|
2090
|
+
"reject",
|
|
2091
|
+
"hangup",
|
|
2092
|
+
"cancel"
|
|
2093
|
+
];
|
|
2094
|
+
function isPlainObject(value) {
|
|
2095
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2096
|
+
}
|
|
2097
|
+
function isCallMediaKind(value) {
|
|
2098
|
+
return isPlainObject(value) && typeof value.audio === "boolean" && typeof value.video === "boolean";
|
|
2099
|
+
}
|
|
2100
|
+
function parseCandidates(value) {
|
|
2101
|
+
if (value === undefined)
|
|
2102
|
+
return;
|
|
2103
|
+
if (!Array.isArray(value))
|
|
2104
|
+
return;
|
|
2105
|
+
const out = [];
|
|
2106
|
+
for (const raw of value) {
|
|
2107
|
+
if (!isPlainObject(raw) || typeof raw.candidate !== "string")
|
|
2108
|
+
continue;
|
|
2109
|
+
out.push({
|
|
2110
|
+
candidate: raw.candidate,
|
|
2111
|
+
sdpMid: typeof raw.sdpMid === "string" ? raw.sdpMid : null,
|
|
2112
|
+
sdpMLineIndex: typeof raw.sdpMLineIndex === "number" ? raw.sdpMLineIndex : null,
|
|
2113
|
+
usernameFragment: typeof raw.usernameFragment === "string" ? raw.usernameFragment : null
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
return out;
|
|
2117
|
+
}
|
|
2118
|
+
function parseSfuFocus(value) {
|
|
2119
|
+
if (value === undefined)
|
|
2120
|
+
return;
|
|
2121
|
+
if (value === null)
|
|
2122
|
+
return null;
|
|
2123
|
+
if (!isPlainObject(value))
|
|
2124
|
+
return;
|
|
2125
|
+
if (typeof value.kind !== "string" || typeof value.url !== "string") {
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
return {
|
|
2129
|
+
kind: value.kind,
|
|
2130
|
+
url: value.url,
|
|
2131
|
+
token: typeof value.token === "string" ? value.token : undefined,
|
|
2132
|
+
room: typeof value.room === "string" ? value.room : undefined
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
function parseRtcSignalEnvelope(input) {
|
|
2136
|
+
if (!isPlainObject(input))
|
|
2137
|
+
return null;
|
|
2138
|
+
if (input.v !== RTC_SIGNAL_ENVELOPE_VERSION)
|
|
2139
|
+
return null;
|
|
2140
|
+
const { callId, from, to, type, ts, ttlMs } = input;
|
|
2141
|
+
if (typeof callId !== "string" || callId.length === 0 || callId.length > 200 || typeof from !== "string" || from.length === 0 || typeof to !== "string" || to.length === 0 || typeof type !== "string" || !SIGNAL_TYPES.includes(type) || typeof ts !== "number" || !Number.isFinite(ts) || typeof ttlMs !== "number" || !Number.isFinite(ttlMs) || ttlMs < 0) {
|
|
2142
|
+
return null;
|
|
2143
|
+
}
|
|
2144
|
+
const sdp = typeof input.sdp === "string" ? input.sdp : undefined;
|
|
2145
|
+
if (sdp !== undefined && sdp.length > 1e5)
|
|
2146
|
+
return null;
|
|
2147
|
+
return {
|
|
2148
|
+
v: RTC_SIGNAL_ENVELOPE_VERSION,
|
|
2149
|
+
callId,
|
|
2150
|
+
from,
|
|
2151
|
+
to,
|
|
2152
|
+
type,
|
|
2153
|
+
media: isCallMediaKind(input.media) ? input.media : undefined,
|
|
2154
|
+
sdp,
|
|
2155
|
+
candidates: parseCandidates(input.candidates),
|
|
2156
|
+
sfuFocus: parseSfuFocus(input.sfuFocus),
|
|
2157
|
+
reason: typeof input.reason === "string" ? input.reason.slice(0, 200) : undefined,
|
|
2158
|
+
ts,
|
|
2159
|
+
ttlMs
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
function isEnvelopeFresh(envelope, now) {
|
|
2163
|
+
const skewToleranceMs = 30000;
|
|
2164
|
+
if (envelope.ts - now > skewToleranceMs)
|
|
2165
|
+
return false;
|
|
2166
|
+
return now - envelope.ts <= envelope.ttlMs;
|
|
2167
|
+
}
|
|
1372
2168
|
export {
|
|
1373
2169
|
voteOnStory,
|
|
1374
2170
|
validateFile,
|
|
@@ -1412,6 +2208,9 @@ export {
|
|
|
1412
2208
|
rejectCommunityJoinRequest,
|
|
1413
2209
|
registerNotificationPusher,
|
|
1414
2210
|
refreshBrowserNotificationPush,
|
|
2211
|
+
parseRtcSignalEnvelope,
|
|
2212
|
+
parseRealtimeServerFrame,
|
|
2213
|
+
parseRealtimeClientFrame,
|
|
1415
2214
|
normalizeStory,
|
|
1416
2215
|
normalizePost,
|
|
1417
2216
|
normalizeNotification,
|
|
@@ -1433,6 +2232,8 @@ export {
|
|
|
1433
2232
|
likePost,
|
|
1434
2233
|
leaveCommunity,
|
|
1435
2234
|
joinCommunity,
|
|
2235
|
+
isTerminalCallState,
|
|
2236
|
+
isEnvelopeFresh,
|
|
1436
2237
|
getYurucommuApiTransport,
|
|
1437
2238
|
getStoryViewers,
|
|
1438
2239
|
getBrowserNotificationPushState,
|
|
@@ -1484,6 +2285,7 @@ export {
|
|
|
1484
2285
|
deleteCommunityMessage,
|
|
1485
2286
|
deleteAccount,
|
|
1486
2287
|
createStory,
|
|
2288
|
+
createRealtimeClient,
|
|
1487
2289
|
createPost,
|
|
1488
2290
|
createNote,
|
|
1489
2291
|
createCommunityInvite,
|
|
@@ -1508,6 +2310,9 @@ export {
|
|
|
1508
2310
|
allowedMimeTypes,
|
|
1509
2311
|
acceptFollowRequest,
|
|
1510
2312
|
acceptCommunityJoinRequest,
|
|
2313
|
+
TERMINAL_CALL_STATES,
|
|
2314
|
+
RTC_SIGNAL_ENVELOPE_VERSION,
|
|
1511
2315
|
FileValidationError,
|
|
2316
|
+
CallClient,
|
|
1512
2317
|
ApiError
|
|
1513
2318
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realtime stream client — the ONE WebSocket a product keeps open per signed-in
|
|
3
|
+
* user. Replaces the short-interval polling loops (talk messages 4s, typing 4s,
|
|
4
|
+
* contacts 20s, unread badges 20-30s): the server pushes `RealtimeEvent`s and
|
|
5
|
+
* the client fans them out to `on(type, handler)` subscribers.
|
|
6
|
+
*
|
|
7
|
+
* Transport: mints a one-time ticket over the normal authenticated fetch path
|
|
8
|
+
* (`POST /api/realtime/ticket` — works with both cookie and bearer transports;
|
|
9
|
+
* the browser WebSocket API cannot send an Authorization header), then connects
|
|
10
|
+
* to `wss://…/api/realtime/socket?actor=…&ticket=…`.
|
|
11
|
+
*
|
|
12
|
+
* Lifecycle: `start()` probes `GET /api/realtime/config`. When the server has
|
|
13
|
+
* no realtime runtime (`available:false` — e.g. a Bun self-host without
|
|
14
|
+
* Durable Objects) the client settles in status "unavailable" and the product
|
|
15
|
+
* enables its LOW-FREQUENCY fallback polling instead. Transient failures
|
|
16
|
+
* (network, worker restart) reconnect with exponential backoff and replay the
|
|
17
|
+
* missed gap via `hello{lastEventId}`; a `resync` answer means the gap
|
|
18
|
+
* outlived the server buffer and subscribers should re-fetch via REST.
|
|
19
|
+
*/
|
|
20
|
+
import type { RealtimeEvent, RealtimeEventType } from "../types/realtime.js";
|
|
21
|
+
export type RealtimeStatus = "idle" | "connecting" | "connected" | "reconnecting" | "unavailable";
|
|
22
|
+
export type RealtimeEventHandler = (event: RealtimeEvent) => void;
|
|
23
|
+
export type RealtimeResyncHandler = () => void;
|
|
24
|
+
export type RealtimeStatusHandler = (status: RealtimeStatus) => void;
|
|
25
|
+
export interface RealtimeClient {
|
|
26
|
+
start(): void;
|
|
27
|
+
stop(): void;
|
|
28
|
+
status(): RealtimeStatus;
|
|
29
|
+
/** True while the live socket is up — products gate fallback polling on it. */
|
|
30
|
+
isConnected(): boolean;
|
|
31
|
+
on(type: RealtimeEventType | "*", handler: RealtimeEventHandler): () => void;
|
|
32
|
+
/** Fired on reconnect gaps the server could not replay: re-fetch via REST. */
|
|
33
|
+
onResync(handler: RealtimeResyncHandler): () => void;
|
|
34
|
+
onStatus(handler: RealtimeStatusHandler): () => void;
|
|
35
|
+
}
|
|
36
|
+
export declare function createRealtimeClient(): RealtimeClient;
|
|
37
|
+
//# sourceMappingURL=realtime-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-client.d.ts","sourceRoot":"","sources":["../../src/lib/realtime-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG7E,MAAM,MAAM,cAAc,GACxB,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,MAAM,IAAI,CAAC;AAC/C,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,IAAI,CAAC;IACb,MAAM,IAAI,cAAc,CAAC;IACzB,+EAA+E;IAC/E,WAAW,IAAI,OAAO,CAAC;IACvB,EAAE,CAAC,IAAI,EAAE,iBAAiB,GAAG,GAAG,EAAE,OAAO,EAAE,oBAAoB,GAAG,MAAM,IAAI,CAAC;IAC7E,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,IAAI,CAAC;IACrD,QAAQ,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,IAAI,CAAC;CACtD;AA6BD,wBAAgB,oBAAoB,IAAI,cAAc,CAoQrD"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CallClient — framework-agnostic browser WebRTC engine for the call feature.
|
|
3
|
+
*
|
|
4
|
+
* Owns the WebSocket to the per-user signaling hub (`/api/rtc/socket`), the
|
|
5
|
+
* `RTCPeerConnection` lifecycle, `getUserMedia`, half-trickle ICE, and the call
|
|
6
|
+
* state machine as the browser sees it. Both products (yurucommu / yurumeet)
|
|
7
|
+
* wrap this with their own reactive state layer (SolidJS signals / jotai) — no
|
|
8
|
+
* WebRTC logic is duplicated per product, and the wire protocol is single-
|
|
9
|
+
* sourced with the backend via `./types/call.ts`.
|
|
10
|
+
*
|
|
11
|
+
* 1:1 only for now: the caller offers, the callee answers, media is P2P over the
|
|
12
|
+
* server-provided ICE (STUN/TURN) servers. Camera on/off toggles the existing
|
|
13
|
+
* video track (no mid-call renegotiation, so no glare at the PC level).
|
|
14
|
+
*/
|
|
15
|
+
import type { CallMediaKind } from "../types/call.js";
|
|
16
|
+
export type CallUiState = "idle" | "calling" | "incoming" | "connecting" | "connected" | "ended";
|
|
17
|
+
export interface IncomingCallInfo {
|
|
18
|
+
callId: string;
|
|
19
|
+
from: string;
|
|
20
|
+
media: CallMediaKind;
|
|
21
|
+
}
|
|
22
|
+
export interface CallClientEvents {
|
|
23
|
+
state: (state: CallUiState) => void;
|
|
24
|
+
incoming: (info: IncomingCallInfo) => void;
|
|
25
|
+
localstream: (stream: MediaStream | null) => void;
|
|
26
|
+
remotestream: (stream: MediaStream | null) => void;
|
|
27
|
+
muted: (muted: boolean) => void;
|
|
28
|
+
cameraoff: (off: boolean) => void;
|
|
29
|
+
error: (code: string, message?: string) => void;
|
|
30
|
+
}
|
|
31
|
+
export interface CallClientOptions {
|
|
32
|
+
/** Origin the app is served from (defaults to the page origin). */
|
|
33
|
+
origin?: string;
|
|
34
|
+
/** WebSocket reconnect backoff ceiling (ms). */
|
|
35
|
+
maxBackoffMs?: number;
|
|
36
|
+
}
|
|
37
|
+
export declare class CallClient {
|
|
38
|
+
private readonly options;
|
|
39
|
+
private ws;
|
|
40
|
+
private wantConnected;
|
|
41
|
+
private backoff;
|
|
42
|
+
private reconnectTimer;
|
|
43
|
+
private readonly listeners;
|
|
44
|
+
private call;
|
|
45
|
+
private localStream;
|
|
46
|
+
private state;
|
|
47
|
+
constructor(options?: CallClientOptions);
|
|
48
|
+
on<K extends keyof CallClientEvents>(event: K, listener: CallClientEvents[K]): () => void;
|
|
49
|
+
private emit;
|
|
50
|
+
private setState;
|
|
51
|
+
getState(): CallUiState;
|
|
52
|
+
private origin;
|
|
53
|
+
private socketUrl;
|
|
54
|
+
connect(): void;
|
|
55
|
+
disconnect(): void;
|
|
56
|
+
private openSocket;
|
|
57
|
+
private scheduleReconnect;
|
|
58
|
+
private send;
|
|
59
|
+
/** Place an outgoing call. Fetches a callId + ICE, then rings the peer. */
|
|
60
|
+
startCall(peer: string, media: CallMediaKind): Promise<void>;
|
|
61
|
+
/** Accept the current incoming call. */
|
|
62
|
+
accept(): Promise<void>;
|
|
63
|
+
/** Decline the current incoming call. */
|
|
64
|
+
reject(reason?: string): void;
|
|
65
|
+
/** Hang up / cancel the active call. */
|
|
66
|
+
hangup(reason?: string): void;
|
|
67
|
+
setMuted(muted: boolean): void;
|
|
68
|
+
setCameraEnabled(enabled: boolean): void;
|
|
69
|
+
private onFrame;
|
|
70
|
+
private onRinging;
|
|
71
|
+
private onCallState;
|
|
72
|
+
private onRemoteOffer;
|
|
73
|
+
private onRemoteAnswer;
|
|
74
|
+
private onRemoteCandidates;
|
|
75
|
+
private drainRemoteCandidates;
|
|
76
|
+
private setupMedia;
|
|
77
|
+
private buildPeerConnection;
|
|
78
|
+
private makeOffer;
|
|
79
|
+
private bufferCandidate;
|
|
80
|
+
private flushCandidates;
|
|
81
|
+
private newCall;
|
|
82
|
+
private teardown;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=rtc-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rtc-client.d.ts","sourceRoot":"","sources":["../../src/lib/rtc-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,aAAa,EAId,MAAM,kBAAkB,CAAC;AAE1B,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,SAAS,GACT,UAAU,GACV,YAAY,GACZ,WAAW,GACX,OAAO,CAAC;AAEZ,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC3C,WAAW,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,YAAY,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACnD,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAChC,SAAS,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAmBD,qBAAa,UAAU;IAUT,OAAO,CAAC,QAAQ,CAAC,OAAO;IATpC,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,OAAO,CAAO;IACtB,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoD;IAC9E,OAAO,CAAC,IAAI,CAA2B;IACvC,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,KAAK,CAAuB;gBAEP,OAAO,GAAE,iBAAsB;IAG5D,EAAE,CAAC,CAAC,SAAS,MAAM,gBAAgB,EACjC,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAC5B,MAAM,IAAI;IAUb,OAAO,CAAC,IAAI;IASZ,OAAO,CAAC,QAAQ;IAMhB,QAAQ,IAAI,WAAW;IAKvB,OAAO,CAAC,MAAM;IAOd,OAAO,CAAC,SAAS;IAKjB,OAAO,IAAI,IAAI;IAKf,UAAU,IAAI,IAAI;IAQlB,OAAO,CAAC,UAAU;IA4BlB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,IAAI;IAQZ,2EAA2E;IACrE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BlE,wCAAwC;IAClC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAc7B,yCAAyC;IACzC,MAAM,CAAC,MAAM,SAAa,GAAG,IAAI;IAOjC,wCAAwC;IACxC,MAAM,CAAC,MAAM,SAAW,GAAG,IAAI;IAO/B,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAO9B,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;YAQ1B,OAAO;IAiDrB,OAAO,CAAC,SAAS;IAqBjB,OAAO,CAAC,WAAW;YAeL,aAAa;YAsBb,cAAc;YAUd,kBAAkB;YAkBlB,qBAAqB;YAarB,UAAU;IAoBxB,OAAO,CAAC,mBAAmB;YA2Bb,SAAS;IAOvB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,OAAO;IAqBf,OAAO,CAAC,QAAQ;CA0BjB"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call signaling wire contract (voice + video).
|
|
3
|
+
*
|
|
4
|
+
* SINGLE SOURCE OF TRUTH shared by:
|
|
5
|
+
* - the backend cross-instance signaling ingest (`/ap/rtc/signal`) + the
|
|
6
|
+
* Signaling Durable Object (server-to-server + browser fan-out), and
|
|
7
|
+
* - the browser `CallClient` (`../lib/rtc-client.ts`).
|
|
8
|
+
*
|
|
9
|
+
* Kept deliberately DOM-structural (no `RTCIceCandidateInit` / `RTCIceServer`
|
|
10
|
+
* imports) so the same file type-checks in the server context (which never runs
|
|
11
|
+
* the browser WebRTC APIs) and the browser bundle. The `CallClient` maps these
|
|
12
|
+
* structural shapes to/from the real DOM `RTCSessionDescriptionInit` /
|
|
13
|
+
* `RTCIceCandidateInit` / `RTCIceServer`, which are structurally compatible.
|
|
14
|
+
*
|
|
15
|
+
* Design: signaling travels over federation (server-to-server, HTTP-Signature
|
|
16
|
+
* authenticated) as `RtcSignalEnvelopeV1`; media is P2P WebRTC + STUN/TURN for
|
|
17
|
+
* 1:1 (`sfuFocus: null`) and a pluggable WHIP/WHEP SFU focus for group calls.
|
|
18
|
+
*/
|
|
19
|
+
export declare const RTC_SIGNAL_ENVELOPE_VERSION: 1;
|
|
20
|
+
/** Which media tracks a call carries. `video:false` => audio-only call. */
|
|
21
|
+
export interface CallMediaKind {
|
|
22
|
+
audio: boolean;
|
|
23
|
+
video: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Cross-instance signaling message kinds (Matrix-VoIP inspired). */
|
|
26
|
+
export type RtcSignalType = "offer" | "answer" | "candidate" | "accept" | "reject" | "hangup" | "cancel";
|
|
27
|
+
/**
|
|
28
|
+
* Selected SFU focus for a group call. `null`/absent means pure P2P (1:1).
|
|
29
|
+
* `kind` names the adapter (`whip` / `livekit` / `cloudflare-realtime` / ...);
|
|
30
|
+
* the client talks WHIP/WHEP so the SFU backend stays vendor-neutral.
|
|
31
|
+
*/
|
|
32
|
+
export interface SfuFocus {
|
|
33
|
+
kind: string;
|
|
34
|
+
/** WHIP (publish) / WHEP (subscribe) endpoint base, or SFU signaling URL. */
|
|
35
|
+
url: string;
|
|
36
|
+
/** Short-lived join token when the adapter requires one. */
|
|
37
|
+
token?: string;
|
|
38
|
+
room?: string;
|
|
39
|
+
}
|
|
40
|
+
/** Structural mirror of `RTCIceCandidateInit` (no DOM dependency). */
|
|
41
|
+
export interface CallIceCandidate {
|
|
42
|
+
candidate: string;
|
|
43
|
+
sdpMid?: string | null;
|
|
44
|
+
sdpMLineIndex?: number | null;
|
|
45
|
+
usernameFragment?: string | null;
|
|
46
|
+
}
|
|
47
|
+
/** Structural mirror of `RTCIceServer` (no DOM dependency). */
|
|
48
|
+
export interface IceServerConfig {
|
|
49
|
+
urls: string | string[];
|
|
50
|
+
username?: string;
|
|
51
|
+
credential?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Server-to-server signaling envelope. Delivered by the sending instance to the
|
|
55
|
+
* recipient instance's `/ap/rtc/signal` endpoint, signed with the sender actor's
|
|
56
|
+
* HTTP Signature key (keyId-owner === `from`). `callId` doubles as the anti-
|
|
57
|
+
* replay nonce; `ts`/`ttlMs` bound its freshness (the DO drops stale frames).
|
|
58
|
+
*/
|
|
59
|
+
export interface RtcSignalEnvelopeV1 {
|
|
60
|
+
v: typeof RTC_SIGNAL_ENVELOPE_VERSION;
|
|
61
|
+
callId: string;
|
|
62
|
+
from: string;
|
|
63
|
+
to: string;
|
|
64
|
+
type: RtcSignalType;
|
|
65
|
+
media?: CallMediaKind;
|
|
66
|
+
/** SDP for `offer` / `answer`. */
|
|
67
|
+
sdp?: string;
|
|
68
|
+
/** Half-trickle ICE bundle for `offer` / `answer` / `candidate`. */
|
|
69
|
+
candidates?: CallIceCandidate[];
|
|
70
|
+
sfuFocus?: SfuFocus | null;
|
|
71
|
+
/** Free-text end/reject reason (`busy`, `declined`, `timeout`, ...). */
|
|
72
|
+
reason?: string;
|
|
73
|
+
ts: number;
|
|
74
|
+
ttlMs: number;
|
|
75
|
+
}
|
|
76
|
+
/** Lifecycle of a single call, mirrored client-side and in `call_sessions`. */
|
|
77
|
+
export type CallState = "idle" | "ringing" | "connecting" | "connected" | "ended" | "rejected" | "missed" | "failed" | "cancelled";
|
|
78
|
+
export type CallDirection = "incoming" | "outgoing";
|
|
79
|
+
/** Terminal states — a call in one of these is over and not resumable. */
|
|
80
|
+
export declare const TERMINAL_CALL_STATES: readonly CallState[];
|
|
81
|
+
export declare function isTerminalCallState(state: CallState): boolean;
|
|
82
|
+
/** Frames the browser sends up to its own instance's Signaling DO. */
|
|
83
|
+
export type ClientToHubFrame = {
|
|
84
|
+
t: "hello";
|
|
85
|
+
} | {
|
|
86
|
+
t: "invite";
|
|
87
|
+
callId: string;
|
|
88
|
+
to: string;
|
|
89
|
+
media: CallMediaKind;
|
|
90
|
+
} | {
|
|
91
|
+
t: "offer";
|
|
92
|
+
callId: string;
|
|
93
|
+
sdp: string;
|
|
94
|
+
} | {
|
|
95
|
+
t: "answer";
|
|
96
|
+
callId: string;
|
|
97
|
+
sdp: string;
|
|
98
|
+
} | {
|
|
99
|
+
t: "candidates";
|
|
100
|
+
callId: string;
|
|
101
|
+
candidates: CallIceCandidate[];
|
|
102
|
+
} | {
|
|
103
|
+
t: "accept";
|
|
104
|
+
callId: string;
|
|
105
|
+
} | {
|
|
106
|
+
t: "reject";
|
|
107
|
+
callId: string;
|
|
108
|
+
reason?: string;
|
|
109
|
+
} | {
|
|
110
|
+
t: "hangup";
|
|
111
|
+
callId: string;
|
|
112
|
+
reason?: string;
|
|
113
|
+
} | {
|
|
114
|
+
t: "resume";
|
|
115
|
+
callId: string;
|
|
116
|
+
} | {
|
|
117
|
+
t: "ping";
|
|
118
|
+
};
|
|
119
|
+
/** Frames the Signaling DO pushes down to the browser. */
|
|
120
|
+
export type HubToClientFrame = {
|
|
121
|
+
t: "ready";
|
|
122
|
+
} | {
|
|
123
|
+
t: "ringing";
|
|
124
|
+
callId: string;
|
|
125
|
+
from: string;
|
|
126
|
+
media: CallMediaKind;
|
|
127
|
+
} | {
|
|
128
|
+
t: "offer";
|
|
129
|
+
callId: string;
|
|
130
|
+
sdp: string;
|
|
131
|
+
media?: CallMediaKind;
|
|
132
|
+
} | {
|
|
133
|
+
t: "answer";
|
|
134
|
+
callId: string;
|
|
135
|
+
sdp: string;
|
|
136
|
+
} | {
|
|
137
|
+
t: "candidates";
|
|
138
|
+
callId: string;
|
|
139
|
+
candidates: CallIceCandidate[];
|
|
140
|
+
} | {
|
|
141
|
+
t: "peer-accepted";
|
|
142
|
+
callId: string;
|
|
143
|
+
} | {
|
|
144
|
+
t: "peer-rejected";
|
|
145
|
+
callId: string;
|
|
146
|
+
reason?: string;
|
|
147
|
+
} | {
|
|
148
|
+
t: "peer-hangup";
|
|
149
|
+
callId: string;
|
|
150
|
+
reason?: string;
|
|
151
|
+
} | {
|
|
152
|
+
t: "ice-servers";
|
|
153
|
+
callId: string;
|
|
154
|
+
iceServers: IceServerConfig[];
|
|
155
|
+
sfuFocus?: SfuFocus | null;
|
|
156
|
+
} | {
|
|
157
|
+
t: "call-state";
|
|
158
|
+
callId: string;
|
|
159
|
+
state: CallState;
|
|
160
|
+
} | {
|
|
161
|
+
t: "pong";
|
|
162
|
+
} | {
|
|
163
|
+
t: "error";
|
|
164
|
+
code: string;
|
|
165
|
+
message?: string;
|
|
166
|
+
};
|
|
167
|
+
export interface StartCallRequest {
|
|
168
|
+
to: string;
|
|
169
|
+
media: CallMediaKind;
|
|
170
|
+
}
|
|
171
|
+
export interface StartCallResponse {
|
|
172
|
+
callId: string;
|
|
173
|
+
iceServers: IceServerConfig[];
|
|
174
|
+
sfuFocus?: SfuFocus | null;
|
|
175
|
+
}
|
|
176
|
+
export interface IceServersResponse {
|
|
177
|
+
iceServers: IceServerConfig[];
|
|
178
|
+
}
|
|
179
|
+
export interface CallSessionSummary {
|
|
180
|
+
id: string;
|
|
181
|
+
peer: string;
|
|
182
|
+
direction: CallDirection;
|
|
183
|
+
state: CallState;
|
|
184
|
+
media: CallMediaKind;
|
|
185
|
+
createdAt: string;
|
|
186
|
+
connectedAt?: string | null;
|
|
187
|
+
endedAt?: string | null;
|
|
188
|
+
endReason?: string | null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Parse + validate an inbound cross-instance signaling envelope. Returns the
|
|
192
|
+
* normalized envelope or `null` when the shape is invalid. Callers additionally
|
|
193
|
+
* enforce that the HTTP-Signature signer equals `from` and that the recipient
|
|
194
|
+
* (`to`) is a local actor.
|
|
195
|
+
*/
|
|
196
|
+
export declare function parseRtcSignalEnvelope(input: unknown): RtcSignalEnvelopeV1 | null;
|
|
197
|
+
/** True when the envelope is still within its freshness window. */
|
|
198
|
+
export declare function isEnvelopeFresh(envelope: RtcSignalEnvelopeV1, now: number): boolean;
|
|
199
|
+
//# sourceMappingURL=call.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call.d.ts","sourceRoot":"","sources":["../../src/types/call.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realtime stream wire contract (browser <-> per-user RealtimeStreamDO).
|
|
3
|
+
*
|
|
4
|
+
* One authenticated WebSocket per user carries every live update the client
|
|
5
|
+
* used to poll for: talk messages, typing, read receipts, contact-list
|
|
6
|
+
* changes, new notifications, and the authoritative unread counters. The
|
|
7
|
+
* server pushes `RealtimeEvent` envelopes; the client sends only the small
|
|
8
|
+
* control frames below (writes stay on the REST API).
|
|
9
|
+
*
|
|
10
|
+
* Event ids are a per-user monotonic sequence assigned by the Durable Object.
|
|
11
|
+
* A reconnecting client offers its last seen id in `hello`; the DO replays the
|
|
12
|
+
* gap from its ring buffer, or answers `resync` when the gap is older than the
|
|
13
|
+
* buffer so the client re-fetches via the normal REST reads.
|
|
14
|
+
*/
|
|
15
|
+
export type RealtimeEventType = "talk.message" | "talk.typing" | "talk.read" | "talk.contacts_changed" | "notification.new" | "unread";
|
|
16
|
+
export interface RealtimeEvent {
|
|
17
|
+
/** Per-user monotonic sequence number (assigned by the stream DO). */
|
|
18
|
+
id: number;
|
|
19
|
+
type: RealtimeEventType;
|
|
20
|
+
data: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
/** `talk.message` payload. `other_ap_id` is from the RECEIVING user's view. */
|
|
23
|
+
export interface TalkMessageEventData {
|
|
24
|
+
kind: "dm" | "community";
|
|
25
|
+
/** DM: the counterpart actor (per-recipient). */
|
|
26
|
+
other_ap_id?: string;
|
|
27
|
+
/** Community chat: the community actor. */
|
|
28
|
+
community_ap_id?: string;
|
|
29
|
+
conversation_id?: string;
|
|
30
|
+
message: {
|
|
31
|
+
id: string;
|
|
32
|
+
sender: {
|
|
33
|
+
ap_id: string;
|
|
34
|
+
username: string;
|
|
35
|
+
preferred_username: string | null;
|
|
36
|
+
name: string | null;
|
|
37
|
+
icon_url: string | null;
|
|
38
|
+
};
|
|
39
|
+
content: string | null;
|
|
40
|
+
attachments?: unknown[];
|
|
41
|
+
created_at: string | null;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export interface TalkTypingEventData {
|
|
45
|
+
other_ap_id: string;
|
|
46
|
+
is_typing: boolean;
|
|
47
|
+
typed_at: string;
|
|
48
|
+
}
|
|
49
|
+
export interface TalkReadEventData {
|
|
50
|
+
other_ap_id: string;
|
|
51
|
+
conversation_id: string;
|
|
52
|
+
last_read_at: string;
|
|
53
|
+
}
|
|
54
|
+
/** Authoritative unread counters (server-computed; never client-derived). */
|
|
55
|
+
export interface UnreadEventData {
|
|
56
|
+
dm: number;
|
|
57
|
+
community: number;
|
|
58
|
+
talk_total: number;
|
|
59
|
+
notifications: number;
|
|
60
|
+
}
|
|
61
|
+
export type RealtimeClientFrame = {
|
|
62
|
+
t: "hello";
|
|
63
|
+
lastEventId?: number;
|
|
64
|
+
} | {
|
|
65
|
+
t: "ping";
|
|
66
|
+
} | {
|
|
67
|
+
t: "pong";
|
|
68
|
+
};
|
|
69
|
+
export type RealtimeServerFrame = {
|
|
70
|
+
t: "hello_ok";
|
|
71
|
+
lastEventId: number;
|
|
72
|
+
} | {
|
|
73
|
+
t: "event";
|
|
74
|
+
event: RealtimeEvent;
|
|
75
|
+
}
|
|
76
|
+
/** The requested replay gap is older than the buffer: re-fetch via REST. */
|
|
77
|
+
| {
|
|
78
|
+
t: "resync";
|
|
79
|
+
} | {
|
|
80
|
+
t: "ping";
|
|
81
|
+
} | {
|
|
82
|
+
t: "pong";
|
|
83
|
+
};
|
|
84
|
+
export declare function parseRealtimeClientFrame(raw: unknown): RealtimeClientFrame | null;
|
|
85
|
+
export declare function parseRealtimeServerFrame(raw: unknown): RealtimeServerFrame | null;
|
|
86
|
+
//# sourceMappingURL=realtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../src/types/realtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,iBAAiB,GACzB,cAAc,GACd,aAAa,GACb,WAAW,GACX,uBAAuB,GACvB,kBAAkB,GAClB,QAAQ,CAAC;AAEb,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,+EAA+E;AAC/E,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,IAAI,GAAG,WAAW,CAAC;IACzB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE;YACN,KAAK,EAAE,MAAM,CAAC;YACd,QAAQ,EAAE,MAAM,CAAC;YACjB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;YAClC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;YACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;SACzB,CAAC;QACF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;QACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAID,MAAM,MAAM,mBAAmB,GAC7B;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAIvE,MAAM,MAAM,mBAAmB,GAC3B;IAAE,CAAC,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE;AACtC,4EAA4E;GAC1E;IAAE,CAAC,EAAE,QAAQ,CAAA;CAAE,GACf;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GACb;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElB,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,OAAO,GACX,mBAAmB,GAAG,IAAI,CAc5B;AAED,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,OAAO,GACX,mBAAmB,GAAG,IAAI,CA8B5B"}
|