Skip to content
Open
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
12 changes: 11 additions & 1 deletion ios/app/VoltraModuleImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,24 @@ public class VoltraModuleImpl {
}()
let relevanceScore: Double = options?.relevanceScore ?? 0.0

let pushType: PushType? = {
if let channelId = options?.channelId {
if #available(iOS 18.0, *) {
return .channel(channelId)
}
return nil
}
return pushNotificationsEnabled ? .token : nil
}()

// Create request struct with compressed JSON
let createRequest = CreateActivityRequest(
activityId: activityName,
deepLinkUrl: options?.deepLinkUrl,
jsonString: compressedJson,
staleDate: staleDate,
relevanceScore: relevanceScore,
pushType: pushNotificationsEnabled ? .token : nil,
pushType: pushType,
endExistingWithSameName: true
)

Expand Down
4 changes: 4 additions & 0 deletions ios/app/VoltraOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public struct StartVoltraOptions: Record {
@Field
public var relevanceScore: Double?

/// Channel ID for broadcast push notifications (iOS 18+).
@Field
public var channelId: String?

public init() {}
}

Expand Down
5 changes: 5 additions & 0 deletions src/VoltraModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export type StartVoltraOptions = {
* Double value between 0.0 and 1.0, defaults to 0.0
*/
relevanceScore?: number
/**
* Channel ID for broadcast push notifications (iOS 18+).
* When provided, the Live Activity subscribes to broadcast updates on this channel.
*/
channelId?: string
}

/**
Expand Down
11 changes: 11 additions & 0 deletions src/live-activity/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export type StartLiveActivityOptions = {
* URL to open when the Live Activity is tapped.
*/
deepLinkUrl?: string
/**
* Channel ID for broadcast push notifications (iOS 18+).
* When provided, the Live Activity subscribes to broadcast updates on this channel
* instead of receiving individual push tokens.
*/
channelId?: string
} & SharedLiveActivityOptions

export type UpdateLiveActivityOptions = SharedLiveActivityOptions
Expand All @@ -60,6 +66,10 @@ export type UseLiveActivityOptions = {
* URL to open when the Live Activity is tapped.
*/
deepLinkUrl?: string
/**
* Channel ID for broadcast push notifications (iOS 18+).
*/
channelId?: string
}

