@poolse/react 2.12.1 → 2.13.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.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { PoolseConfig, Poolse, Me, Uuid, Conversation, ConversationCreateRequest, Membership, MemberRole, Message, MessageCreateRequest, RealtimeStatus, Attachment, AttachmentUploadInput, PoolseUserProfile, CallRoomStatus, CallParticipant, LiveKitModule, VoiceStatus, VoiceParticipant, VoiceRoomOptions, IncomingCall, OutgoingCall, CallMedia } from '@poolse/sdk';
2
+ import { PoolseConfig, Poolse, Me, Uuid, Conversation, ConversationCreateRequest, Membership, MemberRole, Message, MessageCreateRequest, RealtimeStatus, Attachment, AttachmentUploadInput, PoolseUserProfile, CallRoomStatus, CallParticipant, CameraFacing, LiveKitModule, VoiceStatus, VoiceParticipant, VoiceRoomOptions, IncomingCall, OutgoingCall, CallMedia } from '@poolse/sdk';
3
3
  export { ApiError, Attachment, AttachmentStatus, AttachmentUploadInput, AttachmentUploadRequest, AttachmentUploadResponse, AuthError, Conversation, ConversationCreateRequest, Me, MemberRole, Membership, Message, MessageCreateRequest, MessageType, NetworkError, PoolseConfig, PoolseError, PoolseUserProfile, RateLimitedError, RealtimeStatus, Uuid } from '@poolse/sdk';
4
4
  import { ReactNode } from 'react';
5
5
 
@@ -501,6 +501,14 @@ interface UseCallRoomOptions {
501
501
  video?: boolean;
502
502
  /** Join muted. */
503
503
  muted?: boolean;
504
+ /** Which camera to open with. Defaults to the front one. */
505
+ facing?: CameraFacing;
506
+ /**
507
+ * How long the last participant may sit alone before {@link
508
+ * UseCallRoom.alone} turns true, in milliseconds. Defaults to 1500.
509
+ * Pass `0` to never report it.
510
+ */
511
+ aloneGraceMs?: number;
504
512
  }
