@poolse/react 2.14.1 → 2.15.1

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.js CHANGED
@@ -1081,6 +1081,7 @@ function useCallRoom(conversationId, opts) {
1081
1081
  const optsRef = useRef(opts);
1082
1082
  optsRef.current = opts;
1083
1083
  const joined = useRef(false);
1084
+ const held = useRef(null);
1084
1085
  const room = useMemo(() => {
1085
1086
  if (!conversationId) return null;
1086
1087
  return poolse.call(conversationId, {
@@ -1120,6 +1121,7 @@ function useCallRoom(conversationId, opts) {
1120
1121
  }
1121
1122
  setStatus(room.getStatus());
1122
1123
  setParticipants(room.getParticipants());
1124
+ held.current = room;
1123
1125
  setAlone(false);
1124
1126
  const offStatus = room.onStatus(setStatus);
1125
1127
  const offRoster = room.onParticipants((next) => {
@@ -1135,6 +1137,7 @@ function useCallRoom(conversationId, opts) {
1135
1137
  offAlone();
1136
1138
  offDevices();
1137
1139
  offError();
1140
+ held.current = null;
1138
1141
  if (joined.current) {
1139
1142
  joined.current = false;
1140
1143
  room.leave();
@@ -1142,14 +1145,19 @@ function useCallRoom(conversationId, opts) {
1142
1145
  };
1143
1146
  }, [room, sync, refreshDevices]);
1144
1147
  const join = useCallback(async () => {
1145
- if (!room) return false;
1148
+ const target = room;
1149
+ if (!target) return false;
1146
1150
  setError(null);
1147
1151
  try {
1148
- const connected = await room.join();
1152
+ const connected = await target.join();
1153
+ if (held.current !== target) {
1154
+ if (connected) target.leave();
1155
+ return false;
1156
+ }
1149
1157
  joined.current = connected;
1150
1158
  setSfuAvailable(connected);
1151
- sync(room);
1152
- if (connected) refreshDevices(room);
1159
+ sync(target);
1160
+ if (connected) refreshDevices(target);
1153
1161
  return connected;
1154
1162
  } catch (err) {
1155
1163
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -1256,12 +1264,14 @@ function useVoiceRoom(conversationId, opts = {}) {
1256
1264
  const [error, setError] = useState(null);
1257
1265
  const optsRef = useRef(opts);
1258
1266
  const joined = useRef(false);
1267
+ const held = useRef(null);
1259
1268
  const room = useMemo(
1260
1269
  () => conversationId ? poolse.realtime.voice(conversationId, optsRef.current) : null,
1261
1270
  [poolse, conversationId]
1262
1271
  );
1263
1272
  useEffect(() => {
1264
1273
  if (!room) return;
1274
+ held.current = room;
1265
1275
  setStatus(room.getStatus());
1266
1276
  setParticipants(room.getParticipants());
1267
1277
  setMuted(room.isMuted());
@@ -1272,6 +1282,7 @@ function useVoiceRoom(conversationId, opts = {}) {
1272
1282
  offStatus();
1273
1283
  offRoster();
1274
1284
  offError();
1285
+ held.current = null;
1275
1286
  if (joined.current) {
1276
1287
  joined.current = false;
1277
1288
  room.leave();
@@ -1279,10 +1290,15 @@ function useVoiceRoom(conversationId, opts = {}) {
1279
1290
  };
1280
1291
  }, [room]);
1281
1292
  const join = useCallback(async () => {
1282
- if (!room) return;
1293
+ const target = room;
1294
+ if (!target) return;
1283
1295
  setError(null);
1284
1296
  try {
1285
- await room.join();
1297
+ await target.join();
1298
+ if (held.current !== target) {
1299
+ target.leave();
1300
+ return;
1301
+ }
1286
1302
  joined.current = true;
1287
1303
  } catch (err) {
1288
1304
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -1308,10 +1324,23 @@ function useCalls(userId, opts = {}) {
1308
1324
  const ringTimeoutMs = opts.ringTimeoutMs ?? 45e3;
1309
1325
  const poolse = usePoolse();
1310
1326
  const [phase, setPhase] = useState("idle");
1327
+ const phaseRef = useRef("idle");
1328
+ phaseRef.current = phase;
1311
1329
  const [incoming, setIncoming] = useState(null);
1312
1330
  const [outgoing, setOutgoing] = useState(null);
1313
1331
  const [activeConversationId, setActiveConversationId] = useState(null);
1332
+ const [activeCallId, setActiveCallId] = useState(null);
1333
+ const [activeMedia, setActiveMedia] = useState(null);
1314
1334
  const [error, setError] = useState(null);
1335
+ const incomingRef = useRef(null);
1336
+ incomingRef.current = incoming;
1337
+ const outgoingRef = useRef(null);
1338
+ outgoingRef.current = outgoing;
1339
+ const activeCallIdRef = useRef(null);
1340
+ activeCallIdRef.current = activeCallId;
1341
+ const isCurrent = useCallback((callId) => {
1342
+ return callId === activeCallIdRef.current || callId === incomingRef.current?.callId || callId === outgoingRef.current?.callId;
1343
+ }, []);
1315
1344
  const actionsRef = useRef({
1316
1345
  cancel: async () => {
1317
1346
  },
@@ -1322,40 +1351,49 @@ function useCalls(userId, opts = {}) {
1322
1351
  useEffect(() => {
1323
1352
  if (!calls) return;
1324
1353
  const offIncoming = calls.onIncoming((call2) => {
1325
- setPhase((current) => {
1326
- if (current !== "idle") {
1327
- void calls.markBusy(call2).catch(() => {
1328
- });
1329
- return current;
1330
- }
1331
- setIncoming(call2);
1332
- return "ringing-in";
1333
- });
1354
+ if (phaseRef.current !== "idle") {
1355
+ void calls.markBusy(call2).catch(() => {
1356
+ });
1357
+ return;
1358
+ }
1359
+ setIncoming(call2);
1360
+ setPhase("ringing-in");
1334
1361
  });
1335
1362
  const offAccepted = calls.onAccepted((call2) => {
1363
+ if (outgoingRef.current && call2.callId !== outgoingRef.current.callId) return;
1336
1364
  setPhase("active");
1337
1365
  setActiveConversationId(call2.conversationId);
1366
+ setActiveCallId(call2.callId);
1338
1367
  });
1339
- const offDeclined = calls.onDeclined(() => {
1368
+ const offDeclined = calls.onDeclined((call2) => {
1369
+ if (!isCurrent(call2.callId)) return;
1340
1370
  setPhase("declined");
1341
1371
  setOutgoing(null);
1372
+ setActiveCallId(null);
1373
+ setActiveMedia(null);
1342
1374
  });
1343
- const offBusy = calls.onBusy(() => {
1375
+ const offBusy = calls.onBusy((call2) => {
1376
+ if (!isCurrent(call2.callId)) return;
1344
1377
  setPhase("busy");
1345
1378
  setOutgoing(null);
1379
+ setActiveCallId(null);
1380
+ setActiveMedia(null);
1346
1381
  });
1347
- const offEnded = calls.onEnded(() => {
1382
+ const offEnded = calls.onEnded((call2) => {
1383
+ if (!isCurrent(call2.callId)) return;
1348
1384
  setPhase("idle");
1349
1385
  setIncoming(null);
1350
1386
  setOutgoing(null);
1351
1387
  setActiveConversationId(null);
1388
+ setActiveCallId(null);
1389
+ setActiveMedia(null);
1352
1390
  });
1353
1391
  const offCancelled = calls.onCancelled((call2) => {
1354
- setIncoming((current) => {
1355
- if (current && current.callId !== call2.callId) return current;
1356
- setPhase("idle");
1357
- return null;
1358
- });
1392
+ if (call2.callId !== incomingRef.current?.callId) return;
1393
+ setIncoming(null);
1394
+ setPhase("idle");
1395
+ setActiveCallId(null);
1396
+ setActiveMedia(null);
1359
1397
  });
1360
1398
  return () => {
1361
1399
  offIncoming();
@@ -1365,7 +1403,7 @@ function useCalls(userId, opts = {}) {
1365
1403
  offEnded();
1366
1404
  offCancelled();
1367
1405
  };
1368
- }, [calls]);
1406
+ }, [calls, isCurrent]);
1369
1407
  const call = useCallback(
1370
1408
  async (conversationId, media2 = "audio") => {
1371
1409
  if (!calls) return;
@@ -1373,6 +1411,8 @@ function useCalls(userId, opts = {}) {
1373
1411
  try {
1374
1412
  const placed = await calls.invite(conversationId, media2);
1375
1413
  setOutgoing(placed);
1414
+ setActiveCallId(placed.callId);
1415
+ setActiveMedia(placed.media);
1376
1416
  setPhase("ringing-out");
1377
1417
  } catch (err) {
1378
1418
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -1388,6 +1428,8 @@ function useCalls(userId, opts = {}) {
1388
1428
  try {
1389
1429
  await calls.accept(target);
1390
1430
  setActiveConversationId(target.conversationId);
1431
+ setActiveCallId(target.callId);
1432
+ setActiveMedia(target.media);
1391
1433
  setPhase("active");
1392
1434
  setIncoming(null);
1393
1435
  } catch (err) {
@@ -1406,6 +1448,8 @@ function useCalls(userId, opts = {}) {
1406
1448
  setError(err instanceof Error ? err : new Error(String(err)));
1407
1449
  } finally {
1408
1450
  setIncoming(null);
1451
+ setActiveCallId(null);
1452
+ setActiveMedia(null);
1409
1453
  setPhase("idle");
1410
1454
  }
1411
1455
  },
@@ -1419,6 +1463,8 @@ function useCalls(userId, opts = {}) {
1419
1463
  setError(err instanceof Error ? err : new Error(String(err)));
1420
1464
  } finally {
1421
1465
  setOutgoing(null);
1466
+ setActiveCallId(null);
1467
+ setActiveMedia(null);
1422
1468
  setPhase("idle");
1423
1469
  }
1424
1470
  }, [calls, outgoing]);
@@ -1436,7 +1482,7 @@ function useCalls(userId, opts = {}) {
1436
1482
  }, [phase, ringTimeoutMs]);
1437
1483
  const hangUp = useCallback(async () => {
1438
1484
  const conversationId = activeConversationId ?? incoming?.conversationId ?? outgoing?.conversationId;
1439
- const callId = incoming?.callId ?? outgoing?.callId;
1485
+ const callId = activeCallId ?? incoming?.callId ?? outgoing?.callId;
1440
1486
  try {
1441
1487
  if (!calls || !callId || !conversationId) return;
1442
1488
  if (phase === "ringing-out" && outgoing) {
@@ -1453,16 +1499,20 @@ function useCalls(userId, opts = {}) {
1453
1499
  setIncoming(null);
1454
1500
  setOutgoing(null);
1455
1501
  setActiveConversationId(null);
1502
+ setActiveCallId(null);
1503
+ setActiveMedia(null);
1456
1504
  }
1457
- }, [calls, phase, incoming, outgoing, activeConversationId]);
1505
+ }, [calls, phase, incoming, outgoing, activeConversationId, activeCallId]);
1458
1506
  const reset = useCallback(() => {
1459
1507
  setPhase("idle");
1460
1508
  setIncoming(null);
1461
1509
  setOutgoing(null);
1462
1510
  setActiveConversationId(null);
1511
+ setActiveCallId(null);
1512
+ setActiveMedia(null);
1463
1513
  setError(null);
1464
1514
  }, []);
1465
- const media = incoming?.media ?? outgoing?.media ?? "audio";
1515
+ const media = incoming?.media ?? outgoing?.media ?? activeMedia ?? "audio";
1466
1516
  return {
1467
1517
  phase,
1468
1518
  incoming,