npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

expo-onesignal-live-activities

v0.2.10

Published

The complete Live Activity setup for Expo + OneSignal. Handles Widget Extension target, entitlements, Info.plist, AppDelegate, push-to-start tokens, EAS credentials, and widget UI scaffolding — you just write SwiftUI.

Readme

expo-onesignal-live-activities

npm version License: MIT iOS Expo SDK

The complete Live Activity setup for Expo + OneSignal. One package handles everything — Widget Extension target, entitlements, Info.plist, AppDelegate, push-to-start token registration, EAS credentials, and widget UI scaffolding. You write SwiftUI, the package handles the rest.

[!IMPORTANT] iOS only. All functions gracefully return no-ops on Android and web. Requires iOS 16.2+ and a physical device for full functionality.

What This Replaces

Setting up OneSignal Live Activities in Expo normally requires 15+ manual steps:

  1. Create a Widget Extension target in Xcode (File > New > Target > Widget Extension)
  2. Add NSSupportsLiveActivities to Info.plist
  3. Add NSSupportsLiveActivitiesFrequentUpdates to Info.plist
  4. Configure aps-environment entitlement
  5. Set up App Group entitlements for data sharing
  6. Edit the Podfile to add OneSignalXCFramework to the widget target
  7. Run pod install
  8. Write a Swift struct conforming to OneSignalLiveActivityAttributes
  9. Write the full SwiftUI widget UI (lock screen + Dynamic Island)
  10. Add the widget target to the main app target's membership
  11. Call OneSignal.LiveActivities.setupDefault() in AppDelegate
  12. Set up an async task for pushToStartTokenUpdates (iOS 17.2+)
  13. Register push-to-start tokens with OneSignal manually
  14. Add appExtensions to extra.eas.build.experimental.ios for EAS Build signing
  15. Upload .p8 APNs key to OneSignal dashboard
  16. Build server-side API calls with exact property-name parity

With this package:

npx expo install expo-onesignal-live-activities  # 1. Install
npx expo-onesignal-live-activities               # 2. Scaffold widget UI templates
# 3. Add plugin to app.config.ts, customize SwiftUI, prebuild

The config plugin handles steps 1–14 automatically. You write the SwiftUI widget UI (step 9) and set up your server API (step 16).

Features

Zero-config setup (handled by the config plugin)

  • Info.plist — sets NSSupportsLiveActivities and NSSupportsLiveActivitiesFrequentUpdates
  • Entitlements — configures aps-environment and App Group
  • AppDelegate — injects the Live Activity coordinator before the RN bridge boots
  • Widget Extension — creates the Xcode target with correct build settings, signing, versioning, and font registration
  • EAS Build — auto-registers the extension for credential provisioning (no manual appExtensions config)
  • Push-to-start tokens — registers tokens with OneSignal at app launch, including a synchronous fallback for the iOS 17.2–18.x timing race
  • Activity token relay — automatically calls OneSignal enter()/exit() as activities start and end
  • Widget UI scaffolding — CLI generates starter SwiftUI templates you can customize

JavaScript API

  • Client-side updates — update Live Activity content state from JavaScript without a push round-trip
  • End activities — programmatically dismiss activities from JavaScript
  • Activity listing — enumerate all active activities with tokens, attributes, and state
  • Device support check — check if Live Activities are supported before showing related UI
  • React hooksuseLiveActivityToken and useLiveActivities for declarative usage

Prerequisites

  • Expo SDK 53+ (requires Swift AppDelegate)
  • OneSignal React Native SDK 5.x (react-native-onesignal or onesignal-expo-plugin)
  • iOS 16.2+ deployment target

[!WARNING] Do not call OneSignal.LiveActivities.setupDefault() in your app. This package replaces that functionality. Using both causes duplicate token registrations and sequence races.

Not compatible with Expo Go — use EAS Build or npx expo prebuild.

Installation

npx expo install expo-onesignal-live-activities

1. Scaffold the widget UI (recommended)

Run the CLI to generate starter SwiftUI templates for your Live Activity widget:

