Skip to content

yanqianglu/tauri-plugin-mobile-push

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tauri-plugin-mobile-push

npm

Push notifications for Tauri v2 mobile apps -- iOS (APNs) and Android (FCM).

A Tauri v2 plugin that provides native remote push notification support using Apple Push Notification service (APNs) on iOS and Firebase Cloud Messaging (FCM) on Android. Unlike tauri-plugin-notification which only handles local notifications, this plugin handles server-sent remote push notifications -- the kind you need for chat apps, alerts, and real-time engagement.

Zero-config on iOS. No AppDelegate file, no method swizzling, no manual setup. The plugin automatically injects APNs handlers into the Tao AppDelegate at runtime using ObjC runtime APIs. Just enable the Push Notifications capability in Xcode and you are ready to go.

Features

  • APNs on iOS -- native device token registration and push delivery
  • FCM on Android -- Firebase Cloud Messaging integration with automatic token management
  • Zero iOS configuration -- no AppDelegate.swift file needed; APNs methods are injected automatically at runtime
  • No method swizzling -- uses direct @_cdecl FFI and class_addMethod injection, which is transparent and future-proof
  • Foreground notifications -- receive and display pushes while the app is open
  • Notification tap handling -- deep-link into your app when users tap a notification
  • Token refresh events -- stay in sync when the OS rotates device tokens
  • Desktop no-op -- compiles on macOS/Windows/Linux without error; commands return stub values so you can gate push logic behind platform checks
  • TypeScript API -- fully typed async functions and event listeners

Platform Support

Platform Push Token Foreground Notifications Notification Tap Token Refresh
iOS 13+ APNs device token (hex) Yes Yes Yes
Android 7+ (API 24) FCM registration token Yes Yes Yes
Desktop No-op (stub values) N/A N/A N/A

Why This Plugin?

The official tauri-plugin-notification only supports local notifications. It cannot receive server-sent pushes. If you need to send notifications from your backend to your users' devices -- the standard push notification flow for any chat app, messaging service, or alert system -- you need a remote push plugin.

Third-party alternatives use method swizzling, which intercepts Objective-C method calls at runtime. This technique is fragile: it breaks when multiple plugins swizzle the same methods, produces difficult-to-debug failures, and Apple has been deprecating the APIs that enable it.

This plugin takes a different approach. On iOS, it uses direct @_cdecl FFI between Rust and Swift, bypassing Tauri's standard run_mobile_plugin dispatch entirely. APNs delegate methods are injected into the Tao-generated AppDelegate at runtime using imp_implementationWithBlock and class_addMethod. This means:

  • Zero configuration -- no AppDelegate.swift file to create or maintain
  • Reliable -- uses the same FFI mechanism (@_cdecl) that Tauri uses internally for init_plugin_<name>()
  • Debuggable -- all operations are logged via NSLog with the [mobile-push] prefix
  • Composable -- does not conflict with other Tauri plugins or native code

Installation

Rust

Add to src-tauri/Cargo.toml:

[dependencies]
tauri-plugin-mobile-push = "0.1"

Or track the latest from git:

[dependencies]
tauri-plugin-mobile-push = { git = "https://github.com/yanqianglu/tauri-plugin-mobile-push" }

JavaScript / TypeScript

npm install tauri-plugin-mobile-push-api
# or
pnpm add tauri-plugin-mobile-push-api
# or
bun add tauri-plugin-mobile-push-api

Requires @tauri-apps/api >= 2.0.0 as a peer dependency.

Capabilities

Add to your capabilities file (e.g., src-tauri/capabilities/mobile.json):

{
  "permissions": ["mobile-push:default"]
}

This grants both allow-request-permission and allow-get-token.

Plugin Registration

In src-tauri/src/lib.rs:

tauri::Builder::default()
    .plugin(tauri_plugin_mobile_push::init())
    // ... other plugins
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

Foreground presentation (iOS, optional)

When a push arrives while the app is foreground, iOS asks the delegate what to present. The plugin's default is banner + list + sound + badge, which is right for reminder/alert-style apps. Chat-style apps where the notification content is already visible in-app usually want to suppress the banner instead:

use tauri_plugin_mobile_push::{Builder, ForegroundPresentationOptions};

tauri::Builder::default()
    .plugin(
        Builder::new()
            .ios_foreground_presentation(ForegroundPresentationOptions::silent())
            .build(),
    )
    // ... other plugins
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