export type UseLiveActivityResult = {
Expand Down Expand Up @@ -271,6 +281,7 @@ export const startLiveActivity = async (
target: 'liveActivity',
deepLinkUrl: options?.deepLinkUrl,
activityId: options?.activityName,
channelId: options?.channelId,
...normalizedSharedOptions,
})
return targetId
Expand Down
15 changes: 15 additions & 0 deletions website/docs/ios/api/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,21 @@ await startLiveActivity(variants, {

**Valid range:** 0.0 to 1.0 (default: 0.0)

### Channel ID (broadcast push, iOS 18+)

The `channelId` option subscribes the Live Activity to a broadcast channel for server-side updates. When provided, the activity receives updates via broadcast push notifications instead of individual device tokens—one server notification updates all activities on that channel. Requires `enablePushNotifications: true` and the Broadcast Capability enabled in your Apple Developer account.

```typescript
import { startLiveActivity } from 'voltra/client'

await startLiveActivity(variants, {
activityName: 'match-123',
channelId: 'CTrNsYq/Ee8AALLzHQaVlA==', // From APNs channel management
})
```

For full broadcast setup, see [Server-side updates - Broadcast push notifications](../development/server-side-updates.md#broadcast-push-notifications-ios-18).

These options can be used together with dismissal policy and other configuration options:

```typescript
Expand Down
17 changes: 16 additions & 1 deletion website/docs/ios/development/managing-live-activities-locally.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ const variants = {
}

const activityId = await startLiveActivity(variants, {
activityId: 'order-123', // Optional: for re-binding on app restart
activityName: 'order-123', // Optional: for re-binding on app restart
deepLinkUrl: 'myapp://order/123', // Optional: URL to open when tapped
channelId: 'CTrNsYq/Ee8AALLzHQaVlA==', // Optional: broadcast channel (iOS 18+)
dismissalPolicy: { after: 30 },
staleDate: Date.now() + 60 * 60 * 1000, // 1 hour
relevanceScore: 0.8,
Expand Down Expand Up @@ -257,6 +258,20 @@ await startLiveActivity(variants, {

Live Activities can receive updates even when your app is in the background or terminated, but they cannot execute JavaScript code. For real-time updates from backgrounded apps, use server-side push notifications.

### Channel ID (broadcast push, iOS 18+)

Subscribes the Live Activity to a broadcast channel for server-side updates. When provided, the activity receives updates via broadcast push notifications instead of individual device tokens—one server notification updates all activities on that channel.

```typescript
// For shared content (e.g., live sports, flight status)
await startLiveActivity(variants, {
activityName: 'match-123',
channelId: 'CTrNsYq/Ee8AALLzHQaVlA==', // From APNs channel management
})
```

Requires `enablePushNotifications: true` in the Voltra plugin and the Broadcast Capability enabled in your Apple Developer account. See [Server-side updates](./server-side-updates.md#broadcast-push-notifications-ios-18) for details.

## Best practices

### Activity lifecycle management
Expand Down
47 changes: 47 additions & 0 deletions website/docs/ios/development/server-side-updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,53 @@ useEffect(() => {

Use only Voltra-provided tokens, which are specialized for Live Activity push notifications and different from regular device tokens. Update tokens are tied to specific Live Activities, while push-to-start tokens are for starting new activities. Update tokens are provided when Live Activities are started and may change during the activity's lifecycle. When sending notifications via APNS, use these push tokens as the target device token to route notifications to the correct Live Activity or device.

## Broadcast push notifications (iOS 18+)

Starting with iOS 18 and iPadOS 18, you can use **broadcast push notifications** to update many Live Activities with a single push notification. Instead of sending individual notifications to each device token, you send one broadcast to a shared channel—all Live Activities subscribed to that channel receive the update. This is ideal for scenarios like live sports scores or flight status where many users follow the same event.

### Prerequisites

1. **Enable Broadcast Capability:** In your [Apple Developer account](https://developer.apple.com/account), go to Certificates, Identifiers & Profiles > Identifiers, select your App ID, and enable **Broadcast Capability** under Push Notifications.

2. **Create a channel:** Your server creates a channel via APNs and receives a channel ID. You can maintain up to 10,000 channels per app. Use [Apple Push Notification Console](https://icloud.developer.apple.com/dashboard/notifications/) or the [channel management API](https://developer.apple.com/documentation/usernotifications/sending-channel-management-requests-to-apns) to create channels.

3. **Plugin configuration:** Keep `enablePushNotifications: true` in your Voltra plugin config—the `aps-environment` entitlement is still required for broadcast push.

### Starting a Live Activity with a channel

Pass the `channelId` option when starting a Live Activity to subscribe it to a broadcast channel:

```typescript
import { startLiveActivity } from 'voltra/client'
import { Voltra } from 'voltra'

const activityId = await startLiveActivity(variants, {
activityName: 'match-123',
channelId: 'CTrNsYq/Ee8AALLzHQaVlA==', // Channel ID from your server
})
```

When `channelId` is provided, the Live Activity subscribes to broadcast updates. On iOS versions before 18, `channelId` is ignored and the activity starts without push support.

### Sending broadcast updates

To update all Live Activities on a channel, send a POST request to APNs with:

- **Path:** `/4/broadcasts/apps/<your.bundle.id>` (bundle ID without the `.push-type.liveactivity` suffix)
- **Header:** `apns-channel-id: <Channel ID>`
- **Payload:** Same structure as individual updates—`event: "update"`, `content-state`, `timestamp`, etc.

For the full broadcast payload format and headers, see [Apple's broadcast push documentation](https://developer.apple.com/documentation/usernotifications/sending-broadcast-push-notification-requests-to-apns).

### Broadcast vs. individual tokens

| Aspect | Individual tokens | Broadcast |
|--------|-------------------|-----------|
| Server sends | One notification per device | One notification per channel |
| `activityTokenReceived` event | Fires for each activity | Does not fire |
| Best for | Per-user content (orders, rides) | Shared content (scores, flights) |
| iOS version | 16.2+ | 18+ |

## Handling background execution

When Live Activity tokens change or need to be refreshed, iOS may wake your app in the background to deliver new tokens. The app has a limited window of time (typically around 30 seconds) to handle the event and communicate with your server before iOS may suspend or terminate the background process.
Expand Down
Loading