← Back to work

IRFCM

An internal tool for pushing FCM requests to thousands of managed corporate phones

CompanySole designer & developer (built independently while at IRLink, later deployed company-wide)2023.03 — 2024.03

Next.jsTypeScriptFirebaseMaterial-UI

About the project

IRLink provisions and manages thousands of corporate phones on behalf of client companies, mostly insurers. Whenever a device needed remote attention, an admin had to look up its FCM token directly in the Firebase Realtime Database and fire a raw JSON payload through Postman. It got worse once legacy FCM was retired in favor of HTTP v1 with OAuth 2.0 — every single request now meant hand-issuing a fresh access token first.

I built IRFCM on my own time and shipped it in-house to kill that workflow. After a one-time OAuth 2.0 login against a pre-registered GCP admin account, the access token is cached in localStorage. From then on, an admin just types in a phone number and IRFCM looks up its token in the Realtime Database and handles the FCM request for them.

What it does

Firebase integration & FCM requests

  • Initializes the Firebase app per client using a FirebaseConfig (API key, auth domain, database URL, project ID, storage bucket, messaging sender ID, app ID, measurement ID). If an app instance already exists it’s torn down and re-initialized, so switching between clients never collides.
  • requestFcm wraps building the JSON body and attaching auth headers for the actual FCM call.

OAuth 2.0 authentication

  • getNewOAuthCode issues an auth code, exchanged via client ID + redirect URI, with the resulting access token cached locally so re-auth isn’t needed on every request.

Log & recording retrieval (remote device control)

  • Request a device’s logs → uploaded to Firebase Storage → viewable directly in IRFCM.
  • Request a device’s stored call recordings → same flow, pulled through Storage.

Batch automation utilities (6 endpoints)

  • Batch Rename /api/rename — bulk-renames AMR files in a local directory
  • Compress /api/compress — zips a local directory for transfer to the file server
  • Search /api/search — scans TXT files for keyword matches and writes the results out
  • Batch Download /api/download — bulk-downloads files from Firebase Storage
  • Batch API Request & Validation /api/message, /api/message/validation — bulk-replays the message-history API (to recover missing records) and verifies the results
  • Batch Upload /api/upload — bulk-uploads AMR files from a local directory

Technical challenges & what I learned

  • Lived through the Next.js v13 App Router migration in productionpages to app, the server/client component split, the 'use client' directive, moving useRouter from next/router to next/navigation, and adopting new hooks like useSearchParams, all inside one project.
  • Managed form state with React Hook Form.
  • Combined Firebase Realtime Database, Storage, and FCM in a single service.
  • Implemented the GCP OAuth 2.0 flow myself.
  • Built Next.js API Routes called via fetch, plus Axios calls against our internal staging/production servers.
  • Replaced repetitive manual work (renaming, zipping, searching files) with batch APIs.
  • Tagged logs by source file — debugging got noticeably faster and the console got a lot more readable.
  • Applied TypeScript, Prettier, and Material-UI’s DataGrid at production scale for the first time.

Troubleshooting

Env vars were always undefined inside components OAuth credentials and Firebase config lived in .env.local for security, but reading them directly from a component always returned undefined. The cause: Next.js v13’s client components can’t read env vars directly, for security reasons. I fixed it by nesting the client component inside an SSR page and passing the needed values down as props.

A client’s server outage wiped message history — recovered via IRFCM’s bulk tools Every time a phone sent or received a message, it called an API to log the record. When Chubb CDM’s production server went down, every one of those calls timed out and the history was never recorded. Luckily the phones’ own app logs still had the full send/receive record. I used IRFCM to fire a bulk FCM request across every affected device, pulled the logs, filtered the TXT output down to the message-API call attempts, verified the extraction was correct, and then bulk-resent everything through IRFCM to restore the missing history.