@poolse/react 2.14.0 → 2.15.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 +42 -0
- package/dist/index.cjs +69 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +69 -26
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -532,6 +532,17 @@ interface UseCallRoom {
|
|
|
532
532
|
activeDevices: Partial<Record<CallDeviceKind, string>>;
|
|
533
533
|
/** Move the call onto another camera, microphone or output. */
|
|
534
534
|
selectDevice: (kind: CallDeviceKind, deviceId: string) => Promise<void>;
|
|
535
|
+
/**
|
|
536
|
+
* Re-read the device list.
|
|
537
|
+
*
|
|
538
|
+
* Called for you on connect and whenever devices change, so this is
|
|
539
|
+
* for the case neither covers: a browser only reveals device ids and
|
|
540
|
+
* labels once the page holds a matching permission, and the moment
|
|
541
|
+
* that permission lands is not an event you can subscribe to. Call it
|
|
542
|
+
* when opening a picker and the list will be right by the time it is
|
|
543
|
+
* on screen.
|
|
544
|
+
*/
|
|
545
|
+
refreshDevices: () => void;
|
|
535
546
|
/**
|
|
536
547
|
* Everyone else has gone and is not coming back.
|
|
537
548
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -532,6 +532,17 @@ interface UseCallRoom {
|
|
|
532
532
|
activeDevices: Partial<Record<CallDeviceKind, string>>;
|
|
533
533
|
/** Move the call onto another camera, microphone or output. */
|
|
534
534
|
selectDevice: (kind: CallDeviceKind, deviceId: string) => Promise<void>;
|
|
535
|
+
/**
|
|
536
|
+
* Re-read the device list.
|
|
537
|
+
*
|
|
538
|
+
* Called for you on connect and whenever devices change, so this is
|
|
539
|
+
* for the case neither covers: a browser only reveals device ids and
|
|
540
|
+
* labels once the page holds a matching permission, and the moment
|
|
541
|
+
* that permission lands is not an event you can subscribe to. Call it
|
|
542
|
+
* when opening a picker and the list will be right by the time it is
|
|
543
|
+
* on screen.
|
|
544
|
+
*/
|
|
545
|
+
refreshDevices: () => void;
|
|
535
546
|
/**
|
|
536
547
|
* Everyone else has gone and is not coming back.
|
|
537
548
|
*
|
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
|
-
|
|
1148
|
+
const target = room;
|
|
1149
|
+
if (!target) return false;
|
|
1146
1150
|
setError(null);
|
|
1147
1151
|
try {
|
|
1148
|
-
const connected = await
|
|
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(
|
|
1152
|
-
if (connected) refreshDevices(
|
|
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)));
|
|
@@ -1191,6 +1199,9 @@ function useCallRoom(conversationId, opts) {
|
|
|
1191
1199
|
await room.switchCamera();
|
|
1192
1200
|
setFacing(room.getFacingMode());
|
|
1193
1201
|
}, [room]);
|
|
1202
|
+
const refresh = useCallback(() => {
|
|
1203
|
+
if (room) refreshDevices(room);
|
|
1204
|
+
}, [room, refreshDevices]);
|
|
1194
1205
|
const selectDevice = useCallback(
|
|
1195
1206
|
async (kind, deviceId) => {
|
|
1196
1207
|
if (!room) return;
|
|
@@ -1211,6 +1222,7 @@ function useCallRoom(conversationId, opts) {
|
|
|
1211
1222
|
alone,
|
|
1212
1223
|
devices,
|
|
1213
1224
|
activeDevices,
|
|
1225
|
+
refreshDevices: refresh,
|
|
1214
1226
|
error,
|
|
1215
1227
|
sfuAvailable,
|
|
1216
1228
|
join,
|
|
@@ -1252,12 +1264,14 @@ function useVoiceRoom(conversationId, opts = {}) {
|
|
|
1252
1264
|
const [error, setError] = useState(null);
|
|
1253
1265
|
const optsRef = useRef(opts);
|
|
1254
1266
|
const joined = useRef(false);
|
|
1267
|
+
const held = useRef(null);
|
|
1255
1268
|
const room = useMemo(
|
|
1256
1269
|
() => conversationId ? poolse.realtime.voice(conversationId, optsRef.current) : null,
|
|
1257
1270
|
[poolse, conversationId]
|
|
1258
1271
|
);
|
|
1259
1272
|
useEffect(() => {
|
|
1260
1273
|
if (!room) return;
|
|
1274
|
+
held.current = room;
|
|
1261
1275
|
setStatus(room.getStatus());
|
|
1262
1276
|
setParticipants(room.getParticipants());
|
|
1263
1277
|
setMuted(room.isMuted());
|
|
@@ -1268,6 +1282,7 @@ function useVoiceRoom(conversationId, opts = {}) {
|
|
|
1268
1282
|
offStatus();
|
|
1269
1283
|
offRoster();
|
|
1270
1284
|
offError();
|
|
1285
|
+
held.current = null;
|
|
1271
1286
|
if (joined.current) {
|
|
1272
1287
|
joined.current = false;
|
|
1273
1288
|
room.leave();
|
|
@@ -1275,10 +1290,15 @@ function useVoiceRoom(conversationId, opts = {}) {
|
|
|
1275
1290
|
};
|
|
1276
1291
|
}, [room]);
|
|
1277
1292
|
const join = useCallback(async () => {
|
|
1278
|
-
|
|
1293
|
+
const target = room;
|
|
1294
|
+
if (!target) return;
|
|
1279
1295
|
setError(null);
|
|
1280
1296
|
try {
|
|
1281
|
-
await
|
|
1297
|
+
await target.join();
|
|
1298
|
+
if (held.current !== target) {
|
|
1299
|
+
target.leave();
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1282
1302
|
joined.current = true;
|
|
1283
1303
|
} catch (err) {
|
|
1284
1304
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -1304,10 +1324,22 @@ function useCalls(userId, opts = {}) {
|
|
|
1304
1324
|
const ringTimeoutMs = opts.ringTimeoutMs ?? 45e3;
|
|
1305
1325
|
const poolse = usePoolse();
|
|
1306
1326
|
const [phase, setPhase] = useState("idle");
|
|
1327
|
+
const phaseRef = useRef("idle");
|
|
1328
|
+
phaseRef.current = phase;
|
|
1307
1329
|
const [incoming, setIncoming] = useState(null);
|
|
1308
1330
|
const [outgoing, setOutgoing] = useState(null);
|
|
1309
1331
|
const [activeConversationId, setActiveConversationId] = useState(null);
|
|
1332
|
+
const [activeCallId, setActiveCallId] = useState(null);
|
|
1310
1333
|
const [error, setError] = useState(null);
|
|
1334
|
+
const incomingRef = useRef(null);
|
|
1335
|
+
incomingRef.current = incoming;
|
|
1336
|
+
const outgoingRef = useRef(null);
|
|
1337
|
+
outgoingRef.current = outgoing;
|
|
1338
|
+
const activeCallIdRef = useRef(null);
|
|
1339
|
+
activeCallIdRef.current = activeCallId;
|
|
1340
|
+
const isCurrent = useCallback((callId) => {
|
|
1341
|
+
return callId === activeCallIdRef.current || callId === incomingRef.current?.callId || callId === outgoingRef.current?.callId;
|
|
1342
|
+
}, []);
|
|
1311
1343
|
const actionsRef = useRef({
|
|
1312
1344
|
cancel: async () => {
|
|
1313
1345
|
},
|
|
@@ -1318,40 +1350,45 @@ function useCalls(userId, opts = {}) {
|
|
|
1318
1350
|
useEffect(() => {
|
|
1319
1351
|
if (!calls) return;
|
|
1320
1352
|
const offIncoming = calls.onIncoming((call2) => {
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
return "ringing-in";
|
|
1329
|
-
});
|
|
1353
|
+
if (phaseRef.current !== "idle") {
|
|
1354
|
+
void calls.markBusy(call2).catch(() => {
|
|
1355
|
+
});
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
setIncoming(call2);
|
|
1359
|
+
setPhase("ringing-in");
|
|
1330
1360
|
});
|
|
1331
1361
|
const offAccepted = calls.onAccepted((call2) => {
|
|
1362
|
+
if (outgoingRef.current && call2.callId !== outgoingRef.current.callId) return;
|
|
1332
1363
|
setPhase("active");
|
|
1333
1364
|
setActiveConversationId(call2.conversationId);
|
|
1365
|
+
setActiveCallId(call2.callId);
|
|
1334
1366
|
});
|
|
1335
|
-
const offDeclined = calls.onDeclined(() => {
|
|
1367
|
+
const offDeclined = calls.onDeclined((call2) => {
|
|
1368
|
+
if (!isCurrent(call2.callId)) return;
|
|
1336
1369
|
setPhase("declined");
|
|
1337
1370
|
setOutgoing(null);
|
|
1371
|
+
setActiveCallId(null);
|
|
1338
1372
|
});
|
|
1339
|
-
const offBusy = calls.onBusy(() => {
|
|
1373
|
+
const offBusy = calls.onBusy((call2) => {
|
|
1374
|
+
if (!isCurrent(call2.callId)) return;
|
|
1340
1375
|
setPhase("busy");
|
|
1341
1376
|
setOutgoing(null);
|
|
1377
|
+
setActiveCallId(null);
|
|
1342
1378
|
});
|
|
1343
|
-
const offEnded = calls.onEnded(() => {
|
|
1379
|
+
const offEnded = calls.onEnded((call2) => {
|
|
1380
|
+
if (!isCurrent(call2.callId)) return;
|
|
1344
1381
|
setPhase("idle");
|
|
1345
1382
|
setIncoming(null);
|
|
1346
1383
|
setOutgoing(null);
|
|
1347
1384
|
setActiveConversationId(null);
|
|
1385
|
+
setActiveCallId(null);
|
|
1348
1386
|
});
|
|
1349
1387
|
const offCancelled = calls.onCancelled((call2) => {
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
});
|
|
1388
|
+
if (call2.callId !== incomingRef.current?.callId) return;
|
|
1389
|
+
setIncoming(null);
|
|
1390
|
+
setPhase("idle");
|
|
1391
|
+
setActiveCallId(null);
|
|
1355
1392
|
});
|
|
1356
1393
|
return () => {
|
|
1357
1394
|
offIncoming();
|
|
@@ -1361,7 +1398,7 @@ function useCalls(userId, opts = {}) {
|
|
|
1361
1398
|
offEnded();
|
|
1362
1399
|
offCancelled();
|
|
1363
1400
|
};
|
|
1364
|
-
}, [calls]);
|
|
1401
|
+
}, [calls, isCurrent]);
|
|
1365
1402
|
const call = useCallback(
|
|
1366
1403
|
async (conversationId, media2 = "audio") => {
|
|
1367
1404
|
if (!calls) return;
|
|
@@ -1369,6 +1406,7 @@ function useCalls(userId, opts = {}) {
|
|
|
1369
1406
|
try {
|
|
1370
1407
|
const placed = await calls.invite(conversationId, media2);
|
|
1371
1408
|
setOutgoing(placed);
|
|
1409
|
+
setActiveCallId(placed.callId);
|
|
1372
1410
|
setPhase("ringing-out");
|
|
1373
1411
|
} catch (err) {
|
|
1374
1412
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -1384,6 +1422,7 @@ function useCalls(userId, opts = {}) {
|
|
|
1384
1422
|
try {
|
|
1385
1423
|
await calls.accept(target);
|
|
1386
1424
|
setActiveConversationId(target.conversationId);
|
|
1425
|
+
setActiveCallId(target.callId);
|
|
1387
1426
|
setPhase("active");
|
|
1388
1427
|
setIncoming(null);
|
|
1389
1428
|
} catch (err) {
|
|
@@ -1402,6 +1441,7 @@ function useCalls(userId, opts = {}) {
|
|
|
1402
1441
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1403
1442
|
} finally {
|
|
1404
1443
|
setIncoming(null);
|
|
1444
|
+
setActiveCallId(null);
|
|
1405
1445
|
setPhase("idle");
|
|
1406
1446
|
}
|
|
1407
1447
|
},
|
|
@@ -1415,6 +1455,7 @@ function useCalls(userId, opts = {}) {
|
|
|
1415
1455
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1416
1456
|
} finally {
|
|
1417
1457
|
setOutgoing(null);
|
|
1458
|
+
setActiveCallId(null);
|
|
1418
1459
|
setPhase("idle");
|
|
1419
1460
|
}
|
|
1420
1461
|
}, [calls, outgoing]);
|
|
@@ -1432,7 +1473,7 @@ function useCalls(userId, opts = {}) {
|
|
|
1432
1473
|
}, [phase, ringTimeoutMs]);
|
|
1433
1474
|
const hangUp = useCallback(async () => {
|
|
1434
1475
|
const conversationId = activeConversationId ?? incoming?.conversationId ?? outgoing?.conversationId;
|
|
1435
|
-
const callId = incoming?.callId ?? outgoing?.callId;
|
|
1476
|
+
const callId = activeCallId ?? incoming?.callId ?? outgoing?.callId;
|
|
1436
1477
|
try {
|
|
1437
1478
|
if (!calls || !callId || !conversationId) return;
|
|
1438
1479
|
if (phase === "ringing-out" && outgoing) {
|
|
@@ -1449,13 +1490,15 @@ function useCalls(userId, opts = {}) {
|
|
|
1449
1490
|
setIncoming(null);
|
|
1450
1491
|
setOutgoing(null);
|
|
1451
1492
|
setActiveConversationId(null);
|
|
1493
|
+
setActiveCallId(null);
|
|
1452
1494
|
}
|
|
1453
|
-
}, [calls, phase, incoming, outgoing, activeConversationId]);
|
|
1495
|
+
}, [calls, phase, incoming, outgoing, activeConversationId, activeCallId]);
|
|
1454
1496
|
const reset = useCallback(() => {
|
|
1455
1497
|
setPhase("idle");
|
|
1456
1498
|
setIncoming(null);
|
|
1457
1499
|
setOutgoing(null);
|
|
1458
1500
|
setActiveConversationId(null);
|
|
1501
|
+
setActiveCallId(null);
|
|
1459
1502
|
setError(null);
|
|
1460
1503
|
}, []);
|
|
1461
1504
|
const media = incoming?.media ?? outgoing?.media ?? "audio";
|