505
513
  interface UseCallRoom {
506
514
  status: CallRoomStatus;
@@ -508,6 +516,22 @@ interface UseCallRoom {
508
516
  participants: CallParticipant[];
509
517
  cameraEnabled: boolean;
510
518
  micEnabled: boolean;
519
+ /** Which camera is publishing. Meaningless while the camera is off. */
520
+ facingMode: CameraFacing;
521
+ /**
522
+ * Everyone else has gone and is not coming back.
523
+ *
524
+ * Turns true only after somebody else has actually been on the call,
525
+ * so the caller waiting for an answer never trips it. Wire it to your
526
+ * hang-up path — a peer whose app was killed or whose phone lost
527
+ * signal sends no `call:hangup`, and without this the survivor is
528
+ * left on a call screen that will never close on its own.
529
+ *
530
+ * useEffect(() => {
531
+ * if (media.alone) void calls.hangUp();
532
+ * }, [media.alone]);
533
+ */
534
+ alone: boolean;
511
535
  error: Error | null;
512
536
  /**
513
537
  * Whether this deployment has an SFU.
@@ -522,6 +546,8 @@ interface UseCallRoom {
522
546
  leave: () => void;
523
547
  setCameraEnabled: (enabled: boolean) => Promise<void>;
524
548
  toggleCamera: () => Promise<void>;
549
+ /** Swap between the front and rear cameras. */
550
+ switchCamera: () => Promise<void>;
525
551
  setMicrophoneEnabled: (enabled: boolean) => Promise<void>;
526
552
  toggleMic: () => Promise<void>;
527
553
  }
@@ -547,6 +573,16 @@ interface UseVoiceRoom {
547
573
  /** Everyone currently in the room, oldest join first. */
548
574
  participants: VoiceParticipant[];
549
575
  muted: boolean;
576
+ /**
577
+ * Everyone else has left and is not coming back.
578
+ *
579
+ * The mesh equivalent of `useCallRoom`'s flag of the same name — see
580
+ * there for why a call needs this on top of the explicit hang-up
581
+ * signal. Only meaningful for a room being used to carry a call; a
582
+ * Discord-style room you sit in alone is a normal state, so wire this
583
+ * to hang-up only on the call surface.
584
+ */
585
+ alone: boolean;
550
586
  error: Error | null;
551
587
  /** Acquire the mic and connect. Safe to call twice. */
552
588
  join: () => Promise<void>;
@@ -566,7 +602,10 @@ interface UseVoiceRoom {
566
602
  *
567
603
  * For ringing someone who hasn't joined yet, see `useCalls`.
568
604
  */
569
- declare function useVoiceRoom(conversationId: string | null, opts?: VoiceRoomOptions): UseVoiceRoom;
605
+ declare function useVoiceRoom(conversationId: string | null, opts?: VoiceRoomOptions & {
606
+ /** Grace before {@link UseVoiceRoom.alone}. Defaults to 1500; `0` disables. */
607
+ aloneGraceMs?: number;
608
+ }): UseVoiceRoom;
570
609
 
571
610
  /** Where a call is in its lifecycle, from this device's point of view. */
572
611
  type CallPhase = 'idle'
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { PoolseConfig, Poolse, Me, Uuid, Conversation, ConversationCreateRequest, Membership, MemberRole, Message, MessageCreateRequest, RealtimeStatus, Attachment, AttachmentUploadInput, PoolseUserProfile, CallRoomStatus, CallParticipant, LiveKitModule, VoiceStatus, VoiceParticipant, VoiceRoomOptions, IncomingCall, OutgoingCall, CallMedia } from '@poolse/sdk';
2
+ import { PoolseConfig, Poolse, Me, Uuid, Conversation, ConversationCreateRequest, Membership, MemberRole, Message, MessageCreateRequest, RealtimeStatus, Attachment, AttachmentUploadInput, PoolseUserProfile, CallRoomStatus, CallParticipant, CameraFacing, LiveKitModule, VoiceStatus, VoiceParticipant, VoiceRoomOptions, IncomingCall, OutgoingCall, CallMedia } from '@poolse/sdk';
3
3
  export { ApiError, Attachment, AttachmentStatus, AttachmentUploadInput, AttachmentUploadRequest, AttachmentUploadResponse, AuthError, Conversation, ConversationCreateRequest, Me, MemberRole, Membership, Message, MessageCreateRequest, MessageType, NetworkError, PoolseConfig, PoolseError, PoolseUserProfile, RateLimitedError, RealtimeStatus, Uuid } from '@poolse/sdk';
4
4
  import { ReactNode } from 'react';
5
5
 
@@ -501,6 +501,14 @@ interface UseCallRoomOptions {
501
501
  video?: boolean;
502
502
  /** Join muted. */
503
503
  muted?: boolean;
504
+ /** Which camera to open with. Defaults to the front one. */
505
+ facing?: CameraFacing;
506
+ /**
507
+ * How long the last participant may sit alone before {@link
508
+ * UseCallRoom.alone} turns true, in milliseconds. Defaults to 1500.
509
+ * Pass `0` to never report it.
510
+ */
511
+ aloneGraceMs?: number;
504
512
  }
505
513
  interface UseCallRoom {
506
514
  status: CallRoomStatus;
@@ -508,6 +516,22 @@ interface UseCallRoom {
508
516
  participants: CallParticipant[];
509
517
  cameraEnabled: boolean;
510
518
  micEnabled: boolean;
519
+ /** Which camera is publishing. Meaningless while the camera is off. */
520
+ facingMode: CameraFacing;
521
+ /**
522
+ * Everyone else has gone and is not coming back.
523
+ *
524
+ * Turns true only after somebody else has actually been on the call,
525
+ * so the caller waiting for an answer never trips it. Wire it to your
526
+ * hang-up path — a peer whose app was killed or whose phone lost
527
+ * signal sends no `call:hangup`, and without this the survivor is
528
+ * left on a call screen that will never close on its own.
529
+ *
530
+ * useEffect(() => {
531
+ * if (media.alone) void calls.hangUp();
532
+ * }, [media.alone]);
533
+ */
534
+ alone: boolean;
511
535
  error: Error | null;
512
536
  /**
513
537
  * Whether this deployment has an SFU.
@@ -522,6 +546,8 @@ interface UseCallRoom {
522
546
  leave: () => void;
523
547
  setCameraEnabled: (enabled: boolean) => Promise<void>;
524
548
  toggleCamera: () => Promise<void>;
549
+ /** Swap between the front and rear cameras. */
550
+ switchCamera: () => Promise<void>;
525
551
  setMicrophoneEnabled: (enabled: boolean) => Promise<void>;
526
552
  toggleMic: () => Promise<void>;
527
553
  }
@@ -547,6 +573,16 @@ interface UseVoiceRoom {
547
573
  /** Everyone currently in the room, oldest join first. */
548
574
  participants: VoiceParticipant[];
549
575
  muted: boolean;
576
+ /**
577
+ * Everyone else has left and is not coming back.
578
+ *
579
+ * The mesh equivalent of `useCallRoom`'s flag of the same name — see
580
+ * there for why a call needs this on top of the explicit hang-up
581
+ * signal. Only meaningful for a room being used to carry a call; a
582
+ * Discord-style room you sit in alone is a normal state, so wire this
583
+ * to hang-up only on the call surface.
584
+ */
585
+ alone: boolean;
550
586
  error: Error | null;
551
587
  /** Acquire the mic and connect. Safe to call twice. */
552
588
  join: () => Promise<void>;
@@ -566,7 +602,10 @@ interface UseVoiceRoom {
566
602
  *
567
603
  * For ringing someone who hasn't joined yet, see `useCalls`.
568
604
  */
569
- declare function useVoiceRoom(conversationId: string | null, opts?: VoiceRoomOptions): UseVoiceRoom;
605
+ declare function useVoiceRoom(conversationId: string | null, opts?: VoiceRoomOptions & {
606
+ /** Grace before {@link UseVoiceRoom.alone}. Defaults to 1500; `0` disables. */
607
+ aloneGraceMs?: number;
608
+ }): UseVoiceRoom;
570
609
 
571
610
  /** Where a call is in its lifecycle, from this device's point of view. */
572
611
  type CallPhase = 'idle'
package/dist/index.js CHANGED
@@ -1068,22 +1068,28 @@ function useCallRoom(conversationId, opts) {
1068
1068
  const [participants, setParticipants] = useState([]);
1069
1069
  const [cameraEnabled, setCamera] = useState(false);
1070
1070
  const [micEnabled, setMic] = useState(false);
1071
+ const [facingMode, setFacing] = useState(opts.facing ?? "user");
1072
+ const [alone, setAlone] = useState(false);
1071
1073
  const [error, setError] = useState(null);
1072
1074
  const [sfuAvailable, setSfuAvailable] = useState(null);
1073
1075
  const optsRef = useRef(opts);
1076
+ optsRef.current = opts;
1074
1077
  const joined = useRef(false);
1075
1078
  const room = useMemo(() => {
1076
1079
  if (!conversationId) return null;
1077
1080
  return poolse.call(conversationId, {
1078
1081
  livekit: optsRef.current.livekit,
1079
1082
  ...optsRef.current.video !== void 0 ? { video: optsRef.current.video } : {},
1080
- ...optsRef.current.muted !== void 0 ? { muted: optsRef.current.muted } : {}
1083
+ ...optsRef.current.muted !== void 0 ? { muted: optsRef.current.muted } : {},
1084
+ ...optsRef.current.facing !== void 0 ? { facing: optsRef.current.facing } : {},
1085
+ ...optsRef.current.aloneGraceMs !== void 0 ? { aloneGraceMs: optsRef.current.aloneGraceMs } : {}
1081
1086
  });
1082
1087
  }, [poolse, conversationId]);
1083
1088
  const sync = useCallback((target) => {
1084
1089
  if (!target) return;
1085
1090
  setCamera(target.isCameraEnabled());
1086
1091
  setMic(target.isMicrophoneEnabled());
1092
+ setFacing(target.getFacingMode());
1087
1093
  }, []);
1088
1094
  useEffect(() => {
1089
1095
  if (!room) {
@@ -1093,15 +1099,18 @@ function useCallRoom(conversationId, opts) {
1093
1099
  }
1094
1100
  setStatus(room.getStatus());
1095
1101
  setParticipants(room.getParticipants());
1102
+ setAlone(false);
1096
1103
  const offStatus = room.onStatus(setStatus);
1097
1104
  const offRoster = room.onParticipants((next) => {
1098
1105
  setParticipants(next);
1099
1106
  sync(room);
1100
1107
  });
1108
+ const offAlone = room.onAlone(() => setAlone(true));
1101
1109
  const offError = room.onError(setError);
1102
1110
  return () => {
1103
1111
  offStatus();
1104
1112
  offRoster();
1113
+ offAlone();
1105
1114
  offError();
1106
1115
  if (joined.current) {
1107
1116
  joined.current = false;
@@ -1153,21 +1162,50 @@ function useCallRoom(conversationId, opts) {
1153
1162
  if (!room) return;
1154
1163
  await setMicrophoneEnabled(!room.isMicrophoneEnabled());
1155
1164
  }, [room, setMicrophoneEnabled]);
1165
+ const switchCamera = useCallback(async () => {
1166
+ if (!room) return;
1167
+ await room.switchCamera();
1168
+ setFacing(room.getFacingMode());
1169
+ }, [room]);
1156
1170
  return {
1157
1171
  status,
1158
1172
  participants,
1159
1173
  cameraEnabled,
1160
1174
  micEnabled,
1175
+ facingMode,
1176
+ alone,
1161
1177
  error,
1162
1178
  sfuAvailable,
1163
1179
  join,
1164
1180
  leave,
1165
1181
  setCameraEnabled,
1166
1182
  toggleCamera,
1183
+ switchCamera,
1167
1184
  setMicrophoneEnabled,
1168
1185
  toggleMic
1169
1186
  };
1170
1187
  }
1188
+ function useAloneWatch({ active, others, graceMs }) {
1189
+ const [alone, setAlone] = useState(false);
1190
+ const seenPeer = useRef(false);
1191
+ useEffect(() => {
1192
+ if (!active) {
1193
+ seenPeer.current = false;
1194
+ setAlone(false);
1195
+ return;
1196
+ }
1197
+ if (others > 0) {
1198
+ seenPeer.current = true;
1199
+ return;
1200
+ }
1201
+ if (graceMs <= 0 || !seenPeer.current) return;
1202
+ const timer = setTimeout(() => setAlone(true), graceMs);
1203
+ return () => clearTimeout(timer);
1204
+ }, [active, others, graceMs]);
1205
+ return alone;
1206
+ }
1207
+
1208
+ // src/use-voice-room.ts
1171
1209
  function useVoiceRoom(conversationId, opts = {}) {
1172
1210
  const poolse = usePoolse();
1173
1211
  const [status, setStatus] = useState("idle");
@@ -1217,7 +1255,12 @@ function useVoiceRoom(conversationId, opts = {}) {
1217
1255
  if (!room) return;
1218
1256
  setMuted(room.toggleMute());
1219
1257
  }, [room]);
1220
- return { status, participants, muted, error, join, leave, toggleMute };
1258
+ const alone = useAloneWatch({
1259
+ active: status === "connected",
1260
+ others: Math.max(0, participants.length - 1),
1261
+ graceMs: optsRef.current.aloneGraceMs ?? 1500
1262
+ });
1263
+ return { status, participants, muted, alone, error, join, leave, toggleMute };
1221
1264
  }
1222
1265
  function useCalls(userId, opts = {}) {
1223
1266
  const ringTimeoutMs = opts.ringTimeoutMs ?? 45e3;