Presets on ForegroundPresentationOptions:

  • default() -- banner + list + sound + badge (preserves pre-0.1.4 behavior)
  • silent() -- list + badge only (recommended for chat / messaging apps)
  • none() -- fully invisible (data-only push)

Or set individual flags:

ForegroundPresentationOptions {
    banner: false,
    list: true,
    sound: false,
    badge: true,
}

This only affects foreground behavior on iOS. When the app is backgrounded or the phone is locked, iOS bypasses the delegate entirely and shows the notification natively. Android follows FCM's standard behavior (banner when backgrounded, silent in-app when foreground).

Setup

iOS

1. Enable Push Notifications Capability

In Xcode, select your target, go to Signing & Capabilities, and add the Push Notifications capability. This adds the aps-environment entitlement automatically.

2. Verify Entitlements

Ensure your .entitlements file includes:

<key>aps-environment</key>
<string>development</string>

Change to production for App Store / TestFlight builds. If you added the Push Notifications capability via Xcode, this is handled automatically.

That is the complete iOS setup. No AppDelegate.swift file is needed -- the plugin handles all APNs delegate methods automatically via runtime injection.

Android

1. Add Firebase

  1. Create a Firebase project at console.firebase.google.com and add your Android app.
  2. Download google-services.json and place it in src-tauri/gen/android/app/.
  3. Configure your Gradle files:
// project-level build.gradle.kts
plugins {
    id("com.google.gms.google-services") version "4.4.2" apply false
}

// app-level build.gradle.kts
plugins {
    id("com.google.gms.google-services")
}

dependencies {
    implementation(platform("com.google.firebase:firebase-bom:33.8.0"))
    implementation("com.google.firebase:firebase-messaging")
}

2. Register the FCM Service

Add to your AndroidManifest.xml inside the <application> tag:

<service
    android:name="app.tauri.mobilepush.FCMService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

This registers the plugin's FCMService which forwards incoming messages and token refreshes to the Tauri event system.

Usage

Request Permission

Shows the system permission dialog on iOS. On Android 13+ (API 33), requests the POST_NOTIFICATIONS runtime permission. Earlier Android versions return { granted: true } immediately.

import { requestPermission } from "tauri-plugin-mobile-push-api";

const { granted } = await requestPermission();
if (!granted) {
  console.warn("Push notification permission denied");
}

Get Device Token

Returns the APNs device token (hex string) on iOS or the FCM registration token on Android. On iOS, this triggers registerForRemoteNotifications() and resolves when the OS delivers the token.

import { getToken } from "tauri-plugin-mobile-push-api";

const token = await getToken();
console.log("Device push token:", token);
// Send this token to your backend for server-side push delivery

Complete Registration Flow

The typical integration: request permission, get the token, and register it with your backend.

import {
  requestPermission,
  getToken,
  onNotificationReceived,
  onNotificationTapped,
  onTokenRefresh,
} from "tauri-plugin-mobile-push-api";

// 1. Request permission
const { granted } = await requestPermission();
if (!granted) {
  console.warn("Push permission denied");
  return;
}

// 2. Get the device push token
const token = await getToken();

// 3. Send token to your backend
await fetch("https://your-api.com/push/register", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ token, platform: "ios" }),
});

// 4. Listen for foreground notifications
const unsubReceived = await onNotificationReceived((notification) => {
  console.log("Received:", notification.title, notification.body);
  console.log("Custom data:", notification.data);
});

// 5. Listen for notification taps (user opened app from notification)
const unsubTapped = await onNotificationTapped((notification) => {
  console.log("Tapped:", notification.data);
  // Navigate to the relevant screen based on notification.data
});

// 6. Listen for token refreshes (re-register with your backend)
const unsubToken = await onTokenRefresh(({ token }) => {
  console.log("Token refreshed:", token);
  // Send new token to your backend
});

// Cleanup when your component unmounts
unsubReceived.unregister();
unsubTapped.unregister();
unsubToken.unregister();

API Reference

Commands

requestPermission()

function requestPermission(): Promise<{ granted: boolean }>;

Request push notification permission from the user.

  • iOS: Triggers the system permission dialog requesting .alert, .badge, and .sound. Blocks until the user responds (30s timeout).
  • Android 13+: Requests the POST_NOTIFICATIONS runtime permission.
  • Android < 13: Returns { granted: true } immediately (no runtime permission needed).
  • Desktop: Returns { granted: false }.

getToken()

