This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A Firebase Extension — not a regular Firebase project. This means:
- The root defines the extension itself:
extension.yaml(manifest),PREINSTALL.md/POSTINSTALL.md(marketplace docs),firebase.json(emulator config for local dev). - All Cloud Functions source lives in
functions/— this is where the actual code, tests, and build config are. - Configuration is declared as extension params in
extension.yamland injected as environment variables at runtime. There is no.envfile or hardcoded config — Firebase handles param injection when the extension is installed.
The extension receives Purchasely webhook events (in-app purchases & subscriptions), syncs them to Cloud Firestore, and optionally updates Firebase Auth custom claims with subscription status. Published as purchasely/purchasely-in-app-purchases.
All commands run from functions/:
cd functions
# Build
npm run build # tsc --build tsconfig.json
# Lint
npm run lint # eslint '**/*.ts'
npm run lint:fix
npm run prettier # prettier -c .
npm run prettier:fix
# Test (requires Firestore emulator)
firebase emulators:exec --only firestore "cd ./functions && npm test"
# npm test = nyc --reporter html --reporter text ava --verbose
# Run a single test file
firebase emulators:exec --only firestore "cd ./functions && npx ava --verbose functions/__tests__/v3/Unit/event-signature-validation.test.ts"Tests use ava with ts-node/register. Integration tests hit the Firestore emulator on localhost:8080. Unit tests use sinon stubs.
ICM context: ignoreDeprecations: "6.0" in tsconfig.json breaks ts-node (TS5103 error). If added for build fixes, the valid value is "5.0", or better: change moduleResolution: "node" to "node16"/"nodenext" and remove ignoreDeprecations entirely.
functions/src/functions.ts — exports purchaselyWebhookHandler, an HTTPS Cloud Function that:
- Validates HMAC signature (
X-PURCHASELY-SIGNATUREheader) against the shared secret - Validates request body with AJV JSON Schema
- Routes to v2 or v3 handler based on
request.body.api_version - Dispatches to the correct controller by
purchase_type(subscription, consumable, non-consumable)
functions/src/v2/ and functions/src/v3/ are parallel structures, each containing:
purchasely-webhook/— controllers + DTO schemas + signature validationpurchasely-subscriptions/— domain model, service, Firestore repositorypurchasely-consumables/— same patternpurchasely-non-consumables/— same patternpurchasely-events/— raw event storagefirebase-custom-claims/— Auth custom claims managementcustomer-users/— user domainservices.ts— wires repositories and services together
Each module follows: domain model → service → Firestore repository, composed via curried factory functions (not classes/DI).
functions/src/purchasely.config.ts — reads all config from environment variables set by Firebase Extensions params (extension.yaml). Key params: PURCHASELY_SHARED_SECRET, collection names, UPDATE_FIREBASE_AUTH_CUSTOM_CLAIMS, LOCATION.
functions/__tests__/
├── _configFactory.ts # shared config builder
├── v2/ and v3/
│ ├── _webhookEventFactory.ts
│ ├── _requestsFactory.ts
│ ├── _servicesFactory.ts
│ ├── Unit/ # sinon stubs, no emulator needed
│ └── FirebaseIntegration/ # Firestore emulator CRUD tests
extension.yaml — defines the extension's params, roles (firebaseauth.admin, datastore.user), and the single Cloud Function resource. Current version: 1.0.9, runtime: nodejs22.
GitHub Actions (.github/workflows/):
- ci-pr.yml — build → test (with Firestore emulator) → coverage archive
- ci-develop.yml — same + deploy to Purchasely's Firebase project using
FIREBASE_TOKENandPURCHASELY_SHARED_SECRETsecrets - Node version:
lts/jod(Node 22)