npx expo-onesignal-live-activities

This creates widgets/live-activity/ with four customizable SwiftUI files:

  • LiveActivityWidget.swift — lock screen and Dynamic Island UI
  • LiveActivityWidgetBundle.swift — widget bundle entry point
  • CachedImageView.swift — App Group image cache helper
  • Theme.swift — colors and layout constants

2. Configure the plugin

Add to your app.json or app.config.ts:

{
  "expo": {
    "plugins": [
      ["expo-onesignal-live-activities", {
        "mode": "production",
        "activityIdKey": "onesignal_activity_id",
        "appGroupIdentifier": "group.com.myapp.onesignal",
        "enableFrequentUpdates": true,
        "widgetTarget": {
          "name": "MyAppLiveActivity",
          "widgetDir": "./widgets/live-activity",
          "fonts": ["./assets/fonts/MyFont-Bold.ttf"]
        }
      }]
    ]
  }
}

The plugin automatically handles:

  • Info.plist — sets NSSupportsLiveActivities and NSSupportsLiveActivitiesFrequentUpdates
  • Entitlements — configures aps-environment and App Group
  • AppDelegate — injects LiveActivityCoordinator.shared.start() before the RN bridge boots
  • Widget Extension — creates the Xcode target with correct build settings, signing, and versioning
  • EAS Build — auto-registers the extension in appExtensions for credential provisioning (no manual config needed)

3. Rebuild

npx expo prebuild -p ios --clean

Plugin Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | activityIdKey | string | "onesignal_activity_id" | Key in DefaultLiveActivityAttributes.data used to resolve the OneSignal activity ID | | enableFrequentUpdates | boolean | true | Enable NSSupportsLiveActivitiesFrequentUpdates in Info.plist | | mode | "development" \| "production" | "development" | Sets the aps-environment entitlement. Use "production" for TestFlight and App Store builds | | appGroupIdentifier | string | — | App Group ID for sharing data between the app and widget (e.g., "group.com.myapp.onesignal") | | widgetTarget | object | — | Configure automatic Widget Extension target creation (see below) |

widgetTarget Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | name | string | (required) | Widget Extension target name (e.g., "MyAppLiveActivity") | | widgetDir | string | (required) | Path to your widget Swift files (e.g., "./widgets/live-activity") | | deploymentTarget | string | "16.4" | Minimum iOS deployment target for the widget | | fonts | string[] | — | Font files to bundle with the widget (e.g., ["assets/fonts/Inter-Bold.ttf"]) | | pods | Array<{name, version?}> | — | Additional CocoaPods for the widget target |

Quick Start

With the config plugin configured, observation is fully automatic — push-to-start token registration, activity token relay to OneSignal, and lifecycle tracking all happen at app launch with zero JavaScript. The only function most apps need is updateLiveActivity for client-side content updates:

import { updateLiveActivity } from 'expo-onesignal-live-activities';

// Update a running Live Activity's content state (matched by attribute key/value)
await updateLiveActivity('orderId', 'order_123', {
  status: 'Out for delivery',
  eta: '12:30 PM',
  progress: 0.75,
});

That's it. The config plugin injects LiveActivityCoordinator.shared.start() into your AppDelegate before the React Native bridge boots, so all ActivityKit sequences are observed from the moment the app launches — including background push-to-start launches.

API Reference

Core Functions

updateLiveActivity(matchKey, matchValue, contentState)

Update the content state of a running Live Activity. Finds the activity where attributes.data[matchKey] equals matchValue.

await updateLiveActivity('deliveryId', 'del_456', {
  status: 'Arriving soon',
  eta: '2 minutes',
  driverName: 'Alex',
});

| Parameter | Type | Description | |-----------|------|-------------| | matchKey | string | Key in the activity's attributes data to match on | | matchValue | string | Value to match | | contentState | Record<string, unknown> | New content state fields |

endLiveActivity(matchKey, matchValue)

End a running Live Activity immediately.

await endLiveActivity('deliveryId', 'del_456');

listActiveActivities()

