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

@crvouga/mockingbird-service-fcm

v1.3.0

Published

Stateful mock of the Firebase Cloud Messaging HTTP v1 send API used by firebase-admin: fixture projects and registration tokens, an outbox, and a device inbox.

Downloads

883

Readme

@crvouga/mockingbird-service-fcm

Familiar calls. Faithful echoes. Part of Mockingbird.

Stateful mock of the Firebase Cloud Messaging HTTP v1 send API (POST /v1/projects/{project_id}/messages:send), the call firebase-admin makes. Projects, access tokens, and registration tokens are fixtures. There is no FCM sandbox. This package is status: "wip".

  • Operation coverage: SUPPORT.md
  • The contract (openapi.yaml) is hand-authored from the FCM REST reference and firebase-admin 12.7.0 / 13.5.0.

Install

npm install -D @crvouga/mockingbird-service-fcm

ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with npx mockingbird-fcm serve, createServer from ./server (Node), or createRuntime with any Fetch server.

Usage

firebase-admin hardcodes https://fcm.googleapis.com. Point it at the mock with createAdminTransport (an https.Agent that dials the mock) and call enableLegacyHttpTransport() so multicast sends honor that agent. send is already HTTP/1.1. The fixture project is demo-project, the fixture device token is fixture-device-token, and any bearer is accepted until you turn on strict.

import { createServer } from "@crvouga/mockingbird-service-fcm/server"
import { createAdminTransport } from "@crvouga/mockingbird-service-fcm/admin"

const fcm = await createServer()
const transport = await createAdminTransport({ origin: fcm.url, token: "fixture-token" })
// initializeApp({ projectId: "demo-project", credential: transport.credential, httpAgent: transport.agent })
// getMessaging(app).enableLegacyHttpTransport()
await transport.close()
await fcm.close()

Without the SDK, POST the wire body and read the outbox:

import { createRuntime, FCM_FIXTURE_PROJECT, FCM_FIXTURE_TOKEN } from "@crvouga/mockingbird-service-fcm"

const fcm = createRuntime()
const response = await fcm.fetch(
  new Request(`http://fcm.test/v1/projects/${FCM_FIXTURE_PROJECT}/messages:send`, {
    method: "POST",
    headers: { authorization: "Bearer fixture-token", "content-type": "application/json" },
    body: JSON.stringify({
      message: { token: FCM_FIXTURE_TOKEN, notification: { title: "Hello", body: "World" } },
    }),
  }),
)
const body = (await response.json()) as { name: string }
const outbox = await (await fcm.fetch(new Request("http://fcm.test/__admin/outbox"))).json()
void body
void outbox

Routes

| Route | Behaviour | | --- | --- | | POST /v1/projects/{project_id}/messages:send | Bearer required. Body { message, validate_only? }. One registration token (topic and condition are rejected). Success is { name: "projects/{project_id}/messages/{id}" }. Errors are a Google RPC envelope. validate_only: true validates and returns a name without storing anything. | | GET /__admin/health | Liveness, plus the x-mockingbird header on every response. |

Admin

| Route | Behaviour | | --- | --- | | GET /__admin/outbox | Accepted messages, oldest first. Filters: token, platform, project, since (epoch ms), collapseKey, state. Each row has the normalized fields and the original wire message. | | POST /__admin/tokens | Register { token, platform?, project?, state?, appId? }. tokens: [...] registers a batch. | | POST /__admin/tokens/:token/expire | Mark expired. Optional { replacement }. | | POST /__admin/tokens/:token/state | { state: active \| expired \| unregistered \| sender_mismatch }. | | GET /__admin/inbox/:token | Device inbox, in delivery order. | | POST /__admin/inbox/:token | { action: "ack" } drops the oldest item. { action: "clear" } empties it. | | POST /__admin/deliver | Deliver accepted messages. Expired ones stay in the outbox as expired. | | POST /__admin/messages/:id/deliver | Deliver one accepted message. | | POST /__admin/messages/:id/drop | Mark dropped and do not inbox it. | | POST /__admin/messages/:id/duplicate | Copy the inbox entry again. | | GET /__admin/settings, PUT /__admin/settings | strict, automaticDelivery (default true), collapse (default false), credentials (bearer → { project, expired? }), clockOffsetMs. | | POST /__admin/time | { advanceMs } moves this namespace's offset only. | | POST /__admin/scripts | { token?, errorCode, count, httpStatus? }. The next count sends to that token (or every token when token is omitted) return that Google RPC error and store nothing. Then the token's own state applies. | | GET /__admin/scripts, DELETE /__admin/scripts | List or clear scripts. ?token= clears one. |

