@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 CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from "./lib/api.js";
2
2
  export * from "./lib/transport.js";
3
+ export * from "./lib/rtc-client.js";
4
+ export * from "./lib/realtime-client.js";
3
5
  export * from "./social-server.js";
4
6
  export * from "./types/index.js";
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -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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"}
@@ -1,3 +1,5 @@
1
+ export * from "./call.js";
2
+ export * from "./realtime.js";
1
3
  export interface Actor {
2
4
  ap_id: string;
3
5
  username: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takosjp/yurucommu-api",
3
- "version": "3.2.1",
3
+ "version": "3.4.0",
4
4
  "description": "Typed client SDK and public API contract for yurucommu-server clients.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",