From 375e64a9bbb4134b2d85117dc736ba04255c3b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Thu, 13 Aug 2026 16:08:07 +0200 Subject: [PATCH 1/2] Remove the voip-call example The example now lives in the examples repo, at mobile-react-native/voip-call in fishjam-cloud/examples. Claude-Session: https://claude.ai/code/session_01WiSQ8BmVqGQUMRJ5t1Qvcz --- examples/mobile-client/voip-call/README.md | 62 ---- .../mobile-client/voip-call/app/.env.example | 3 - .../mobile-client/voip-call/app/.eslintrc.js | 16 - .../mobile-client/voip-call/app/.gitignore | 24 -- examples/mobile-client/voip-call/app/App.tsx | 107 ------ .../mobile-client/voip-call/app/README.md | 26 -- examples/mobile-client/voip-call/app/app.json | 69 ---- .../app/assets/images/adaptive-icon.png | Bin 10933 -> 0 bytes .../voip-call/app/assets/images/favicon.png | Bin 1701 -> 0 bytes .../voip-call/app/assets/images/icon.png | Bin 38730 -> 0 bytes .../voip-call/app/assets/images/splash.png | Bin 64768 -> 0 bytes .../voip-call/app/babel.config.js | 6 - examples/mobile-client/voip-call/app/index.js | 6 - .../mobile-client/voip-call/app/package.json | 30 -- .../voip-call/app/prettier.config.js | 8 - .../voip-call/app/src/components/Avatar.tsx | 62 ---- .../app/src/components/InCallButton.tsx | 70 ---- .../app/src/components/VideoCallView.tsx | 99 ----- .../voip-call/app/src/components/index.ts | 3 - .../voip-call/app/src/hooks/useCallRoom.ts | 89 ----- .../app/src/hooks/useCallSignaling.ts | 125 ------- .../app/src/hooks/useDeviceRegistration.ts | 27 -- .../voip-call/app/src/hooks/usePlaceCall.ts | 50 --- .../app/src/hooks/useRecentsRedial.ts | 25 -- .../app/src/hooks/useRequestPermissions.ts | 30 -- .../voip-call/app/src/screens/CallScreen.tsx | 44 --- .../voip-call/app/src/screens/InCallView.tsx | 343 ------------------ .../voip-call/app/src/screens/LoginScreen.tsx | 150 -------- .../app/src/screens/OutgoingCallView.tsx | 81 ----- .../voip-call/app/src/screens/UsersScreen.tsx | 149 -------- .../voip-call/app/src/theme/colors.ts | 27 -- .../voip-call/app/src/user/UserContext.ts | 24 -- .../voip-call/app/src/user/UserProvider.tsx | 93 ----- .../mobile-client/voip-call/app/tsconfig.json | 9 - .../voip-call/server/.env.example | 0 .../mobile-client/voip-call/server/.gitignore | 3 - .../mobile-client/voip-call/server/README.md | 146 -------- .../voip-call/server/avatars/coral.png | Bin 10720 -> 0 bytes .../voip-call/server/avatars/mint.png | Bin 11036 -> 0 bytes .../voip-call/server/avatars/ocean.png | Bin 10867 -> 0 bytes .../voip-call/server/avatars/orchid.png | Bin 10897 -> 0 bytes .../voip-call/server/avatars/sunny.png | Bin 10136 -> 0 bytes .../mobile-client/voip-call/server/deno.json | 12 - .../mobile-client/voip-call/server/deno.lock | 207 ----------- .../mobile-client/voip-call/server/main.ts | 323 ----------------- 45 files changed, 2548 deletions(-) delete mode 100644 examples/mobile-client/voip-call/README.md delete mode 100644 examples/mobile-client/voip-call/app/.env.example delete mode 100644 examples/mobile-client/voip-call/app/.eslintrc.js delete mode 100644 examples/mobile-client/voip-call/app/.gitignore delete mode 100644 examples/mobile-client/voip-call/app/App.tsx delete mode 100644 examples/mobile-client/voip-call/app/README.md delete mode 100644 examples/mobile-client/voip-call/app/app.json delete mode 100644 examples/mobile-client/voip-call/app/assets/images/adaptive-icon.png delete mode 100644 examples/mobile-client/voip-call/app/assets/images/favicon.png delete mode 100644 examples/mobile-client/voip-call/app/assets/images/icon.png delete mode 100644 examples/mobile-client/voip-call/app/assets/images/splash.png delete mode 100644 examples/mobile-client/voip-call/app/babel.config.js delete mode 100644 examples/mobile-client/voip-call/app/index.js delete mode 100644 examples/mobile-client/voip-call/app/package.json delete mode 100644 examples/mobile-client/voip-call/app/prettier.config.js delete mode 100644 examples/mobile-client/voip-call/app/src/components/Avatar.tsx delete mode 100644 examples/mobile-client/voip-call/app/src/components/InCallButton.tsx delete mode 100644 examples/mobile-client/voip-call/app/src/components/VideoCallView.tsx delete mode 100644 examples/mobile-client/voip-call/app/src/components/index.ts delete mode 100644 examples/mobile-client/voip-call/app/src/hooks/useCallRoom.ts delete mode 100644 examples/mobile-client/voip-call/app/src/hooks/useCallSignaling.ts delete mode 100644 examples/mobile-client/voip-call/app/src/hooks/useDeviceRegistration.ts delete mode 100644 examples/mobile-client/voip-call/app/src/hooks/usePlaceCall.ts delete mode 100644 examples/mobile-client/voip-call/app/src/hooks/useRecentsRedial.ts delete mode 100644 examples/mobile-client/voip-call/app/src/hooks/useRequestPermissions.ts delete mode 100644 examples/mobile-client/voip-call/app/src/screens/CallScreen.tsx delete mode 100644 examples/mobile-client/voip-call/app/src/screens/InCallView.tsx delete mode 100644 examples/mobile-client/voip-call/app/src/screens/LoginScreen.tsx delete mode 100644 examples/mobile-client/voip-call/app/src/screens/OutgoingCallView.tsx delete mode 100644 examples/mobile-client/voip-call/app/src/screens/UsersScreen.tsx delete mode 100644 examples/mobile-client/voip-call/app/src/theme/colors.ts delete mode 100644 examples/mobile-client/voip-call/app/src/user/UserContext.ts delete mode 100644 examples/mobile-client/voip-call/app/src/user/UserProvider.tsx delete mode 100644 examples/mobile-client/voip-call/app/tsconfig.json delete mode 100644 examples/mobile-client/voip-call/server/.env.example delete mode 100644 examples/mobile-client/voip-call/server/.gitignore delete mode 100644 examples/mobile-client/voip-call/server/README.md delete mode 100644 examples/mobile-client/voip-call/server/avatars/coral.png delete mode 100644 examples/mobile-client/voip-call/server/avatars/mint.png delete mode 100644 examples/mobile-client/voip-call/server/avatars/ocean.png delete mode 100644 examples/mobile-client/voip-call/server/avatars/orchid.png delete mode 100644 examples/mobile-client/voip-call/server/avatars/sunny.png delete mode 100644 examples/mobile-client/voip-call/server/deno.json delete mode 100644 examples/mobile-client/voip-call/server/deno.lock delete mode 100644 examples/mobile-client/voip-call/server/main.ts diff --git a/examples/mobile-client/voip-call/README.md b/examples/mobile-client/voip-call/README.md deleted file mode 100644 index bd4c5ac21..000000000 --- a/examples/mobile-client/voip-call/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# VoIP call example - -A two-user calling app: an Expo React Native client (`app/`) and a small Deno -push + signaling server (`server/`). Calls ring through the native call UI -(CallKit on iOS, Telecom on Android) even when the app is backgrounded or -killed, and connect through a Fishjam room. - -How it all works (native configuration, `VoIPProvider`, and the JS call flow) -is documented in the -[VoIP calls guide](https://documentation.fishjam.io/docs/how-to/client/voip-calls); -this README only covers running the example. - -## Running the example - -VoIP pushes can only be delivered through your own Apple and Firebase accounts, -so the first step is minting push credentials. Configure APNs to call iOS -devices, FCM to call Android devices, or both to call between them. - -1. **Server credentials.** Create the APNs VoIP certificate (`apns.pem`) and/or - the FCM service account key (`fcm-credentials.json`) as described in - [`server/README.md`](./server/README.md), then start the server: - - ```bash - cd server - deno task start # listens on :4400 - ``` - -2. **`google-services.json` (Android only).** The file is gitignored, so you - have to fetch your own. In the - [Firebase console](https://console.firebase.google.com/), open the same - project the server's `fcm-credentials.json` came from → _Project settings_ → - _Your apps_ → add an **Android** app if none exists → download - `google-services.json` → save it at `app/google-services.json`. - - Its `package_name` must match `android.package` in `app.json` exactly - (`io.fishjam.example.voipcall`), or the build fails with - `No matching client found for package name`. Keep the file at the app root: - `expo prebuild` copies it into `android/` for you, and a copy placed inside - `android/` by hand doesn't survive `expo prebuild --clean`. - -3. **App environment.** Copy `app/.env.example` to `app/.env` and fill it in: - - - `EXPO_PUBLIC_FISHJAM_ID`: your Fishjam app id. - - `EXPO_PUBLIC_SANDBOX_API_URL`: the Sandbox API url from your Fishjam - dashboard, used to mint peer tokens. - - `EXPO_PUBLIC_VOIP_SERVER_URL`: where the devices can reach the server - above. Not `localhost` when running on a physical phone; use your - machine's LAN address. - -4. **Run on real devices.** VoIP pushes never reach the iOS Simulator, and FCM - needs Google Play services. Install dependencies from the repo root - (`yarn`), then: - - ```bash - cd app - yarn ios # or: yarn android - ``` - -To test it, register two users on two devices and call between them. The -[guide's checklist](https://documentation.fishjam.io/docs/how-to/client/voip-calls#11-test-it) -lists the paths worth checking: ringing with the app killed, Recents redial, -and Hold & Accept. diff --git a/examples/mobile-client/voip-call/app/.env.example b/examples/mobile-client/voip-call/app/.env.example deleted file mode 100644 index 6b4e7bee2..000000000 --- a/examples/mobile-client/voip-call/app/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -EXPO_PUBLIC_FISHJAM_ID= -EXPO_PUBLIC_SANDBOX_API_URL= -EXPO_PUBLIC_VOIP_SERVER_URL=http://localhost:4400 diff --git a/examples/mobile-client/voip-call/app/.eslintrc.js b/examples/mobile-client/voip-call/app/.eslintrc.js deleted file mode 100644 index 61c762552..000000000 --- a/examples/mobile-client/voip-call/app/.eslintrc.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - root: true, - extends: ['expo'], - ignorePatterns: [ - 'dist/*', - 'node_modules/*', - 'ios/*', - 'android/*', - 'server/*', - '.eslintrc.js', - 'prettier.config.js', - ], - rules: { - 'import/no-unresolved': 'off', - }, -}; diff --git a/examples/mobile-client/voip-call/app/.gitignore b/examples/mobile-client/voip-call/app/.gitignore deleted file mode 100644 index 935c0172d..000000000 --- a/examples/mobile-client/voip-call/app/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# dependencies -node_modules/ - -# Expo -.expo/ -dist/ -expo-env.d.ts - -# debug -npm-debug.* -yarn-debug.* -yarn-error.* - -# macOS -.DS_Store - -# typescript -*.tsbuildinfo - -# generated native folders -/ios -/android - -google-services.json \ No newline at end of file diff --git a/examples/mobile-client/voip-call/app/App.tsx b/examples/mobile-client/voip-call/app/App.tsx deleted file mode 100644 index 2eec33bb9..000000000 --- a/examples/mobile-client/voip-call/app/App.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { - FishjamProvider, - useVoIP, - VoIPProvider, -} from '@fishjam-cloud/react-native-client'; -import { StatusBar } from 'expo-status-bar'; -import { useCallback, useEffect, useRef } from 'react'; -import { ActivityIndicator, StyleSheet, View } from 'react-native'; -import { SafeAreaProvider } from 'react-native-safe-area-context'; - -import type { VoIPIncomingPayload } from '@fishjam-cloud/react-native-client'; -import { - useCallSignaling, - type SendSignalRef, -} from './src/hooks/useCallSignaling'; -import { useDeviceRegistration } from './src/hooks/useDeviceRegistration'; -import { useRecentsRedial } from './src/hooks/useRecentsRedial'; -import { useRequestPermissions } from './src/hooks/useRequestPermissions'; -import { CallScreen } from './src/screens/CallScreen'; -import { LoginScreen } from './src/screens/LoginScreen'; -import { UsersScreen } from './src/screens/UsersScreen'; -import { BrandColors } from './src/theme/colors'; -import { useUser } from './src/user/UserContext'; -import { UserProvider } from './src/user/UserProvider'; - -function Main({ sendSignalRef }: { sendSignalRef: SendSignalRef }) { - const { username, isLoading } = useUser(); - const { callStatus, lastEndedReason } = useVoIP(); - - useRequestPermissions(); - useCallSignaling(sendSignalRef); - useDeviceRegistration(); - useRecentsRedial(); - - useEffect(() => { - if (!lastEndedReason) return; - console.log( - `On user: ${username}, [VoIP] Call ended — reason: ${lastEndedReason}`, - ); - }, [lastEndedReason, username]); - - // Checked before the session gates below: a call can exist while the user session - // is still loading (answering a VoIP push right after a cold start) or missing - // (still registered for pushes after a logout), and it must connect regardless. - if (callStatus === 'connecting' || callStatus === 'active') { - return ; - } - - if (isLoading) { - return ( - - - - ); - } - - if (!username) { - return ; - } - - return ; -} - -function VoIPApp() { - const sendSignalRef: SendSignalRef = useRef(undefined); - - // A call we declined while another one was ringing never reaches the callee's - // signaling flow, so tell the caller ourselves. - const onWaitingCallDeclined = useCallback((payload: VoIPIncomingPayload) => { - sendSignalRef.current?.({ - type: 'call-rejected', - to: payload.handle, - roomName: payload.roomName, - }); - }, []); - - return ( - - - - -
- - - - ); -} - -const App = () => ( - - - - - -); - -export default App; - -const styles = StyleSheet.create({ - root: { flex: 1, backgroundColor: BrandColors.seaBlue20 }, - center: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: BrandColors.seaBlue20, - }, -}); diff --git a/examples/mobile-client/voip-call/app/README.md b/examples/mobile-client/voip-call/app/README.md deleted file mode 100644 index 89382b031..000000000 --- a/examples/mobile-client/voip-call/app/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# voip-call-app - -The Expo React Native client of the VoIP call example. Setup and run -instructions (push credentials, `google-services.json`, `.env`) are in the -[example README](../README.md) one level up; how the integration works is in -the [VoIP calls guide](https://documentation.fishjam.io/docs/how-to/client/voip-calls). - -## Run - -With the [server](../server/README.md) running and `.env` filled in: - -```bash -yarn ios # or: yarn android -``` - -Real devices only: VoIP pushes never reach the iOS Simulator, and FCM needs -Google Play services. - -## iOS native registration - -On Expo, the app depends on -[`@fishjam-cloud/ios-expo-voip`](https://www.npmjs.com/package/@fishjam-cloud/ios-expo-voip), -which registers the native subscriptions the SDK needs at launch: PushKit and -the Recents redial intent forwarding. Without the PushKit registration the app -never receives `didReceiveIncomingPush`, so VoIP pushes don't work at all, and -tap-to-redial silently does nothing. diff --git a/examples/mobile-client/voip-call/app/app.json b/examples/mobile-client/voip-call/app/app.json deleted file mode 100644 index 2f1e7ceb2..000000000 --- a/examples/mobile-client/voip-call/app/app.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "expo": { - "name": "voip-call", - "slug": "voip-call", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/images/icon.png", - "scheme": "voipcall", - "userInterfaceStyle": "automatic", - "newArchEnabled": true, - "ios": { - "bundleIdentifier": "io.fishjam.example.voipcall", - "infoPlist": { - "NSMicrophoneUsageDescription": "Allow $(PRODUCT_NAME) to access your microphone.", - "NSCameraUsageDescription": "Allow $(PRODUCT_NAME) to access your camera for video calls.", - "ITSAppUsesNonExemptEncryption": false - }, - "entitlements": { - "aps-environment": "development" - }, - "appleTeamId": "J5FM626PE2" - }, - "android": { - "package": "io.fishjam.example.voipcall", - "googleServicesFile": "./google-services.json", - "adaptiveIcon": { - "foregroundImage": "./assets/images/adaptive-icon.png", - "monochromeImage": "./assets/images/adaptive-icon.png", - "backgroundColor": "#F1FAFE" - }, - "edgeToEdgeEnabled": true, - "permissions": [ - "android.permission.CAMERA", - "android.permission.RECORD_AUDIO", - "android.permission.MODIFY_AUDIO_SETTINGS", - "android.permission.ACCESS_NETWORK_STATE", - "android.permission.ACCESS_WIFI_STATE" - ] - }, - "web": { - "favicon": "./assets/images/favicon.png" - }, - "plugins": [ - [ - "@fishjam-cloud/react-native-client", - { - "android": { - "enableVoIP": true - }, - "ios": { - "enableVoIPBackgroundMode": true - }, - "voip": { - "incomingCallTimeout": 20, - "outgoingCallTimeout": 20 - } - } - ], - [ - "expo-splash-screen", - { - "image": "./assets/images/splash.png", - "resizeMode": "contain", - "backgroundColor": "#F1FAFE" - } - ] - ] - } -} diff --git a/examples/mobile-client/voip-call/app/assets/images/adaptive-icon.png b/examples/mobile-client/voip-call/app/assets/images/adaptive-icon.png deleted file mode 100644 index 471f7c36f4623959b366e2e7f26f561379fbba80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10933 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�=LBN2_&` zrhmRLdYX#A%*@J7OP@9xvo+n$K5Whlx`NFuw<29HZH4r7wt5`cC65^;y#I{;Ui=JQ z*ETo%Ge6iuN;wh-=#@L;xr}R}FDOR(9<|^-pt`yxb2GB;zaP#*jrMUSvP+t=U+C|% z8@IJ3kdQpip>xm)0ozwY%t4@?DyiTuv2pl~rAWFP$~H7%mjq7badlS86{a4`ERVwk+FOo`-NY8>gl%DtyLby&BpGvGfgiZ%M=}!Rk*3bIZ3$>knK^`O1($vFwIuBq774IM> zn)b>^|8BFWozpOL&TVig4G*wr{DC{SfX8zX~dZO!ZB6fFSl43Wr<1W>vqOC3Yt6g(p z!^puDFu0FGNKpTEqj7khW>m7DXk!&bVoCC+zq^(5>cr&GMq+n zmJ|qZwexgb`Fz{@v~~W*_hI+yB9H*(gYk@2T@W6oFOuw`5WqalW$ig~Q94BS*rJ zk}$7UAmcL#1R@D&3p$3~``U+f&Pv?OQZVPWouqYWGsXd@`U{ZbOdoZByeb~-pLpt* z8g3xT)TL3|;eI;ReEEiH?9NhI`F9=EslTy!VDzMQ*z5V=*PM}NB8*A8^%D;ujGCN1A_(f>_X!lHxWbQXCRw;o6Ke-X634jUTX4CQkh@<<)U@ zq)NvM`P59hEk$1yVJd@C!(;BK^j4W>*xrRM4N9SUA^3gDFo=zbhKi8V79!G zC@zjpXa??O^zrN1tY3fVp$=Q3&j*YC(BkL1Ok{@s#_<*&a>~P$K<~E>>I>DP6m^*E zA7#+F*F)w6l5-&PsMGa}^z)xclZJt)@WLB3Ch9eh`vRKWtcJV~lfd&Fhv~~tAxgH8 z=J3tHn>nc5trwdOj=?`Ho>6nh`7f2mkm1>XQ@GbQ@P&PP)#gaP24(9#46qW%Q`)T(Ye&jf3hVG9?qs|B)eJM4!#5(a%dvd zcgzGgBaZ^y*jNosK#0gC z@Uq!__BI=2;T#{q;!1|T*uw#wf|qel*XRogzAOEkNsHQU16B|H|^ugB6eji)r zU5S=NMG8nvXxzrQxSA(>dyh2C^ExDfYZU*BD}4Xgj)05lBjVbO}qp~`l|7sNF L)MTrrEkgeX^-aiv diff --git a/examples/mobile-client/voip-call/app/assets/images/favicon.png b/examples/mobile-client/voip-call/app/assets/images/favicon.png deleted file mode 100644 index f8d9dd101b4ff5648d16de61a5d0a54bd79c7128..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1701 zcmV;W23q-vP)000JTNklu*#=6hG(8+3v4B+-as)GvNA8Xuqh5B%mwzi1+nm}oQx ziQ$8qXiV^hCiPJbijM@)+Da)XU|ZPk-Q9cd%p5cK);{TO>2@ji*W0Gu)0umI=QU?$ z@M35n%^*lQ2vQD$l!GAUAV@g~QVxQYgCOM~NI3{n4uX_J&T|~I{fQX=%6R$|_x19r z0W)+S3ZrZc0$?^nD_6n#cIe!M>)R&=#-pashBH2#8*m=YoYjwF*A++M=n?btk8pZ0 zD+m}g8)af{0E8$B3CUMVT(cgwZsRRCVZFh4R1X`6NH8IP$EfYnF(3qU#ctRALTJ&9AwC%QudCZvhnf-5MgtX2a zHxmE~8dBPM;l+guq4=$)|4;GZdrd?Cj)vhdTcg>48ULR|@5w^|{3!C3@}i;Uk3J~< z_N(6WFs*C_tx*Vb?jLb*f2d7S1vq&ukrM$f%`aRMC)$74E#KJZoc(7w2qI%}lFh2X zGGq?4z7U4l{$A&`z2?`WAO!n9?tjaPr=n!G{~w6|WTUIkJXa4SzSduU-g5X*L8$<7 zinZp3B;_wek*8GV;5+4!Veac)c5!4#DZ2dGE{8y@z3TaT>ET18VWgQa@xE!HKRbvy zV2+|_$tNF2#tds+Pc5GV0cM+R11H@N-!B@YjG6vEB%5+t+ZaOxA%G|e{aHE0#n;z5 zDY*5Z7Wvqm7)9pb0l};cPaiCtmYa%izHVL7U=CD6j%bebEYmtPW{Ms}TrOqqygR#l zk6m)crSY-=T05QBHa+>YjiLs7fU1kx81vJQ6|Jo+#(@Ybq7^H14?Z-$+>S*&;#eNX z9Ng_YxLmf<#UcP_2J!5fau`jiDu^5e?)5j=pr5S|i*4_1+X2p^h7kXLe2Ea|5++2k zc3riRu`9%?R%&Xt**xE7vt9z`WWR}5^_e1+wbu|7!iLjgCh@tm>XO|Evl$twF_zgB zi(D$%9B3E^s-c5lREDQyaMI~Y8J##@b)?OW=)^HgKnPeg4}_r&p=tJGgPs@l{t^E2 zv$d|C;i9D14}!{9htY8sGlZ~;NC^Mk1NzMAF#{Vj_Qbst7~>z_4hI5`$7LZIhiHhTrM zhuQ@4D;(dCl&aBhyDoU%#D_^TToF)pF|J!HlE|)(WU(86t{|wo7&|(nWUFpd$sBhwL5j%c3VZMM(<1JjoR6XZ5@>joh`0w8rBjw z{8U2Rzo%?i63x0WU%dp#b~U(qjq2K!cin~E9I~wq^a)ybv5X zep;k-iOiKC5GzfOJziM7cDd`a5cR|oBMQV8&nw=tBWi7Ja-4cNT#h+28F=EE!iLTj zu7d#c$Z2bM#3m`F>y>stB=$Ti9(hay$~r7EX9Dpl17@=D_){0}=yIgYJB~dNLgYEC z?hq~ayl~Ar{p@qN{dQIg+d6`K?p^9Q^V9w^FF0Z&+3#KVnN8P~zxXus`?0JPr6>xt z-Gap(2imW=c3Gpwn;UyWBW2c+qi&|K^KQ6xr#%ob#%}qUW!J8ezj~MT_l<<%l-0%_ zYhpw0$dVnSeZ^mWbmRx~$8joL^n}py*|p&@Ehsj*Ao8P#)~>@FxAIN5U~_Y#+}J@2 z$5clP@ajt$-w(8=@w+nsm#CP@I?unb&;+L5QbMW31O43J$AbefGQ{NoRAf(8Gajy3 zMI9TVb2G1Rv-VCdN|MvNX*M~3Rt^tO-dr=-yC0SgZR~6SfvW}tDF;EyL6C9~q#Oh( v2SLg~ka7^D90Vx`LCQgpa?oNcP%r)iNR~%lS!U=!00000NkvXXu0mjfVS+KE diff --git a/examples/mobile-client/voip-call/app/assets/images/icon.png b/examples/mobile-client/voip-call/app/assets/images/icon.png deleted file mode 100644 index 348683ffa4a0d16009f2873ada8ad0d3cc336410..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38730 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 diff --git a/examples/mobile-client/voip-call/app/assets/images/splash.png b/examples/mobile-client/voip-call/app/assets/images/splash.png deleted file mode 100644 index 63aec3ce0c75d315f8f5f62c50d8bc29c2389545..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64768 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 setFailed(false), [avatarUrl]); - const showImage = Boolean(avatarUrl) && !failed; - return ( - - {/* Initials sit underneath as the fallback while the image loads or if it fails. */} - {initial} - {showImage && ( - setFailed(true)} - accessibilityIgnoresInvertColors - /> - )} - - ); -} - -const styles = StyleSheet.create({ - avatar: { - backgroundColor: BrandColors.darkBlue60, - alignItems: 'center', - justifyContent: 'center', - borderColor: BrandColors.seaBlue80, - overflow: 'hidden', - }, - text: { - color: TextColors.white, - fontWeight: '700', - }, -}); diff --git a/examples/mobile-client/voip-call/app/src/components/InCallButton.tsx b/examples/mobile-client/voip-call/app/src/components/InCallButton.tsx deleted file mode 100644 index 6035a6143..000000000 --- a/examples/mobile-client/voip-call/app/src/components/InCallButton.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { MaterialCommunityIcons } from '@expo/vector-icons'; -import { - type GestureResponderEvent, - StyleSheet, - TouchableOpacity, -} from 'react-native'; - -import { AdditionalColors, BrandColors } from '../theme/colors'; - -type InCallButtonType = 'primary' | 'disconnect'; - -type InCallButtonProps = { - type?: InCallButtonType; - active?: boolean; - onPress: (event: GestureResponderEvent) => void; - iconName: keyof typeof MaterialCommunityIcons.glyphMap; - accessibilityLabel?: string; - disabled?: boolean; -}; - -export function InCallButton({ - type = 'primary', - active = false, - onPress, - iconName, - accessibilityLabel, - disabled = false, -}: InCallButtonProps) { - const isDisconnect = type === 'disconnect'; - const filled = isDisconnect || active; - - const backgroundColor = isDisconnect - ? AdditionalColors.red80 - : active - ? BrandColors.darkBlue100 - : AdditionalColors.white; - - const iconColor = filled ? AdditionalColors.white : BrandColors.darkBlue100; - - return ( - - - - ); -} - -const styles = StyleSheet.create({ - button: { - width: 52, - height: 52, - borderRadius: 26, - justifyContent: 'center', - alignItems: 'center', - }, - outline: { - borderWidth: 1, - borderColor: BrandColors.darkBlue80, - }, - disabled: { opacity: 0.45 }, -}); diff --git a/examples/mobile-client/voip-call/app/src/components/VideoCallView.tsx b/examples/mobile-client/voip-call/app/src/components/VideoCallView.tsx deleted file mode 100644 index bb48d77d3..000000000 --- a/examples/mobile-client/voip-call/app/src/components/VideoCallView.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { - RTCView, - type Track, - useCamera, - usePeers, -} from '@fishjam-cloud/react-native-client'; -import { StyleSheet, View } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; - -import { BrandColors } from '../theme/colors'; -import { Avatar } from './Avatar'; - -function streamOf(track: Track | null | undefined) { - return track?.stream && !track?.metadata?.paused ? track.stream : null; -} - -type VideoCallViewProps = { - remoteName: string; - remoteAvatarUrl?: string | null; - localName: string; - localAvatarUrl?: string | null; -}; - -export function VideoCallView({ - remoteName, - remoteAvatarUrl, - localName, - localAvatarUrl, -}: VideoCallViewProps) { - const insets = useSafeAreaInsets(); - const { remotePeers } = usePeers(); - const { cameraStream } = useCamera(); - - const primaryRemote = remotePeers[0]; - const remoteStream = streamOf(primaryRemote?.cameraTrack); - const localStream = cameraStream; - - return ( - - {remoteStream ? ( - - ) : ( - - - - )} - - - {localStream ? ( - - ) : ( - - - - )} - - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: BrandColors.darkBlue100 }, - remoteVideo: { flex: 1 }, - remoteNoVideo: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: BrandColors.darkBlue60, - }, - pip: { - position: 'absolute', - right: 16, - width: 108, - height: 156, - // Square corners on purpose: RTCView is a SurfaceView on Android and cannot - // be clipped by borderRadius/overflow, so a rounded PiP overshoots there. - backgroundColor: BrandColors.seaBlue60, - borderWidth: 1, - borderColor: 'rgba(255, 255, 255, 0.6)', - shadowColor: '#000', - shadowOpacity: 0.25, - shadowRadius: 6, - shadowOffset: { width: 0, height: 2 }, - elevation: 6, - }, - pipVideo: { flex: 1 }, - pipNoVideo: { flex: 1, alignItems: 'center', justifyContent: 'center' }, -}); diff --git a/examples/mobile-client/voip-call/app/src/components/index.ts b/examples/mobile-client/voip-call/app/src/components/index.ts deleted file mode 100644 index d7e1cba09..000000000 --- a/examples/mobile-client/voip-call/app/src/components/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { Avatar } from './Avatar'; -export { InCallButton } from './InCallButton'; -export { VideoCallView } from './VideoCallView'; diff --git a/examples/mobile-client/voip-call/app/src/hooks/useCallRoom.ts b/examples/mobile-client/voip-call/app/src/hooks/useCallRoom.ts deleted file mode 100644 index 47f869713..000000000 --- a/examples/mobile-client/voip-call/app/src/hooks/useCallRoom.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - useCamera, - useConnection, - useMicrophone, - useSandbox, - useVoIP, -} from '@fishjam-cloud/react-native-client'; -import { useEffect, useState } from 'react'; - -import { useUser } from '../user/UserContext'; -import { IS_VIDEO_CALL } from './usePlaceCall'; - -const SANDBOX_API_URL = process.env.EXPO_PUBLIC_SANDBOX_API_URL ?? ''; - -// Serializes joins and leaves. React runs an effect's cleanup before the next effect -// body, but `leaveRoom` and the media teardown are async — without this chain an -// "End & Accept" swap when accepting waiting call while we are in an ongoing one -// could start joining the new room while the old one is still being torn down, and -// we would briefly be in two rooms. Module-level so the leave scheduled by an -// unmounting `CallScreen` still runs before the next call's join. -let roomOperations: Promise = Promise.resolve(); - -/** - * Ties room membership to the caller's mount lifetime: joins the current call's room - * (starting media first) on mount, leaves and stops media on unmount, and swaps rooms - * when `currentCall` moves to a different one. - * - * Call from the screen that is visible for exactly the duration of a call. - * Returns the room we have actually joined, or `null` while still connecting. - */ -export function useCallRoom(): string | null { - const { currentCall, reportConnectFailed } = useVoIP(); - const { username } = useUser(); - const { joinRoom, leaveRoom } = useConnection(); - const { startCamera, stopCamera } = useCamera(); - const { startMicrophone, stopMicrophone } = useMicrophone(); - const { getSandboxPeerToken } = useSandbox({ - sandboxApiUrl: SANDBOX_API_URL, - }); - - const roomName = currentCall?.roomName ?? null; - const [joinedRoom, setJoinedRoom] = useState(null); - - useEffect(() => { - if (!roomName) return; - let cancelled = false; - - roomOperations = roomOperations.then(async () => { - if (cancelled) return; - try { - const peerToken = await getSandboxPeerToken( - roomName, - username ?? 'unknown', - 'conference', - ); - if (cancelled) return; - - if (IS_VIDEO_CALL) await startCamera(); - await startMicrophone(); - if (cancelled) return; - - await joinRoom({ peerToken }); - if (cancelled) return; - setJoinedRoom(roomName); - } catch (err) { - console.error('[voip] failed to join call room:', err); - if (!cancelled) await reportConnectFailed(); - } - }); - - return () => { - cancelled = true; - setJoinedRoom(null); - roomOperations = roomOperations.then(async () => { - try { - await stopCamera(); - await stopMicrophone(); - await leaveRoom(); - } catch (err) { - console.error('[voip] failed to leave call room:', err); - } - }); - }; - // Only the room decides when to join or leave - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [roomName]); - - return joinedRoom; -} diff --git a/examples/mobile-client/voip-call/app/src/hooks/useCallSignaling.ts b/examples/mobile-client/voip-call/app/src/hooks/useCallSignaling.ts deleted file mode 100644 index b9ebf0a66..000000000 --- a/examples/mobile-client/voip-call/app/src/hooks/useCallSignaling.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { useCallback, useEffect, useRef, type MutableRefObject } from 'react'; - -import { - useVoIP, - type CurrentCall, - type VoIPCallStatus, -} from '@fishjam-cloud/react-native-client'; - -import { useUser } from '../user/UserContext'; - -const SERVER_URL = - process.env.EXPO_PUBLIC_VOIP_SERVER_URL ?? 'http://localhost:4400'; - -export type SendSignal = (msg: Record) => void; - -/** Filled by {@link useCallSignaling} so App can wire `VoIPProvider.onWaitingCallDeclined`. */ -export type SendSignalRef = MutableRefObject; - -export function useCallSignaling(sendSignalRef: SendSignalRef): void { - const { endCall, currentCall, callStatus, lastEndedReason } = useVoIP(); - const { username } = useUser(); - - const socketRef = useRef(null); - - const handlersRef = useRef({ endCall, currentCall }); - handlersRef.current = { endCall, currentCall }; - - const sendSignal = useCallback((msg: Record) => { - const ws = socketRef.current; - if (ws?.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify(msg)); - } else { - console.warn('[signaling] message not sent — socket not open', msg); - } - }, []); - - useEffect(() => { - sendSignalRef.current = sendSignal; - }, [sendSignal, sendSignalRef]); - - useEffect(() => { - if (!username) return; - - const wsUrl = - SERVER_URL.replace(/^http/, 'ws') + - '/ws?username=' + - encodeURIComponent(username); - - const ws = new WebSocket(wsUrl); - socketRef.current = ws; - - ws.onmessage = (e) => { - let msg: Record; - try { - msg = JSON.parse(e.data); - } catch { - return; - } - - const { endCall, currentCall } = handlersRef.current; - if (!currentCall || currentCall.startedAt !== null) return; - if (currentCall.roomName !== msg.roomName) return; - - // The caller cancelled while we (the callee) are still ringing — from - // our side this incoming call rang and was never answered. - if (msg.type === 'call-cancelled' && !currentCall.isOutgoing) { - void endCall('missed'); - } - // The callee rejected while we (the caller) are still ringing out — the - // other party declined, not just hung up. - else if (msg.type === 'call-rejected' && currentCall.isOutgoing) { - void endCall('rejected'); - } - }; - - return () => { - ws.close(); - socketRef.current = null; - }; - }, [username]); - - // Detect the local user ending a call before it connected, and notify the - // other party so their ringing UI can be dismissed. - - const prevRef = useRef<{ - callStatus: VoIPCallStatus; - call: CurrentCall | null; - }>({ - callStatus, - call: currentCall, - }); - - useEffect(() => { - const { callStatus: prevStatus, call: prevCall } = prevRef.current; - - const userEndedCall = - lastEndedReason === 'local' || lastEndedReason === 'rejected'; - - if ( - prevCall && - prevCall.startedAt === null && - callStatus === 'available' && - userEndedCall - ) { - // Caller cancelled an outgoing call that was still ringing. - if (prevStatus === 'connecting' && prevCall.isOutgoing) { - sendSignal({ - type: 'call-cancelled', - to: prevCall.handle, - roomName: prevCall.roomName, - }); - } - // Callee rejected an incoming call before answering. - else if (prevStatus === 'incoming' && !prevCall.isOutgoing) { - sendSignal({ - type: 'call-rejected', - to: prevCall.handle, - roomName: prevCall.roomName, - }); - } - } - - prevRef.current = { callStatus, call: currentCall }; - }, [callStatus, currentCall, lastEndedReason, sendSignal]); -} diff --git a/examples/mobile-client/voip-call/app/src/hooks/useDeviceRegistration.ts b/examples/mobile-client/voip-call/app/src/hooks/useDeviceRegistration.ts deleted file mode 100644 index 196d5c000..000000000 --- a/examples/mobile-client/voip-call/app/src/hooks/useDeviceRegistration.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useVoIP } from '@fishjam-cloud/react-native-client'; -import { useEffect } from 'react'; -import { Platform } from 'react-native'; - -import { useUser } from '../user/UserContext'; - -const SERVER_URL = - process.env.EXPO_PUBLIC_VOIP_SERVER_URL ?? 'http://localhost:4400'; - -/** - * Registers this device's VoIP push token with the signaling server so other - * users can ring it. - */ -export function useDeviceRegistration(): void { - const { username } = useUser(); - const { voipToken } = useVoIP(); - - useEffect(() => { - if (!username || !voipToken) return; - if (Platform.OS !== 'ios' && Platform.OS !== 'android') return; - fetch(`${SERVER_URL}/register`, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ username, voipToken, platform: Platform.OS }), - }).catch(() => {}); - }, [username, voipToken]); -} diff --git a/examples/mobile-client/voip-call/app/src/hooks/usePlaceCall.ts b/examples/mobile-client/voip-call/app/src/hooks/usePlaceCall.ts deleted file mode 100644 index f50afcad4..000000000 --- a/examples/mobile-client/voip-call/app/src/hooks/usePlaceCall.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { useVoIP } from '@fishjam-cloud/react-native-client'; -import { useCallback } from 'react'; - -import { useUser } from '../user/UserContext'; - -const SERVER_URL = - process.env.EXPO_PUBLIC_VOIP_SERVER_URL ?? 'http://localhost:4400'; - -/** Must match the `isVideo` prop passed to `VoIPProvider` in App.tsx. */ -export const IS_VIDEO_CALL = true; - -/** Random room name for the call. */ -function makeRoomName() { - const bytes = crypto.getRandomValues(new Uint8Array(6)); - const id = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); - return `voip-${id}`; -} - -/** - * Places an outgoing call: rings the callee through our own signaling server first, - * then reports the call to CallKit/Telecom via `startCall`. - * - * The order matters and is ours to choose. `startCall` only touches the native call - * UI, so if signaling fails we never show a call that cannot connect. - */ -export function usePlaceCall(): (to: string) => Promise { - const { startCall } = useVoIP(); - const { username } = useUser(); - - return useCallback( - async (to: string) => { - const roomName = makeRoomName(); - - const res = await fetch(`${SERVER_URL}/call`, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - from: username, - to, - roomName, - isVideo: IS_VIDEO_CALL, - }), - }); - if (!res.ok) throw new Error('Failed to initiate call'); - - await startCall(to, roomName); - }, - [startCall, username], - ); -} diff --git a/examples/mobile-client/voip-call/app/src/hooks/useRecentsRedial.ts b/examples/mobile-client/voip-call/app/src/hooks/useRecentsRedial.ts deleted file mode 100644 index cad0c9cd3..000000000 --- a/examples/mobile-client/voip-call/app/src/hooks/useRecentsRedial.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useVoIP } from '@fishjam-cloud/react-native-client'; -import { useEffect } from 'react'; - -import { useUser } from '../user/UserContext'; -import { usePlaceCall } from './usePlaceCall'; - -/** - * Places a call when the user redials from the iOS Recents list. The SDK holds the - * intent until we are ready, so we can simply wait for the session to be restored. - */ -export function useRecentsRedial(): void { - const { pendingCallIntent, clearCallIntent } = useVoIP(); - const { username } = useUser(); - const placeCall = usePlaceCall(); - - useEffect(() => { - if (!pendingCallIntent || !username) return; - - const { handle } = pendingCallIntent; - clearCallIntent(); - placeCall(handle).catch((err) => - console.error('[voip] failed to start call from a Recents intent:', err), - ); - }, [pendingCallIntent, username, clearCallIntent, placeCall]); -} diff --git a/examples/mobile-client/voip-call/app/src/hooks/useRequestPermissions.ts b/examples/mobile-client/voip-call/app/src/hooks/useRequestPermissions.ts deleted file mode 100644 index 02b0e7739..000000000 --- a/examples/mobile-client/voip-call/app/src/hooks/useRequestPermissions.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - useCameraPermissions, - useMicrophonePermissions, -} from '@fishjam-cloud/react-native-client'; -import { useEffect } from 'react'; -import { PermissionsAndroid, Platform } from 'react-native'; - -/** Requests the camera, microphone, and (Android 13+) notification permissions once. */ -export function useRequestPermissions(): void { - const [, requestCamera] = useCameraPermissions(); - const [, requestMicrophone] = useMicrophonePermissions(); - - useEffect(() => { - (async () => { - const microphoneStatus = await requestMicrophone(); - if (microphoneStatus !== 'granted') { - console.warn('Microphone permission not granted — calls will be muted'); - } - const cameraStatus = await requestCamera(); - if (cameraStatus !== 'granted') { - console.warn('Camera permission not granted — video will be disabled'); - } - if (Platform.OS === 'android' && Number(Platform.Version) >= 33) { - await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS, - ); - } - })().catch((err) => console.error('Failed to request permissions:', err)); - }, [requestCamera, requestMicrophone]); -} diff --git a/examples/mobile-client/voip-call/app/src/screens/CallScreen.tsx b/examples/mobile-client/voip-call/app/src/screens/CallScreen.tsx deleted file mode 100644 index 8e9603892..000000000 --- a/examples/mobile-client/voip-call/app/src/screens/CallScreen.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { usePeers, useVoIP } from '@fishjam-cloud/react-native-client'; -import { useEffect } from 'react'; - -import { useCallRoom } from '../hooks/useCallRoom'; -import { InCallView } from './InCallView'; -import { OutgoingCallView } from './OutgoingCallView'; - -/** - * The screen shown for the whole lifetime of a call (`callStatus` is `connecting` or - * `active`). It owns the Fishjam side of the call: mounting joins the current call's - * room via `useCallRoom`, unmounting leaves it, and the remote peer's presence is - * reported back to the call as connect / hang-up. - */ -export function CallScreen() { - const { callStatus, currentCall, reportConnected, endCall } = useVoIP(); - const { remotePeers } = usePeers(); - - const joinedRoom = useCallRoom(); - - // The remote peer showing up is what "connected" means for us, and it going away is - // the remote hanging up. Gated on `joinedRoom` so a stale peer list from the room we - // just left cannot connect (or end) the room we are moving into. - useEffect(() => { - if (!currentCall || joinedRoom !== currentCall.roomName) return; - - if (callStatus === 'connecting' && remotePeers.length > 0) { - void reportConnected(); - } else if (callStatus === 'active' && remotePeers.length === 0) { - void endCall('remote'); - } - }, [ - callStatus, - currentCall, - joinedRoom, - remotePeers.length, - reportConnected, - endCall, - ]); - - if (callStatus === 'active') { - return ; - } - return ; -} diff --git a/examples/mobile-client/voip-call/app/src/screens/InCallView.tsx b/examples/mobile-client/voip-call/app/src/screens/InCallView.tsx deleted file mode 100644 index 46ea012ab..000000000 --- a/examples/mobile-client/voip-call/app/src/screens/InCallView.tsx +++ /dev/null @@ -1,343 +0,0 @@ -import { - setCallMuted, - useAudioOutput, - useCamera, - useMicrophone, - usePeers, - useVAD, -} from '@fishjam-cloud/react-native-client'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { StatusBar } from 'expo-status-bar'; -import { Platform, StyleSheet, Text, View } from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; - -import { Avatar, InCallButton, VideoCallView } from '../components'; -import { AdditionalColors, BrandColors, TextColors } from '../theme/colors'; -import { useUser } from '../user/UserContext'; -import { useVoIP } from '@fishjam-cloud/react-native-client'; - -type PeerMeta = { displayName?: string }; - -function formatDuration(totalSeconds: number): string { - const m = Math.floor(totalSeconds / 60) - .toString() - .padStart(2, '0'); - const s = (totalSeconds % 60).toString().padStart(2, '0'); - return `${m}:${s}`; -} - -function useElapsed(startedAt: number | null): number { - const [elapsed, setElapsed] = useState(0); - useEffect(() => { - if (!startedAt) { - setElapsed(0); - return; - } - setElapsed(Math.floor((Date.now() - startedAt) / 1000)); - const id = setInterval(() => { - setElapsed(Math.floor((Date.now() - startedAt) / 1000)); - }, 1000); - return () => clearInterval(id); - }, [startedAt]); - return elapsed; -} - -/** - * Sets aside the live devices when the call goes on hold and restores exactly those - * devices on resume. Runs only while mounted, i.e. only during an active call. - */ -function useHoldMediaSync() { - const { isOnHold } = useVoIP(); - const { isCameraOn, toggleCamera } = useCamera(); - const { isMicrophoneOn, toggleMicrophone } = useMicrophone(); - - const heldMediaRef = useRef({ - microphoneEnabled: false, - cameraEnabled: false, - }); - const prevOnHoldRef = useRef(isOnHold); - - useEffect(() => { - if (prevOnHoldRef.current === isOnHold) return; - prevOnHoldRef.current = isOnHold; - - (async () => { - if (isOnHold) { - heldMediaRef.current = { - microphoneEnabled: isMicrophoneOn, - cameraEnabled: isCameraOn, - }; - if (isMicrophoneOn) await toggleMicrophone(); - if (isCameraOn) await toggleCamera(); - } else { - const { microphoneEnabled, cameraEnabled } = heldMediaRef.current; - if (microphoneEnabled) await toggleMicrophone(); - if (cameraEnabled) await toggleCamera(); - } - })().catch((err) => - console.error('[voip] failed to update media for held call:', err), - ); - }, [isOnHold, isCameraOn, isMicrophoneOn, toggleCamera, toggleMicrophone]); -} - -/** - * Mirrors the system mute state (CallKit / Telecom) onto the microphone track, so - * muting from the native call UI actually silences us. - */ -function useMuteSync() { - const { isMuted } = useVoIP(); - const { isMicrophoneOn, toggleMicrophone } = useMicrophone(); - - const prevMutedRef = useRef(isMuted); - - useEffect(() => { - if (prevMutedRef.current === isMuted) return; - prevMutedRef.current = isMuted; - - if (isMicrophoneOn !== isMuted) return; - - toggleMicrophone().catch((err) => - console.error('[voip] failed to sync mute state:', err), - ); - }, [isMuted, isMicrophoneOn, toggleMicrophone]); -} - -export function InCallView() { - const { currentCall, endCall, isOnHold, setCallHeld } = useVoIP(); - const { username, avatarUrlFor } = useUser(); - - useHoldMediaSync(); - useMuteSync(); - - const { isMicrophoneOn, toggleMicrophone } = useMicrophone(); - // Mute the mic AND drive the system mute indicator (CallKit on iOS). The - // resulting onMuteChanged is idempotent, so this doesn't loop. - const handleToggleMute = useCallback(async () => { - const willBeMuted = isMicrophoneOn; - await toggleMicrophone(); - await setCallMuted(willBeMuted); - }, [isMicrophoneOn, toggleMicrophone]); - const { isCameraOn, toggleCamera } = useCamera(); - const { currentAudioOutput, availableAudioOutputs, ios, android } = - useAudioOutput(); - const { remotePeers } = usePeers(); - - const peerIds = useMemo(() => remotePeers.map((p) => p.id), [remotePeers]); - const speaking = useVAD({ peerIds }); - - const elapsed = useElapsed(currentCall?.startedAt ?? null); - const isSpeaker = currentAudioOutput?.type === 'speaker'; - - if (!currentCall) return null; - - const isVideo = currentCall.isVideo; - const displayName = currentCall.displayName; - - const toggleSpeaker = () => { - if (Platform.OS === 'ios') { - ios.overrideAudioOutput(isSpeaker ? 'none' : 'speaker'); - return; - } - const target = availableAudioOutputs.find( - (device) => device.type === (isSpeaker ? 'earpiece' : 'speaker'), - ); - if (target) { - android - .selectAudioOutput(target.id) - .catch((err) => console.warn('Failed to switch audio output:', err)); - } - }; - - const bluetoothDevice = availableAudioOutputs.find( - (device) => device.type === 'bluetooth', - ); - const isBluetooth = currentAudioOutput?.type === 'bluetooth'; - - const selectBluetooth = () => { - if (Platform.OS === 'ios') { - // iOS has no public API to force a specific Bluetooth route; restoring - // the default route sends audio back to the connected Bluetooth device. - ios - .overrideAudioOutput('none') - .catch((err) => console.warn('Failed to switch audio output:', err)); - return; - } - if (bluetoothDevice) { - android - .selectAudioOutput(bluetoothDevice.id) - .catch((err) => console.warn('Failed to switch audio output:', err)); - } - }; - - const toggleHold = () => { - setCallHeld(!isOnHold).catch((err) => - console.warn('Failed to change held state:', err), - ); - }; - - const controls = ( - - - {isVideo && ( - - )} - - {bluetoothDevice && ( - - )} - - endCall('local')} - accessibilityLabel="End call" - /> - - ); - - if (isVideo) { - return ( - - {/* Dark video background needs light status bar icons, unlike every other (light) screen. */} - - - - - {displayName} - {formatDuration(elapsed)} - - {controls} - - - ); - } - - return ( - - - On call · {formatDuration(elapsed)} - {remotePeers.length === 0 ? ( - - - {displayName} - - ) : ( - - {remotePeers.map((peer) => { - const name = peer.metadata?.peer?.displayName ?? displayName; - const isTalking = speaking[peer.id] ?? false; - return ( - - - {name} - - ); - })} - - )} - - {controls} - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: BrandColors.seaBlue20 }, - - // audio layout - audioContent: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - gap: 16, - padding: 32, - }, - label: { - fontSize: 13, - color: BrandColors.seaBlue100, - letterSpacing: 1.5, - textTransform: 'uppercase', - fontWeight: '600', - }, - callee: { alignItems: 'center', gap: 16, marginTop: 8 }, - name: { fontSize: 28, fontWeight: '700', color: TextColors.darkText }, - roster: { - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'center', - gap: 24, - marginTop: 8, - }, - rosterItem: { alignItems: 'center', gap: 8 }, - rosterName: { fontSize: 14, color: TextColors.darkText, fontWeight: '500' }, - audioControlsWrap: { paddingBottom: 24, alignItems: 'center' }, - - // video layout (FaceTime style) - videoRoot: { flex: 1, backgroundColor: BrandColors.darkBlue100 }, - overlay: { justifyContent: 'space-between' }, - videoHeader: { paddingTop: 8, alignItems: 'center' }, - videoName: { fontSize: 18, fontWeight: '700', color: AdditionalColors.white }, - videoTimer: { - fontSize: 13, - color: 'rgba(255, 255, 255, 0.85)', - marginTop: 2, - }, - floatingControlsWrap: { alignItems: 'center', paddingBottom: 12 }, - - // shared control bar - controls: { - flexDirection: 'row', - gap: 10, - backgroundColor: 'rgba(255, 255, 255, 0.92)', - paddingHorizontal: 12, - paddingVertical: 14, - borderRadius: 40, - alignItems: 'center', - }, -}); diff --git a/examples/mobile-client/voip-call/app/src/screens/LoginScreen.tsx b/examples/mobile-client/voip-call/app/src/screens/LoginScreen.tsx deleted file mode 100644 index 93779fb8a..000000000 --- a/examples/mobile-client/voip-call/app/src/screens/LoginScreen.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { MaterialCommunityIcons } from '@expo/vector-icons'; -import { useState } from 'react'; -import { - ActivityIndicator, - KeyboardAvoidingView, - Platform, - StyleSheet, - Text, - TextInput, - TouchableOpacity, - View, -} from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; - -import { AdditionalColors, BrandColors, TextColors } from '../theme/colors'; -import { useUser } from '../user/UserContext'; - -export function LoginScreen() { - const { register } = useUser(); - const [input, setInput] = useState(''); - const [isLoading, setIsLoading] = useState(false); - const [isFocused, setIsFocused] = useState(false); - - const handleContinue = async () => { - const name = input.trim(); - if (!name) return; - setIsLoading(true); - try { - await register(name); - } finally { - setIsLoading(false); - } - }; - - const disabled = !input.trim() || isLoading; - - return ( - - - - - - - - Welcome - - Choose a display name so others can call you. - - - setIsFocused(true)} - onBlur={() => setIsFocused(false)} - editable={!isLoading} - autoFocus - /> - - - {isLoading ? ( - - ) : ( - Continue - )} - - - - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: BrandColors.seaBlue20 }, - flex: { flex: 1 }, - content: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - padding: 32, - gap: 16, - }, - iconWrap: { - width: 88, - height: 88, - borderRadius: 44, - backgroundColor: BrandColors.darkBlue100, - alignItems: 'center', - justifyContent: 'center', - marginBottom: 8, - }, - title: { - fontSize: 28, - fontWeight: '700', - color: TextColors.darkText, - textAlign: 'center', - }, - subtitle: { - fontSize: 15, - color: AdditionalColors.grey80, - textAlign: 'center', - marginBottom: 8, - }, - input: { - width: '100%', - height: 56, - borderWidth: 2, - borderColor: BrandColors.darkBlue100, - borderRadius: 40, - paddingHorizontal: 20, - fontSize: 16, - color: TextColors.darkText, - backgroundColor: AdditionalColors.white, - }, - inputFocused: { - borderColor: BrandColors.seaBlue80, - }, - button: { - width: '100%', - height: 56, - backgroundColor: BrandColors.darkBlue100, - borderRadius: 100, - alignItems: 'center', - justifyContent: 'center', - }, - buttonDisabled: { - backgroundColor: AdditionalColors.grey60, - }, - buttonText: { - color: AdditionalColors.white, - fontSize: 18, - fontWeight: '600', - }, -}); diff --git a/examples/mobile-client/voip-call/app/src/screens/OutgoingCallView.tsx b/examples/mobile-client/voip-call/app/src/screens/OutgoingCallView.tsx deleted file mode 100644 index 7629af387..000000000 --- a/examples/mobile-client/voip-call/app/src/screens/OutgoingCallView.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { useEffect } from 'react'; -import { StyleSheet, Text, View } from 'react-native'; -import Animated, { - useAnimatedStyle, - useSharedValue, - withRepeat, - withSequence, - withTiming, -} from 'react-native-reanimated'; -import { SafeAreaView } from 'react-native-safe-area-context'; - -import { Avatar, InCallButton } from '../components'; -import { BrandColors, TextColors } from '../theme/colors'; -import { useUser } from '../user/UserContext'; -import { useVoIP } from '@fishjam-cloud/react-native-client'; - -/** Ringing UI while an outgoing call waits for the other side. Rendered by `CallScreen`. */ -export function OutgoingCallView() { - const { currentCall, endCall } = useVoIP(); - const { avatarUrlFor } = useUser(); - const scale = useSharedValue(1); - - useEffect(() => { - scale.value = withRepeat( - withSequence( - withTiming(1.1, { duration: 800 }), - withTiming(1, { duration: 800 }), - ), - -1, - ); - return () => { - scale.value = 1; - }; - }, [scale]); - - const animatedStyle = useAnimatedStyle(() => ({ - transform: [{ scale: scale.value }], - })); - - if (!currentCall) return null; - const displayName = currentCall.displayName; - - return ( - - - Calling - - - - {displayName} - - - endCall('local')} - accessibilityLabel="Cancel call" - /> - - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: BrandColors.seaBlue20 }, - content: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 16 }, - label: { - fontSize: 13, - color: BrandColors.seaBlue100, - letterSpacing: 1.5, - textTransform: 'uppercase', - fontWeight: '600', - }, - avatarWrap: { marginVertical: 24 }, - name: { fontSize: 28, fontWeight: '700', color: TextColors.darkText }, - controls: { paddingBottom: 32, alignItems: 'center' }, -}); diff --git a/examples/mobile-client/voip-call/app/src/screens/UsersScreen.tsx b/examples/mobile-client/voip-call/app/src/screens/UsersScreen.tsx deleted file mode 100644 index 13ae2bd76..000000000 --- a/examples/mobile-client/voip-call/app/src/screens/UsersScreen.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { MaterialCommunityIcons } from '@expo/vector-icons'; -import { useEffect } from 'react'; -import { - ActivityIndicator, - FlatList, - RefreshControl, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; - -import { Avatar } from '../components'; -import { AdditionalColors, BrandColors, TextColors } from '../theme/colors'; -import { useUser } from '../user/UserContext'; -import { useVoIP } from '@fishjam-cloud/react-native-client'; -import { usePlaceCall } from '../hooks/usePlaceCall'; - -export function UsersScreen() { - const { username, users, refreshUsers, logout } = useUser(); - const { callStatus } = useVoIP(); - const placeCall = usePlaceCall(); - const isCalling = callStatus === 'connecting' || callStatus === 'active'; - - useEffect(() => { - refreshUsers(); - }, [refreshUsers]); - - const handleCall = async (to: string) => { - try { - await placeCall(to); - } catch (err) { - console.error('Failed to start call:', err); - } - }; - - return ( - - - - Users - logout()} - accessibilityLabel="Log out"> - - Log out - - - Signed in as {username} - - - item.username} - contentContainerStyle={styles.list} - refreshControl={ - - } - ListEmptyComponent={ - - - No other users online yet. - - } - renderItem={({ item }) => ( - handleCall(item.username)} - disabled={isCalling} - activeOpacity={0.7}> - - {item.username} - {isCalling ? ( - - ) : ( - - )} - - )} - /> - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: BrandColors.seaBlue20 }, - header: { - padding: 24, - paddingBottom: 12, - }, - headerRow: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - title: { fontSize: 28, fontWeight: '700', color: TextColors.darkText }, - me: { fontSize: 14, color: AdditionalColors.grey80, marginTop: 2 }, - logoutButton: { - flexDirection: 'row', - alignItems: 'center', - gap: 6, - paddingVertical: 6, - paddingHorizontal: 12, - borderRadius: 100, - backgroundColor: AdditionalColors.white, - }, - logoutText: { - fontSize: 14, - fontWeight: '600', - color: AdditionalColors.red80, - }, - list: { padding: 16, gap: 10, flexGrow: 1 }, - row: { - flexDirection: 'row', - alignItems: 'center', - padding: 12, - borderRadius: 16, - backgroundColor: AdditionalColors.white, - gap: 12, - }, - rowDisabled: { opacity: 0.5 }, - name: { - flex: 1, - fontSize: 16, - fontWeight: '500', - color: TextColors.darkText, - }, - empty: { paddingTop: 64, alignItems: 'center', gap: 10 }, - emptyText: { fontSize: 16, fontWeight: '600', color: BrandColors.darkBlue80 }, - emptyHint: { fontSize: 14, color: AdditionalColors.grey80 }, -}); diff --git a/examples/mobile-client/voip-call/app/src/theme/colors.ts b/examples/mobile-client/voip-call/app/src/theme/colors.ts deleted file mode 100644 index 1fd920692..000000000 --- a/examples/mobile-client/voip-call/app/src/theme/colors.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const BrandColors = { - seaBlue100: '#1F7193', - seaBlue80: '#46ADD8', - seaBlue60: '#87CCE8', - seaBlue40: '#BFE7F8', - seaBlue20: '#F1FAFE', - - darkBlue100: '#001A72', - darkBlue80: '#3F57A6', - darkBlue60: '#7089DB', - darkBlue40: '#BFCCF8', - darkBlue20: '#F5F7FE', -}; - -export const AdditionalColors = { - red100: '#981B1B', - red80: '#C32222', - grey80: '#70778F', - grey60: '#B2B9CC', - white: '#FFFFFF', -}; - -export const TextColors = { - darkText: '#001A72', - additionalLightText: '#ACB5D2', - white: '#FFFFFF', -}; diff --git a/examples/mobile-client/voip-call/app/src/user/UserContext.ts b/examples/mobile-client/voip-call/app/src/user/UserContext.ts deleted file mode 100644 index 513dfcf73..000000000 --- a/examples/mobile-client/voip-call/app/src/user/UserContext.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createContext, useContext } from 'react'; - -export type UserSummary = { - username: string; - avatarUrl: string | null; -}; - -export type UserContextValue = { - username: string | null; - users: UserSummary[]; - isLoading: boolean; - register: (name: string) => Promise; - refreshUsers: () => Promise; - logout: () => Promise; - avatarUrlFor: (name: string) => string | null; -}; - -export const UserContext = createContext(null); - -export function useUser(): UserContextValue { - const ctx = useContext(UserContext); - if (!ctx) throw new Error('useUser must be used within UserProvider'); - return ctx; -} diff --git a/examples/mobile-client/voip-call/app/src/user/UserProvider.tsx b/examples/mobile-client/voip-call/app/src/user/UserProvider.tsx deleted file mode 100644 index c2e834d4d..000000000 --- a/examples/mobile-client/voip-call/app/src/user/UserProvider.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { - type PropsWithChildren, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; - -import { UserContext, type UserSummary } from './UserContext'; - -const SERVER_URL = - process.env.EXPO_PUBLIC_VOIP_SERVER_URL ?? 'http://localhost:4400'; -const USERNAME_STORAGE_KEY = 'voip.username'; - -export function UserProvider({ children }: PropsWithChildren) { - const [username, setUsername] = useState(null); - const [allUsers, setAllUsers] = useState([]); - // `true` while we read the persisted session on startup, so the UI can avoid - // flashing the login screen before a saved username is restored. - const [isLoading, setIsLoading] = useState(true); - - const fetchUsers = useCallback(async () => { - try { - const res = await fetch(`${SERVER_URL}/users?exclude=`); - if (!res.ok) return; - const list: UserSummary[] = await res.json(); - setAllUsers(list); - } catch { - // network error — ignore - } - }, []); - - const register = useCallback( - async (name: string) => { - setUsername(name); - await AsyncStorage.setItem(USERNAME_STORAGE_KEY, name); - await fetchUsers(); - }, - [fetchUsers], - ); - - const refreshUsers = useCallback(async () => { - if (username) await fetchUsers(); - }, [username, fetchUsers]); - - const logout = useCallback(async () => { - setUsername(null); - setAllUsers([]); - await AsyncStorage.removeItem(USERNAME_STORAGE_KEY); - }, []); - - // Restore the persisted session on startup so a reload keeps the user logged in. - useEffect(() => { - (async () => { - try { - const saved = await AsyncStorage.getItem(USERNAME_STORAGE_KEY); - if (saved) { - setUsername(saved); - await fetchUsers(); - } - } finally { - setIsLoading(false); - } - })(); - }, [fetchUsers]); - - const users = useMemo( - () => allUsers.filter((u) => u.username !== username), - [allUsers, username], - ); - - const avatarUrlFor = useCallback( - (name: string) => - allUsers.find((u) => u.username === name)?.avatarUrl ?? null, - [allUsers], - ); - - const value = useMemo( - () => ({ - username, - users, - isLoading, - register, - refreshUsers, - logout, - avatarUrlFor, - }), - [username, users, isLoading, register, refreshUsers, logout, avatarUrlFor], - ); - - return {children}; -} diff --git a/examples/mobile-client/voip-call/app/tsconfig.json b/examples/mobile-client/voip-call/app/tsconfig.json deleted file mode 100644 index 6d82505fc..000000000 --- a/examples/mobile-client/voip-call/app/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "expo/tsconfig.base", - "compilerOptions": { - "strict": true, - "jsx": "react-jsx" - }, - "include": ["**/*.ts", "**/*.tsx"], - "exclude": ["server"] -} diff --git a/examples/mobile-client/voip-call/server/.env.example b/examples/mobile-client/voip-call/server/.env.example deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/mobile-client/voip-call/server/.gitignore b/examples/mobile-client/voip-call/server/.gitignore deleted file mode 100644 index af6a1f3b3..000000000 --- a/examples/mobile-client/voip-call/server/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -voip.db -apns.pem -fcm-credentials.json \ No newline at end of file diff --git a/examples/mobile-client/voip-call/server/README.md b/examples/mobile-client/voip-call/server/README.md deleted file mode 100644 index 303f7d534..000000000 --- a/examples/mobile-client/voip-call/server/README.md +++ /dev/null @@ -1,146 +0,0 @@ -# voip-call-server - -## Run - -```bash -deno task start # listens on :4400 -``` - -## Credentials - -Both services are optional — configure APNs to call iOS devices, FCM to call -Android devices, or both to call between them. On startup the server prints which -services are enabled, and `/call` answers `503` when the callee's platform has no -credentials. - -## APNs certificate - -APNs auth is certificate-based — you need a **VoIP Services certificate** from your -Apple Developer account. See Apple's guide: -[Establishing a certificate-based connection to APNs](https://developer.apple.com/documentation/usernotifications/establishing-a-certificate-based-connection-to-apns). - -Drop the VoIP push certificate **and its private key, combined into one PEM**, at -`./apns.pem` — it's presented to APNs as a TLS client certificate. The bundle id and -sandbox host are set at the top of `main.ts`. - -```bash -# combine an exported cert + key into one PEM -cat cert.pem key.pem > apns.pem -``` - -## FCM credentials - -FCM (Android) push uses the [FCM HTTP v1 API](https://firebase.google.com/docs/cloud-messaging/send-message), -which authenticates with a **Firebase service account**. In the Firebase console -go to **Project settings → Service accounts → Generate new private key** and -download the JSON. See Google's guide: -[Authorize send requests](https://firebase.google.com/docs/cloud-messaging/auth-server). - -Drop the downloaded file at `./fcm-credentials.json` — the server reads -`client_email`, `private_key`, and `project_id` from it to mint an OAuth access -token scoped to `firebase.messaging`. - -## Push routing - -A push token is only valid with the service that issued it, so each device records -its `platform` (`ios` or `android`) when it registers. `/call` looks up the -**callee's** platform and rings them through the matching service — an Android -caller reaching an iOS callee goes out over APNs, and an iOS caller reaching an -Android callee goes out over FCM. - -## API - -| Method | Path | Body / Query | Description | -| --------- | --------------------- | ----------------------------------- | ---------------------------------------- | -| POST | `/register` | `{ username, voipToken, platform }` | Register / update device VoIP push token | -| GET | `/users?exclude=` | | List all registered users except `me` | -| POST | `/call` | `{ from, to, roomName, isVideo }` | Send a VoIP push to the callee | -| WebSocket | `/ws?username=` | | Bidirectional signaling socket | - -`platform` must be `"ios"` or `"android"`; anything else is rejected with `400`. - -## Signaling (WebSocket) - -Every connected app opens a persistent WebSocket at `/ws?username=`. The -server maintains an in-memory `username -> WebSocket` map and acts as a simple -relay: any JSON message that contains a `to` field is forwarded to that user's -socket, with `from` stamped to the sender's username. - -### Message: `call-cancelled` - -Sent by the **caller** when they cancel before the callee has answered. The -server relays it immediately to the callee, which calls `endCall()` to dismiss -the ringing UI. - -**Caller → Server:** - -```json -{ "type": "call-cancelled", "to": "", "roomName": "" } -``` - -**Server → Callee:** - -```json -{ - "type": "call-cancelled", - "to": "", - "roomName": "", - "from": "" -} -``` - -The callee only acts on this message when the call is still ringing (i.e. -`startedAt` is `null` and the call is not outgoing). If the call was already -answered the message is silently ignored. - -## APNs VoIP push payload - -The push is sent with the headers `apns-push-type: voip` and -`apns-topic: .voip`, to the VoIP token the app registered. The -payload forwarded to the callee's device (field semantics are in the -[VoIP calls guide](https://documentation.fishjam.io/docs/how-to/client/voip-calls#6-handle-an-incoming-call)): - -```json -{ - "roomName": "", - "displayName": "", - "isVideo": false, - "avatarUrl": "https://…/caller.jpg" -} -``` - -`avatarUrl` is optional. On **Android** the caller photo is downloaded and shown -in the incoming-call notification and full-screen UI (falling back to initials on -failure). On **iOS** CallKit cannot render caller images, so it is delivered to JS -(`onIncoming` payload) only for your own in-app UI. The server assigns each user one of -its bundled avatar images (served from `./avatars`) at registration, and sends -the caller's in the push. - -iOS 13+ requires that every received VoIP push immediately reports an incoming call to CallKit — the `@fishjam-cloud/react-native-webrtc` pod handles this automatically. - -## FCM push payload - -FCM values must be strings, so the same fields are sent as a high-priority -**data** message (`isVideo` is stringified), plus the `fishjam` discriminator -the SDK's messaging service keys on — a data message without it is never -treated as a call: - -```json -{ - "message": { - "token": "", - "data": { - "fishjam": "voip-incoming", - "roomName": "", - "displayName": "", - "isVideo": "false", - "avatarUrl": "https://…/caller.jpg" - }, - "android": { "priority": "high" } - } -} -``` - -The high-priority data message wakes `@fishjam-cloud/react-native-webrtc`'s -messaging service even when the app is backgrounded or killed, which reports the -incoming call to the native Telecom stack. diff --git a/examples/mobile-client/voip-call/server/avatars/coral.png b/examples/mobile-client/voip-call/server/avatars/coral.png deleted file mode 100644 index db8b92bdecc2e01de4971041a873ac8dc18ae531..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10720 zcmXwfWmFtp(=9TAAwU=c1cxDb@ZioMgS$Jy3GO-p1_|yK++7mfouI)Tf(3VX`FP&D z?vL(Xt5=_0ed<(K*RHcel@uf~-x9q=KtRBhmJ(Nay$<|$08w7g6<2nZkqX>o{} z2jXD{dOEc_Y-g-w>CXsjC#&Et0-RmYX8g<_WSY9_>C;DT-P1DjU_c5T1@@KL2 zY`?iD)i*T_qYy+6hL0_thvxriDlIV}4mEFw;GsN$(%(n{UY zl=IY5`=+9Oi?yBk!;{y8rMK6T>d{`wUoY=$!5Wx?y*l{f0hP~FQMq1qhj}r(qTVjP ze!{;}hHrx76A>I20}1ZzHaDHNIHfB*zf$+)hSXFMutI*5X(fGUOFxqZq^`tQ(W4G( z6hS*PFPTPZgN16qazRc(Amb9H(t?JIth=WIW1$oAA-Db3A6%B0hyEZc{Z^xIQ-vqU!s;zffejn7PnCn6+@dV+#$gfANbsXIB|;-!F~V zL3v^)oG-P#t~-K|?}i`az=&P`J=hwGfx=OzM{suPuQ&sQ9-hau_**3ueeG0aTJ6I1lKL8D)-^5>TxHhxM6pco<27$HN$ z@i6Y&=+HhlIPWLJ1hH;lztpQx^z;0+stWEH#f=2#{jjzC7rGDo9BVCsxrK#E`+Gfj z>4XKe9V841XhH zDMM19C*HQrzIZ=>xeGL+mI*pmN`ANc(H>;O`CG;WbK6MxYe)nxh(5t5r#U7#uo5m@053<|6$cEZS+hzG*2+T1DKji4QWE`7)Emp6rkMF z(BUoTfwjjz&O6gJUaLoobo=?VaiKDQcT6z}Y5 zxb^sQZbXV{x<;C^&}?ouH{B4VM2w1F`8)L-TZ#CLQ;4PGXoKk;3FPv0iBHsXHCd&- z-s2|qLogzuPvn?IQNzm=q{clP_?f%rN%VDg=_sM!*Dnk27(W6uJVasP1O)j)SQrp< zi^3IfVzWt1reuBY4 zjWGli8jK5dR98V@Z}-<+Gp#(lT9)7;$<$_(fWd^+J1{Yvyed6%nTJz{6Bxxz8t?v^ z9F8Tw6KC{%_j|wLV;nqRvUIP5)~(cZ2s}|~JANA5Keo&p!}dokfsT;y9>e?zN~iJ{ zf!%$x6Ov3XKI~4@dn*%@eNv(r*s`uyaL|%>&$IKw>SbUkT~qjc~+X1j^e)o1evG{z0pI1c+{_EEwq<;+@yUV0!(6` z2HwAAOE&oI8!7|*d^{{XTgVYzZVgIRWCE7&_Ui2o$Ek4p=?r~o0n z#w;ZfvIZr9^*88$=1ldM$EWF05$ha}kkQ0;?Qf11Q|C=8gk+iHT?&qs2Jv zHwpaAteaM+Tu@iE^i>EGi5cjTq2*Gtq*|2Vw{7d`rQP6Zua4C!FDeWIe+V7??1E1t zb=t08kjhP#v>sU)c@7$9BkVQr`hbp;PpzB9Y$>MN2FZR87z+7g0N-S-6RbJP^Ww2K zpvpk5DYS$EL212~*pZ_Ev~L6wq1It^_WN8wwJ5)+39tq!UK38Q>Kxy`O(`dEI?V4A zs7wJc5%H*A(7PmYF$ZP^06}HF<>4g?fU|;oQhYP{jB{*{HE*nN^i`Ndu1))un}0$T z%tOZ|a$y-@rD*?<9o)Bv%I?~>tqSsIrBD$3hq-|yAN9Z~9cOyMlnU0Nix&BipH1JR z0fW)HIWJ^{gTLNOG6X7-4;JLm>%Bz+aCDK<1X}lSbvM>?>RnAsZkzD5%rpTv&`)|q zrR3jFJkA9AQJ_Hd6J-{IeRfM_`i*KCG)MX!+^j$2h#xJv!4t@)$_^{IsWY&_S^?QJ zs(Fl&>7I}166vNTia#VExTHP3pP&AG{DHPXj;pyx%u>M^$z`^Ty+%Zd&W1r~C-@?8 z*5>}yY(z{;^D@b9Ko~>!r#Y4;Oohpq7ZW)?D-`^-e|DxjUNc1oxsento`AFQx70oc z#14r}R3st)oF>{SIrBNpPpVMJ_J_~Xhuoi{=EKDM9Hhe#Ig4F!8u;zM6`GtM$vJJA zAJehD-=_tV0l=t;3-J@u;1o0)cx$|xr3nzHwnQ?i@wXN!I+UYLjYQLYchZ#+l`W&| zR1IESQc~cxwGSg)+^2_6@lr;SbP5VGC3R}^MUj}{^qAA<%Tm6*URTt8T9;EjNsB%* zJy3m}EG08e6oaPUCqCapZt*?8i3QeXIGV+tf{J?&VJ1Cp*OGk1g6HXvtdQbfTzNUD z=6hx6@OLi>hL9t1De+H`z=UKP?pPN0T9MJY8wab=-w~DT95wa>U2OioOFhCM;*)0n z7){^xXmK?;0}o;*lPG#IZ>S}?;1_?o2)*^%Qz4<=XNN_lV~bZn_{dTEz1iP0nd4L} ztN|kbIN=DAOW_i2q*NoQo%ilZ3#GV92BRkkejPC`)C{_&cxMJi%R@7*s|$a+>`CGl zw>scwBeQn;2x(1frK&d%j+kq`zwA}<8^JZ98ij@m!i3_p!-#q1=#i!m=b9u~dj08w zS@ZiAep2*xI9{R93o`vSLCbZsPurv)_?WR!%SPM*mT}18lVPC#uvQ(@qf%inExkGK zqRq8>{pU6!EY#6hu+oDO;zRxHs#oxF&rysEzZ7IP4aJc{yd$Kaf<|%Zai>3j@^3wf z3m4Zqz8xugbDqVm|6zFt$b5V?$ux(p9NHr+-@xRn=97qf@C$$~^TE-fYINZ(meJU`eUrund*(` zqUD~z9%Yc;HC3mWOECOYF`fjY`6DUI>A0ZpZe!s6IoNR|GZsO(9~>A=GNH2Rcv-Gl z?^%9u^3fYG8+MvgrT6Lye2q36X&ICmLus!5J|8T|e^uaqu)?IuV!s>dds~uRkbspq zVZsD}pWyE#6hjK()wg+1@5sx&&osV1zytnCfZj$UCn&zvr`!4*RB-XP($+R zfZ_}!yHs>ZShE_tgiIXue3pdspnP>~i|Q4qc2`F5#q18P2$zn^phNu-0ahtC9GK9k z%$-mUv<6XGQ))x73L(_#86(AAwVmdac~FO(Qv1b)bKNM}N-G8#F8y^5i?%3AO>6bX zNvG7Y&LkYA`R8vZlDt3tz%nISRYRR~=|3LVj5XT)MuY5^L%KPN=}u!Phl^uPYxJtc z2dHJ^!?Z3Q;gnK;y|yL`M9F^jF5<_BjBPvyRi!xjH_@+-*(MhQzq_F--amPyC$?~$S#p~`AdNp-LErSFa?C@U|A)L2 zm8`}#62uHiz^!5`d73v5(<|=hDrE=1&Hje~o4@$NA&pFqq+D54t>8zZEs3e$`rybL zud02tI`&&z?I@C~-xDfu)@l@5BJ4|NWh2T|DR_=8q8YATY^`S;ITCgCkZ0(eGK`y;*;@8{R)VM~wmd^G{#;O(F0rQr zp&-T5sZ!OMjwA}{NlQ2~hH|%t=sS{~B7rTUN|U8z+2lRyaY`HQP^+Fo&Hc1o0qOMl zl0dniC>NHoMuY8v-Z~R*H6+9e001^*r{eJc_X0|<&KVQKl77km>W*&5w-v4w67NEr z@FC_UaFx?8U`d);)~nU2G)cxQXx|fb5Gh_*(0gJJ(!aObuYTi9EPbkMSg1q#3aXq* z_jJd=XB%ItQEk)QZKYl46odRZ=%7h~UyitPD*DUR!J=XSj;<6OYaR=Ij&l7U-!&#J z5?pGlBmZ5&(_~jm=A;YiDDM-VU|*Pm`#G~e5puA+ePh;*&~yInJZ$xY{gb`#1Z zAwSmQ-g3za-i<#YKt>T(sP1XQv;N1hVi-^T6M6$_hPfGU&G!S%I(vTSoZ;2Bw8)x) z@u^BrqP?^gLGSaF1tx~8xa+)@!oC9czf6_Q54szb>2fT32BCbK)e$pR?uU*cN97W*x)V=Y=XA~d0~Pz#%i}*w zec6xPo%T6(y^6o)~ROOxju-3zI@}P`KCM2CdK9zKQ zT{WOsJj(xA;>j$V4VxM^VdhP$KY3nyp=^J6>X7YSYfIz0+Dzjp)Vm>R=u+pTvwj2>S>J+ zoZg3<&vJ%!VhXN>*6vW`UgHTxt)NS{gt)1vaJVAh)=u>7s-uSK7q_fX?xw9gEf^pI zgs`4r_qf+L-1ddLNN7cZt(4DzULZV>VU9Cw*BjjL*`Clx#`9KJDEFasH*8W_l9ysi zxXnxF$HYcaru2)#SY%cc#HRfWy8iGDd>C)8_7h-pZL6zbUVm9SwHCZXuf8N0Q_7EP zE9?mPwTv4v%iaG?8P3Q*$&qNSt5(&pF|T(ox1Q@I&%e$T$m9B#9_fp4dU8EEDTL0j zp99^_Lk8Hu9r#Vf;Q~1)YlJ0o)me&v%Kqzq{WzjIE;ou;;M)#9`Qdpg|Z#!*NkXH6r?`L~GZztn#dgfPCj7_~bDpCD}tYLc-IegaSPq|Z!bNDqt}_u*OFIu_Go``b z^M!U6nQLy!3z;kG1AO*PO9cjMEP%${A*_((=SLQl?)KO;`wlVjEpGd}R@M%kVV1e` zUCg*yLSN16=k~|xEbOed>-}09@AE3*6Ovy-{d1{#?PdQ&|4ylv8;>bpy6_8jU5nd$ zRXistp;oU7)M$SqCHKZU2qFRbVfPeL$`UDl09%c%PhC`) z+Eu$h+R!GgSYeQ$Q(yHa--f-<=^@H&FllM@I!G-+Z-Irc&&gmPR=I>TFd<0y1vM8O z!K(bIe?KgB-d85fTq%5VwehGr8+Llm^qOegm+p2#lldN(^^WGiGJm{8i;ab)l;L@$ zmLK;W@_O)0)lZF^uHg7@1$Dw(eW5IQl8wiNWg21b8O;jIF4sTwn@*VQah2Kb5+u59 zQ(!;gRO=u-mB@3^{T*+JLRl+jLR(siX zE($b>PeXAJ0`$EXCLR)Bp}E?mj7M4KzC$y!7Db7S6*v68&Wb-(%oc0kGz<<`7YG>- zyst!-CXgpk*Lyga9{Y)%0}Jt&2f77S!~gCI)`5TJ`#J=C$NSqJsMi_tKp5F%hsv(}t15M78{Z;7!-=?OX5(L4%}THy1{C zJIfXsnMOJpz$CZ}r7t-&5L8*;%zRbq_RHP5U;!P{s1cgfvbledEBzyCINkVcI^c*c zUM?7yag(0&=uY&IaelN@qapI6V|143p7=zOp67++2SGtIC!e0GBD>#!O7#?{jGQVf zxx^Ep>lbks$IeY^+nPrMDU~Nn0}|=nRfe!x1}_Y5fs)>+Tui2i*+XyWT-R-#$S;K%t(&VrV zK1P2xH)q@F^?QvCLh0NorsL@y3TZq5B5T?zSlUdsEtNR)d@G)=N;Q`>ER%4kn)=>S zbi(Zk&W$ed6Oj6Xq#*%~`R@J)_ejPVCiOn^t1{1=+VIqle!snP?l4Wc`NCwGTmbPs zQLk^S40XUfmu#?tTiufASrSgs&_$w3lE@Ca#B)}Y*ZK`LS!oP7=H%R7m@b!(Yx6M! zH~Aa1`R|RG;cc|{Kj*KdCrN6iBFh7&#HBT>*Jtp9796+4Lxki zJbW>D&z}JqvcmDr8$K$sUpc6;>^z*Vnn^{A7f*un=QTX*8KfTtThJG_o2MT#8=h4( zSuEV)O=(IFsNHN|=NBKjby$SOk~M>@g>Aj;WIgW;Il0ki$Qswb#Kf%#|2#@gfK|j4 zkXKsdcdIh?_pcbjCehHFC0L2IV`9%bV>Z&O9_w7EjixsUOCxuhpKgC`9<&qM(yQ1^ znI+?vkm=4jd<2JF_9e~7;hFwCSAx_C%5Q9D_9`Gz0w;M#TFSfssT7 zFcPx4EULAC`?qh%b!^&<*5A0NXIk=k{n$T^pPmp913{4iNGTlGvCi$Eg;jd71Gt(@ zFxk3Oa*|tHCpRSUJQ=na4QF<-QzrfcH5}YbE<@oAz93hKu1UuRgbp_pl)(Ym6-f?1 zz7e$PF$#N)aR)}y{WfrpM3SDDlrwNaCj`HlqPP1kaY5LWYxN_l|97CvV_b^WDwmf? zSfv_=SN7_w69(qf_hDW!DtJfXARnrSH$ z=UJhlC{~YrG@`B`zN_}_9*IUZTRITfJ<~j^GVEkes(8NN?jp=XzDFbOMgZYam59T^ zT54HF`pvQ54EQ=_9b#N)7{RDl2HrIZk9q!n(k?Cngre6=H!97AoZQ9~S_@BvE9TNx z8EqCjOpDk{7V@Qo5Ft`(qJo|EI=b?V42zLndc|%aGhc4{+1DGFXZ=nJ~@7@ zF>k(n`u%|-WA38SsN!AYl_r^UMBR&3=xfcNpIG)u_&*|&|9-VZ=1a$teDgcD`YlJP zRWfgPf%r6`=5LB(1#M~bbRjnSMOs{j(>z`w|8b48-#Wg;dxq5CC#*rq>^cTL)w{q^ z9eVEk2!pvqQdL7fX8#W}3_fOZ19h&mW#It{iIjB zf-%i@@69RDESS@ryVri5@UIYOGjEXIh;lU*6tB!m+g07V%q)2+3&CQL$;JJ@T!s{? zyO*D2qw1y1jZ*`^s|^}&^|I_DYke*-wv`L|lE;;Ap&isB$*u{ulJ zNh>&tDD7IZ!*}#L6E8%58?UMMZ{fb3L~^-s`&=t^^8b{q@z-N5y~p+H0wzhhF;VZ4Q$SR)2L!Qv!vMOz!a#d9uWZ z6Mb3>di?Eb(1GvsKb_4O>2`JO;+Bos?&vJ35c7w ziH;(w|I7nGS~v%jzQf$Pi>lh@u;pR7x|XdvV7E+KwE)Pna%u%O+#(nTPVFx?^nJsE zY!1br%%IFX3%XXY-=K<~COOqRCh`(5KrwYa3-uEkw${g4(ZVa~Jfjp_UU5#OVl;}g zZqYEhCWF~tDYC8srj??bv9EtA8640g^Av5QyV0?G8CLijhA5Vug`{b$Uj?qmxg}O= zu(uo&D&+H$*|QB#4MGGqfE3B*x=5HylPqmRXWpz%gYPMl8S#vQJ33jH$!oB-jqIbW z=b6>8aV|9Aw<=FJL3QEeiFNQq6pmg58z`n`nIrC|abrVW7vbsbLkDfHJ@KzZc^PNz zL6z_Ct|d>_#Y9O*Zqg_UPl#XUxB)3Y{+-U=qL1|Kxt{d*wI+i9;T*IVU^2RL4s*dR zNq-2Zd-5RnA;Hellst@Y^MGq>wxC_teRK8U>9{B$7{fa%B7<} zRwUzlMK>H51A1*)p5@=NXfKYc{Jz@px{xaxBI2}p@~4tkW!?v2q%&QBg6?=-FJvYP z4BXoWg{e*cOMDGi=uQ{|#S@$?^s^>tAEWj8sS9qai+9JauN3maV&T{URuR(ScD!YC zm>l8|`{|@pHgSI{DW1ReCwwz!q?!p4n8#j6EaiR}uYZr#-B7!(cUcls51w)nI0ueg zz3&6xe$)LsS}*BDW7_u2+UrMhxHRp6(yY3ANU3Fer(Tr9J#2}jJPA#hb?oQ;QTDbn zyXgQ{Z_Og^2fB=9>i;`3@U9ogTDG&~hkZ2o-HI(!D(2TenwKKG_R~JEq=RV44~4yF z^LtZ@x0tx9!6pa@Xt4iu0ff0A@d@RiEyd(Lg>`IkmX_xIAmHVjFIhsrr8^YO9o>Q^ z8(fS95^5c|W5E#R0z35jsF%q~VFG#rHWXXF#JU#kP*GOGY7txjsr1;VSbH4TH9w`A zn)>n)EA6WdasYY89jYP7Go;IKxF#YweO!)J& zS$a5i#J$Q*ctiH2s*hN4AjP{(cyq~|VdDl`JU#C?*Vad=4#jZqrTwriFVnJRiznw8 zZF-bL?$pU-#-ekO{=01}R6cV`S+l_XB5Ow2Oi7Fh35*6%D zxjvEuh{1;>VLX!oE}Iqj_}Z|PVummLnpkVrcHm+fXz#AY^o>$raQ}Zdhntl!M1l#ZqV+F$DK;zq+cDooM>?OqBVbhYECF=rmOaj$e8{VL9*Wgu3d~PUGx({rVB%^OE8L6 zmF7D6fP#;#pCaiE_2ov?C$eOK@SJ}**3S)b=O3?$tw6nW=p73Al&*&4MZWY^ZFYV;Jlw}PK6i=tp;}vKt@3P+C$0O zg>G;o0aBRVYCF2Avpf<>Bo$aBLZHF@C5^Mwl)413?Ive-qUzsdv!c*RjRatSn zFJqF0e=9qQ8S58+i6sES5s=-N)h`Y|C^3GeK-q5;%f#FCrvRc{=tZ(@9$bC9C~igh z5(~SgGi=Lv)na_Z!i69N7x`hyzNmZsZMmDo^J7Hnn!9ZsDsp1)vZUv50CYh!kH~d7 zJ?kyo+ZmuQiYsANXLsOhS1_8f-v&cMCN0ZMe|yq4yAJU7_5(SVQv*sW7@B53U&C-ce>=gwRDG;SWG0oL$qiuT^3p7F>Wa0p^t z!`5B5uw<5hD!w_fQm|?G*eR(H$@_R@_Ti6G<65LIziDS-vRVEHl%^L`=&oz}VegI@ zUWG9-R#m^D!BJq8wArD$IWL)ZQS-+F2Gy#C+3yJLU6Qw@LKRzIiF97wBOI|&q4)6a zDA0ZXpPVpb=`d&3KK49Z@f1_2)Ii^rC^!|2^5G~@*jBzA_Cx4722|RFUgf_#M%(!; z_R1J8`-7e8q5u#KsztV}^S};h{3eddP`$K$Jsu^#`dK_Jc1k*PTcSXuYeif{otDg)q_h1~%L` zi|Q%Y)M+wjzc`)k-TY-Ryp&x;q4_E2SzI$?^7C$1NvB2|{QIj? zPNNcr3d^+*u=D9SvIfRYA^*p;6e#-CMOPg)M3?DL;D{{ey^uJHf> diff --git a/examples/mobile-client/voip-call/server/avatars/mint.png b/examples/mobile-client/voip-call/server/avatars/mint.png deleted file mode 100644 index 5a8dfd4298a3ebd638a6b78dab4b6b04d5f555a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11036 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 diff --git a/examples/mobile-client/voip-call/server/avatars/ocean.png b/examples/mobile-client/voip-call/server/avatars/ocean.png deleted file mode 100644 index 67df1936941a9a24cf7b43f429df9863b9883509..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10867 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 B_+kJ6 diff --git a/examples/mobile-client/voip-call/server/avatars/orchid.png b/examples/mobile-client/voip-call/server/avatars/orchid.png deleted file mode 100644 index 0e0f011d8b8f7e33ea8c41c4ca82adf5c1d141a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10897 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%0A`H2`A!LX)52PSKxFS2PX-GzKSw%dh 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 diff --git a/examples/mobile-client/voip-call/server/deno.json b/examples/mobile-client/voip-call/server/deno.json deleted file mode 100644 index b95a4f7c2..000000000 --- a/examples/mobile-client/voip-call/server/deno.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "tasks": { - "start": "deno run --allow-net --allow-read --allow-env --allow-ffi main.ts" - }, - "imports": { - "@db/sqlite": "jsr:@db/sqlite@^0.12", - "google-auth-library": "npm:google-auth-library@^10.9.0" - }, - "fmt": { - "singleQuote": true - } -} diff --git a/examples/mobile-client/voip-call/server/deno.lock b/examples/mobile-client/voip-call/server/deno.lock deleted file mode 100644 index 694919a8a..000000000 --- a/examples/mobile-client/voip-call/server/deno.lock +++ /dev/null @@ -1,207 +0,0 @@ -{ - "version": "5", - "specifiers": { - "jsr:@db/sqlite@*": "0.13.0", - "jsr:@db/sqlite@0.12": "0.12.0", - "jsr:@denosaurs/plug@1": "1.1.0", - "jsr:@std/assert@0.217": "0.217.0", - "jsr:@std/encoding@1": "1.0.10", - "jsr:@std/fmt@1": "1.0.10", - "jsr:@std/fs@1": "1.0.24", - "jsr:@std/internal@^1.0.14": "1.0.14", - "jsr:@std/path@0.217": "0.217.0", - "jsr:@std/path@1": "1.1.5", - "jsr:@std/path@1.0": "1.0.9", - "jsr:@std/path@^1.1.5": "1.1.5", - "npm:google-auth-library@^10.9.0": "10.9.0" - }, - "jsr": { - "@db/sqlite@0.12.0": { - "integrity": "dd1ef7f621ad50fc1e073a1c3609c4470bd51edc0994139c5bf9851de7a6d85f", - "dependencies": [ - "jsr:@denosaurs/plug", - "jsr:@std/path@0.217" - ] - }, - "@db/sqlite@0.13.0": { - "integrity": "4545c635e0b3d4ddfdc0f2240f932f24b8ad0178e9c2e3a0f9403e7b18ae2fb5", - "dependencies": [ - "jsr:@denosaurs/plug", - "jsr:@std/path@1.0" - ] - }, - "@denosaurs/plug@1.1.0": { - "integrity": "eb2f0b7546c7bca2000d8b0282c54d50d91cf6d75cb26a80df25a6de8c4bc044", - "dependencies": [ - "jsr:@std/encoding", - "jsr:@std/fmt", - "jsr:@std/fs", - "jsr:@std/path@1" - ] - }, - "@std/assert@0.217.0": { - "integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642" - }, - "@std/encoding@1.0.10": { - "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" - }, - "@std/fmt@1.0.10": { - "integrity": "90dfba288802ac6de82fb31d0917eb9e4450b9925b954d5e51fc29ac07419db5" - }, - "@std/fs@1.0.24": { - "integrity": "f3061b45b81673a2bece689da041df32d174be064c89eb6397fb5718d3fb7877", - "dependencies": [ - "jsr:@std/internal", - "jsr:@std/path@^1.1.5" - ] - }, - "@std/internal@1.0.14": { - "integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7" - }, - "@std/path@0.217.0": { - "integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11", - "dependencies": [ - "jsr:@std/assert" - ] - }, - "@std/path@1.0.9": { - "integrity": "260a49f11edd3db93dd38350bf9cd1b4d1366afa98e81b86167b4e3dd750129e" - }, - "@std/path@1.1.5": { - "integrity": "ccea00982ea28c36becaf6e62f855406c76a8c32d462f66f415bbb7d83a271bc", - "dependencies": [ - "jsr:@std/internal" - ] - } - }, - "npm": { - "agent-base@7.1.4": { - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==" - }, - "base64-js@1.5.1": { - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bignumber.js@9.3.1": { - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==" - }, - "buffer-equal-constant-time@1.0.1": { - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "data-uri-to-buffer@4.0.1": { - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" - }, - "debug@4.4.3": { - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dependencies": [ - "ms" - ] - }, - "ecdsa-sig-formatter@1.0.11": { - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": [ - "safe-buffer" - ] - }, - "extend@3.0.2": { - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "fetch-blob@3.2.0": { - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dependencies": [ - "node-domexception", - "web-streams-polyfill" - ] - }, - "formdata-polyfill@4.0.10": { - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dependencies": [ - "fetch-blob" - ] - }, - "gaxios@7.1.6": { - "integrity": "sha512-aIQ0QL8Or8vsUhHyXGA6AohOFRrAAiHhrvsAG6myzcSlfhxSXtnwXA/pRuQTilFgjhLe30swK5rg1d7E1f8Izw==", - "dependencies": [ - "extend", - "https-proxy-agent", - "node-fetch" - ] - }, - "gcp-metadata@8.1.2": { - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "dependencies": [ - "gaxios", - "google-logging-utils", - "json-bigint" - ] - }, - "google-auth-library@10.9.0": { - "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==", - "dependencies": [ - "base64-js", - "ecdsa-sig-formatter", - "gaxios", - "gcp-metadata", - "google-logging-utils", - "jws" - ] - }, - "google-logging-utils@1.1.3": { - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==" - }, - "https-proxy-agent@7.0.6": { - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dependencies": [ - "agent-base", - "debug" - ] - }, - "json-bigint@1.0.0": { - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dependencies": [ - "bignumber.js" - ] - }, - "jwa@2.0.1": { - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "dependencies": [ - "buffer-equal-constant-time", - "ecdsa-sig-formatter", - "safe-buffer" - ] - }, - "jws@4.0.1": { - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "dependencies": [ - "jwa", - "safe-buffer" - ] - }, - "ms@2.1.3": { - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node-domexception@1.0.0": { - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": true - }, - "node-fetch@3.3.2": { - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dependencies": [ - "data-uri-to-buffer", - "fetch-blob", - "formdata-polyfill" - ] - }, - "safe-buffer@5.2.1": { - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "web-streams-polyfill@3.3.3": { - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" - } - }, - "workspace": { - "dependencies": [ - "jsr:@db/sqlite@0.12", - "npm:google-auth-library@^10.9.0" - ] - } -} diff --git a/examples/mobile-client/voip-call/server/main.ts b/examples/mobile-client/voip-call/server/main.ts deleted file mode 100644 index 4d120f6c6..000000000 --- a/examples/mobile-client/voip-call/server/main.ts +++ /dev/null @@ -1,323 +0,0 @@ -import { Database } from "@db/sqlite"; -import { JWT } from "google-auth-library"; - -const db = new Database("voip.db"); - -type DevicePlatform = "ios" | "android"; - -const isDevicePlatform = (value: unknown): value is DevicePlatform => - value === "ios" || value === "android"; - -db.exec(` - CREATE TABLE IF NOT EXISTS users ( - username TEXT NOT NULL PRIMARY KEY, - voip_token TEXT NOT NULL UNIQUE, - platform TEXT NOT NULL, - avatar TEXT, - updated_at INTEGER NOT NULL - ) -`); - -// --- Avatars (served from ./avatars) --- - -const AVATARS = ["orchid", "mint", "sunny", "coral", "ocean"] as const; -type AvatarName = (typeof AVATARS)[number]; - -const baseUrl = (req: Request) => - Deno.env.get("PUBLIC_BASE_URL") ?? new URL(req.url).origin; - -const avatarUrl = (req: Request, avatar: string) => - `${baseUrl(req)}/avatars/${avatar}.png`; - -/** - * Picks the avatar currently assigned to the fewest users (round-robin balance), - * breaking ties at random. Called once per user at registration. - */ -function assignLeastUsedAvatar(): AvatarName { - const counts = new Map(AVATARS.map((a) => [a, 0])); - const rows = db.sql<{ avatar: string | null }>` - SELECT avatar FROM users WHERE avatar IS NOT NULL - `; - for (const { avatar } of rows) { - if (avatar && counts.has(avatar as AvatarName)) { - counts.set(avatar as AvatarName, counts.get(avatar as AvatarName)! + 1); - } - } - const min = Math.min(...counts.values()); - const leastUsed = AVATARS.filter((a) => counts.get(a) === min); - return leastUsed[Math.floor(Math.random() * leastUsed.length)]; -} - -type PushParams = { - token: string; - roomName: string; - displayName: string; - isVideo: boolean; - avatarUrl?: string; -}; - -async function readIfPresent(path: string): Promise { - try { - return await Deno.readTextFile(path); - } catch (err) { - if (err instanceof Deno.errors.NotFound) return null; - throw err; - } -} - -// --- FCM push (Android) --- - -type ServiceAccount = { - client_email: string; - private_key: string; - project_id: string; -}; - -const fcmCredentials = await readIfPresent("./fcm-credentials.json"); -const serviceAccount: ServiceAccount | null = fcmCredentials - ? JSON.parse(fcmCredentials) - : null; - -const authClient = serviceAccount - ? new JWT({ - email: serviceAccount.client_email, - key: serviceAccount.private_key, - scopes: ["https://www.googleapis.com/auth/firebase.messaging"], - }) - : null; - -async function getAccessToken(client: JWT): Promise { - const { token } = await client.getAccessToken(); - if (!token) throw new Error("Failed to get access token"); - return token; -} - -async function sendFcmPush(params: PushParams): Promise { - if (!serviceAccount || !authClient) { - throw new Error("Android push requires ./fcm-credentials.json"); - } - const accessToken = await getAccessToken(authClient); - - const res = await fetch( - `https://fcm.googleapis.com/v1/projects/${serviceAccount.project_id}/messages:send`, - { - method: "POST", - headers: { - Authorization: `Bearer ${accessToken}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - message: { - token: params.token, - data: { - // Discriminator the SDK's PushNotificationService keys on; a data - // message without it is never treated as a call. - fishjam: "voip-incoming", - roomName: params.roomName, - displayName: params.displayName, - isVideo: String(params.isVideo), - ...(params.avatarUrl ? { avatarUrl: params.avatarUrl } : {}), - }, - android: { priority: "high" }, - }, - }), - }, - ); - - if (!res.ok) { - const text = await res.text(); - throw new Error(`FCM push failed ${res.status}: ${text}`); - } -} - -// --- APNs VoIP push (certificate-based) --- - -const BUNDLE_ID = "io.fishjam.example.voipcall"; -const APNS_HOST = "api.development.push.apple.com"; - -const apnsPem = await readIfPresent("./apns.pem"); -const apnsClient = apnsPem - ? Deno.createHttpClient({ cert: apnsPem, key: apnsPem }) - : null; - -async function sendApnsPush(params: PushParams): Promise { - if (!apnsClient) { - throw new Error("iOS push requires ./apns.pem"); - } - const res = await fetch(`https://${APNS_HOST}/3/device/${params.token}`, { - client: apnsClient, - method: "POST", - headers: { - "apns-push-type": "voip", - "apns-topic": `${BUNDLE_ID}.voip`, - "content-type": "application/json", - }, - body: JSON.stringify({ - roomName: params.roomName, - displayName: params.displayName, - isVideo: params.isVideo, - ...(params.avatarUrl ? { avatarUrl: params.avatarUrl } : {}), - }), - }); - - if (!res.ok) { - const text = await res.text(); - throw new Error(`APNs push failed ${res.status}: ${text}`); - } -} - -// --- Push routing --- - -const sendPush: Record Promise> = - { - ios: sendApnsPush, - android: sendFcmPush, - }; - -// --- Helpers --- - -const json = (data: unknown, status = 200) => Response.json(data, { status }); - -// --- WebSocket signaling registry --- - -const sockets = new Map(); - -// --- Route handler --- - -Deno.serve({ port: 4400 }, async (req) => { - const url = new URL(req.url); - console.log(`${req.method} ${url.pathname}`); - - // POST /register { username, voipToken, platform } - if (req.method === "POST" && url.pathname === "/register") { - const { username, voipToken, platform } = (await req.json()) as { - username: string; - voipToken: string; - platform: string; - }; - if (!username || !voipToken) { - return json({ error: "username and voipToken are required" }, 400); - } - if (!isDevicePlatform(platform)) { - return json({ error: 'platform must be "ios" or "android"' }, 400); - } - const existing = db.sql<{ avatar: string | null }>` - SELECT avatar FROM users WHERE username = ${username} - `; - const avatar = existing[0]?.avatar ?? assignLeastUsedAvatar(); - db.exec( - `INSERT OR REPLACE INTO users (username, voip_token, platform, avatar, updated_at) VALUES (?, ?, ?, ?, ?)`, - [username, voipToken, platform, avatar, Date.now()], - ); - return json({ ok: true, avatarUrl: avatarUrl(req, avatar) }); - } - - // GET /users?exclude= - if (req.method === "GET" && url.pathname === "/users") { - const exclude = url.searchParams.get("exclude") ?? ""; - const rows = db.sql<{ username: string; avatar: string | null }>` - SELECT username, avatar FROM users WHERE username != ${exclude} ORDER BY username - `; - return json( - rows.map((r) => ({ - username: r.username, - avatarUrl: r.avatar ? avatarUrl(req, r.avatar) : null, - })), - ); - } - - // POST /call { from, to, roomName } - if (req.method === "POST" && url.pathname === "/call") { - const { from, to, roomName, isVideo } = (await req.json()) as { - from: string; - to: string; - roomName: string; - isVideo: boolean; - }; - if (!from || !to || !roomName) { - return json({ error: "from, to and roomName are required" }, 400); - } - - const calleeRows = db.sql<{ voip_token: string; platform: string | null }>` - SELECT voip_token, platform FROM users WHERE username = ${to} - `; - if (calleeRows.length === 0) { - return json({ error: "callee not found" }, 404); - } - const { voip_token: voipToken, platform } = calleeRows[0]; - if (!isDevicePlatform(platform)) { - return json({ error: "callee registered without a known platform" }, 409); - } - - const callerRows = db.sql<{ avatar: string | null }>` - SELECT avatar FROM users WHERE username = ${from} - `; - const callerAvatar = callerRows[0]?.avatar; - - try { - await sendPush[platform]({ - token: voipToken, - roomName: roomName, - displayName: from, - isVideo: isVideo, - avatarUrl: callerAvatar ? avatarUrl(req, callerAvatar) : undefined, - }); - } catch (err) { - console.error(`Failed to send ${platform} VoIP push:`, err); - return json({ error: "failed to send VoIP push" }, 502); - } - - return json({ ok: true }); - } - - // GET /ws?username= — bidirectional signaling socket - if (req.method === "GET" && url.pathname === "/ws") { - const username = url.searchParams.get("username"); - if (!username) return json({ error: "username required" }, 400); - - const { socket, response } = Deno.upgradeWebSocket(req); - socket.onopen = () => { - sockets.set(username, socket); - console.log(`${username} connected`); - }; - socket.onclose = () => { - if (sockets.get(username) === socket) sockets.delete(username); - console.log(`${username} disconnected`); - }; - socket.onmessage = (e) => { - let msg: { type?: string; to?: string; [key: string]: unknown }; - try { - msg = JSON.parse(e.data); - } catch { - return; - } - if (!msg.to) return; - const target = sockets.get(msg.to); - if (target?.readyState === WebSocket.OPEN) { - target.send(JSON.stringify({ ...msg, from: username })); - } - }; - return response; - } - - // GET /avatars/.png — serve the bundled avatar images - if (req.method === "GET" && url.pathname.startsWith("/avatars/")) { - const name = url.pathname.slice("/avatars/".length); - if (!/^[a-z0-9_-]+\.png$/.test(name)) { - return new Response("Not found", { status: 404 }); - } - try { - const file = await Deno.readFile(`./avatars/${name}`); - return new Response(file, { - headers: { - "content-type": "image/png", - "cache-control": "public, max-age=86400", - }, - }); - } catch { - return new Response("Not found", { status: 404 }); - } - } - - return new Response("Not found", { status: 404 }); -}); From abe289a98f35d4bb1a96dc30ea31ffc871ad85cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Thu, 13 Aug 2026 16:41:11 +0200 Subject: [PATCH 2/2] Remove the voip-call workspace entry --- package.json | 1 - yarn.lock | 53 +--------------------------------------------------- 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/package.json b/package.json index fb9805c5a..642158667 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "packages/react-native-webrtc", "examples/react-client/*", "examples/mobile-client/*", - "examples/mobile-client/voip-call/app", "e2e-tests/webrtc-client", "e2e-tests/react-client", "e2e-tests/livestream-client" diff --git a/yarn.lock b/yarn.lock index 164c9eec6..c00c8015c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5287,7 +5287,7 @@ __metadata: languageName: node linkType: hard -"@fishjam-cloud/ios-expo-voip@workspace:*, @fishjam-cloud/ios-expo-voip@workspace:packages/ios-expo-voip": +"@fishjam-cloud/ios-expo-voip@workspace:packages/ios-expo-voip": version: 0.0.0-use.local resolution: "@fishjam-cloud/ios-expo-voip@workspace:packages/ios-expo-voip" peerDependencies: @@ -6603,18 +6603,6 @@ __metadata: languageName: node linkType: hard -"@react-native-async-storage/async-storage@npm:^3.1.1": - version: 3.1.1 - resolution: "@react-native-async-storage/async-storage@npm:3.1.1" - dependencies: - idb: "npm:8.0.3" - peerDependencies: - react: "*" - react-native: "*" - checksum: 10c0/20d34973b0a4d1acc133556c52daa59b9826f676abdc351f12c70feb4d557b66e279d1c880662bbfe037b7d79bf64fdee759a5217781c47cd20fd8ba0e0e1b24 - languageName: node - linkType: hard - "@react-native/assets-registry@npm:0.81.5": version: 0.81.5 resolution: "@react-native/assets-registry@npm:0.81.5" @@ -14137,13 +14125,6 @@ __metadata: languageName: node linkType: hard -"idb@npm:8.0.3": - version: 8.0.3 - resolution: "idb@npm:8.0.3" - checksum: 10c0/421cd9a3281b7564528857031cc33fd9e95753f8191e483054cb25d1ceea7303a0d1462f4f69f5b41606f0f066156999e067478abf2460dfcf9cab80dae2a2b2 - languageName: node - linkType: hard - "ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" @@ -18052,17 +18033,6 @@ __metadata: languageName: node linkType: hard -"react-native-get-random-values@npm:^2.0.0": - version: 2.0.0 - resolution: "react-native-get-random-values@npm:2.0.0" - dependencies: - fast-base64-decode: "npm:^1.0.0" - peerDependencies: - react-native: ">=0.81" - checksum: 10c0/fd2e10ab3bca54bff8f5844e3314b797e02822e86663bc2dafe00a511b8a8e1c05d96aa0b8dc1ffad428387d44bbf458ce770a429858433d36f58a16e6e52986 - languageName: node - linkType: hard - "react-native-is-edge-to-edge@npm:^1.1.6, react-native-is-edge-to-edge@npm:^1.2.1": version: 1.3.1 resolution: "react-native-is-edge-to-edge@npm:1.3.1" @@ -21484,27 +21454,6 @@ __metadata: languageName: node linkType: hard -"voip-call@workspace:examples/mobile-client/voip-call/app": - version: 0.0.0-use.local - resolution: "voip-call@workspace:examples/mobile-client/voip-call/app" - dependencies: - "@fishjam-cloud/ios-expo-voip": "workspace:*" - "@fishjam-cloud/react-native-client": "workspace:*" - "@react-native-async-storage/async-storage": "npm:^3.1.1" - "@types/react": "npm:~19.1.0" - eslint-config-expo: "npm:~8.0.1" - expo: "npm:~54.0.30" - expo-splash-screen: "npm:~31.0.13" - expo-status-bar: "npm:~3.0.9" - react: "npm:19.1.0" - react-native: "npm:0.81.5" - react-native-get-random-values: "npm:^2.0.0" - react-native-reanimated: "npm:~4.1.1" - react-native-safe-area-context: "npm:~5.6.0" - typescript: "npm:~5.9.2" - languageName: unknown - linkType: soft - "vscode-languageserver-textdocument@npm:^1.0.12": version: 1.0.12 resolution: "vscode-languageserver-textdocument@npm:1.0.12"