New:Socket for Asana Is Now Available.Learn more
Sign In

@notiflyio/capacitor-push

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@notiflyio/capacitor-push

Capacitor plugin wiring Notifly push into PWA shell apps: FCM channel setup, injected window-event bridge, and same-origin notification deep links

latest
Source
npmnpm
Version
0.1.2
Version published
Weekly downloads
24
-31.43%
Maintainers
1
Weekly downloads
 
Created
Source

@notiflyio/capacitor-push

Capacitor plugin wiring Notifly push into the fleet's PWA shell apps. It owns what every shell used to hand-copy (caly-pwa-android / marka-pwa-android#5, ~350 lines per app):

  • FCM default notification channel created at launch with a real name (config-driven id/name/description).
  • Injected window-event bridge (bridge/notifly-push-bridge.js) installed at document start into every document of the app origin, re-dispatching @capacitor/push-notifications activity as the SAME window CustomEvents the fleet's raw-WKWebView iOS shells emit — the app's web layer keeps one set of listeners, no per-platform fork.
  • Notification tap → same-origin deep link. Warm taps are handled by the plugin automatically; cold-start taps are a ~6-line MainActivity hook (below). Off-origin links from push payloads are rejected (phishing surface).

Event contract (fleet-locked; see pwa-notifications references/contracts.md)

One token event on both platforms, naming its own provider — a web layer keeps one listener and never forks per platform.

DirectionNameDetail
shell → webpush-apns-token{ token: string, provider: 'apns' | 'fcm' }
shell → webpush-apns-errorerror message string
shell → webpush-notificationforeground payload (title, body, link, data)
shell → webpush-permission-request'granted' | 'denied'
shell → webpush-permission-state'authorized' | 'denied' | 'notDetermined'
web → shellwindow.NotiflyPush.requestPermission() / permissionState() / token()control object (plus the jsGlobal alias)
web → shellwindow.webkit.messageHandlers['push-permission-request' | 'push-permission-state' | 'push-token'].postMessage('')same three calls, under the names the fleet's raw-WKWebView shells register

Deprecated aliases (since 0.1.1)

NameDetailStatus
push-fcm-tokendevice token stringAndroid only; emitted alongside push-apns-token. Deprecated — read the primary event's provider instead.
push-fcm-errorerror message stringAndroid only; emitted alongside push-apns-error. Deprecated.

Both aliases keep 0.1.0's bare-string detail, so a consumer written against 0.1.0 needs no change. They will not be removed before 1.0. On iOS the legacy name and the primary name are the same event, so it is dispatched exactly once, with the object detail.

Migration for a 0.1.0 consumer: read the token from push-apns-token, accepting either shape —

const read = (detail) => (typeof detail === 'string' ? { token: detail, provider: 'apns' } : detail);
window.addEventListener('push-apns-token', (e) => { const { token, provider } = read(e.detail); ... });

— and drop the push-fcm-* listeners.

The control object appears asynchronously: the injected script retries for ~10s until Capacitor's runtime is callable (a server.url shell has not bootstrapped it at document start), then warns [notifly] gave up installing the push bridge if it never was. Probe for window.NotiflyPush when you need it, not once at mount.

Install (shell app)

npm install @notiflyio/capacitor-push @capacitor/push-notifications
npx cap sync

@capacitor/push-notifications is a peer dependency and LOAD-BEARING beyond gradle wiring: Capacitor's own JS export writes Capacitor.Plugins.PushNotifications (the probe web layers use to detect the native push leg) only when that plugin is installed.

capacitor.config.json

{
  "plugins": {
    "NotiflyPush": {
      "jsGlobal": "MarkaPush",
      "channelId": "marka_default",
      "channelName": "Marka notifications",
      "channelDescription": "General notifications",
      "allowedHost": "joinmarka.com"
    }
  }
}

All keys optional — channelId defaults to notifly_default, the canonical window.NotiflyPush global is always set, deep links always allow the app origin (server.url).

Android app manifest (stays app-side)

<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="marka_default" />

plus the usual Firebase leg: google-services.json in android/app/ and the com.google.gms.google-services gradle plugin. Firebase project MUST be the app's existing OAuth GCP project (SenderId-mismatch class — see the pwa-notifications skill's portal-setup reference).

MainActivity cold-start hook (the one last mile a plugin cannot own)

The initial page load is already in flight during plugin load, so a cold-start tap must steer that load from the activity:

import io.notifly.capacitorpush.NotiflyPushBridge;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    String link = NotiflyPushBridge.deepLinkFrom(getIntent(), getBridge().getConfig().getServerUrl());
    if (link != null) {
        getBridge().getWebView().loadUrl(link);
    }
}

Warm taps (app already running) are handled by the plugin's handleOnNewIntent — no activity code. If your activity ALSO navigates on new intents, both navigations target the same URL (harmless).

The app-side token relay route (session-authed, forwards the device token to Notifly's per-device endpoints) deliberately stays app-side too — it needs the app's auth. Recipe: pwa-notifications references/wiring.md.

Development

bridge/notifly-push-bridge.js is the canonical script; pnpm build (or node scripts/embed-bridge.mjs) stamps it into the committed native copies (android/src/main/assets/ + generated BridgeScript.swift). node scripts/embed-bridge.mjs --check gates drift (runs in prepublish).

pnpm test runs test/bridge.test.mjs — the real script in a fake document (node:vm, fake clock), pinning the install retry and the event contract. Both also run on every PR (.github/workflows/ci.yml). Point NOTIFLY_BRIDGE_PATH at another copy of the script to run the suite against it: with 0.1.0's bridge, 9 of the 13 checks fail, starting with the install race (notifly#61).

Verification status

  • Android half: ported line-for-line from caly-pwa-android (device-proven on the fleet, 2026-07). This npm packaging device-proven 2026-07-31: marka-pwa-android migrated onto the packed plugin (app-local bridge deleted), gradle assembleDebug green, and on an API-36 emulator logcat showed plugin registration, the marka_default channel created from config, and the injected bridge's addListener → checkPermissions → register → FCM registration event with no registrationError. Remaining before 1.x: a real notification DELIVERY + tap deep-link pass on a physical device. 0.1.1 cures what the superbooks Android device proof found on 2026-08-01 (notifly#61): on that shell the bridge never installed on any page of the app origin, because at document start on a fresh server.url context Capacitor is not callable yet and 0.1.0 returned for good. The install is now self-retrying and test/bridge.test.mjs pins it.
  • iOS half: authored to the same design but COMPILE-GATED — no Mac toolchain has built it as of 2026-07-31. The fleet's shipped iOS shells are raw WKWebView apps (see the NotiflyShellPush Swift package), not Capacitor; this half exists for future Capacitor-built iOS shells. Do not ship it to a device without an Xcode build + the contract smoke run.

Keywords

capacitor

FAQs

Package last updated on 03 Aug 2026

Related posts