@poolse/react-native 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/dist/index.d.cts CHANGED
@@ -1310,32 +1310,6 @@ interface RingToneModule {
1310
1310
  startRingtone?: (...args: never[]) => void;
1311
1311
  stopRingtone?: (...args: never[]) => void;
1312
1312
  }
1313
- /**
1314
- * WhatsApp-style call UI: a full-screen sheet that covers the app while
1315
- * a call is ringing or connected, and renders nothing when idle.
1316
- *
1317
- * Mount it once, high in the tree, so a call can arrive on any screen:
1318
- *
1319
- * <PoolseProvider config={config}>
1320
- * <CallScreen
1321
- * userId={me.id}
1322
- * livekit={livekit}
1323
- * livekitReactNative={livekitRN}
1324
- * />
1325
- * <YourApp />
1326
- * </PoolseProvider>
1327
- *
1328
- * ## Two media paths
1329
- *
1330
- * With `livekit` supplied, media rides the SFU: one upload per
1331
- * participant regardless of call size, and the camera and microphone can
1332
- * be switched on and off at any point. Without it — or against a
1333
- * deployment that has no SFU configured — calls fall back to the
1334
- * peer-to-peer mesh, which is audio only.
1335
- *
1336
- * The fallback is automatic and silent by design. A deployment without
1337
- * an SFU is a supported configuration, not a broken one.
1338
- */
1339
1313
  declare function CallScreen({ userId, labelFor, calls: callsProp, webrtc, livekit, livekitReactNative, onStatusChange, onTransportChange, onVideoChange, inCallManager, nativeIncomingUi, }: CallScreenProps): react_jsx_runtime.JSX.Element;
1340
1314
 