Returns all currently active Live Activities with their attributes, content state, resolved OneSignal ID, and push token.

const activities = await listActiveActivities();
// [
//   {
//     id: "AB12CD34-...",
//     resolvedActivityId: "order_123",
//     pushToken: "a1b2c3d4...",
//     attributes: { orderId: "order_123", storeName: "Pizza Place" },
//     contentState: { status: "Preparing", eta: "20 min" }
//   }
// ]

isLiveActivitiesSupported()

Check if the device supports Live Activities (iOS 16.2+, activities enabled in Settings).

const supported = await isLiveActivitiesSupported();

Advanced Functions

These are escape hatches for advanced use cases. Most apps don't need them.

startObserving()

Manually start the Live Activity coordinator. You don't need this if you use the config plugin — it auto-starts from AppDelegate before the React Native bridge boots. Only use this if you've opted out of the config plugin's AppDelegate injection.

Calling this when observation is already running is a no-op (idempotent).

await startObserving();

stopObserving()

Stop all Live Activity observation. Cancels push-to-start token registration, activity token relay, and lifecycle tracking. This is destructive — after calling it, no tokens are relayed to OneSignal until startObserving() is called again.

Use case: logout flows where you want to fully disconnect from Live Activity observation.

await stopObserving();

Hooks

useLiveActivityToken()

Subscribe to Live Activity token events. Returns the latest token event or null.

import { useLiveActivityToken } from 'expo-onesignal-live-activities';

function MyComponent() {
  const tokenEvent = useLiveActivityToken();

  useEffect(() => {
    if (tokenEvent) {
      console.log(`Activity ${tokenEvent.activityId} token: ${tokenEvent.token}`);
    }
  }, [tokenEvent]);
}

useLiveActivities(pollIntervalMs?)

Poll active Live Activities. Returns the list and a manual refresh function.

import { useLiveActivities } from 'expo-onesignal-live-activities';

function ActiveActivities() {
  const { activities, refresh } = useLiveActivities(5000); // poll every 5s

  return (
    <View>
      {activities.map((a) => (
        <Text key={a.id}>{a.resolvedActivityId}: {a.contentState.status}</Text>
      ))}
      <Button title="Refresh" onPress={refresh} />
    </View>
  );
}

Types

type LiveActivityInfo = {
  id: string;
  resolvedActivityId: string | null;
  pushToken: string | null;
  attributes: Record<string, unknown>;
  contentState: Record<string, unknown>;
};

type LiveActivityTokenEvent = {
  activityId: string;
  token: string;
};

type ContentStateUpdate = Record<string, unknown>;

How It Works

The package uses a LiveActivityCoordinator singleton that:

  1. Pre-seeds existing activities from Activity<DefaultLiveActivityAttributes>.activities on start
  2. Observes new activities via Activity<DefaultLiveActivityAttributes>.activityUpdates
  3. Registers push-to-start tokens via pushToStartTokenUpdates (iOS 17.2+) with a synchronous fallback for the known timing race
  4. Relays tokens to OneSignal via OneSignalLiveActivitiesManagerImpl.enter()/.exit()/.setPushToStartToken()
  5. Emits events to JavaScript for token updates and activity lifecycle changes

The coordinator starts from AppDelegate.didFinishLaunchingWithOptions (injected by the config plugin) — before the React Native bridge boots — so it catches push-to-start launches that happen in the background.

Widget UI

This package handles both the bridge (token observation, updates, lifecycle) and the widget extension setup (Xcode target, build phases, pods).

Scaffolded templates (recommended)

Run npx expo-onesignal-live-activities to generate starter templates, then customize the SwiftUI in widgets/live-activity/. The generated LiveActivityWidget.swift reads from context.attributes.data and context.state.data — the same dictionaries you populate from your OneSignal push-to-start payload.

Data flows through the DefaultLiveActivityAttributes type from OneSignal:

// In your widget — read attributes (static) and state (dynamic)
let title = context.attributes.data["title"]?.asString() ?? "My Activity"
let status = context.state.data["status"]?.asString() ?? "Loading..."
let value = context.state.data["value"]?.asString() ?? ""