function getToken(): Promise<string>;

Get the current device push token.

  • iOS: Calls registerForRemoteNotifications(), waits for the APNs callback, and returns the device token as a hex string. Times out after 15 seconds.
  • Android: Calls FirebaseMessaging.getInstance().token and returns the FCM registration token.
  • Desktop: Returns an empty string.

Events

All event listeners return Promise<PluginListener>. Call .unregister() on the returned listener to stop receiving events.

onNotificationReceived(handler)

function onNotificationReceived(
  handler: (notification: PushNotification) => void,
): Promise<PluginListener>;

Fires when a push notification arrives while the app is in the foreground. On iOS, the notification is also displayed as a banner (with sound and badge).

onNotificationTapped(handler)

function onNotificationTapped(
  handler: (notification: PushNotification) => void,
): Promise<PluginListener>;

Fires when the user taps a push notification to open the app. Use this for deep linking.

onTokenRefresh(handler)

function onTokenRefresh(
  handler: (payload: { token: string }) => void,
): Promise<PluginListener>;

Fires when the OS issues a new push token (APNs token refresh on iOS, FCM token rotation on Android). Send the new token to your backend whenever this fires.

Types

/** Payload delivered with push notification events. */
interface PushNotification {
  title?: string;
  body?: string;
  data: Record<string, unknown>;
  badge?: number;
  sound?: string;
}

Sending Push Notifications from Your Server

Once you have the device token, send pushes from your backend:

  • iOS (APNs): Use the APNs HTTP/2 API with a .p8 signing key or .p12 certificate. The token from getToken() is a hex-encoded APNs device token.
  • Android (FCM): Use the FCM HTTP v1 API with a service account. The token from getToken() is an FCM registration token.

The notification payload should include title, body, and any custom data fields your app needs. These will be delivered to your onNotificationReceived and onNotificationTapped handlers.

Architecture

The plugin uses different strategies per platform to work around limitations in Tauri v2's mobile plugin dispatch.

iOS: Direct @_cdecl FFI

Tauri v2's swift-rs compilation model creates duplicate PluginManager singletons when multiple plugins include Swift code. This causes run_mobile_plugin calls to hang indefinitely -- register_plugin() stores the plugin in one singleton, but run_plugin_command() dispatches through a different one.

This plugin bypasses that system entirely using @_cdecl FFI functions, which is the same mechanism Tauri uses for init_plugin_<name>() and is proven to work reliably:

  • request_permission: Rust spawns a thread that calls extern "C" mobile_push_request_permission() in Swift. The Swift function calls UNUserNotificationCenter.requestAuthorization() and blocks with a DispatchSemaphore until the user responds. Returns 1 (granted) or 0 (denied) to Rust.
  • get_token: Rust spawns a thread that calls extern "C" mobile_push_get_device_token() in Swift. On first call, the Swift function lazily injects APNs delegate methods (didRegisterForRemoteNotificationsWithDeviceToken, didFailToRegisterForRemoteNotificationsWithError) into Tao's dynamically-created AppDelegate using imp_implementationWithBlock + class_addMethod. It then calls registerForRemoteNotifications() and blocks until the APNs callback fires, writing the hex token to a C buffer.
  • register_listener: Handled as a no-op in Rust's generate_handler! to prevent fallthrough to the broken run_mobile_plugin path.

Android: Standard Tauri Plugin Dispatch

On Android, the standard Tauri plugin dispatch works correctly. The Kotlin MobilePushPlugin handles commands directly, and FCMService (a FirebaseMessagingService) forwards incoming messages and token refreshes to the plugin's event system.

TypeScript

Thin async wrappers over invoke() and addPluginListener() from @tauri-apps/api/core. Published as tauri-plugin-mobile-push-api on npm.

Known Limitations

  • iOS event listeners are not yet functional. onNotificationReceived, onNotificationTapped, and onTokenRefresh register successfully but do not deliver events on iOS. This is because the Tauri PluginManager dispatch issue also affects the plugin's trigger() method for emitting events to the webview. The commands (requestPermission, getToken) work correctly via the direct FFI path. A future release will route iOS events through AppHandle.emit() to bypass the PluginManager.
  • Android event listeners work as expected. The standard Tauri plugin dispatch functions correctly on Android.

License

Licensed under either of Apache License, Version 2.0 or MIT License at your option.

About

Tauri v2 plugin for push notifications on iOS (APNs) and Android (FCM). No swizzling.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors