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

@calf/wallet

v0.0.1-alpha.1

Published

Apple Wallet and Google Wallet toolkit module of Calf framework.

Readme

@calf/wallet

Technical toolkit for Apple Wallet and Google Wallet integrations.

The package does not handle database persistence, routes, permissions, customer models, share tokens, device registration storage, or business decisions about when a pass should be updated. Applications map their own domain data to the inputs exported by this package.

Entry points

import { AppleWallet } from "@calf/wallet/apple";
import { GoogleWallet } from "@calf/wallet/google";
import { IWalletTextModule } from "@calf/wallet/shared";

The root entry point also re-exports all Apple, Google and shared APIs.

Apple Wallet

import { AppleWallet } from "@calf/wallet/apple";

const appleWallet = AppleWallet.create({
  passTypeIdentifier,
  teamIdentifier,
  organizationName,
  certificate,
  privateKey,
  wwdrCertificate,
  privateKeyPassphrase,
  webServiceURL,
  environment: "production"
});

environment controls the APNs endpoint used by update notifications. Use "sandbox" for development passes signed with development credentials and "production" for production passes. The older production: false flag is still accepted for backward compatibility, but environment is preferred.

Create pass.json without packaging:

const passJson = appleWallet.passes.createJson({
  serialNumber,
  authenticationToken,
  description: "Loyalty card",
  logoText: "Company",
  colors: {
    backgroundColor: "rgb(198,15,31)",
    foregroundColor: "rgb(255,255,255)",
    labelColor: "rgb(255,255,255)"
  },
  barcode: {
    format: "PKBarcodeFormatQR",
    message: "123456"
  },
  fields: {
    primary: [{ key: "points", label: "Points", value: "120" }],
    secondary: [{ key: "name", label: "Name", value: "Jan Novak" }],
    back: []
  }
});

Create a signed .pkpass package:

const passPackage = await appleWallet.passes.create({
  serialNumber,
  authenticationToken,
  description: "Loyalty card",
  colors: {
    backgroundColor: "#c60f1f",
    foregroundColor: "#ffffff",
    labelColor: "#ffffff"
  },
  barcode: {
    format: "PKBarcodeFormatQR",
    message: "123456"
  },
  fields: {
    primary: [{ key: "points", label: "Points", value: "120" }],
    secondary: [{ key: "name", label: "Name", value: "Jan Novak" }],
    back: []
  },
  assets: {
    icon: iconBuffer,
    logo: logoBuffer,
    logo2x: logo2xBuffer,
    logo3x: logo3xBuffer
  }
});

Apple asset keys are mapped to Wallet package file names:

Send Apple Wallet update push notifications:

const results = await appleWallet.notifications.notifyPassUpdated({
  pushTokens: ["..."]
});

notifyPassUpdated uses passTypeIdentifier from AppleWallet.create(...) as the APNs topic. Pass passTypeIdentifier to notifyPassUpdated only when a single instance needs to notify a different pass type.

await appleWallet.notifications.notifyPassUpdated({
  passTypeIdentifier: "pass.com.example.other",
  pushTokens: ["..."],
  environment: "sandbox"
});

The application remains responsible for implementing Apple's Wallet web service routes and storing device registrations. This package only sends APNs update notifications for push tokens passed to it.

Create a field carrying the latest visible notification message:

const notificationField = appleWallet.notifications.createNotificationField({
  message: {
    id: "lunch",
    title: "Lunch",
    body: "We have free tables today."
  },
  label: "Last notification"
});

const passPackage = await appleWallet.passes.create({
  ...passInput,
  fields: {
    ...passInput.fields,
    back: [
      ...(passInput.fields.back || []),
      notificationField
    ]
  }
});

await appleWallet.notifications.notifyPassUpdated({
  pushTokens
});

Apple Wallet APNs update pushes do not carry a custom notification text. They tell Wallet to fetch the updated pass from the application's Wallet web service. The visible notification comes from the pass update itself, usually from a changed field with changeMessage.

Google Wallet

import { GoogleWallet } from "@calf/wallet/google";

const googleWallet = GoogleWallet.create({
  issuerId,
  serviceAccount,
  origins: ["https://example.com"]
});

serviceAccount is the parsed Google service account JSON object. The package also accepts serviceAccountPath when the application wants the library to read the JSON file:

const googleWallet = GoogleWallet.create({
  issuerId,
  serviceAccountPath: "/secure/google-wallet-service-account.json",
  origins: ["https://example.com"]
});

The service account must contain at least client_email and private_key and must have Wallet Object issuer permissions.

Create or patch a loyalty class:

await googleWallet.loyaltyClasses.upsert({
  classId,
  issuerName: "Company",
  programName: "Loyalty card",
  reviewStatus: "UNDER_REVIEW",
  hexBackgroundColor: "#c60f1f",
  logo: {
    uri: "https://example.com/logo.png",
    description: "Logo"
  }
});

classId and objectId must be fully qualified Google Wallet IDs, for example issuerId.my-class and issuerId.my-object. The package does not prefix local IDs with issuerId, because applications usually need stable control over ID generation and persistence.

Create or patch a loyalty object:

