@poolse/react 2.28.0 → 2.29.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/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All notable changes to `@poolse/react` are documented here. Format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
5
  [semver](https://semver.org).
6
6
 
7
+ ## [2.29.0] — 2026-10-22
8
+
9
+ ### Removed
10
+
11
+ - **`useCalls` and `useVoiceRoom`** — the legacy socket-ringing calls and
12
+ the peer-to-peer voice room, both retired on the server. Use `useRoom`
13
+ (rooms, opt-in ringing) and `useLiveCall` on the web; phones use
14
+ `@poolse/react-native-calls`.
15
+
7
16
  ## [2.24.0] — 2026-10-14
8
17
 
9
18
  ### Fixed
package/dist/index.cjs CHANGED
@@ -1238,384 +1238,6 @@ function useCallRoom(conversationId, opts) {
1238
1238
  toggleMic
1239
1239
  };
1240
1240
  }
1241
- function useAloneWatch({ active, others, graceMs }) {
1242
- const [alone, setAlone] = react.useState(false);
1243
- const seenPeer = react.useRef(false);
1244
- react.useEffect(() => {
1245
- if (!active) {
1246
- seenPeer.current = false;
1247
- setAlone(false);
1248
- return;
1249
- }
1250
- if (others > 0) {
1251
- seenPeer.current = true;
1252
- return;
1253
- }
1254
- if (graceMs <= 0 || !seenPeer.current) return;
1255
- const timer = setTimeout(() => setAlone(true), graceMs);
1256
- return () => clearTimeout(timer);
1257
- }, [active, others, graceMs]);
1258
- return alone;
1259
- }
1260
-
1261
- // src/use-voice-room.ts
1262
- function useVoiceRoom(conversationId, opts = {}) {
1263
- const poolse = usePoolse();
1264
- const [status, setStatus] = react.useState("idle");
1265
- const [participants, setParticipants] = react.useState([]);
1266
- const [muted, setMuted] = react.useState(false);
1267
- const [error, setError] = react.useState(null);
1268
- const optsRef = react.useRef(opts);
1269
- const joined = react.useRef(false);
1270
- const held = react.useRef(null);
1271
- const room = react.useMemo(
1272
- () => conversationId ? poolse.realtime.voice(conversationId, optsRef.current) : null,
1273
- [poolse, conversationId]
1274
- );
1275
- react.useEffect(() => {
1276
- if (!room) return;
1277
- held.current = room;
1278
- setStatus(room.getStatus());
1279
- setParticipants(room.getParticipants());
1280
- setMuted(room.isMuted());
1281
- const offStatus = room.onStatus(setStatus);
1282
- const offRoster = room.onParticipants(setParticipants);
1283
- const offError = room.onError(setError);
1284
- return () => {
1285
- offStatus();
1286
- offRoster();
1287
- offError();
1288
- held.current = null;
1289
- if (joined.current) {
1290
- joined.current = false;
1291
- room.leave();
1292
- }
1293
- };
1294
- }, [room]);
1295
- const join = react.useCallback(async () => {
1296
- const target = room;
1297
- if (!target) return;
1298
- setError(null);
1299
- try {
1300
- await target.join();
1301
- if (held.current !== target) {
1302
- target.leave();
1303
- return;
1304
- }
1305
- joined.current = true;
1306
- } catch (err) {
1307
- setError(err instanceof Error ? err : new Error(String(err)));
1308
- }
1309
- }, [room]);
1310
- const leave = react.useCallback(() => {
1311
- if (!joined.current) return;
1312
- joined.current = false;
1313
- room?.leave();
1314
- }, [room]);
1315
- const toggleMute = react.useCallback(() => {
1316
- if (!room) return;
1317
- setMuted(room.toggleMute());
1318
- }, [room]);
1319
- const alone = useAloneWatch({
1320
- active: status === "connected",
1321
- others: Math.max(0, participants.length - 1),
1322
- graceMs: optsRef.current.aloneGraceMs ?? 1500
1323
- });
1324
- return { status, participants, muted, alone, error, join, leave, toggleMute };
1325
- }
1326
- function useCalls(userId, opts = {}) {
1327
- const ringTimeoutMs = opts.ringTimeoutMs ?? 45e3;
1328
- const poolse = usePoolse();
1329
- const [phase, setPhase] = react.useState("idle");
1330
- const phaseRef = react.useRef("idle");
1331
- phaseRef.current = phase;
1332
- const [incoming, setIncoming] = react.useState(null);
1333
- const [outgoing, setOutgoing] = react.useState(null);
1334
- const [activeConversationId, setActiveConversationId] = react.useState(null);
1335
- const [activeCallId, setActiveCallId] = react.useState(null);
1336
- const [activeMedia, setActiveMedia] = react.useState(null);
1337
- const [error, setError] = react.useState(null);
1338
- const incomingRef = react.useRef(null);
1339
- incomingRef.current = incoming;
1340
- const outgoingRef = react.useRef(null);
1341
- outgoingRef.current = outgoing;
1342
- const activeCallIdRef = react.useRef(null);
1343
- activeCallIdRef.current = activeCallId;
1344
- const isCurrent = react.useCallback((callId) => {
1345
- return callId === activeCallIdRef.current || callId === incomingRef.current?.callId || callId === outgoingRef.current?.callId;
1346
- }, []);
1347
- const rejectedRef = react.useRef(/* @__PURE__ */ new Map());
1348
- const pendingRef = react.useRef(null);
1349
- const actionsRef = react.useRef({
1350
- cancel: async () => {
1351
- },
1352
- decline: async () => {
1353
- }
1354
- });
1355
- const calls = react.useMemo(() => userId ? poolse.realtime.calls(userId) : null, [poolse, userId]);
1356
- const reject = react.useCallback((callId, userId2, kind) => {
1357
- if (phaseRef.current !== "ringing-out") return;
1358
- const out = outgoingRef.current;
1359
- if (!out || out.callId !== callId) return;
1360
- rejectedRef.current.set(userId2, kind);
1361
- const outstanding = out.calleeUserIds.filter((id) => !rejectedRef.current.has(id));
1362
- if (out.calleeUserIds.length > 0 && outstanding.length > 0) return;
1363
- const allBusy = [...rejectedRef.current.values()].every((k) => k === "busy");
1364
- setPhase(allBusy ? "busy" : "declined");
1365
- setOutgoing(null);
1366
- setActiveCallId(null);
1367
- setActiveMedia(null);
1368
- }, []);
1369
- react.useEffect(() => {
1370
- if (!calls) return;
1371
- const offIncoming = calls.onIncoming((call2) => {
1372
- if (call2.callId === incomingRef.current?.callId) return;
1373
- if (call2.callId === activeCallIdRef.current) return;
1374
- if (phaseRef.current !== "idle") {
1375
- void calls.markBusy(call2).catch(() => {
1376
- });
1377
- return;
1378
- }
1379
- setIncoming(call2);
1380
- setPhase("ringing-in");
1381
- });
1382
- const offAccepted = calls.onAccepted((call2) => {
1383
- if (outgoingRef.current && call2.callId !== outgoingRef.current.callId) return;
1384
- setPhase("active");
1385
- setActiveConversationId(call2.conversationId);
1386
- setActiveCallId(call2.callId);
1387
- });
1388
- const offDeclined = calls.onDeclined((call2) => {
1389
- if (!isCurrent(call2.callId)) return;
1390
- reject(call2.callId, call2.userId, "declined");
1391
- });
1392
- const offBusy = calls.onBusy((call2) => {
1393
- if (!isCurrent(call2.callId)) return;
1394
- reject(call2.callId, call2.userId, "busy");
1395
- });
1396
- const offEnded = calls.onEnded((call2) => {
1397
- if (!isCurrent(call2.callId)) return;
1398
- setPhase("idle");
1399
- setIncoming(null);
1400
- setOutgoing(null);
1401
- setActiveConversationId(null);
1402
- setActiveCallId(null);
1403
- setActiveMedia(null);
1404
- });
1405
- const offCancelled = calls.onCancelled((call2) => {
1406
- if (call2.callId !== incomingRef.current?.callId) return;
1407
- setIncoming(null);
1408
- setPhase("idle");
1409
- setActiveCallId(null);
1410
- setActiveMedia(null);
1411
- });
1412
- return () => {
1413
- offIncoming();
1414
- offAccepted();
1415
- offDeclined();
1416
- offBusy();
1417
- offEnded();
1418
- offCancelled();
1419
- };
1420
- }, [calls, isCurrent, reject]);
1421
- react.useEffect(() => {
1422
- if (!calls) return;
1423
- const queued = pendingRef.current;
1424
- if (!queued) return;
1425
- pendingRef.current = null;
1426
- const sent = queued.kind === "accept" ? calls.accept(queued.call) : calls.decline(queued.call);
1427
- void sent.catch((err) => {
1428
- setError(err instanceof Error ? err : new Error(String(err)));
1429
- });
1430
- }, [calls]);
1431
- const call = react.useCallback(
1432
- async (conversationId, media2 = "audio") => {
1433
- if (!calls) return;
1434
- setError(null);
1435
- rejectedRef.current = /* @__PURE__ */ new Map();
1436
- try {
1437
- const placed = await calls.invite(conversationId, media2);
1438
- setOutgoing(placed);
1439
- setActiveCallId(placed.callId);
1440
- setActiveMedia(placed.media);
1441
- setPhase("ringing-out");
1442
- } catch (err) {
1443
- setError(err instanceof Error ? err : new Error(String(err)));
1444
- setPhase("idle");
1445
- }
1446
- },
1447
- [calls]
1448
- );
1449
- const ring = react.useCallback((call2) => {
1450
- if (incomingRef.current?.callId === call2.callId) return;
1451
- if (activeCallIdRef.current === call2.callId) return;
1452
- if (phaseRef.current !== "idle") return;
1453
- setIncoming(call2);
1454
- setPhase("ringing-in");
1455
- }, []);
1456
- const dropped = react.useCallback(
1457
- (callId) => {
1458
- if (pendingRef.current?.call.callId === callId) pendingRef.current = null;
1459
- if (!isCurrent(callId)) return;
1460
- rejectedRef.current = /* @__PURE__ */ new Map();
1461
- setPhase("idle");
1462
- setIncoming(null);
1463
- setOutgoing(null);
1464
- setActiveConversationId(null);
1465
- setActiveCallId(null);
1466
- setActiveMedia(null);
1467
- },
1468
- [isCurrent]
1469
- );
1470
- const accept = react.useCallback(
1471
- async (call2) => {
1472
- const target = call2 ?? incoming;
1473
- if (!target) return;
1474
- if (!calls) {
1475
- pendingRef.current = { kind: "accept", call: target };
1476
- setActiveConversationId(target.conversationId);
1477
- setActiveCallId(target.callId);
1478
- setActiveMedia(target.media);
1479
- setPhase("active");
1480
- setIncoming(null);
1481
- return;
1482
- }
1483
- setActiveConversationId(target.conversationId);
1484
- setActiveCallId(target.callId);
1485
- setActiveMedia(target.media);
1486
- setPhase("active");
1487
- setIncoming(null);
1488
- try {
1489
- await calls.accept(target);
1490
- } catch (err) {
1491
- const message = err instanceof Error ? err.message : String(err);
1492
- if (message.includes("timed out")) return;
1493
- setPhase("idle");
1494
- setActiveConversationId(null);
1495
- setActiveCallId(null);
1496
- setActiveMedia(null);
1497
- setError(err instanceof Error ? err : new Error(String(err)));
1498
- }
1499
- },
1500
- [calls, incoming]
1501
- );
1502
- const decline = react.useCallback(
1503
- async (call2) => {
1504
- const target = call2 ?? incoming;
1505
- if (!target) return;
1506
- if (!calls) {
1507
- pendingRef.current = { kind: "decline", call: target };
1508
- setIncoming(null);
1509
- setActiveCallId(null);
1510
- setActiveMedia(null);
1511
- setPhase("idle");
1512
- return;
1513
- }
1514
- try {
1515
- await calls.decline(target);
1516
- } catch (err) {
1517
- setError(err instanceof Error ? err : new Error(String(err)));
1518
- } finally {
1519
- setIncoming(null);
1520
- setActiveCallId(null);
1521
- setActiveMedia(null);
1522
- setPhase("idle");
1523
- }
1524
- },
1525
- [calls, incoming]
1526
- );
1527
- const cancel = react.useCallback(async () => {
1528
- if (!calls || !outgoing) return;
1529
- try {
1530
- await calls.cancel(outgoing);
1531
- } catch (err) {
1532
- setError(err instanceof Error ? err : new Error(String(err)));
1533
- } finally {
1534
- setOutgoing(null);
1535
- setActiveCallId(null);
1536
- setActiveMedia(null);
1537
- setPhase("idle");
1538
- }
1539
- }, [calls, outgoing]);
1540
- actionsRef.current = { cancel, decline };
1541
- react.useEffect(() => {
1542
- if (ringTimeoutMs <= 0) return;
1543
- if (phase !== "ringing-out" && phase !== "ringing-in") return;
1544
- const outbound = phase === "ringing-out";
1545
- const timer = setTimeout(() => {
1546
- void (outbound ? actionsRef.current.cancel() : actionsRef.current.decline()).finally(() => {
1547
- if (outbound) setPhase("timed-out");
1548
- });
1549
- }, ringTimeoutMs);
1550
- return () => clearTimeout(timer);
1551
- }, [phase, ringTimeoutMs]);
1552
- const othersOnCallRef = react.useRef(0);
1553
- const reportOthersOnCall = react.useCallback((count) => {
1554
- othersOnCallRef.current = count;
1555
- }, []);
1556
- const hangUp = react.useCallback(async () => {
1557
- if (phaseRef.current === "active" && othersOnCallRef.current >= 2) {
1558
- setPhase("idle");
1559
- setIncoming(null);
1560
- setOutgoing(null);
1561
- setActiveConversationId(null);
1562
- setActiveCallId(null);
1563
- setActiveMedia(null);
1564
- setError(null);
1565
- return;
1566
- }
1567
- const conversationId = activeConversationId ?? incoming?.conversationId ?? outgoing?.conversationId;
1568
- const callId = activeCallId ?? incoming?.callId ?? outgoing?.callId;
1569
- try {
1570
- if (!calls || !callId || !conversationId) return;
1571
- if (phase === "ringing-out" && outgoing) {
1572
- await calls.cancel(outgoing);
1573
- } else if (phase === "ringing-in" && incoming) {
1574
- await calls.decline(incoming);
1575
- } else if (phase === "active") {
1576
- await calls.hangUp({ callId, conversationId });
1577
- }
1578
- } catch (err) {
1579
- setError(err instanceof Error ? err : new Error(String(err)));
1580
- } finally {
1581
- setPhase("idle");
1582
- setIncoming(null);
1583
- setOutgoing(null);
1584
- setActiveConversationId(null);
1585
- setActiveCallId(null);
1586
- setActiveMedia(null);
1587
- }
1588
- }, [calls, phase, incoming, outgoing, activeConversationId, activeCallId]);
1589
- const reset = react.useCallback(() => {
1590
- rejectedRef.current = /* @__PURE__ */ new Map();
1591
- pendingRef.current = null;
1592
- setPhase("idle");
1593
- setIncoming(null);
1594
- setOutgoing(null);
1595
- setActiveConversationId(null);
1596
- setActiveCallId(null);
1597
- setActiveMedia(null);
1598
- setError(null);
1599
- }, []);
1600
- const media = incoming?.media ?? outgoing?.media ?? activeMedia ?? "audio";
1601
- return {
1602
- phase,
1603
- incoming,
1604
- outgoing,
1605
- activeConversationId,
1606
- media,
1607
- error,
1608
- call,
1609
- ring,
1610
- dropped,
1611
- accept,
1612
- decline,
1613
- cancel,
1614
- hangUp,
1615
- reportOthersOnCall,
1616
- reset
1617
- };
1618
- }
1619
1241
  function useLiveCall(conversationId) {
1620
1242
  const chat = usePoolse();
1621
1243
  const [call, setCall] = react.useState(null);
@@ -1808,7 +1430,6 @@ exports.sameGroup = sameGroup;
1808
1430
  exports.useAttachmentUpload = useAttachmentUpload;
1809
1431
  exports.useAttachmentUrl = useAttachmentUrl;
1810
1432
  exports.useCallRoom = useCallRoom;
1811
- exports.useCalls = useCalls;
1812
1433
  exports.useConversation = useConversation;
1813
1434
  exports.useConversations = useConversations;
1814
1435
  exports.useLiveCall = useLiveCall;
@@ -1824,6 +1445,5 @@ exports.useRoom = useRoom;
1824
1445
  exports.useThread = useThread;
1825
1446
  exports.useTyping = useTyping;
1826
1447
  exports.useUser = useUser;
1827
- exports.useVoiceRoom = useVoiceRoom;
1828
1448
  //# sourceMappingURL=index.cjs.map
1829
1449
  //# sourceMappingURL=index.cjs.map