1341
1315
  interface CallVideoGridProps {
package/dist/index.d.ts CHANGED
@@ -1310,32 +1310,6 @@ interface RingToneModule {
1310
1310
  startRingtone?: (...args: never[]) => void;
1311
1311
  stopRingtone?: (...args: never[]) => void;
1312
1312
  }
1313
- /**
1314
- * WhatsApp-style call UI: a full-screen sheet that covers the app while
1315
- * a call is ringing or connected, and renders nothing when idle.
1316
- *
1317
- * Mount it once, high in the tree, so a call can arrive on any screen:
1318
- *
1319
- * <PoolseProvider config={config}>
1320
- * <CallScreen
1321
- * userId={me.id}
1322
- * livekit={livekit}
1323
- * livekitReactNative={livekitRN}
1324
- * />
1325
- * <YourApp />
1326
- * </PoolseProvider>
1327
- *
1328
- * ## Two media paths
1329
- *
1330
- * With `livekit` supplied, media rides the SFU: one upload per
1331
- * participant regardless of call size, and the camera and microphone can
1332
- * be switched on and off at any point. Without it — or against a
1333
- * deployment that has no SFU configured — calls fall back to the
1334
- * peer-to-peer mesh, which is audio only.
1335
- *
1336
- * The fallback is automatic and silent by design. A deployment without
1337
- * an SFU is a supported configuration, not a broken one.
1338
- */
1339
1313
  declare function CallScreen({ userId, labelFor, calls: callsProp, webrtc, livekit, livekitReactNative, onStatusChange, onTransportChange, onVideoChange, inCallManager, nativeIncomingUi, }: CallScreenProps): react_jsx_runtime.JSX.Element;
1340
1314
 
1341
1315
  interface CallVideoGridProps {
package/dist/index.js CHANGED
@@ -5809,6 +5809,10 @@ function format(total) {
5809
5809
  return `${hours > 0 ? `${hours}:` : ""}${mm}:${String(seconds).padStart(2, "0")}`;
5810
5810
  }
5811
5811
  var CHROME_IDLE_MS = 4500;
5812
+ var ENDED_LINGER_MS = 2500;
5813
+ function isOver(phase) {
5814
+ return phase === "declined" || phase === "busy" || phase === "timed-out";
5815
+ }
5812
5816
  function CallScreen({
5813
5817
  userId,
5814
5818
  labelFor,
@@ -5924,6 +5928,11 @@ function CallScreen({
5924
5928
  endedOnEmpty.current = true;
5925
5929
  hangUp();
5926
5930
  }, [alone, calls.phase, hangUp]);
5931
+ useEffect(() => {
5932
+ if (!isOver(calls.phase)) return;
5933
+ const timer = setTimeout(calls.reset, ENDED_LINGER_MS);
5934
+ return () => clearTimeout(timer);
5935
+ }, [calls.phase, calls.reset]);
5927
5936
  const micOn = usingSfu ? sfu.micEnabled : !mesh.muted;
5928
5937
  const toggleMic = useCallback(() => {
5929
5938
  if (usingSfu) void sfu.toggleMic();
@@ -5937,7 +5946,7 @@ function CallScreen({
5937
5946
  const label = useMemo(() => labelFor ?? ((id) => `User ${id.slice(0, 6)}`), [labelFor]);
5938
5947
  const ringingIn = calls.phase === "ringing-in";
5939
5948
  const ringingOut = calls.phase === "ringing-out";
5940
- const ended = calls.phase === "declined" || calls.phase === "busy" || calls.phase === "timed-out";
5949
+ const ended = isOver(calls.phase);
5941
5950
  const visible = calls.phase !== "idle" && !(nativeIncomingUi && ringingIn);
5942
5951
  const duration = useCallDuration(connected);
5943
5952
  const participants = usingSfu ? sfu.participants : [];
@@ -6179,6 +6188,26 @@ function isStalePush(sentAt, now, staleAfterMs) {
6179
6188
  if (age < 0) return false;
6180
6189
  return age > staleAfterMs;
6181
6190
  }
6191
+ function readPush(payload) {
6192
+ const callId = payload.call_id;
6193
+ if (!callId) return { kind: "ignore" };
6194
+ if (payload.event === "ended") return { kind: "teardown", callId };
6195
+ const conversationId = payload.conversation_id;
6196
+ if (!conversationId) return { kind: "ignore" };
6197
+ return {
6198
+ kind: "ring",
6199
+ sentAt: payload.sent_at,
6200
+ call: {
6201
+ callId,
6202
+ conversationId,
6203
+ callerUserId: payload.caller_user_id ?? "",
6204
+ callerName: payload.caller_name ?? "Incoming call",
6205
+ // `has_video` is what older servers sent; `media` supersedes it.
6206
+ // Either saying video means the system UI offers video.
6207
+ media: payload.media === "video" || payload.has_video ? "video" : "audio"
6208
+ }
6209
+ };
6210
+ }
6182
6211
  function useVoipCalls({
6183
6212
  voipPush,
6184
6213
  callKeep,
@@ -6191,6 +6220,9 @@ function useVoipCalls({
6191
6220
  }) {
6192
6221
  const poolse = usePoolse();
6193
6222
  const shown = useRef(/* @__PURE__ */ new Map());
6223
+ const [voipToken, setVoipToken] = useState(null);
6224
+ const { me } = useMe();
6225
+ const registered = useRef(null);
6194
6226
  const handlers = useRef({ onAnswer, onDecline, calls });
6195
6227
  handlers.current = { onAnswer, onDecline, calls };
6196
6228
  const staleRef = useRef(staleAfterMs);
@@ -6230,24 +6262,37 @@ function useVoipCalls({
6230
6262
  void ready.current.then(done, done);
6231
6263
  if (isStalePush(sentAt, Date.now(), staleRef.current)) dismiss.current(call.callId);
6232
6264
  });
6265
+ useEffect(() => {
6266
+ const userId = me?.id;
6267
+ if (!voipToken || !userId) return;
6268
+ const pair = `${userId}:${voipToken}`;
6269
+ if (registered.current === pair) return;
6270
+ void poolse.devices.register({
6271
+ token: voipToken,
6272
+ platform: "ios",
6273
+ environment: apnsEnvironment ?? guessApnsEnvironment()
6274
+ }).then(() => {
6275
+ registered.current = pair;
6276
+ }).catch(() => {
6277
+ });
6278
+ }, [poolse, voipToken, me?.id, apnsEnvironment]);
6233
6279
  useEffect(() => {
6234
6280
  let cancelled = false;
6235
6281
  const fromPush = (payload) => {
6236
- const callId = payload.call_id;
6237
- const conversationId = payload.conversation_id;
6238
- if (!callId || !conversationId) return;
6239
- present.current(
6240
- {
6241
- callId,
6242
- conversationId,
6243
- callerUserId: payload.caller_user_id ?? "",
6244
- callerName: payload.caller_name ?? "Incoming call",
6245
- // `has_video` is what older servers sent; `media` supersedes
6246
- // it. Either says video, and the system UI offers video.
6247
- media: payload.media === "video" || payload.has_video ? "video" : "audio"
6248
- },
6249
- payload.sent_at
6250
- );
6282
+ const intent = readPush(payload);
6283
+ if (intent.kind === "ignore") return;
6284
+ if (intent.kind === "teardown") {
6285
+ dismiss.current(intent.callId);
6286
+ handlers.current.calls?.dropped(intent.callId);
6287
+ return;
6288
+ }
6289
+ present.current(intent.call, intent.sentAt);
6290
+ handlers.current.calls?.ring({
6291
+ callId: intent.call.callId,
6292
+ conversationId: intent.call.conversationId,
6293
+ callerUserId: intent.call.callerUserId,
6294
+ media: intent.call.media
6295
+ });
6251
6296
  };
6252
6297
  ready.current = callKeep.setup({
6253
6298
  ios: { appName },
@@ -6262,12 +6307,7 @@ function useVoipCalls({
6262
6307
  });
6263
6308
  voipPush.addEventListener("register", ((token) => {
6264
6309
  if (cancelled || !token) return;
6265
- void poolse.devices.register({
6266
- token,
6267
- platform: "ios",
6268
- environment: apnsEnvironment ?? guessApnsEnvironment()
6269
- }).catch(() => {
6270
- });
6310
+ setVoipToken(token);
6271
6311
  }));
6272
6312
  voipPush.addEventListener("notification", ((payload) => {
6273
6313
  fromPush(payload);