Add VoIP calling functionality to Fishjam - #575
Merged
Conversation
…IOS (#557) ## Description Adds a complete iOS VoIP calling example (examples/mobile-client/voip-call/) showing how to wire native PushKit VoIP pushes and CallKit into a Fishjam call, end to end: - Example app (Expo / React Native): - VoipProvider + useVoip context that owns the call lifecycle (available → incoming/connecting → active), driving the Fishjam connection (join on answer/start, leave on end) and the CallKit session. Media (mic/camera) is acquired before joining so tracks publish reliably. - Screens matching the fishjam-chat look & feel: Login, Directory, Outgoing call, and an In-call screen with an audio layout and a FaceTime-style video layout (remote full-screen + local PiP). - Reusable UI (Avatar, InCallButton, VideoCallView), a shared theme, and login persistence via AsyncStorage with logout. - A minimal Deno signaling backend (server/) using certificate-based APNs auth to send VoIP pushes, with setup docs. - SDK: exports useVoIPEvents (and VoIPEventHandlers / VoipIncomingPayload types) from @fishjam-cloud/mobile-client, so apps can subscribe to PushKit/CallKit events (token registered, incoming, answered, ended) from JS. - Submodule bump: packages/react-native-webrtc is moved forward to pick up the native PushKit registration + CallKit incoming-call reporting that the example relies on. ## Motivation and Context The mobile SDK already had CallKit primitives, but there was no end-to-end reference for the full native VoIP flow - receiving a PushKit push while backgrounded, presenting the system incoming-call UI via CallKit, and connecting that to a Fishjam room. This PR provides that reference implementation plus the useVoIPEvents bridge and a sample backend, so developers can see and reproduce the whole path. ## Documentation impact - [X] Documentation update required - [ ] Documentation updated [in another PR](_) - [ ] No documentation update required ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [X] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
## Description
Added android side of voip-calls
## Motivation and Context
Now both users from android and ios can access voip-calls functionality
## Documentation impact
- [X] Documentation update required
- [ ] Documentation updated [in another PR](_)
- [ ] No documentation update required
## Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to
not work as expected)
…waiting and Recents redial to VoIP calls (#562) ## Description Disclaimer: for now I'm using expo plugins to configure appDelegate on iOS but I will fix it to use our wrapper package in future PR Builds on the Android integration from #561 with the remaining call-lifecycle and native-call features: - **WebSocket call signaling** (`useCallSignaling`, server support) with an answer-to-connect handshake on iOS and Android — the native call UI only reports the call as connected once the callee has actually joined, with a `connecting` state in between (`fulfillIncomingCallConnected` / `failIncomingCallConnected`, `reportOutgoingCallConnected`). - **Native call timeouts** for incoming, outgoing, and answer-fulfillment phases, configurable via new plugin options on iOS and Android. - **Hold / resume** (`setCallHeld`, `isCallHeld`) and **call waiting** for a second incoming call (via a `react-native-webrtc` submodule bump). - **Native mute surfaced to JS** and end-call events with reasons, so the app stays in sync with CallKit/Telecom actions. - **`enableCallIntents` plugin option** so calls appear in the system Recents and can be redialed from there (iOS). - **Example polish** — Fishjam branding (icons, splash), server-served fish avatars, `DirectoryScreen` → `UsersScreen`, speaker toggle fixes, and camera PIP improvements. ## Motivation and Context #561 made basic calls work on both platforms, but the example didn't yet behave like a real phone app: answering connected immediately even if signaling later failed, unanswered calls rang forever, there was no hold or second-call handling, and muting from the native call UI desynced the app. This PR closes those gaps so the example serves as a complete reference for production VoIP behavior on Fishjam. ## Documentation impact - [x] Documentation update required - [ ] Documentation updated [in another PR](_) - [ ] No documentation update required ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
## Description
Replaces the `withAppDelegate` regex block in the iOS config plugin with
a proper Expo module.
- **New package `@fishjam-cloud/expo-voip`** — glue-only, iOS-only, no
JS. One `ExpoAppDelegateSubscriber` that registers PushKit at launch and
forwards `INStart*` call intents. Both gated on the `FishjamVoipEnabled`
Info.plist flag.
- **`withFishjamIos.ts`** — regex block deleted. The plugin now just
writes `FishjamVoipEnabled` and throws if `voip` options are set without
the new package installed.
- **`react-native-webrtc` submodule bump** — `DEFINES_MODULE => YES` so
the Swift subscriber can `import FishjamReactNativeWebrtc`.
No Info.plist keys are lost. Verified on device: the subscriber runs at
launch, a VoIP token is issued, and the device registers.
## Motivation and Context
The old approach string-spliced generated Swift in `AppDelegate.swift`.
It silently no-ops whenever Expo changes its template or the regex stops
matching — and the failure mode is an app that builds and launches fine
but can never receive a call. It also forced consumers to hand-add a
bridging-header import and an early `registerForVoIPPushes()` call.
`ExpoAppDelegateSubscriber` is the supported extension point for this.
It's re-applied by autolinking instead of patching generated code, so it
survives prebuild, and both manual steps go away.
## Documentation impact
- [x] Documentation update required
- [ ] Documentation updated [in another PR](_)
- [ ] No documentation update required
The VoIP README still documents the old flow (hand-editing
`AppDelegate.swift`, bridging header) and wrongly claims
`aps-environment` is already present and should be set in Xcode.
## Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [x] Breaking change (fix or feature that would cause existing
functionality to
not work as expected)
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…to the app's other notification library (#564) ## Description Android delivers each FCM message to exactly one `MESSAGING_EVENT` service per app, so our VoIP `PushNotificationService` used to swallow every push — apps also using expo-notifications or `@react-native-firebase/messaging` silently lost all their notifications and token callbacks (repro'd on baseline: builds clean, expo receives nothing). This PR makes the two coexist: - **Explicit payload discriminator** — a data message is a call iff it carries `fishjam: "voip-incoming"` (namespaced key, typed value so future kinds like a cancel push fit the same key). The example server now sends it. - **Native relay** — non-VoIP messages, token callbacks (`onNewToken` + `onRegistered`), and `onDeletedMessages` are handed to the app's other messaging service, configured via `VoipFallbackMessagingService` manifest meta-data. The fallback class is instantiated reflectively (`attachBaseContext` injected, same pattern Braze uses in production), so its real native code runs — killed-state behavior preserved, no JS required. - **Config plugin** — new `android.voipFallbackMessagingService` option (`"expo-notifications"`, `"@react-native-firebase/messaging"`, or a class name): writes the meta-data, strips the library's own service with `tools:node="remove"`, and gives ours `android:priority="1"`. New `android.voipMessagingService: false` opt-out for apps bringing their own dispatcher. - **Escape hatch** — public `PushNotificationService.handleVoipMessage(context, message): Boolean` / `handleNewToken(token)` for apps that own their messaging service. ## Motivation and Context A host app cannot currently use any other push-notification library alongside Fishjam VoIP on Android: our service claims the single FCM slot and drops everything that isn't a call — with no error anywhere (the manifest merges fine; expo-notifications even ships its service at priority −1, so ours always wins and theirs is dead code). This is a hard blocker for real apps, which virtually always have non-VoIP notifications. ## Documentation impact - [ ] Documentation update required - [ ] Documentation updated [in another PR](_) - [x] No documentation update required (README §9.6 "Coexisting with other push-notification libraries" — setup, wire format, bare-RN recipe, dispatcher example — is included in this PR.) ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) ### ✅ Runtime-tested (Pixel 9 emulator, real FCM v1 sends, expo-notifications 0.32.17 as fallback) | Test | Result | |---|---| | Merged manifest / `dumpsys`: our service sole `MESSAGING_EVENT` handler, expo's removed | ✅ | | Token relay → expo's `onNewToken` (its token listeners keep working) | ✅ | | Non-VoIP push → relayed → expo JS listener received full payload (25 ms e2e) | ✅ | | Non-VoIP push, **app killed** (`pidof` empty) → native relay, expo shows notification | ✅ | | VoIP push (`fishjam: "voip-incoming"`) rings in fg / bg / killed | ✅ | | Foreign payload containing `roomName` but no/wrong `fishjam` key → relayed, no phantom call | ✅ | | Baseline (no relay) + expo-notifications: builds fine, but expo silently receives **nothing** — the bug this PR fixes | ✅ repro'd | --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
# Conflicts: # .github/workflows/release.yaml # package.json # packages/mobile-client/src/index.ts # packages/react-native-webrtc # release-automation/bump-version.sh
## Description Moves `VoipProvider` and `useVoip` out of the `voip-call` example app and into `@fishjam-cloud/react-native-client` as public SDK API. - `VoipProvider` is an **opt-in wrapper component**, mounted inside `FishjamProvider`, whose connection it drives. It registers the native CallKit/Telecom listeners and tracks the state of the current call. Apps that don't need native calls simply don't mount it — no listeners, no cost for plain video apps. - New public exports: `VoipProvider`, `VoipProviderProps`, `useVoip`, `VoipCallStatus`, `CurrentCall`, `VoipContextValue`. `useVoip` is the single hook for the call lifecycle (`status`, `currentCall`, `voipToken`, `lastEndedReason`, `isOnHold`, `startCall`, `answerCall`, `endCall`, `setCallHeld`). - App-specific glue stays in the app and is injected as props on `VoipProvider`: `getPeerToken`, `requestCall`, `onWaitingCallDeclined`, plus the `isVideo` and `canStartOutgoingCall` switches. - **Behaviour change** — accepted waiting calls: `onIncoming` now treats a payload for a different room while already joined as an accepted waiting call, and tears the old room down inline before joining the new one, so the client can never sit in two rooms at once. A guard stops the "no remote peers" watchdog from mistaking the brief peerless window during the swap for the remote hanging up. - Example updated to consume the SDK API: `App.tsx` reordered so `UserProvider` sits above `FishjamProvider`, the wrapper renamed `VoipWrapper` → `FishjamWithVoip`, and the local `src/voip/` folder deleted. - `withFishjamIos.ts` drops a now-unused `withAppDelegate` import — the AppDelegate glue lives in `@fishjam-cloud/ios-expo-voip`. - Bumps the `packages/react-native-webrtc` submodule. ## Motivation and Context We need native VoIP calls supported by the SDK itself. The only working implementation lived in the `voip-call` example, so any app that wanted CallKit/Telecom calls had to copy the provider out of the example and maintain its own fork of it. Promoting it into `@fishjam-cloud/react-native-client` makes it a clear, supported API that ships and versions with the SDK. ## Documentation impact - [x] Documentation update required - [ ] Documentation updated [in another PR](_) - [ ] No documentation update required ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
## Description
`VoipProvider` is now only the native CallKit / Core-Telecom call state
machine.
Joining and leaving the room, peer tokens and camera/mic lifecycle move
to the app.
- Props reduced to `isVideo` and `onWaitingCallDeclined`. Removed
`getPeerToken`,
`requestCall`, `canStartOutgoingCall`.
- `useVoip()` gains `reportConnected()` / `reportConnectFailed()`, which
the app calls
when its own join succeeds or fails. This fulfills CallKit's answer
action.
- `useVoip()` also gains `isMuted` and `pendingCallIntent` /
`clearCallIntent()`.
- `answerCall` removed — an answer can only originate from the system
call UI. (we only supported it anyway because we always shown the
ssystem notification)
- `startCall(to, roomName)` only reports the call to CallKit; it no
longer signals or joins.
- Hold and mute are reported as state instead of applied to tracks the
SDK doesn't own.
Example app gains `src/voip/useVoipRoomConnection.ts` (join/leave,
report connected,
apply hold and mute) and `src/voip/usePlaceCall.ts` (room name,
signaling, `startCall`).
## Motivation and Context
Adopting VoIP previously meant handing your connection to the SDK:
`VoipProvider` had to
live inside `FishjamProvider`, took over join/leave, and only offered a
`getPeerToken`
hole to fill. Now a VoIP call connects through the same code as any
other Fishjam session.
Fulfilling CallKit's answer action can't move, since the `requestId` is
a native handle
only the SDK sees — hence the `reportConnected()` acknowledgement.
## Documentation impact
- [x] Documentation update required
- [ ] Documentation updated [in another PR](_)
- [ ] No documentation update required
## Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [x] Breaking change (fix or feature that would cause existing
functionality to
not work as expected)
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…ce fixes (#574) ## Description - **`reportConnected`'s failure branches end only their own call.** The fulfil-failed and catch branches ended whatever call was current after the native await, so a call-waiting swap mid-fulfil tore down the freshly accepted call. They now end the call only if the one captured at entry is still current — the same identity re-check the success path already does. - **Submodule bump** to `fishjam-react-native-webrtc@9636038` — the CallKitManager/CallManager/VoipManager race fixes (fishjam-cloud/fishjam-react-native-webrtc#77). Merge that PR first so the pointer lands on `feature/voip-calls`. ## Motivation and Context Found in a deep review of the VoIP call flow: an internal race between a call-waiting swap and an in-flight `reportConnected`, not avoidable by the app. Verified with `tsc --noEmit`, eslint, prettier, and an adversarial review pass. ## Documentation impact - [ ] Documentation update required - [ ] Documentation updated [in another PR](_) - [x] No documentation update required ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
Contributor
There was a problem hiding this comment.
Pull request overview
Adds first-class VoIP calling support to the Fishjam React Native SDK, including native call UI integration (CallKit on iOS / Telecom on Android), Expo config-plugin automation for required native configuration, and a full end-to-end example app + push/signaling server.
Changes:
- Introduces a
VoIPProvider+useVoIP()call state machine in@fishjam-cloud/react-native-client, plus re-exports of lower-level VoIP primitives from the WebRTC fork. - Extends the Expo config plugin to support Android VoIP manifest injection and iOS VoIP Info.plist/intents configuration; adds the new
@fishjam-cloud/ios-expo-voippackage for AppDelegate glue. - Adds a
voip-callexample (Expo app + Deno server) and updates release/versioning automation to publish/version the new package.
Reviewed changes
Copilot reviewed 54 out of 67 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Adds workspace + example dependencies for VoIP packages/apps |
| typedoc.json | Excludes packages/ios-expo-voip from Typedoc |
| spelling.txt | Adds “Recents” to allowlist |
| release-automation/bump-version.sh | Versions the new @fishjam-cloud/ios-expo-voip workspace |
| packages/mobile-client/src/voip/VoIPProvider.tsx | Implements VoIP call state machine driven by native events |
| packages/mobile-client/src/voip/VoIPContext.ts | Defines VoIP context types + useVoIP() hook |
| packages/mobile-client/src/index.ts | Re-exports VoIP APIs and primitives from the SDK entrypoint |
| packages/mobile-client/README.md | Documents Android VoIP setup + timeouts for Expo/bare RN |
| packages/mobile-client/plugin/src/withFishjamVoIPAndroid.ts | Adds Android VoIP manifest + permissions + timeout metadata injection |
| packages/mobile-client/plugin/src/withFishjamVoIP.ts | Duplicate/unused Android VoIP plugin implementation (see comment) |
| packages/mobile-client/plugin/src/withFishjamIos.ts | Adds iOS VoIP timeout keys, enables iOS Expo VoIP glue, declares Recents intents |
| packages/mobile-client/plugin/src/withFishjamAndroid.ts | Wires VoIP plugin into Android config flow + foreground service gating |
| packages/mobile-client/plugin/src/types.ts | Adds VoIP-related plugin options (enableVoIP, timeouts, FCM routing) |
| packages/ios-expo-voip/README.md | Documents purpose and installation/entitlement requirements |
| packages/ios-expo-voip/package.json | Defines new package metadata + peer deps |
| packages/ios-expo-voip/ios/VoIPAppDelegateSubscriber.swift | Starts PushKit registry at launch + forwards call intents when enabled |
| packages/ios-expo-voip/ios/FishjamExpoVoip.podspec | Podspec for the Expo iOS glue package |
| packages/ios-expo-voip/expo-module.config.json | Registers the AppDelegate subscriber for Expo Modules |
| package.json | Adds new workspace package + example app workspace path |
| examples/mobile-client/voip-call/server/README.md | Documents running server + APNs/FCM credentials + payloads |
| examples/mobile-client/voip-call/server/main.ts | Deno push + signaling server implementation |
| examples/mobile-client/voip-call/server/deno.lock | Locks Deno dependencies |
| examples/mobile-client/voip-call/server/deno.json | Defines tasks/imports for the Deno server |
| examples/mobile-client/voip-call/server/.gitignore | Ignores local DB and credential files |
| examples/mobile-client/voip-call/server/.env.example | Placeholder env file for server setup |
| examples/mobile-client/voip-call/README.md | Top-level instructions for running the example |
| examples/mobile-client/voip-call/app/tsconfig.json | TypeScript config for the Expo example app |
| examples/mobile-client/voip-call/app/src/user/UserProvider.tsx | User session persistence + users list retrieval |
| examples/mobile-client/voip-call/app/src/user/UserContext.ts | User context types + hook |
| examples/mobile-client/voip-call/app/src/theme/colors.ts | Example UI theme colors |
| examples/mobile-client/voip-call/app/src/screens/UsersScreen.tsx | User list UI + call initiation UI |
| examples/mobile-client/voip-call/app/src/screens/OutgoingCallView.tsx | Outgoing ringing UI |
| examples/mobile-client/voip-call/app/src/screens/LoginScreen.tsx | Simple login UI |
| examples/mobile-client/voip-call/app/src/screens/InCallView.tsx | In-call UI (audio/video) + hold/mute/audio-route sync |
| examples/mobile-client/voip-call/app/src/screens/CallScreen.tsx | Owns room join/leave lifecycle and connect/hangup reporting |
| examples/mobile-client/voip-call/app/src/hooks/useRequestPermissions.ts | Requests mic/camera/notifications permissions |
| examples/mobile-client/voip-call/app/src/hooks/useRecentsRedial.ts | Handles iOS Recents redial intents |
| examples/mobile-client/voip-call/app/src/hooks/usePlaceCall.ts | Calls server to ring callee then starts native call UI |
| examples/mobile-client/voip-call/app/src/hooks/useDeviceRegistration.ts | Registers VoIP push token with the server |
| examples/mobile-client/voip-call/app/src/hooks/useCallSignaling.ts | WebSocket signaling for cancel/reject while ringing |
| examples/mobile-client/voip-call/app/src/hooks/useCallRoom.ts | Joins/leaves Fishjam room tied to call lifetime |
| examples/mobile-client/voip-call/app/src/components/VideoCallView.tsx | FaceTime-style video layout with PiP |
| examples/mobile-client/voip-call/app/src/components/index.ts | Component barrel export |
| examples/mobile-client/voip-call/app/src/components/InCallButton.tsx | Call control button component |
| examples/mobile-client/voip-call/app/src/components/Avatar.tsx | Avatar image/initials component with error fallback |
| examples/mobile-client/voip-call/app/README.md | Example app run notes + iOS native registration note |
| examples/mobile-client/voip-call/app/prettier.config.js | Prettier config for example app |
| examples/mobile-client/voip-call/app/package.json | Example app dependencies/scripts |
| examples/mobile-client/voip-call/app/index.js | Expo entrypoint + crypto polyfill |
| examples/mobile-client/voip-call/app/babel.config.js | Babel config for Expo app |
| examples/mobile-client/voip-call/app/App.tsx | Wires providers + VoIP signaling/registration hooks + screens |
| examples/mobile-client/voip-call/app/app.json | Expo config enabling VoIP plugin options + app identifiers |
| examples/mobile-client/voip-call/app/.gitignore | Ignores generated native folders + google-services.json |
| examples/mobile-client/voip-call/app/.eslintrc.js | ESLint config for example app |
| examples/mobile-client/voip-call/app/.env.example | Example app env var template |
| .github/workflows/release.yaml | Publishes @fishjam-cloud/ios-expo-voip in release workflow |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
MiloszFilimowski
approved these changes
Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds VoIP calling to the Fishjam React Native SDK. Calls ring through the native call UI —
CallKit on iOS, Core Telecom on Android — even when the app is backgrounded or killed, and the
media runs over a regular Fishjam room.
SDK —
@fishjam-cloud/react-native-clientVoIPProvider+useVoIP(): a call state machine (available → incoming | connecting → active)driven by native call events. The SDK owns the CallKit/Telecom session, the VoIP push token, and
hold/mute/end-reason state; joining the room, peer tokens and media stay in the app, which reacts
to
statusand reports back withreportConnected()/reportConnectFailed().useVoIPEvents,useTelecom,fulfillIncomingCallConnected, …) for apps that want to drive the native layer themselves.android.enableVoIP, FCM coexistence options (voipMessagingService,voipFallbackMessagingService) and avoipblock for the native call timeouts and notificationicon. On Android it injects the manifest entries (
IncomingCallActivity,EndCallNotificationReceiver,PushNotificationService+ FCM intent filter), the requiredpermissions and the foreground service. On iOS it writes
FishjamVoIPEnabled, the timeoutInfo.plistkeys and theINStartCallIntentactivity types needed for Recents redial.New package —
@fishjam-cloud/ios-expo-voipAn
ExpoAppDelegateSubscriberwith no JS and no VoIP logic of its own. It starts the PushKitregistry at launch so a push arriving before the JS bundle loads can still ring, and routes
Siri / Phone-app Recents intents into the SDK. It is a no-op unless the config plugin enabled VoIP.
Added to the release workflow (published after the WebRTC fork, which its pod depends on) and to
bump-version.sh; excluded from typedoc.Example —
examples/mobile-client/voip-callA two-user calling app: an Expo client plus a small Deno push + signaling server
(
/register,/users,/call,/ws) that sends the APNs/FCM pushes. It exercises the wholesurface — device registration, ringing while killed, hold & accept, and Recents redial.
Motivation and Context
Fishjam could already carry the media for a call, but not the call itself. To build one-to-one
calling on top of it, an app had to write its own CallKit and Core Telecom integration, VoIP push
handling, cold-start wake-up and answer-fulfillment handshake before it could join a room — a large
amount of platform-specific native work, duplicated by every consumer.
This moves that layer into the SDK. The app keeps what is genuinely its own (rooms, peer tokens,
media, signaling between users) and reacts to a single
statusvalue, while the SDK owns the nativecall session on both platforms.
Documentation impact
Types of changes
not work as expected)