npm package: @kapsula-chat/capacitor-push-calls
Description: Capacitor plugin with push routing (message/call), CallKit (iOS), and ConnectionService (Android)
Latest version: 1.0.3

# Capacitor Push Calls Plugin

`@kapsula-chat/capacitor-push-calls` is a unified Capacitor plugin for:
- regular push notifications (API compatible in shape with `@capacitor/push-notifications`)
- VoIP/telephony call handling

Platform behavior:
- iOS: regular push via APNS registration + VoIP via PushKit + CallKit UI
- Android: single FCM router service inside this plugin (`type=message|money|system|call`) + ConnectionService UI for calls

## Install

```bash
npm install @kapsula-chat/capacitor-push-calls
npx cap sync
```

## Add Plugin To App (iOS + Android)

1. Install and sync:
```bash
npm install @kapsula-chat/capacitor-push-calls
npx cap sync
```
2. Open native projects at least once so Capacitor updates plugin wiring:
```bash
npx cap open ios
npx cap open android
```
3. iOS:
- Ensure VoIP background mode is enabled in `Info.plist` (see iOS section below).
- If you use FCM for regular pushes on iOS, keep your APNs + Firebase setup configured in the app.
4. Android:
- Ensure your app is configured for Firebase Messaging (`google-services.json`, Firebase project).
- Register the phone account once on app startup (recommended):

```kotlin
import com.capacitor.voipcalls.VoipConnectionService

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    VoipConnectionService.registerPhoneAccount(this)
}
```

## Import

```ts
import { CapacitorPushCalls } from '@kapsula-chat/capacitor-push-calls';
```

## Push API (regular notifications)

### Register / permissions

```ts
const permissions = await CapacitorPushCalls.checkPermissions();
if (permissions.receive !== 'granted') {
  await CapacitorPushCalls.requestPermissions();
}

await CapacitorPushCalls.register();

CapacitorPushCalls.addListener('registration', token => {
  console.log('push token:', token.value);
  // Send token to backend and bind with authenticated user/device
});

CapacitorPushCalls.addListener('registrationError', error => {
  console.error('registration error:', error.error);
});
```

### Receive and tap events

```ts
CapacitorPushCalls.addListener('pushNotificationReceived', notification => {
  console.log('push received:', notification);
});

CapacitorPushCalls.addListener('pushNotificationActionPerformed', action => {
  console.log('push action:', action.actionId, action.notification);
});
```

### Delivered notifications and channels

```ts
const delivered = await CapacitorPushCalls.getDeliveredNotifications();
await CapacitorPushCalls.removeAllDeliveredNotifications();

await CapacitorPushCalls.createChannel({
  id: 'messages',
  name: 'Messages',
  importance: 4,
});

const channels = await CapacitorPushCalls.listChannels();
await CapacitorPushCalls.deleteChannel({ id: 'messages' });
```

Note: channel APIs are Android-only (iOS returns no-op / empty list).

### Badge count

```ts
const current = await CapacitorPushCalls.getBadgeCount();
await CapacitorPushCalls.setBadgeCount({ count: current.count + 1 });
await CapacitorPushCalls.clearBadgeCount();
```

## Android Device Registration

Recommended bootstrap flow on Android:

```ts
const permissions = await CapacitorPushCalls.checkPermissions();
if (permissions.receive !== 'granted') {
  await CapacitorPushCalls.requestPermissions();
}

await CapacitorPushCalls.register();

CapacitorPushCalls.addListener('registration', async ({ value }) => {
  // Persist FCM token on backend for this device/session
  await api.registerPushDevice({ token: value, platform: 'android' });
});
```

Use the same `registration` flow on iOS for regular push token registration.

## VoIP / calls API

### iOS VoIP registration

```ts
await CapacitorPushCalls.registerVoipNotifications();

CapacitorPushCalls.addListener('voipPushToken', ({ token }) => {
  console.log('voip token:', token);
});
```

### Start / control calls

```ts
const { callId } = await CapacitorPushCalls.startCall({
  handle: '+1234567890',
  displayName: 'John Doe',
  handleType: 'phone',
  video: false,
});

await CapacitorPushCalls.setMuted({ muted: true });
await CapacitorPushCalls.setAudioRoute({ route: 'speaker' });
await CapacitorPushCalls.setCallOnHold({ callId, onHold: true });
await CapacitorPushCalls.endCall({ callId });
```

### Call events

```ts
CapacitorPushCalls.addListener('incomingCall', call => {});
CapacitorPushCalls.addListener('callStarted', call => {});
CapacitorPushCalls.addListener('callAnswered', call => {});
CapacitorPushCalls.addListener('callEnded', call => {});
CapacitorPushCalls.addListener('callRejected', call => {});
CapacitorPushCalls.addListener('callHeld', call => {});
```

## Android FCM routing contract

This plugin owns one `FirebaseMessagingService` and routes by `data.type`.

- `type=call` -> reported as incoming call to system UI
- `type=message|money|system` -> emitted as `pushNotificationReceived`
- anything else (or missing `type`) -> emitted as `pushNotificationReceived`

Example FCM message payloads:

### Message push

```json
{
  "data": {
    "type": "message",
    "messageId": "msg-123",
    "chatId": "room-7"
  },
  "notification": {
    "title": "New message",
    "body": "Hello"
  }
}
```

### Money push

```json
{
  "data": {
    "type": "money",
    "transactionId": "txn-99",
    "amount": "250.00",
    "currency": "USD"
  },
  "notification": {
    "title": "Payment received",
    "body": "You received $250.00"
  }
}
```

### System push

```json
{
  "data": {
    "type": "system",
    "code": "maintenance",
    "severity": "info"
  },
  "notification": {
    "title": "System notice",
    "body": "Scheduled maintenance at 02:00 UTC"
  }
}
```

### Call push

```json
{
  "data": {
    "type": "call",
    "callId": "call-123",
    "handle": "+1234567890",
    "displayName": "John Doe",
    "handleType": "phone",
    "video": "false"
  }
}
```

## Foreground Service (Android)

The plugin currently routes FCM and triggers ConnectionService/Call UI, but it does not run a long-lived custom foreground service for your app logic.

Use your own foreground service if you need:
- long-running signaling/reconnect loops in background,
- persistent background media/session processing,
- guaranteed ongoing processing beyond call UI handoff.

Typical split:
- plugin handles push ingress + system call UI bridge,
- app service handles long-lived networking/media responsibilities.

## iOS setup notes

Add VoIP background mode in `Info.plist`:

```xml
<key>UIBackgroundModes</key>
<array>
  <string>voip</string>
</array>
```

Server side:
- regular push -> APNS/FCM token from `registration`
- VoIP call push -> PushKit token from `voipPushToken`

## Test app helper

Use included helper scripts and UI:

```bash
npm run test:setup
npm run test:update
```

The generated test app uses `scripts/test-app-ui.html`.

## API Types

See `/src/definitions.ts` for the complete TypeScript contract.