@poolse/react-native 2.5.0 → 2.7.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,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ReactElement } from 'react';
3
- import { Conversation, Uuid, Message, AttachmentUploadInput, Membership, Attachment, WebRtcAdapter } from '@poolse/sdk';
3
+ import { Conversation, Uuid, Message, AttachmentUploadInput, Membership, Attachment, WebRtcAdapter, VoiceStatus } from '@poolse/sdk';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
  import { UseCalls } from '@poolse/react';
6
6
 
@@ -1045,6 +1045,15 @@ interface VoiceRoomBarProps {
1045
1045
  * want in Expo Go.
1046
1046
  */
1047
1047
  webrtc?: NativeWebRtcModule | null;
1048
+ /**
1049
+ * Called whenever the room's connection state changes.
1050
+ *
1051
+ * Audio routing is the app's job — speakerphone, the proximity sensor
1052
+ * that blanks the screen at your ear, and holding the audio session
1053
+ * open all need a native module this package deliberately doesn't
1054
+ * depend on. Use this to start and stop it.
1055
+ */
1056
+ onStatusChange?: (status: VoiceStatus) => void;
1048
1057
  /** Resolves a user id to a display label. Defaults to a short id. */
1049
1058
  labelFor?: (userId: string) => string;
1050
1059
  /**
@@ -1061,7 +1070,7 @@ interface VoiceRoomBarProps {
1061
1070
  * always-on voice channel. For ringing a specific person, use
1062
1071
  * `<IncomingCallSheet>` together with `useCalls`.
1063
1072
  */
1064
- declare function VoiceRoomBar({ conversationId, labelFor, unavailableSlot, webrtc, }: VoiceRoomBarProps): react_jsx_runtime.JSX.Element;
1073
+ declare function VoiceRoomBar({ conversationId, labelFor, unavailableSlot, webrtc, onStatusChange, }: VoiceRoomBarProps): react_jsx_runtime.JSX.Element;
1065
1074
 
1066
1075
  interface CallScreenProps {
1067
1076
  /** The signed-in user's id — the topic calls are delivered on. */
@@ -1080,6 +1089,22 @@ interface CallScreenProps {
1080
1089
  * that there is no audio. See {@link VoiceRoomBarProps.webrtc}.
1081
1090
  */
1082
1091
  webrtc?: NativeWebRtcModule | null;
1092
+ /**
1093
+ * Called whenever the call's audio connection state changes. Drive
1094
+ * speakerphone and the proximity sensor from here — see
1095
+ * {@link VoiceRoomBarProps.onStatusChange}.
1096
+ */
1097
+ onStatusChange?: (status: VoiceStatus) => void;
1098
+ /**
1099
+ * Hide this screen while a call is merely ringing in.
1100
+ *
1101
+ * Set it when `useVoipCalls` is wired: CallKit already shows the
1102
+ * system call UI on the lock screen and in the Dynamic Island, and a
1103
+ * second in-app sheet on top of it means one tap of Call produces two
1104
+ * competing rings. The screen still appears once the call connects, to
1105
+ * carry the in-call controls.
1106
+ */
1107
+ nativeIncomingUi?: boolean;
1083
1108
  }
1084
1109
  /**
1085
1110
  * WhatsApp-style call UI: a full-screen sheet that covers the app while
@@ -1096,58 +1121,58 @@ interface CallScreenProps {
1096
1121
  * to whatever button your UI wants. Pass the same instance in via
1097
1122
  * `calls` so both halves share one state machine.
1098
1123
  */
1099
- declare function CallScreen({ userId, labelFor, calls: external, webrtc }: CallScreenProps): react_jsx_runtime.JSX.Element;
1124
+ declare function CallScreen({ userId, labelFor, calls: external, webrtc, onStatusChange, nativeIncomingUi, }: CallScreenProps): react_jsx_runtime.JSX.Element;
1100
1125
 
1101
1126
  /**
1102
- * Bridges iOS PushKit + CallKit to poolse calls, so a call rings a
1103
- * backgrounded or closed app.
1127
+ * Bridges iOS PushKit + CallKit to poolse calls, and owns the incoming
1128
+ * call UI end to end.
1129
+ *
1130
+ * ## Why this coordinates rather than just forwards
1104
1131
  *
1105
- * ## Why this is needed at all
1132
+ * A call arrives by two routes: `call:incoming` over the WebSocket when
1133
+ * the app is foregrounded, and a VoIP push when it isn't. The server
1134
+ * fires both, always — deciding server-side whether a socket is really
1135
+ * alive is a race it would lose.
1106
1136
  *
1107
- * `call:incoming` travels over the WebSocket, which only exists while
1108
- * the app is foregrounded — iOS suspends sockets on background and
1109
- * drops them on close. Reaching a sleeping phone requires the OS to wake
1110
- * the app, which means a VoIP push against a PushKit token.
1137
+ * Reconciling them is therefore the client's job, and doing it badly is
1138
+ * very visible: forward each route to its own UI and one tap of Call
1139
+ * shows the callee two rings, an in-app sheet fighting a system call
1140
+ * screen.
1141
+ *
1142
+ * So this hook is the single place a call becomes visible. Both routes
1143
+ * feed one map keyed by `callId`, CallKit is the only ring UI, and the
1144
+ * native call is torn down whenever the call reaches a terminal state —
1145
+ * declined, cancelled, timed out, answered. Pass the same `calls` object
1146
+ * to `<CallScreen>` and it stays out of the way while a ring is up.
1111
1147
  *
1112
1148
  * ## The rule Apple enforces
1113
1149
  *
1114
1150
  * Every delivered VoIP push MUST report an incoming call to CallKit,
1115
1151
  * essentially immediately. Miss it and iOS terminates the app; do it
1116
- * repeatedly and the system stops delivering VoIP pushes to you at all.
1117
- * `reportCall` below is not optional politeness — it is the contract.
1152
+ * repeatedly and the system stops delivering VoIP pushes at all.
1118
1153
  *
1119
1154
  * ## Dependencies
1120
1155
  *
1121
1156
  * `react-native-voip-push-notification` and `react-native-callkeep`,
1122
- * both native, both requiring a development build. Like
1123
- * `react-native-webrtc`, Metro only bundles what it can see statically,
1124
- * so YOUR APP imports them and passes them in:
1125
- *
1126
- * import VoipPushNotification from 'react-native-voip-push-notification';
1127
- * import RNCallKeep from 'react-native-callkeep';
1157
+ * both native and both requiring a development build. Metro only bundles
1158
+ * what it can see statically, so YOUR APP imports them and passes them
1159
+ * in. The iOS AppDelegate also needs PushKit delegate methods, which
1160
+ * Expo does not generate — see the sample's `plugins/withVoipPush.js`.
1128
1161
  *
1129
1162
  * useVoipCalls({
1130
1163
  * voipPush: VoipPushNotification,
1131
1164
  * callKeep: RNCallKeep,
1132
- * onAnswer: (call) => { ... },
1165
+ * calls, // the same useCalls() the UI uses
1166
+ * apnsEnvironment: 'sandbox',
1133
1167
  * });
1134
1168
  */
1135
- /**
1136
- * The slice of `react-native-voip-push-notification` used here.
1137
- *
1138
- * Loose on purpose. The real module types `addEventListener` as a
1139
- * generic whose handler payload varies per event, and pinning exact
1140
- * signatures here makes `voipPush={VoipPushNotification}` fail to
1141
- * compile — the prop becomes unusable, which is the whole point of it.
1142
- * Method syntax (not property arrow syntax) keeps the check bivariant.
1143
- */
1169
+ /** The slice of `react-native-voip-push-notification` used here. */
1144
1170
  interface VoipPushModule {
1145
1171
  addEventListener(event: string, handler: (payload: unknown) => void): void;
1146
1172
  removeEventListener(event: string): void;
1147
1173
  registerVoipToken(): void;
1148
1174
  }
1149
- /** The slice of `react-native-callkeep` used here. Loose for the same
1150
- * reason — `setup` resolves to a boolean in the real module. */
1175
+ /** The slice of `react-native-callkeep` used here. */
1151
1176
  interface CallKeepModule {
1152
1177
  setup(options: unknown): Promise<unknown>;
1153
1178
  displayIncomingCall(uuid: string, handle: string, localizedCallerName?: string, handleType?: string, hasVideo?: boolean): unknown;
@@ -1166,22 +1191,48 @@ interface UseVoipCallsOptions {
1166
1191
  voipPush: VoipPushModule;
1167
1192
  callKeep: CallKeepModule;
1168
1193
  /**
1169
- * Fired when the user answers from the native call UI. Accept the
1170
- * call and join the room here — the app may have been launched cold,
1171
- * so don't assume any prior state.
1194
+ * The `useCalls(...)` instance driving this screen. Supplying it lets
1195
+ * the hook answer, decline and dismiss through the same state machine
1196
+ * the UI uses, rather than two halves disagreeing about what is
1197
+ * happening.
1172
1198
  */
1199
+ calls?: UseCalls;
1200
+ /** Called when the user answers from the native call UI. */
1173
1201
  onAnswer?: (call: VoipIncomingCall) => void;
1174
- /** Fired when the user declines from the native call UI. */
1202
+ /** Called when the user declines from the native call UI. */
1175
1203
  onDecline?: (call: VoipIncomingCall) => void;
1176
1204
  /** CallKit display name for your app. Shown on the lock screen. */
1177
1205
  appName?: string;
1206
+ /**
1207
+ * Which APNs environment issued this build's PushKit token — it must
1208
+ * match the app's `aps-environment` entitlement.
1209
+ *
1210
+ * Pass it explicitly. The fallback guesses from `__DEV__`, which
1211
+ * describes the JS bundle and NOT the entitlement: an ad-hoc or
1212
+ * TestFlight build has `__DEV__ === false` while still carrying the
1213
+ * `development` entitlement, so it receives a sandbox token and
1214
+ * reports it as production. The server then pushes to the wrong
1215
+ * environment, Apple answers BadDeviceToken, and the phone never
1216
+ * rings — with nothing in the app to suggest why.
1217
+ */
1218
+ apnsEnvironment?: 'production' | 'sandbox';
1219
+ /**
1220
+ * How old a pushed call may be before it is ignored, in ms. Defaults
1221
+ * to 45s, matching `useCalls`' ring timeout — past that the caller has
1222
+ * already given up, so there is nothing live to answer.
1223
+ *
1224
+ * PushKit holds a push that lands before the JS bundle is up and
1225
+ * replays it on launch, so opening the app can ring you for a call the
1226
+ * caller hung up on minutes ago — a ring with nothing live to accept.
1227
+ */
1228
+ staleAfterMs?: number;
1178
1229
  }
1179
1230
  /**
1180
- * Registers this device for VoIP pushes and wires the native call UI.
1231
+ * Registers this device for VoIP pushes and drives the native call UI.
1181
1232
  *
1182
- * Safe to mount once at the app root. Does nothing on Android, which
1183
- * needs a separate FCM + ConnectionService integration.
1233
+ * Mount once, as high in the signed-in tree as possible. Does nothing on
1234
+ * Android, which needs a separate FCM + ConnectionService integration.
1184
1235
  */
1185
- declare function useVoipCalls({ voipPush, callKeep, onAnswer, onDecline, appName, }: UseVoipCallsOptions): void;
1236
+ declare function useVoipCalls({ voipPush, callKeep, calls, onAnswer, onDecline, appName, apnsEnvironment, staleAfterMs, }: UseVoipCallsOptions): void;
1186
1237
 
1187
1238
  export { AttachmentPicker, type AttachmentPickerProps, AttachmentPreview, type AttachmentPreviewProps, Avatar, type AvatarProps, type CallKeepModule, CallScreen, type CallScreenProps, ChatHeader, type ChatHeaderProps, ConversationList, type ConversationListProps, ConversationView, type ConversationViewProps, DaySeparator, type DaySeparatorProps, GroupDetailsSheet, type GroupDetailsSheetProps, type IconName, ImageMosaic, type ImageMosaicProps, type InboxUser, MemberList, type MemberListProps, MentionInput, type MentionInputProps, MessageActions, type MessageActionsProps, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerProps, MessageList, type MessageListItem, type MessageListProps, MessageRow, type MessageRowProps, type NativeWebRtcModule, PoolseIcon, type PoolseIconProps, PoolseInbox, type PoolseInboxHandle, type PoolseInboxProps, PoolseLogo, type PoolseLogoProps, type PoolseLogoVariant, type PoolseRNTheme, type PoolseRNThemeOverrides, PoolseTheme, type PoolseThemeProps, primitives as Primitives, ReactionPicker, type ReactionPickerProps, ReactionStrip, type ReactionStripProps, type ThemeColors, type ThemeRadii, type ThemeShadows, type ThemeSpacing, type ThemeType, ThreadView, type ThreadViewProps, TypingIndicator, type TypingIndicatorProps, UploadQueueStrip, type UseVoipCallsOptions, UserName, type UserNameProps, VoiceRoomBar, type VoiceRoomBarProps, type VoipIncomingCall, type VoipPushModule, createNativeWebRtcAdapter, defaultDarkTheme, defaultLightTheme, isNativeWebRtcAvailable, useAppStateLifecycle, useDisplayName, usePoolseTheme, useVoipCalls };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ReactElement } from 'react';
3
- import { Conversation, Uuid, Message, AttachmentUploadInput, Membership, Attachment, WebRtcAdapter } from '@poolse/sdk';
3
+ import { Conversation, Uuid, Message, AttachmentUploadInput, Membership, Attachment, WebRtcAdapter, VoiceStatus } from '@poolse/sdk';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
  import { UseCalls } from '@poolse/react';
6
6
 
@@ -1045,6 +1045,15 @@ interface VoiceRoomBarProps {
1045
1045
  * want in Expo Go.
1046
1046
  */
1047
1047
  webrtc?: NativeWebRtcModule | null;
1048
+ /**
1049
+ * Called whenever the room's connection state changes.
1050
+ *
1051
+ * Audio routing is the app's job — speakerphone, the proximity sensor
1052
+ * that blanks the screen at your ear, and holding the audio session
1053
+ * open all need a native module this package deliberately doesn't
1054
+ * depend on. Use this to start and stop it.
1055
+ */
1056
+ onStatusChange?: (status: VoiceStatus) => void;
1048
1057
  /** Resolves a user id to a display label. Defaults to a short id. */
1049
1058
  labelFor?: (userId: string) => string;
1050
1059
  /**
@@ -1061,7 +1070,7 @@ interface VoiceRoomBarProps {
1061
1070
  * always-on voice channel. For ringing a specific person, use
1062
1071
  * `<IncomingCallSheet>` together with `useCalls`.
1063
1072
  */
1064
- declare function VoiceRoomBar({ conversationId, labelFor, unavailableSlot, webrtc, }: VoiceRoomBarProps): react_jsx_runtime.JSX.Element;
1073
+ declare function VoiceRoomBar({ conversationId, labelFor, unavailableSlot, webrtc, onStatusChange, }: VoiceRoomBarProps): react_jsx_runtime.JSX.Element;
1065
1074
 
1066
1075
  interface CallScreenProps {
1067
1076
  /** The signed-in user's id — the topic calls are delivered on. */
@@ -1080,6 +1089,22 @@ interface CallScreenProps {
1080
1089
  * that there is no audio. See {@link VoiceRoomBarProps.webrtc}.
1081
1090
  */
1082
1091
  webrtc?: NativeWebRtcModule | null;
1092
+ /**
1093
+ * Called whenever the call's audio connection state changes. Drive
1094
+ * speakerphone and the proximity sensor from here — see
1095
+ * {@link VoiceRoomBarProps.onStatusChange}.
1096
+ */
1097
+ onStatusChange?: (status: VoiceStatus) => void;
1098
+ /**
1099
+ * Hide this screen while a call is merely ringing in.
1100
+ *
1101
+ * Set it when `useVoipCalls` is wired: CallKit already shows the
1102
+ * system call UI on the lock screen and in the Dynamic Island, and a
1103
+ * second in-app sheet on top of it means one tap of Call produces two
1104
+ * competing rings. The screen still appears once the call connects, to
1105
+ * carry the in-call controls.
1106
+ */
1107
+ nativeIncomingUi?: boolean;
1083
1108
  }
1084
1109
  /**
1085
1110
  * WhatsApp-style call UI: a full-screen sheet that covers the app while
@@ -1096,58 +1121,58 @@ interface CallScreenProps {
1096
1121
  * to whatever button your UI wants. Pass the same instance in via
1097
1122
  * `calls` so both halves share one state machine.
1098
1123
  */
1099
- declare function CallScreen({ userId, labelFor, calls: external, webrtc }: CallScreenProps): react_jsx_runtime.JSX.Element;
1124
+ declare function CallScreen({ userId, labelFor, calls: external, webrtc, onStatusChange, nativeIncomingUi, }: CallScreenProps): react_jsx_runtime.JSX.Element;
1100
1125
 
1101
1126
  /**
1102
- * Bridges iOS PushKit + CallKit to poolse calls, so a call rings a
1103
- * backgrounded or closed app.
1127
+ * Bridges iOS PushKit + CallKit to poolse calls, and owns the incoming
1128
+ * call UI end to end.
1129
+ *
1130
+ * ## Why this coordinates rather than just forwards
1104
1131
  *
1105
- * ## Why this is needed at all
1132
+ * A call arrives by two routes: `call:incoming` over the WebSocket when
1133
+ * the app is foregrounded, and a VoIP push when it isn't. The server
1134
+ * fires both, always — deciding server-side whether a socket is really
1135
+ * alive is a race it would lose.
1106
1136
  *
1107
- * `call:incoming` travels over the WebSocket, which only exists while
1108
- * the app is foregrounded — iOS suspends sockets on background and
1109
- * drops them on close. Reaching a sleeping phone requires the OS to wake
1110
- * the app, which means a VoIP push against a PushKit token.
1137
+ * Reconciling them is therefore the client's job, and doing it badly is
1138
+ * very visible: forward each route to its own UI and one tap of Call
1139
+ * shows the callee two rings, an in-app sheet fighting a system call
1140
+ * screen.
1141
+ *
1142
+ * So this hook is the single place a call becomes visible. Both routes
1143
+ * feed one map keyed by `callId`, CallKit is the only ring UI, and the
1144
+ * native call is torn down whenever the call reaches a terminal state —
1145
+ * declined, cancelled, timed out, answered. Pass the same `calls` object
1146
+ * to `<CallScreen>` and it stays out of the way while a ring is up.
1111
1147
  *
1112
1148
  * ## The rule Apple enforces
1113
1149
  *
1114
1150
  * Every delivered VoIP push MUST report an incoming call to CallKit,
1115
1151
  * essentially immediately. Miss it and iOS terminates the app; do it
1116
- * repeatedly and the system stops delivering VoIP pushes to you at all.
1117
- * `reportCall` below is not optional politeness — it is the contract.
1152
+ * repeatedly and the system stops delivering VoIP pushes at all.
1118
1153
  *
1119
1154
  * ## Dependencies
1120
1155
  *
1121
1156
  * `react-native-voip-push-notification` and `react-native-callkeep`,
1122
- * both native, both requiring a development build. Like
1123
- * `react-native-webrtc`, Metro only bundles what it can see statically,
1124
- * so YOUR APP imports them and passes them in:
1125
- *
1126
- * import VoipPushNotification from 'react-native-voip-push-notification';
1127
- * import RNCallKeep from 'react-native-callkeep';
1157
+ * both native and both requiring a development build. Metro only bundles
1158
+ * what it can see statically, so YOUR APP imports them and passes them
1159
+ * in. The iOS AppDelegate also needs PushKit delegate methods, which
1160
+ * Expo does not generate — see the sample's `plugins/withVoipPush.js`.
1128
1161
  *
1129
1162
  * useVoipCalls({
1130
1163
  * voipPush: VoipPushNotification,
1131
1164
  * callKeep: RNCallKeep,
1132
- * onAnswer: (call) => { ... },
1165
+ * calls, // the same useCalls() the UI uses
1166
+ * apnsEnvironment: 'sandbox',
1133
1167
  * });
1134
1168
  */
1135
- /**
1136
- * The slice of `react-native-voip-push-notification` used here.
1137
- *
1138
- * Loose on purpose. The real module types `addEventListener` as a
1139
- * generic whose handler payload varies per event, and pinning exact
1140
- * signatures here makes `voipPush={VoipPushNotification}` fail to
1141
- * compile — the prop becomes unusable, which is the whole point of it.
1142
- * Method syntax (not property arrow syntax) keeps the check bivariant.
1143
- */
1169
+ /** The slice of `react-native-voip-push-notification` used here. */
1144
1170
  interface VoipPushModule {
1145
1171
  addEventListener(event: string, handler: (payload: unknown) => void): void;
1146
1172
  removeEventListener(event: string): void;
1147
1173
  registerVoipToken(): void;
1148
1174
  }
1149
- /** The slice of `react-native-callkeep` used here. Loose for the same
1150
- * reason — `setup` resolves to a boolean in the real module. */
1175
+ /** The slice of `react-native-callkeep` used here. */
1151
1176
  interface CallKeepModule {
1152
1177
  setup(options: unknown): Promise<unknown>;
1153
1178
  displayIncomingCall(uuid: string, handle: string, localizedCallerName?: string, handleType?: string, hasVideo?: boolean): unknown;
@@ -1166,22 +1191,48 @@ interface UseVoipCallsOptions {
1166
1191
  voipPush: VoipPushModule;
1167
1192
  callKeep: CallKeepModule;
1168
1193
  /**
1169
- * Fired when the user answers from the native call UI. Accept the
1170
- * call and join the room here — the app may have been launched cold,
1171
- * so don't assume any prior state.
1194
+ * The `useCalls(...)` instance driving this screen. Supplying it lets
1195
+ * the hook answer, decline and dismiss through the same state machine
1196
+ * the UI uses, rather than two halves disagreeing about what is
1197
+ * happening.
1172
1198
  */
1199
+ calls?: UseCalls;
1200
+ /** Called when the user answers from the native call UI. */
1173
1201
  onAnswer?: (call: VoipIncomingCall) => void;
1174
- /** Fired when the user declines from the native call UI. */
1202
+ /** Called when the user declines from the native call UI. */
1175
1203
  onDecline?: (call: VoipIncomingCall) => void;
1176
1204
  /** CallKit display name for your app. Shown on the lock screen. */
1177
1205
  appName?: string;
1206
+ /**
1207
+ * Which APNs environment issued this build's PushKit token — it must
1208
+ * match the app's `aps-environment` entitlement.
1209
+ *
1210
+ * Pass it explicitly. The fallback guesses from `__DEV__`, which
1211
+ * describes the JS bundle and NOT the entitlement: an ad-hoc or
1212
+ * TestFlight build has `__DEV__ === false` while still carrying the
1213
+ * `development` entitlement, so it receives a sandbox token and
1214
+ * reports it as production. The server then pushes to the wrong
1215
+ * environment, Apple answers BadDeviceToken, and the phone never
1216
+ * rings — with nothing in the app to suggest why.
1217
+ */
1218
+ apnsEnvironment?: 'production' | 'sandbox';
1219
+ /**
1220
+ * How old a pushed call may be before it is ignored, in ms. Defaults
1221
+ * to 45s, matching `useCalls`' ring timeout — past that the caller has
1222
+ * already given up, so there is nothing live to answer.
1223
+ *
1224
+ * PushKit holds a push that lands before the JS bundle is up and
1225
+ * replays it on launch, so opening the app can ring you for a call the
1226
+ * caller hung up on minutes ago — a ring with nothing live to accept.
1227
+ */
1228
+ staleAfterMs?: number;
1178
1229
  }
1179
1230
  /**
1180
- * Registers this device for VoIP pushes and wires the native call UI.
1231
+ * Registers this device for VoIP pushes and drives the native call UI.
1181
1232
  *
1182
- * Safe to mount once at the app root. Does nothing on Android, which
1183
- * needs a separate FCM + ConnectionService integration.
1233
+ * Mount once, as high in the signed-in tree as possible. Does nothing on
1234
+ * Android, which needs a separate FCM + ConnectionService integration.
1184
1235
  */
1185
- declare function useVoipCalls({ voipPush, callKeep, onAnswer, onDecline, appName, }: UseVoipCallsOptions): void;
1236
+ declare function useVoipCalls({ voipPush, callKeep, calls, onAnswer, onDecline, appName, apnsEnvironment, staleAfterMs, }: UseVoipCallsOptions): void;
1186
1237
 
1187
1238
  export { AttachmentPicker, type AttachmentPickerProps, AttachmentPreview, type AttachmentPreviewProps, Avatar, type AvatarProps, type CallKeepModule, CallScreen, type CallScreenProps, ChatHeader, type ChatHeaderProps, ConversationList, type ConversationListProps, ConversationView, type ConversationViewProps, DaySeparator, type DaySeparatorProps, GroupDetailsSheet, type GroupDetailsSheetProps, type IconName, ImageMosaic, type ImageMosaicProps, type InboxUser, MemberList, type MemberListProps, MentionInput, type MentionInputProps, MessageActions, type MessageActionsProps, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerProps, MessageList, type MessageListItem, type MessageListProps, MessageRow, type MessageRowProps, type NativeWebRtcModule, PoolseIcon, type PoolseIconProps, PoolseInbox, type PoolseInboxHandle, type PoolseInboxProps, PoolseLogo, type PoolseLogoProps, type PoolseLogoVariant, type PoolseRNTheme, type PoolseRNThemeOverrides, PoolseTheme, type PoolseThemeProps, primitives as Primitives, ReactionPicker, type ReactionPickerProps, ReactionStrip, type ReactionStripProps, type ThemeColors, type ThemeRadii, type ThemeShadows, type ThemeSpacing, type ThemeType, ThreadView, type ThreadViewProps, TypingIndicator, type TypingIndicatorProps, UploadQueueStrip, type UseVoipCallsOptions, UserName, type UserNameProps, VoiceRoomBar, type VoiceRoomBarProps, type VoipIncomingCall, type VoipPushModule, createNativeWebRtcAdapter, defaultDarkTheme, defaultLightTheme, isNativeWebRtcAvailable, useAppStateLifecycle, useDisplayName, usePoolseTheme, useVoipCalls };
package/dist/index.js CHANGED
@@ -4945,7 +4945,8 @@ function VoiceRoomBar({
4945
4945
  conversationId,
4946
4946
  labelFor,
4947
4947
  unavailableSlot,
4948
- webrtc
4948
+ webrtc,
4949
+ onStatusChange
4949
4950
  }) {
4950
4951
  const theme = usePoolseTheme();
4951
4952
  const available = isNativeWebRtcAvailable(webrtc);
@@ -4957,6 +4958,9 @@ function VoiceRoomBar({
4957
4958
  available ? conversationId : null,
4958
4959
  opts
4959
4960
  );
4961
+ useEffect(() => {
4962
+ onStatusChange?.(status);
4963
+ }, [status, onStatusChange]);
4960
4964
  const label = labelFor ?? ((id) => `User ${id.slice(0, 6)}`);
4961
4965
  const connected = status === "connected";
4962
4966
  if (!available) {
@@ -5079,7 +5083,14 @@ var styles23 = StyleSheet.create({
5079
5083
  hint: { fontSize: 13, flexShrink: 1 },
5080
5084
  error: { fontSize: 12, marginTop: 6 }
5081
5085
  });
5082
- function CallScreen({ userId, labelFor, calls: external, webrtc }) {
5086
+ function CallScreen({
5087
+ userId,
5088
+ labelFor,
5089
+ calls: external,
5090
+ webrtc,
5091
+ onStatusChange,
5092
+ nativeIncomingUi = false
5093
+ }) {
5083
5094
  const theme = usePoolseTheme();
5084
5095
  const own = useCalls(external ? null : userId);
5085
5096
  const calls = external ?? own;
@@ -5095,8 +5106,12 @@ function CallScreen({ userId, labelFor, calls: external, webrtc }) {
5095
5106
  useEffect(() => {
5096
5107
  if (calls.phase === "active" && voice.status === "idle") void voice.join();
5097
5108
  }, [calls.phase, voice]);
5109
+ useEffect(() => {
5110
+ onStatusChange?.(voice.status);
5111
+ }, [voice.status, onStatusChange]);
5098
5112
  const label = labelFor ?? ((id) => `User ${id.slice(0, 6)}`);
5099
- const visible = calls.phase !== "idle";
5113
+ const ringingIn = calls.phase === "ringing-in";
5114
+ const visible = calls.phase !== "idle" && !(nativeIncomingUi && ringingIn);
5100
5115
  const hangUp = () => {
5101
5116
  voice.leave();
5102
5117
  calls.reset();
@@ -5197,28 +5212,50 @@ var styles24 = StyleSheet.create({
5197
5212
  function useVoipCalls({
5198
5213
  voipPush,
5199
5214
  callKeep,
5215
+ calls,
5200
5216
  onAnswer,
5201
5217
  onDecline,
5202
- appName = "poolse"
5218
+ appName = "poolse",
5219
+ apnsEnvironment,
5220
+ staleAfterMs = 45e3
5203
5221
  }) {
5204
5222
  const poolse = usePoolse();
5205
- const pending = useRef(/* @__PURE__ */ new Map());
5206
- const handlers = useRef({ onAnswer, onDecline });
5207
- handlers.current = { onAnswer, onDecline };
5223
+ const shown = useRef(/* @__PURE__ */ new Map());
5224
+ const seen = useRef(/* @__PURE__ */ new Set());
5225
+ const handlers = useRef({ onAnswer, onDecline, calls });
5226
+ handlers.current = { onAnswer, onDecline, calls };
5227
+ const staleRef = useRef(staleAfterMs);
5228
+ staleRef.current = staleAfterMs;
5229
+ const dismiss = useRef((callId) => {
5230
+ if (!shown.current.has(callId)) return;
5231
+ shown.current.delete(callId);
5232
+ try {
5233
+ callKeep.endCall(callId);
5234
+ } catch {
5235
+ }
5236
+ });
5237
+ const present = useRef((call, sentAt) => {
5238
+ if (seen.current.has(call.callId)) return;
5239
+ if (typeof sentAt === "number" && Date.now() - sentAt > staleRef.current) return;
5240
+ seen.current.add(call.callId);
5241
+ shown.current.set(call.callId, call);
5242
+ callKeep.displayIncomingCall(call.callId, call.callerUserId, call.callerName, "generic", false);
5243
+ });
5208
5244
  useEffect(() => {
5209
5245
  let cancelled = false;
5210
- const reportCall = (payload) => {
5246
+ const fromPush = (payload) => {
5211
5247
  const callId = payload.call_id;
5212
5248
  const conversationId = payload.conversation_id;
5213
5249
  if (!callId || !conversationId) return;
5214
- const call = {
5215
- callId,
5216
- conversationId,
5217
- callerUserId: payload.caller_user_id ?? "",
5218
- callerName: payload.caller_name ?? "Incoming call"
5219
- };
5220
- pending.current.set(callId, call);
5221
- callKeep.displayIncomingCall(callId, call.callerUserId, call.callerName, "generic", false);
5250
+ present.current(
5251
+ {
5252
+ callId,
5253
+ conversationId,
5254
+ callerUserId: payload.caller_user_id ?? "",
5255
+ callerName: payload.caller_name ?? "Incoming call"
5256
+ },
5257
+ payload.sent_at
5258
+ );
5222
5259
  };
5223
5260
  void callKeep.setup({
5224
5261
  ios: { appName },
@@ -5233,27 +5270,44 @@ function useVoipCalls({
5233
5270
  });
5234
5271
  voipPush.addEventListener("register", ((token) => {
5235
5272
  if (cancelled || !token) return;
5236
- void poolse.devices.register({ token, platform: "ios", environment: apnsEnvironment() }).catch(() => {
5273
+ void poolse.devices.register({
5274
+ token,
5275
+ platform: "ios",
5276
+ environment: apnsEnvironment ?? guessApnsEnvironment()
5277
+ }).catch(() => {
5237
5278
  });
5238
5279
  }));
5239
5280
  voipPush.addEventListener("notification", ((payload) => {
5240
- reportCall(payload);
5281
+ fromPush(payload);
5241
5282
  }));
5242
5283
  voipPush.addEventListener("didLoadWithEvents", ((events) => {
5243
5284
  for (const event of events ?? []) {
5244
5285
  if (event.name === "RNVoipPushRemoteNotificationReceivedEvent") {
5245
- reportCall(event.data);
5286
+ fromPush(event.data);
5246
5287
  }
5247
5288
  }
5248
5289
  }));
5249
5290
  callKeep.addEventListener("answerCall", (({ callUUID }) => {
5250
- const call = pending.current.get(callUUID);
5251
- if (call) handlers.current.onAnswer?.(call);
5291
+ const call = shown.current.get(callUUID);
5292
+ if (!call) return;
5293
+ shown.current.delete(callUUID);
5294
+ void handlers.current.calls?.accept({
5295
+ callId: call.callId,
5296
+ conversationId: call.conversationId,
5297
+ callerUserId: call.callerUserId
5298
+ });
5299
+ handlers.current.onAnswer?.(call);
5252
5300
  }));
5253
5301
  callKeep.addEventListener("endCall", (({ callUUID }) => {
5254
- const call = pending.current.get(callUUID);
5255
- pending.current.delete(callUUID);
5256
- if (call) handlers.current.onDecline?.(call);
5302
+ const call = shown.current.get(callUUID);
5303
+ shown.current.delete(callUUID);
5304
+ if (!call) return;
5305
+ void handlers.current.calls?.decline({
5306
+ callId: call.callId,
5307
+ conversationId: call.conversationId,
5308
+ callerUserId: call.callerUserId
5309
+ });
5310
+ handlers.current.onDecline?.(call);
5257
5311
  }));
5258
5312
  voipPush.registerVoipToken();
5259
5313
  return () => {
@@ -5264,9 +5318,25 @@ function useVoipCalls({
5264
5318
  callKeep.removeEventListener("answerCall");
5265
5319
  callKeep.removeEventListener("endCall");
5266
5320
  };
5267
- }, [poolse, voipPush, callKeep, appName]);
5321
+ }, [poolse, voipPush, callKeep, appName, apnsEnvironment]);
5322
+ const incoming = calls?.incoming ?? null;
5323
+ const phase = calls?.phase;
5324
+ useEffect(() => {
5325
+ if (phase === "ringing-in" && incoming) {
5326
+ present.current({
5327
+ callId: incoming.callId,
5328
+ conversationId: incoming.conversationId,
5329
+ callerUserId: incoming.callerUserId,
5330
+ callerName: incoming.callerUserId
5331
+ });
5332
+ }
5333
+ }, [phase, incoming]);
5334
+ useEffect(() => {
5335
+ if (phase === "ringing-in" || phase === "ringing-out") return;
5336
+ for (const callId of [...shown.current.keys()]) dismiss.current(callId);
5337
+ }, [phase]);
5268
5338
  }
5269
- function apnsEnvironment() {
5339
+ function guessApnsEnvironment() {
5270
5340
  return __DEV__ ? "sandbox" : "production";
5271
5341
  }
5272
5342