diff --git a/astro.config.mjs b/astro.config.mjs index bc5b58c46..5ec6c4e36 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -363,6 +363,11 @@ export default defineConfig({ description: 'in-app review prompt plugin for app store ratings', paths: ['docs/plugins/in-app-review/**'], }, + { + label: 'Plugin Incoming Call Kit', + description: 'native incoming call UI plugin with iOS CallKit and Android full-screen notifications', + paths: ['docs/plugins/incoming-call-kit/**'], + }, { label: 'Plugin Intent Launcher', description: 'Android intent launcher plugin', @@ -1089,6 +1094,16 @@ export default defineConfig({ ], collapsed: true, }, + { + label: 'Incoming Call Kit', + items: [ + { label: 'Overview', link: '/docs/plugins/incoming-call-kit/' }, + { label: 'Getting started', link: '/docs/plugins/incoming-call-kit/getting-started' }, + { label: 'iOS', link: '/docs/plugins/incoming-call-kit/ios' }, + { label: 'Android', link: '/docs/plugins/incoming-call-kit/android' }, + ], + collapsed: true, + }, { label: 'Intercom', items: [ diff --git a/public/icons/plugins/incoming-call-kit.svg b/public/icons/plugins/incoming-call-kit.svg new file mode 100644 index 000000000..e8b3c04a3 --- /dev/null +++ b/public/icons/plugins/incoming-call-kit.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/config/plugins.ts b/src/config/plugins.ts index 7a3977619..ac619ec0d 100644 --- a/src/config/plugins.ts +++ b/src/config/plugins.ts @@ -484,6 +484,14 @@ export const actions = [ title: 'Text Interaction', icon: 'DocumentText', }, + { + name: '@capgo/capacitor-incoming-call-kit', + author: 'github.com/Cap-go', + description: 'Present native incoming call UI with iOS CallKit and Android full-screen notifications', + href: 'https://github.com/Cap-go/capacitor-incoming-call-kit/', + title: 'Incoming Call Kit', + icon: 'Phone', + }, { name: '@capgo/capacitor-twilio-voice', author: 'github.com/Cap-go', diff --git a/src/content/docs/docs/plugins/incoming-call-kit/android.mdx b/src/content/docs/docs/plugins/incoming-call-kit/android.mdx new file mode 100644 index 000000000..c9b5659f0 --- /dev/null +++ b/src/content/docs/docs/plugins/incoming-call-kit/android.mdx @@ -0,0 +1,75 @@ +--- +title: Android +description: Configure notifications, full-screen intents, and Android-specific incoming call behavior. +sidebar: + order: 4 +--- + +## How Android behavior works + +On Android, the plugin posts a high-priority incoming-call notification and can raise a full-screen activity when the platform and user settings allow it. + +The plugin manifest already includes: + +```xml + + +``` + +After installation, `bunx cap sync` is enough to merge that configuration into your host app. + +## Runtime permissions + +Call these methods during onboarding or before you rely on incoming-call presentation: + +```ts +import { IncomingCallKit } from '@capgo/capacitor-incoming-call-kit'; + +await IncomingCallKit.requestPermissions(); +await IncomingCallKit.requestFullScreenIntentPermission(); +``` + +- `requestPermissions()` requests notification permission on Android 13 and later. +- `requestFullScreenIntentPermission()` opens the Android 14 and later settings page for full-screen intents when needed. + +## Basic example + +```ts +await IncomingCallKit.showIncomingCall({ + callId: 'call-42', + callerName: 'Ada Lovelace', + appName: 'Capgo Phone', + timeoutMs: 45_000, + android: { + channelId: 'calls', + channelName: 'Incoming Calls', + showFullScreen: true, + isHighPriority: true, + accentColor: '#0F766E', + }, +}); +``` + +## Android-specific options + +- `channelId`: identifier for the notification channel. +- `channelName`: user-visible channel name. +- `showFullScreen`: request the full-screen activity. +- `isHighPriority`: keep the notification disruptive enough for ringing flows. +- `accentColor`: tint compatible notification surfaces. +- `ringtoneUri`: point at a custom Android ringtone resource or URI. + +## Behavior notes + +- Full-screen presentation is best-effort. If the device or user settings block it, Android still shows the incoming-call notification. +- Timeout handling is also best-effort. The plugin tracks `timeoutMs` and emits `callTimedOut`, but your backend should still reconcile missed calls on its side. +- Accept, decline, and end actions are emitted back through Capacitor listeners so your app can join or clean up the real call session. + +## Recommended production model + +Use Android push or your calling SDK for transport, then let this plugin handle the last mile of native ringing UI. Keep these responsibilities outside the plugin: + +- FCM registration and token management +- Media session lifecycle +- Backend call state +- Retry and missed-call business logic diff --git a/src/content/docs/docs/plugins/incoming-call-kit/getting-started.mdx b/src/content/docs/docs/plugins/incoming-call-kit/getting-started.mdx new file mode 100644 index 000000000..85e5f57a3 --- /dev/null +++ b/src/content/docs/docs/plugins/incoming-call-kit/getting-started.mdx @@ -0,0 +1,109 @@ +--- +title: Getting Started +description: Install and wire incoming call presentation in Capacitor with a small, transport-agnostic API. +sidebar: + order: 2 +--- + +## Install + +```bash +bun add @capgo/capacitor-incoming-call-kit +bunx cap sync +``` + +## Decide where the ring event comes from + +Before writing code, decide how your app learns that someone is calling: + +1. A foreground JavaScript event from your backend. +2. An Android FCM notification or iOS PushKit/APNs flow. +3. A vendor SDK such as Twilio, Stream, SIP, or your own native layer. + +When that ring event reaches your app, call `showIncomingCall()`. When the user accepts, join your real voice or video session from your own SDK or backend flow. + +## Minimal integration + +```ts +import { IncomingCallKit } from '@capgo/capacitor-incoming-call-kit'; + +await IncomingCallKit.requestPermissions(); +await IncomingCallKit.requestFullScreenIntentPermission(); + +await IncomingCallKit.addListener('callAccepted', async ({ call }) => { + console.log('Accepted', call.callId, call.extra); + // Start or join your real call session here. +}); + +await IncomingCallKit.addListener('callDeclined', ({ call }) => { + console.log('Declined', call.callId); + // Tell your backend or SDK that the user declined. +}); + +await IncomingCallKit.addListener('callTimedOut', ({ call }) => { + console.log('Timed out', call.callId); + // Clear ringing state in your backend or SDK. +}); + +await IncomingCallKit.showIncomingCall({ + callId: 'call-42', + callerName: 'Ada Lovelace', + handle: '+39 555 010 020', + appName: 'Capgo Phone', + hasVideo: true, + timeoutMs: 45_000, + extra: { + roomId: 'room-42', + callerUserId: 'user_ada', + }, + android: { + channelId: 'calls', + channelName: 'Incoming Calls', + showFullScreen: true, + }, + ios: { + handleType: 'phoneNumber', + }, +}); +``` + +## Important options + +- `callId`: stable identifier you reuse later with `endCall()`. +- `timeoutMs`: best-effort unanswered timeout. +- `extra`: arbitrary JSON echoed back in all listener payloads. +- `android.channelId` and `android.channelName`: tune the Android notification channel. +- `android.showFullScreen`: request the Android full-screen incoming-call activity. +- `ios.handleType`: choose `generic`, `phoneNumber`, or `emailAddress` for CallKit. + +## Managing active calls + +```ts +const { calls } = await IncomingCallKit.getActiveCalls(); + +await IncomingCallKit.endCall({ + callId: 'call-42', + reason: 'remote-ended', +}); + +await IncomingCallKit.endAllCalls({ + reason: 'session-reset', +}); +``` + +## Production architecture + +The safest model is: + +1. Your backend or calling SDK emits a ring event. +2. Your app calls `showIncomingCall()`. +3. The plugin shows native UI. +4. `callAccepted` tells your app to join the actual room or VoIP session. +5. `callDeclined`, `callEnded`, or `callTimedOut` tells your app to clean up remote state. + +Keep the plugin focused on native presentation. Keep call transport, authentication, and media session ownership in the system you already trust for those responsibilities. + +## Platform setup + +- Read the [iOS guide](/docs/plugins/incoming-call-kit/ios/) before wiring CallKit into a PushKit or APNs flow. +- Read the [Android guide](/docs/plugins/incoming-call-kit/android/) before relying on full-screen intents on Android 14 and later. diff --git a/src/content/docs/docs/plugins/incoming-call-kit/index.mdx b/src/content/docs/docs/plugins/incoming-call-kit/index.mdx new file mode 100644 index 000000000..762d0e789 --- /dev/null +++ b/src/content/docs/docs/plugins/incoming-call-kit/index.mdx @@ -0,0 +1,68 @@ +--- +title: "@capgo/capacitor-incoming-call-kit" +description: Native incoming call presentation for Capacitor with iOS CallKit and Android full-screen notifications. +tableOfContents: false +next: false +prev: false +sidebar: + order: 1 + label: "Introduction" +hero: + tagline: Present native incoming call UI in Capacitor with iOS CallKit and Android full-screen notifications, while keeping your push and media stack under your control. + image: + file: ~public/icons/plugins/incoming-call-kit.svg + actions: + - text: Get started + link: /docs/plugins/incoming-call-kit/getting-started/ + icon: right-arrow + variant: primary + - text: GitHub + link: https://github.com/Cap-go/capacitor-incoming-call-kit/ + icon: external + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +## Overview + +`@capgo/capacitor-incoming-call-kit` gives your Capacitor app the native ringing layer for incoming calls without forcing a specific VoIP vendor or push provider. + +Use it when you already have your own signaling flow, SIP stack, Twilio integration, Stream integration, FCM delivery, or PushKit delivery and you want the platform-native incoming call experience on top of that. + + + + Report incoming calls to CallKit and react to accepted, declined, ended, and timed-out events. + + + Show a high-priority notification and raise a full-screen activity when Android allows it. + + + User actions are retained until the Capacitor bridge consumes them, which helps when JavaScript starts late. + + + Bring your own FCM, APNs, PushKit, SIP, or backend event flow. The plugin focuses on presentation, not transport. + + + Use one small API to show calls, end calls, inspect active calls, and listen for state changes. + + + Start with the [Getting started guide](/docs/plugins/incoming-call-kit/getting-started/), then read the [iOS guide](/docs/plugins/incoming-call-kit/ios/) and [Android guide](/docs/plugins/incoming-call-kit/android/). + + + +## What the plugin handles + +- Native incoming call presentation +- Active call tracking +- Accept, decline, end, and timeout events +- Android notification and full-screen intent wiring +- iOS CallKit reporting + +## What the plugin does not handle + +- FCM, APNs, or PushKit registration +- Starting or managing the real audio or video session +- Vendor-specific call logic from Twilio, Stream, Daily, Agora, SIP, or WebRTC SDKs + +If you need a complete communications stack, use this plugin as the native incoming-call surface and keep your transport and media session in your existing backend or SDK. diff --git a/src/content/docs/docs/plugins/incoming-call-kit/ios.mdx b/src/content/docs/docs/plugins/incoming-call-kit/ios.mdx new file mode 100644 index 000000000..cb59dc7f7 --- /dev/null +++ b/src/content/docs/docs/plugins/incoming-call-kit/ios.mdx @@ -0,0 +1,69 @@ +--- +title: iOS +description: Configure CallKit behavior and understand production limits on iOS. +sidebar: + order: 3 +--- + +## How iOS behavior works + +On iOS, the plugin reports the incoming call to CallKit. That gives you the system incoming-call sheet and standardized call actions without building your own native UI. + +`requestPermissions()` resolves immediately on iOS because CallKit itself does not require a runtime permission dialog. + +## Basic example + +```ts +import { IncomingCallKit } from '@capgo/capacitor-incoming-call-kit'; + +await IncomingCallKit.showIncomingCall({ + callId: 'call-42', + callerName: 'Ada Lovelace', + handle: '+1 555 010 020', + ios: { + handleType: 'phoneNumber', + supportsHolding: true, + supportsDTMF: false, + }, +}); +``` + +## Handle types + +Use the `ios.handleType` option to control how CallKit formats the handle: + +- `generic` for app-specific identifiers +- `phoneNumber` for real phone numbers +- `emailAddress` for email-based identities + +## If you need real background incoming calls + +This plugin does not register PushKit or APNs for you. + +For true background or terminated-state ringing on iOS, your host app still needs the native Apple push setup that matches your transport strategy: + +1. Enable the Push Notifications capability when your transport uses Apple push delivery. +2. Enable the Voice over IP background mode when your app uses a VoIP push flow. +3. Deliver the incoming-call event to your app and invoke this plugin as soon as the Capacitor bridge is available. + +If your ring event exists only in JavaScript, you will get the best experience while the app is already running in the foreground. + +## Microphone and camera permissions + +CallKit does not replace your media SDK. If the real call session uses microphone or camera access, those usage descriptions still belong in your app: + +```xml +NSMicrophoneUsageDescription +This app uses the microphone for calls. +NSCameraUsageDescription +This app uses the camera for video calls. +``` + +Add only the keys your real calling flow needs. + +## What to keep in your app layer + +- PushKit and APNs registration +- Authentication and token refresh +- Joining the real room or VoIP session after `callAccepted` +- Ending or reconciling remote call state when the plugin emits `callDeclined`, `callEnded`, or `callTimedOut` diff --git a/src/content/docs/docs/plugins/index.mdx b/src/content/docs/docs/plugins/index.mdx index f6bb69570..cbaae1b5d 100644 --- a/src/content/docs/docs/plugins/index.mdx +++ b/src/content/docs/docs/plugins/index.mdx @@ -243,6 +243,11 @@ The Updater plugin is the foundation of Capgo Cloud, enabling you to: ## 📞 Communication & Analytics + { + console.log('Accepted', call.callId); + // Join the real room or VoIP session here. +}); + +await IncomingCallKit.showIncomingCall({ + callId: 'call-42', + callerName: 'Ada Lovelace', + handle: '+39 555 010 020', + appName: 'Capgo Phone', + hasVideo: true, + timeoutMs: 45_000, + extra: { + roomId: 'room-42', + }, + android: { + channelId: 'calls', + channelName: 'Incoming Calls', + showFullScreen: true, + }, + ios: { + handleType: 'phoneNumber', + }, +}); +``` + +## Wire it to your real call flow + +The normal production pattern looks like this: + +1. Your backend or communications SDK emits a ring event. +2. Your Capacitor app calls `showIncomingCall()`. +3. The plugin presents native incoming-call UI. +4. `callAccepted` tells your app to join the actual call. +5. `callDeclined`, `callEnded`, or `callTimedOut` tells your app to clean up remote state. + +This separation is useful because it lets you swap transport or media providers later without rewriting the native ringing layer. + +## Platform notes + +### iOS + +- Uses CallKit for the system incoming-call sheet. +- Does not register PushKit or APNs for you. +- Real background ringing still depends on your host app's Apple push setup. + +### Android + +- Uses a high-priority notification and optional full-screen activity. +- Requests notification permission on Android 13 and later. +- Can open the Android 14 full-screen intent settings page when needed. + +## Useful API methods + +- `showIncomingCall()` to present the native UI. +- `endCall()` to end a single tracked call. +- `endAllCalls()` to clear all tracked calls. +- `getActiveCalls()` to inspect what the native layer still considers active. + +## Learn more + +- Full documentation: [Incoming Call Kit docs](https://capgo.app/docs/plugins/incoming-call-kit/) +- GitHub repository: [Cap-go/capacitor-incoming-call-kit](https://github.com/Cap-go/capacitor-incoming-call-kit) + +If your app already has transport and media solved, this plugin is the missing native presentation layer that makes incoming calls feel like a real mobile calling experience.