Push workflow

Push is not a single display action. Permission, device registration, application-server registration, foreground event handling, and removal are separate stages. This page shows how those stages fit together.

The workflow below is implemented by the push runtime binding and sercrod.push adapter.

1. Smallest useful declaration

<p *push="/api/push"></p>

A non-clickable base declaration will perform the workflow in order:

  1. Check notification permission.
  2. Register the app or browser with its push provider.
  3. Send the normalized registration to the application's /api/push endpoint.
  4. Start the default foreground receive/action watch.

/api/push is the application's push registration endpoint. It is not an FCM/APNs sending endpoint and is not the Web Push subscription's own subscription.endpoint. Use <p *push></p> when registration must remain device-side and no application endpoint should receive it.

2. Browser fallback

Capacitor can invoke the native OS permission request during automatic setup. A browser may require a click before showing its permission prompt. When a click can continue setup, Sercrod reports $push.needs_action === true.

<p *push="/api/push"></p>

<button
  *if="$push && $push.needs_action"
  *push.register>
  Enable notifications
</button>

The application owns the visible explanation and control. Sercrod does not insert fallback copy, localization, layout, or buttons. A denied permission uses $push.status === "denied" with $push.needs_action === false; an application cannot select Allow for the user.

3. Choose the shortest test path

  1. Capacitor Android registration: use app/push, add google-services.json, build, and confirm the FCM token.
  2. Browser FCM registration: use app/push, add the public Web app configuration and public VAPID key, then register over HTTPS.
  3. Complete server delivery: use app/push-server, test the upper Node.js path, then test PHP only after Node works.

An FCM registration token confirms provider registration; it does not by itself confirm server storage or delivery.

4. One application registration

Provider registration is application-wide, not owned by one <serc-rod>. Multiple declarations and controls with the same effective configuration reuse the in-progress or completed registration.

<p *push="/api/push"></p>

<serc-rod>
  <button *push.register>Enable notifications</button>
</serc-rod>

<serc-rod>
  <button *push.register>Enable notifications here too</button>
</serc-rod>

Two different active registration endpoints are a configuration conflict; they are not silently combined. The initial design supports one active application registration endpoint. A different endpoint may be used after a complete unregister.

5. Application endpoint contract

{
  "action": "register",
  "registration": {
    "platform": "android",
    "provider": "fcm",
    "token": "..."
  }
}

Browser Web Push sends its normalized subscription information instead. Client deduplication reduces repeated handoff, but the application endpoint must still handle repeated registration safely. Provider accounts, private sending credentials, token storage, recipients, and server-side delivery remain application responsibilities.

6. Receive events in the running page

An unnamed declaration is the default watch:

<section
  *push.watch
  *response="'notification'">
  <p *if="notification">
    %notification.notification.body%
  </p>
</section>

Background display remains an OS, service-worker, FCM, or APNs responsibility. *push.watch handles events delivered to the running page.

Use names only when local consumers must be managed independently:

<header
  *push.watch="'header'"
  *response="'header_notification'">
  <p *if="header_notification">
    %header_notification.notification.title%
  </p>
</header>

<main
  *push.watch="'messages'"
  *response="'message_notification'">
  <p *if="message_notification">
    %message_notification.notification.body%
  </p>
</main>

Names identify local consumers; they do not filter notification categories. Without an application-defined filter, every watch receives every normalized event.

7. Clear watches

<button *push.clear>Stop the default watch</button>

<button *push.clear="'messages'">
  Stop the messages watch
</button>

The declared name is the watch identifier. Sercrod retains the associated native or browser listener handle internally. Watch names are unique within their owning <serc-rod>.

8. Unregister

<button *push.unregister>
  Disable notifications
</button>

When an application endpoint is active, Sercrod asks it to remove the saved registration first, then unregisters the provider subscription. If server removal fails, the provider registration remains available for retry. Without an application endpoint, unregister is device-side only.

9. Try the Capacitor Android sample

The distribution includes app/push, ready for the shared Windows Capacitor builder.

  1. Install Android Studio, its Android SDK, Node.js, and MSYS2 as required by app/build.bat.
  2. In Firebase, create an Android app with the Capacitor App ID. A new sample uses com.sercrod.push.
  3. Save the downloaded file as app/push/google-services.json.
  4. Connect an Android device with USB debugging enabled.
  5. Run app/build.bat, select push, and choose whether to synchronize the public samples first.
  6. Open the installed app. The token shown under Registration confirms client registration.

The builder installs @capacitor/push-notifications, creates and synchronizes Android, copies google-services.json into android/app, builds the debug APK, and installs it. Receiving a message additionally requires sending through Firebase or your provider server.

For a custom Capacitor project:

npm install @capacitor/push-notifications
npx cap sync

On Android, the app-level google-services.json remains required. On iOS, add the iOS platform, enable the Push Notifications capability in Xcode, add Capacitor's current remote-notification forwarding methods to AppDelegate.swift, and test on a signed physical device.

10. Try browser FCM

Browser FCM uses a Firebase Web app, not Android's google-services.json.

  1. Add a Web app in Firebase project settings and copy its public firebaseConfig.
  2. Create a Web Push certificate under Cloud Messaging and copy its public VAPID key.
  3. Put those public values in app/push/www/firebase-config.js.
  4. Serve over HTTPS, activate registration, grant permission, and confirm platform: "web" and provider: "fcm".

The Web configuration and VAPID public key are browser-visible by design. A service-account JSON file, its private_key, APNs private key, and administrator password are server-only. Never paste or place them in HTML, chat, an app bundle, a public directory, or the repository. Revoke an exposed private key before continuing.

11. Try server delivery

The app/push-server sample uses one browser/Capacitor client with Node.js above PHP. Test Node first.

  1. For Capacitor, add Firebase Android package com.sercrod.push_server and place its google-services.json under app/push-server/.
  2. Keep the service-account JSON outside the repository and public web root; pass its path to the server through PUSH_FIREBASE_CREDENTIAL_FILE.
  3. Publish the Node registration API behind HTTPS.
  4. Allow the public site plus the actual Capacitor origins in PUSH_ALLOWED_ORIGIN. Common values are https://localhost, http://localhost, and capacitor://localhost.
  5. Register in the upper Node section, open its authenticated admin page, select the masked device, and send.
  6. Unregister before switching to PHP.

The live PHP registration endpoint is https://sercrod.com/push-php/api/push, with its admin page at /push-php/admin. Node and PHP have both been verified through FCM with the same provider-neutral registration contract.

Responsibility summary