await googleWallet.loyaltyObjects.upsert({
  objectId,
  classId,
  state: "ACTIVE",
  accountId: "123456",
  accountName: "Jan Novak",
  barcode: {
    type: "QR_CODE",
    value: "123456"
  },
  textModules: [
    { id: "points", header: "Points", body: "120" }
  ]
});

Create an Add to Google Wallet save URL:

const saveLink = googleWallet.saveLinks.create({
  loyaltyObjects: [{ id: objectId, classId }]
});

console.log(saveLink.url);

Google must be able to fetch image URLs over public HTTPS. This package does not host assets or validate that an image URL is publicly reachable.

Send a Google Wallet message and trigger a push notification:

await googleWallet.notifications.notifyObject({
  objectId,
  message: {
    id: "lunch",
    title: "Lunch",
    body: "We have free tables today."
  }
});

For class-level notifications:

await googleWallet.notifications.notifyClass({
  classId,
  message: {
    id: "campaign-2026-06",
    title: "June offer",
    body: "Show your card today and get an extra reward."
  }
});

Google Wallet notifications use the Add Message API with messageType: "TEXT_AND_NOTIFY" by default. Google controls the lock-screen notification appearance, users must have Wallet notifications enabled, and Google documents a limit of 3 notification-triggering messages per pass in 24 hours. Use messageType: "TEXT" when you want to add a message without triggering a push notification.

Updating Existing Passes

The application decides when a pass should be updated and persists its own timestamps. A typical loyalty points update looks like this:

// 1. Application updates its own domain data.
client.clientPoints = 180;
await ClientModel.updateOne({ _id: client._id }, { $set: { clientPoints: client.clientPoints } });

// 2. Application loads its own Wallet metadata.
const pushTokens = client.appleWallet.registrations.map((registration) => registration.pushToken);
const objectId = client.googleWallet.objectId;
const classId = client.googleWallet.classId;

// 3. Apple Wallet devices are told to fetch the updated pass from the app's web service.
await appleWallet.notifications.notifyPassUpdated({
  pushTokens
});

// 4. Google Wallet object is patched or upserted with mapped data.
await googleWallet.loyaltyObjects.upsert({
  objectId,
  classId,
  state: "ACTIVE",
  accountId: client.clientCardIdentifier,
  accountName: client.name || "-",
  loyaltyPoints: {
    label: "Points",
    balance: { string: String(client.clientPoints || 0) }
  },
  textModules: [
    { id: "points", header: "Points", body: String(client.clientPoints || 0) }
  ]
});

// 5. Application stores its own update metadata.
await ClientModel.updateOne({ _id: client._id }, { $set: { "wallet.lastUpdatedAt": new Date() } });

The model names above are only an application-side example. They are not part of this package and are not runtime dependencies.

Migration/reference mapping

The implementation was derived from the existing backend Wallet services, but the public API is generalized:

  • IAppleWalletPassConfig became IAppleWalletConfig for platform credentials and identifiers.
  • IAppleWalletPassJson, Apple barcode, field, file map and package concepts were kept as generic Apple Wallet types.
  • Apple color normalization from customer settings was generalized: both #rrggbb and rgb(r,g,b) are accepted.
  • Google environment config became IGoogleWalletConfig, with issuerId, serviceAccount or serviceAccountPath, and origins.
  • Google JWT claims and save link response became IGoogleWalletJwtClaims and IGoogleWalletSaveLink.
  • Google loyalty class/object payload creation is now driven by explicit IGoogleWalletLoyaltyClassInput and IGoogleWalletLoyaltyObjectInput.

Application responsibility remains outside the package:

  • customer/client models such as IClient
  • Mongo/Mongoose documents and _id
  • clientCardIdentifier, clientPoints, walletShare
  • local Apple authentication tokens and serial number storage
  • Apple Wallet device registration routes and storage
  • pass status, download/update timestamps and device counts
  • deciding when a pass should be updated
  • serving Google Wallet image assets

Example domain mapping in an application:

const appleInput = {
  serialNumber: client.appleWallet.serialNumber,
  authenticationToken: client.appleWallet.authenticationToken,
  description: settings.description,
  colors: {
    backgroundColor: settings.backgroundColor,
    foregroundColor: settings.textColor,
    labelColor: settings.textColor
  },
  barcode: {
    format: "PKBarcodeFormatCode128" as const,
    message: client.clientCardIdentifier,
    altText: client.clientCardIdentifier
  },
  fields: {
    header: [{ key: "points", label: "Points", value: String(client.clientPoints || 0) }],
    secondary: [{ key: "name", label: "Customer", value: client.name || "-" }]
  },
  assets: {
    icon: defaultIcon,
    logo: settingsAppleLogo
  }
};

const googleObjectInput = {
  objectId: client.googleWallet.objectId,
  classId: client.googleWallet.classId,
  state: "ACTIVE" as const,
  accountId: client.clientCardIdentifier,
  accountName: client.name || "-",
  barcode: {
    type: "CODE_128" as const,
    value: client.clientCardIdentifier,
    alternateText: client.clientCardIdentifier
  },
  loyaltyPoints: {
    label: "Points",
    balance: { string: String(client.clientPoints || 0) }
  }
};