@poolse/react 2.17.1 → 2.19.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 +38 -0
- package/dist/index.cjs +75 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +75 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -682,6 +682,33 @@ interface UseCalls {
|
|
|
682
682
|
error: Error | null;
|
|
683
683
|
/** Ring every other member of a conversation. */
|
|
684
684
|
call: (conversationId: string, media?: CallMedia) => Promise<void>;
|
|
685
|
+
/**
|
|
686
|
+
* Register a ring that arrived by some route other than the socket —
|
|
687
|
+
* in practice a VoIP push.
|
|
688
|
+
*
|
|
689
|
+
* Without this the push path shows a ring the state machine knows
|
|
690
|
+
* nothing about, and every signal that ends a call is guarded on
|
|
691
|
+
* state the machine never had: the caller hanging up sent
|
|
692
|
+
* `call:cancelled`, the guard found no matching `incoming`, and the
|
|
693
|
+
* callee's phone rang on until its own timeout.
|
|
694
|
+
*
|
|
695
|
+
* Idempotent per call id, because the socket almost always delivers
|
|
696
|
+
* the same call a moment later — that is one ring arriving twice, not
|
|
697
|
+
* a second caller.
|
|
698
|
+
*/
|
|
699
|
+
ring: (call: IncomingCall) => void;
|
|
700
|
+
/**
|
|
701
|
+
* A call was torn down from outside this state machine.
|
|
702
|
+
*
|
|
703
|
+
* The counterpart to {@link ring}: the caller withdrew, or the call
|
|
704
|
+
* ended, and the news reached this device as a VoIP push rather than
|
|
705
|
+
* over the socket — which is the normal case for a phone that was
|
|
706
|
+
* asleep when the ring arrived and whose socket may not be up yet.
|
|
707
|
+
*
|
|
708
|
+
* Ignores call ids this client is not in, so a teardown for a
|
|
709
|
+
* previous call cannot end the current one.
|
|
710
|
+
*/
|
|
711
|
+
dropped: (callId: string) => void;
|
|
685
712
|
/**
|
|
686
713
|
* Answer the inbound ring.
|
|
687
714
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -682,6 +682,33 @@ interface UseCalls {
|
|
|
682
682
|
error: Error | null;
|
|
683
683
|
/** Ring every other member of a conversation. */
|
|
684
684
|
call: (conversationId: string, media?: CallMedia) => Promise<void>;
|
|
685
|
+
/**
|
|
686
|
+
* Register a ring that arrived by some route other than the socket —
|
|
687
|
+
* in practice a VoIP push.
|
|
688
|
+
*
|
|
689
|
+
* Without this the push path shows a ring the state machine knows
|
|
690
|
+
* nothing about, and every signal that ends a call is guarded on
|
|
691
|
+
* state the machine never had: the caller hanging up sent
|
|
692
|
+
* `call:cancelled`, the guard found no matching `incoming`, and the
|
|
693
|
+
* callee's phone rang on until its own timeout.
|
|
694
|
+
*
|
|
695
|
+
* Idempotent per call id, because the socket almost always delivers
|
|
696
|
+
* the same call a moment later — that is one ring arriving twice, not
|
|
697
|
+
* a second caller.
|
|
698
|
+
*/
|
|
699
|
+
ring: (call: IncomingCall) => void;
|
|
700
|
+
/**
|
|
701
|
+
* A call was torn down from outside this state machine.
|
|
702
|
+
*
|
|
703
|
+
* The counterpart to {@link ring}: the caller withdrew, or the call
|
|
704
|
+
* ended, and the news reached this device as a VoIP push rather than
|
|
705
|
+
* over the socket — which is the normal case for a phone that was
|
|
706
|
+
* asleep when the ring arrived and whose socket may not be up yet.
|
|
707
|
+
*
|
|
708
|
+
* Ignores call ids this client is not in, so a teardown for a
|
|
709
|
+
* previous call cannot end the current one.
|
|
710
|
+
*/
|
|
711
|
+
dropped: (callId: string) => void;
|
|
685
712
|
/**
|
|
686
713
|
* Answer the inbound ring.
|
|
687
714
|
*
|
package/dist/index.js
CHANGED
|
@@ -1341,6 +1341,8 @@ function useCalls(userId, opts = {}) {
|
|
|
1341
1341
|
const isCurrent = useCallback((callId) => {
|
|
1342
1342
|
return callId === activeCallIdRef.current || callId === incomingRef.current?.callId || callId === outgoingRef.current?.callId;
|
|
1343
1343
|
}, []);
|
|
1344
|
+
const rejectedRef = useRef(/* @__PURE__ */ new Map());
|
|
1345
|
+
const pendingRef = useRef(null);
|
|
1344
1346
|
const actionsRef = useRef({
|
|
1345
1347
|
cancel: async () => {
|
|
1346
1348
|
},
|
|
@@ -1348,9 +1350,24 @@ function useCalls(userId, opts = {}) {
|
|
|
1348
1350
|
}
|
|
1349
1351
|
});
|
|
1350
1352
|
const calls = useMemo(() => userId ? poolse.realtime.calls(userId) : null, [poolse, userId]);
|
|
1353
|
+
const reject = useCallback((callId, userId2, kind) => {
|
|
1354
|
+
if (phaseRef.current !== "ringing-out") return;
|
|
1355
|
+
const out = outgoingRef.current;
|
|
1356
|
+
if (!out || out.callId !== callId) return;
|
|
1357
|
+
rejectedRef.current.set(userId2, kind);
|
|
1358
|
+
const outstanding = out.calleeUserIds.filter((id) => !rejectedRef.current.has(id));
|
|
1359
|
+
if (out.calleeUserIds.length > 0 && outstanding.length > 0) return;
|
|
1360
|
+
const allBusy = [...rejectedRef.current.values()].every((k) => k === "busy");
|
|
1361
|
+
setPhase(allBusy ? "busy" : "declined");
|
|
1362
|
+
setOutgoing(null);
|
|
1363
|
+
setActiveCallId(null);
|
|
1364
|
+
setActiveMedia(null);
|
|
1365
|
+
}, []);
|
|
1351
1366
|
useEffect(() => {
|
|
1352
1367
|
if (!calls) return;
|
|
1353
1368
|
const offIncoming = calls.onIncoming((call2) => {
|
|
1369
|
+
if (call2.callId === incomingRef.current?.callId) return;
|
|
1370
|
+
if (call2.callId === activeCallIdRef.current) return;
|
|
1354
1371
|
if (phaseRef.current !== "idle") {
|
|
1355
1372
|
void calls.markBusy(call2).catch(() => {
|
|
1356
1373
|
});
|
|
@@ -1367,17 +1384,11 @@ function useCalls(userId, opts = {}) {
|
|
|
1367
1384
|
});
|
|
1368
1385
|
const offDeclined = calls.onDeclined((call2) => {
|
|
1369
1386
|
if (!isCurrent(call2.callId)) return;
|
|
1370
|
-
|
|
1371
|
-
setOutgoing(null);
|
|
1372
|
-
setActiveCallId(null);
|
|
1373
|
-
setActiveMedia(null);
|
|
1387
|
+
reject(call2.callId, call2.userId, "declined");
|
|
1374
1388
|
});
|
|
1375
1389
|
const offBusy = calls.onBusy((call2) => {
|
|
1376
1390
|
if (!isCurrent(call2.callId)) return;
|
|
1377
|
-
|
|
1378
|
-
setOutgoing(null);
|
|
1379
|
-
setActiveCallId(null);
|
|
1380
|
-
setActiveMedia(null);
|
|
1391
|
+
reject(call2.callId, call2.userId, "busy");
|
|
1381
1392
|
});
|
|
1382
1393
|
const offEnded = calls.onEnded((call2) => {
|
|
1383
1394
|
if (!isCurrent(call2.callId)) return;
|
|
@@ -1403,11 +1414,22 @@ function useCalls(userId, opts = {}) {
|
|
|
1403
1414
|
offEnded();
|
|
1404
1415
|
offCancelled();
|
|
1405
1416
|
};
|
|
1406
|
-
}, [calls, isCurrent]);
|
|
1417
|
+
}, [calls, isCurrent, reject]);
|
|
1418
|
+
useEffect(() => {
|
|
1419
|
+
if (!calls) return;
|
|
1420
|
+
const queued = pendingRef.current;
|
|
1421
|
+
if (!queued) return;
|
|
1422
|
+
pendingRef.current = null;
|
|
1423
|
+
const sent = queued.kind === "accept" ? calls.accept(queued.call) : calls.decline(queued.call);
|
|
1424
|
+
void sent.catch((err) => {
|
|
1425
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1426
|
+
});
|
|
1427
|
+
}, [calls]);
|
|
1407
1428
|
const call = useCallback(
|
|
1408
1429
|
async (conversationId, media2 = "audio") => {
|
|
1409
1430
|
if (!calls) return;
|
|
1410
1431
|
setError(null);
|
|
1432
|
+
rejectedRef.current = /* @__PURE__ */ new Map();
|
|
1411
1433
|
try {
|
|
1412
1434
|
const placed = await calls.invite(conversationId, media2);
|
|
1413
1435
|
setOutgoing(placed);
|
|
@@ -1421,10 +1443,40 @@ function useCalls(userId, opts = {}) {
|
|
|
1421
1443
|
},
|
|
1422
1444
|
[calls]
|
|
1423
1445
|
);
|
|
1446
|
+
const ring = useCallback((call2) => {
|
|
1447
|
+
if (incomingRef.current?.callId === call2.callId) return;
|
|
1448
|
+
if (activeCallIdRef.current === call2.callId) return;
|
|
1449
|
+
if (phaseRef.current !== "idle") return;
|
|
1450
|
+
setIncoming(call2);
|
|
1451
|
+
setPhase("ringing-in");
|
|
1452
|
+
}, []);
|
|
1453
|
+
const dropped = useCallback(
|
|
1454
|
+
(callId) => {
|
|
1455
|
+
if (pendingRef.current?.call.callId === callId) pendingRef.current = null;
|
|
1456
|
+
if (!isCurrent(callId)) return;
|
|
1457
|
+
rejectedRef.current = /* @__PURE__ */ new Map();
|
|
1458
|
+
setPhase("idle");
|
|
1459
|
+
setIncoming(null);
|
|
1460
|
+
setOutgoing(null);
|
|
1461
|
+
setActiveConversationId(null);
|
|
1462
|
+
setActiveCallId(null);
|
|
1463
|
+
setActiveMedia(null);
|
|
1464
|
+
},
|
|
1465
|
+
[isCurrent]
|
|
1466
|
+
);
|
|
1424
1467
|
const accept = useCallback(
|
|
1425
1468
|
async (call2) => {
|
|
1426
1469
|
const target = call2 ?? incoming;
|
|
1427
|
-
if (!
|
|
1470
|
+
if (!target) return;
|
|
1471
|
+
if (!calls) {
|
|
1472
|
+
pendingRef.current = { kind: "accept", call: target };
|
|
1473
|
+
setActiveConversationId(target.conversationId);
|
|
1474
|
+
setActiveCallId(target.callId);
|
|
1475
|
+
setActiveMedia(target.media);
|
|
1476
|
+
setPhase("active");
|
|
1477
|
+
setIncoming(null);
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1428
1480
|
try {
|
|
1429
1481
|
await calls.accept(target);
|
|
1430
1482
|
setActiveConversationId(target.conversationId);
|
|
@@ -1441,7 +1493,15 @@ function useCalls(userId, opts = {}) {
|
|
|
1441
1493
|
const decline = useCallback(
|
|
1442
1494
|
async (call2) => {
|
|
1443
1495
|
const target = call2 ?? incoming;
|
|
1444
|
-
if (!
|
|
1496
|
+
if (!target) return;
|
|
1497
|
+
if (!calls) {
|
|
1498
|
+
pendingRef.current = { kind: "decline", call: target };
|
|
1499
|
+
setIncoming(null);
|
|
1500
|
+
setActiveCallId(null);
|
|
1501
|
+
setActiveMedia(null);
|
|
1502
|
+
setPhase("idle");
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1445
1505
|
try {
|
|
1446
1506
|
await calls.decline(target);
|
|
1447
1507
|
} catch (err) {
|
|
@@ -1504,6 +1564,8 @@ function useCalls(userId, opts = {}) {
|
|
|
1504
1564
|
}
|
|
1505
1565
|
}, [calls, phase, incoming, outgoing, activeConversationId, activeCallId]);
|
|
1506
1566
|
const reset = useCallback(() => {
|
|
1567
|
+
rejectedRef.current = /* @__PURE__ */ new Map();
|
|
1568
|
+
pendingRef.current = null;
|
|
1507
1569
|
setPhase("idle");
|
|
1508
1570
|
setIncoming(null);
|
|
1509
1571
|
setOutgoing(null);
|
|
@@ -1521,6 +1583,8 @@ function useCalls(userId, opts = {}) {
|
|
|
1521
1583
|
media,
|
|
1522
1584
|
error,
|
|
1523
1585
|
call,
|
|
1586
|
+
ring,
|
|
1587
|
+
dropped,
|
|
1524
1588
|
accept,
|
|
1525
1589
|
decline,
|
|
1526
1590
|
cancel,
|