Logical message time is clockOffsetMs plus the process-wide runtime clock. POST /__admin/clock still moves every namespace. Records, faults, outboxes, inboxes, and message ids are per namespace.

Presets

POST /__admin/faults {"preset": "<name>"}. Each one targets SendMessage:

| Preset | Effect | | --- | --- | | invalid_auth | 401 UNAUTHENTICATED, nothing stored | | permission_denied | 403 PERMISSION_DENIED, nothing stored | | invalid_argument | 400 INVALID_ARGUMENT, nothing stored | | unregistered | 404 NOT_FOUND + FcmError UNREGISTERED, nothing stored | | sender_mismatch | 403 + FcmError SENDER_ID_MISMATCH, nothing stored | | quota_exceeded | 429 RESOURCE_EXHAUSTED, Retry-After, google.rpc.RetryInfo, FcmError QUOTA_EXCEEDED | | unavailable | 503 + FcmError UNAVAILABLE | | internal | 500 + FcmError INTERNAL | | slow | waits 15s (the Firebase Admin send timeout) | | drop | connection drop before accept; outbox stays empty | | accepted_then_network_drop | outbox record is stored, then the connection drops |

Namespaces

x-mockingbird-namespace, a /__admin/ns/<name>/ prefix, or PUT /__admin/credentials (bearer → namespace). A bearer listed in settings.credentials authorizes only that project. strict: true rejects bearers that are not listed. Webhooks: none. The device inbox is a test control, not a callback.

SDK error codes

firebase-admin maps error.details[].errorCode when @type is type.googleapis.com/google.firebase.fcm.v1.FcmError, otherwise error.status. Two mappings differ from a naive reading of the error-code names, and the mock follows firebase-admin 12.7.0 and 13.5.0:

  • A JSON 401 with status UNAUTHENTICATED and no FcmError details becomes messaging/third-party-auth-error. messaging/authentication-error is what the SDK uses for a non-JSON 401.
  • FcmError DEADLINE_EXCEEDED is not in the messaging map, so the SDK reports messaging/unknown-error. UNAVAILABLE is messaging/server-unavailable.

INVALID_ARGUMENT, SENDER_ID_MISMATCH, QUOTA_EXCEEDED, UNAVAILABLE, and INTERNAL map to messaging/invalid-argument, messaging/mismatched-credential, messaging/message-rate-exceeded, messaging/server-unavailable, and messaging/internal-error. Status PERMISSION_DENIED (a bearer for another project) is also messaging/mismatched-credential.

Deliberately not modelled

  • Firebase Auth, Firestore, Realtime Database, Hosting, Remote Config, Analytics, and client installation APIs.
  • Topic and condition sends, and topic subscription management.
  • APNs and Android transport, real devices, app callbacks, notification rendering, badges, and sounds.
  • Production quotas and Google's unpublished retry schedule. The Admin SDK's own retry (503, ECONNRESET, ETIMEDOUT) is left to the SDK.

API

| Export | Kind | Description | | --- | --- | --- | | FcmAPI | class | In-process mock: fetch(request), reset(), logicalNow(), outbox(query), inbox(token), registerToken, setTokenState, ackInbox, deliverPending, dropMessage, duplicateMessage, putScript. Options: sqlite, now, namespace, settings. | | createRuntime | function | Mock plus /__admin/health, /__admin/*, namespaces, clock, presets, and the journal. Options: sqlite, clock, seed, adminKey, onLog, settings. | | FCM_PRESETS | object | Named fault presets. | | FCM_NAMESPACE | string | Service name, "fcm". | | FCM_FIXTURE_PROJECT | string | Seeded project id, "demo-project". | | FCM_FIXTURE_TOKEN | string | Seeded Android registration token, "fixture-device-token". | | FCM_FIXTURE_BEARER | string | Bearer the playground sends, "fixture-token". Accepted whenever strict is off. | | fcmCredential | function | (accessToken?) → { getAccessToken } for initializeApp({ credential }). | | document, operationIds, supportedOperationIds | values | The OpenAPI contract and its operation ids. | | createServer, serveTarget, DEFAULT_PORT (./server) | Node | Serve over node:http. CLI flag --strict. Port 8826. | | createAdminTransport (./admin) | Node | { origin, token? } → { credential, agent, port, close }. agent is an https.Agent aimed at the mock. |

Part of mockingbird.