Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/src/config/llmsCustomSets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Plugin Home Indicator|iOS home indicator visibility control plugin|docs/plugins/
Plugin iBeacon|iBeacon proximity detection plugin|docs/plugins/ibeacon/**
Plugin InAppBrowser|in-app browser plugin for opening web content|docs/plugins/inappbrowser/**
Plugin In App Review|in-app review prompt plugin for app store ratings|docs/plugins/in-app-review/**
Plugin Incoming Call Kit|native incoming call presentation with iOS CallKit and Android full-screen notifications|docs/plugins/incoming-call-kit/**
Plugin Intent Launcher|Android intent launcher plugin|docs/plugins/intent-launcher/**
Plugin Intercom|Intercom customer messaging and support plugin for native in-app chat|docs/plugins/intercom/**
Plugin Is Root|root/jailbreak detection plugin|docs/plugins/is-root/**
Expand Down
1 change: 1 addition & 0 deletions apps/docs/src/config/sidebar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const pluginEntries = [
['iBeacon', 'ibeacon'],
['In App Review', 'in-app-review'],
['InAppBrowser', 'inappbrowser'],
['Incoming Call Kit', 'incoming-call-kit', [linkItem('iOS', '/docs/plugins/incoming-call-kit/ios'), linkItem('Android', '/docs/plugins/incoming-call-kit/android')]],
['Intent Launcher', 'intent-launcher'],
['Intercom', 'intercom'],
['Is Root', 'is-root'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
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
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
```

After installation, `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
import { IncomingCallKit } from '@capgo/capacitor-incoming-call-kit';

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 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
title: Getting Started
description: Install and wire incoming call presentation in Capacitor with a transport-agnostic API.
sidebar:
order: 2
---

import { PackageManagers } from 'starlight-package-managers'
import { Steps } from '@astrojs/starlight/components';

<Steps>
1. **Install the package**
<PackageManagers pkg="@capgo/capacitor-incoming-call-kit" pkgManagers={['npm', 'pnpm', 'yarn', 'bun']} />

2. **Sync native projects**
<PackageManagers type="exec" pkg="cap" args="sync" pkgManagers={['npm', 'pnpm', 'yarn', 'bun']} />

3. **Choose your ring source**
Decide whether the incoming-call event comes from your backend, an SDK such as Twilio or Stream, or a native push path such as FCM or PushKit.
</Steps>

## How the integration fits together

This plugin only owns native incoming-call presentation. Your app still owns transport, authentication, and the actual media session.

The common production pattern is:

1. Your backend or calling SDK emits a ring event.
2. Your app calls `showIncomingCall()`.
3. The plugin presents native incoming-call 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.

## 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 reused later with `endCall()`
- `timeoutMs`: best-effort unanswered timeout
- `extra`: arbitrary JSON echoed back in listener payloads
- `android.channelId` and `android.channelName`: Android notification channel tuning
- `android.showFullScreen`: requests 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',
});
```

## Event model

- `incomingCallDisplayed`: native UI was shown successfully
- `callAccepted`: user accepted from the native UI
- `callDeclined`: user declined before joining
- `callEnded`: your app or the platform ended the tracked call
- `callTimedOut`: the call stayed unanswered until `timeoutMs`

Each event carries the normalized `call` payload and your original `extra` object.

## Platform notes

- 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.
- Web is not supported.
Original file line number Diff line number Diff line change
@@ -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 surface for incoming calls without forcing a specific VoIP vendor, push transport, or backend architecture.

Use it when you already have your own signaling flow, SIP stack, Twilio integration, Stream integration, FCM delivery, or PushKit delivery and want the platform-native incoming-call experience layered onto that flow.

<CardGrid stagger>
<Card title="CallKit on iOS" icon="approve-check">
Report incoming calls to CallKit and react to accepted, declined, ended, and timed-out events.
</Card>
<Card title="Full-screen Android UI" icon="rocket">
Show a high-priority notification and raise a full-screen activity when Android allows it.
</Card>
<Card title="Buffered listener events" icon="star">
User actions are retained until the Capacitor bridge consumes them, which helps when JavaScript starts late.
</Card>
<Card title="Transport agnostic" icon="setting">
Bring your own FCM, APNs, PushKit, SIP, or backend event flow. The plugin focuses on presentation, not transport.
</Card>
<Card title="Typed cross-platform API" icon="comment">
Use one small API to show calls, end calls, inspect active calls, and listen for state changes.
</Card>
<Card title="Platform guides" icon="open-book">
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/).
</Card>
</CardGrid>

## 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 transport plus media sessions in your existing backend or SDK.
69 changes: 69 additions & 0 deletions apps/docs/src/content/docs/docs/plugins/incoming-call-kit/ios.mdx
Original file line number Diff line number Diff line change
@@ -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 incoming-call 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 `ios.handleType` to control how CallKit formats the handle:

- `generic` for app-specific identifiers
- `phoneNumber` for real phone numbers
- `emailAddress` for email-based identities

## 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 Push Notifications 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
<key>NSMicrophoneUsageDescription</key>
<string>This app uses the microphone for calls.</string>
<key>NSCameraUsageDescription</key>
<string>This app uses the camera for video calls.</string>
```

Add only the keys your real calling flow needs.

## Keep these responsibilities 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`
6 changes: 6 additions & 0 deletions apps/web/public/icons/plugins/incoming-call-kit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions apps/web/src/config/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const actionDefinitionRows =
@capgo/capacitor-age-range|github.com/Cap-go|Cross-platform age range detection using Google Play Age Signals (Android) and Apple DeclaredAgeRange (iOS)|https://github.com/Cap-go/capacitor-age-range/|Age Range
@capgo/capacitor-persona|github.com/Cap-go|Launch Persona identity verification inquiries with native iOS and Android SDKs|https://github.com/Cap-go/capacitor-persona/|Persona
@capgo/capacitor-intune|github.com/Cap-go|Microsoft Intune MAM, app protection policy, app config, and MSAL authentication for Capacitor|https://github.com/Cap-go/capacitor-intune/|Intune
@capgo/capacitor-incoming-call-kit|github.com/Cap-go|Present native incoming-call UI with iOS CallKit and Android full-screen notifications|https://github.com/Cap-go/capacitor-incoming-call-kit/|Incoming Call Kit
@capgo/capacitor-android-age-signals|github.com/Cap-go|Google Play Age Signals API wrapper - detect supervised accounts and verified users|https://github.com/Cap-go/capacitor-android-age-signals/|Age Signals
@capgo/capacitor-barometer|github.com/Cap-go|Access device barometer for atmospheric pressure and altitude readings|https://github.com/Cap-go/capacitor-barometer/|Barometer
@capgo/capacitor-accelerometer|github.com/Cap-go|Read device accelerometer for motion detection and orientation tracking|https://github.com/Cap-go/capacitor-accelerometer/|Accelerometer
Expand Down Expand Up @@ -214,6 +215,7 @@ const pluginIconsByName: Record<string, string> = {
'@capgo/capacitor-age-range': 'UserGroup',
'@capgo/capacitor-persona': 'UserCircle',
'@capgo/capacitor-intune': 'ShieldCheck',
'@capgo/capacitor-incoming-call-kit': 'Phone',
'@capgo/capacitor-android-age-signals': 'UserGroup',
'@capgo/capacitor-barometer': 'ChartBar',
'@capgo/capacitor-accelerometer': 'ArrowsPointingOut',
Expand Down
Loading