Update from JavaScript:

await updateLiveActivity('orderId', 'order_123', {
  status: 'Out for delivery',
  value: '$42.50',
});

Manual widget setup

If you prefer to manage the Widget Extension target yourself (e.g., in Xcode), omit widgetTarget from the plugin config. You'll need to:

  1. Create a Widget Extension target in Xcode manually
  2. Use DefaultLiveActivityAttributes from OneSignalLiveActivities
  3. Add OneSignalXCFramework pod to the widget target's Podfile
  4. Configure entitlements and Info.plist for the widget target

Server-Side: Starting a Live Activity

Use OneSignal's push-to-start API to create Live Activities. The activityIdKey you configured in the plugin must match a key in your payload's attributes.data:

{
  "app_id": "YOUR_ONESIGNAL_APP_ID",
  "include_player_ids": ["PLAYER_ID"],
  "name": "LIVE_ACTIVITY_NOTIFICATION",
  "content_available": true,
  "data": {
    "activity_type": "default",
    "activity_attributes": {
      "data": {
        "onesignal_activity_id": "order_123",
        "storeName": "Pizza Place",
        "orderId": "order_123"
      }
    },
    "content_state": {
      "data": {
        "status": "Order confirmed",
        "eta": "25 min"
      }
    },
    "push_to_start_token": "TOKEN_FROM_CLIENT"
  }
}

Troubleshooting

"No recipients" on push-to-start

The device hasn't registered a push-to-start token with OneSignal. Check:

  1. The coordinator is starting at app launch (verify LiveActivityCoordinator.shared.start() in your AppDelegate)
  2. Live Activities are enabled in device Settings > Your App > Live Activities
  3. The app has been launched at least once after install to register the token
  4. You're running on a physical device — Simulator doesn't support push-to-start

Token not arriving

  1. Ensure startObserving() is called before the Live Activity starts, or use the config plugin for auto-start
  2. Verify NSSupportsLiveActivities is true in your Info.plist
  3. Check that the Widget Extension target uses DefaultLiveActivityAttributes (from OneSignal)
  4. Run on a physical device — Simulator has limited Live Activity support

Conflict with setupDefault()

This package replaces OneSignal.LiveActivities.setupDefault(). If both are active, you'll get duplicate token registrations and sequence races. Remove the setupDefault() call from your app.

CFBundleVersion missing on install

If you see "does not have a CFBundleVersion key" during device install, ensure you're on v0.2.3+ which resolves widget extension version build settings from the main target.

iOS 18 throttling

iOS 18 may throttle Live Activity updates. Set enableFrequentUpdates: true in the plugin config. Even so, iOS may batch updates during periods of heavy system load.

Activities not ending

If activities persist after calling endLiveActivity(), verify:

  • The matchKey/matchValue combination matches an active activity
  • Check await listActiveActivities() to see what's currently running

Build errors

  • "No such module 'OneSignalLiveActivities'": Add OneSignalXCFramework pod to your Widget Extension target's Podfile
  • "Unsupported deployment target": Ensure your Widget Extension targets iOS 16.2+

Known Limitations

  • iOS only — all functions no-op on Android and web
  • Requires OneSignal SDK 5.x — not compatible with OneSignal SDK 3.x or 4.x
  • No client-side activity creation — activities must be started via OneSignal's push-to-start API (server-initiated)
  • updateLiveActivity matches first activity — if multiple activities share the same value for the match key, only the first is updated
  • Push-to-start token unreliability — Apple's pushToStartTokenUpdates is unreliable on ~50% of devices (all iOS versions). The package includes a synchronous fallback but cannot fully work around this Apple bug.
  • Requires physical device for full functionality (Simulator support is limited)

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Commit your changes (git commit -m 'feat: add my feature')
  4. Push to the branch (git push origin feature/my-feature)
  5. Open a Pull Request

Development

# Install dependencies
bun install

# Build
bun run build

# Run tests
bun run test

# Lint
bun run lint

License

MIT