or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

index.mddocs/

APN (Apple Push Notification)

APN is a Node.js interface to the Apple Push Notification service, providing reliable delivery of push notifications to iOS devices using Apple's HTTP/2-based provider API. It features automatic connection management with connection pooling, retry mechanisms for failed notifications, and support for both token-based and certificate-based authentication.

Package Information

  • Package Name: apn
  • Package Type: npm
  • Language: JavaScript/TypeScript
  • Installation: npm install apn

Core Imports

const apn = require("apn");

ES6/TypeScript:

import * as apn from "apn";
// or destructured
import { Provider, Notification, token } from "apn";

Basic Usage

const apn = require("apn");

// Create provider with token-based authentication
const provider = new apn.Provider({
  token: {
    key: "path/to/APNsAuthKey_XXXXXXXXXX.p8",
    keyId: "key-id",
    teamId: "developer-team-id"
  },
  production: false // Use sandbox environment
});

// Create notification
const notification = new apn.Notification();
notification.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires in 1 hour
notification.badge = 3;
notification.sound = "ping.aiff";
notification.alert = "📧 ✉ You have a new message";
notification.payload = { messageFrom: "John Appleseed" };
notification.topic = "com.example.MyApp"; // Your app bundle ID

// Send notification
const deviceToken = "a9d0ed10e9cfd022a61cb08753f49c5a0b0dfb383697bf9f9d750a1003da19c7";
provider.send(notification, deviceToken).then((result) => {
  console.log("Sent:", result.sent.length);
  console.log("Failed:", result.failed.length);
  
  // Log any failures
  result.failed.forEach((failure) => {
    console.error("Failed to send to", failure.device, ":", failure.error || failure.response?.reason);
  });
});

// Shutdown when done
provider.shutdown();

Architecture

The APN package is built around three core components:

  • Provider: Main interface that manages connections to Apple's servers and handles notification delivery
  • Notification: Data structure representing a push notification with all its properties and methods
  • Token Utility: Helper function for validating and normalizing device tokens
  • Internal Systems: Credential management, HTTP/2 protocol handling, and connection pooling

Capabilities

Provider Management

The Provider class manages connections to Apple Push Notification service and handles notification delivery.

class Provider extends EventEmitter {
  constructor(options: ProviderOptions);
  send(notification: Notification, recipients: string | string[]): Promise<Responses>;
  shutdown(): void;
}

interface ProviderOptions {
  token?: ProviderToken;
  cert?: string | Buffer;
  key?: string | Buffer;
  ca?: (string | Buffer)[];
  pfx?: string | Buffer;
  passphrase?: string;
  production?: boolean;
  rejectUnauthorized?: boolean;
  connectionRetryLimit?: number;
}

interface ProviderToken {
  key: Buffer | string;
  keyId: string;
  teamId: string;
}

interface Responses {
  sent: ResponseSent[];
  failed: ResponseFailure[];
}

interface ResponseSent {
  device: string;
}

interface ResponseFailure {
  device: string;
  error?: Error;
  status?: string;
  response?: {
    reason: string;
    timestamp?: string;
  };
}

Usage Examples:

// Token-based authentication (recommended)
const provider = new apn.Provider({
  token: {
    key: "path/to/APNsAuthKey_XXXXXXXXXX.p8", // or Buffer with key data
    keyId: "ABCDEFGHIJ",
    teamId: "TEAMID1234"
  },
  production: true
});

// Certificate-based authentication
const provider = new apn.Provider({
  cert: "path/to/cert.pem", // or Buffer with certificate data
  key: "path/to/key.pem",   // or Buffer with key data
  passphrase: "keypassword", // if key is encrypted
  production: true
});

// PKCS12 format
const provider = new apn.Provider({
  pfx: "path/to/cert.p12", // or Buffer with PKCS12 data
  passphrase: "p12password",
  production: true
});

// Send to multiple devices
const deviceTokens = ["token1", "token2", "token3"];
provider.send(notification, deviceTokens).then((result) => {
  console.log(`Successfully sent to ${result.sent.length} devices`);
  console.log(`Failed to send to ${result.failed.length} devices`);
});

Notification Creation

The Notification class represents a push notification with all its properties and serialization methods.

class Notification {
  constructor(payload?: any);
  
  // Core properties
  topic: string;
  id: string;
  expiry: number;
  priority: number;
  collapseId: string;
  threadId: string;
  payload: any;
  rawPayload: any;
  
  // APS properties
  aps: Aps;
  badge: number;
  sound: string;
  alert: string | NotificationAlertOptions;
  contentAvailable: boolean;
  mutableContent: boolean;
  mdm: string | Object;
  urlArgs: string[];
  category: string;
  
  // Methods
  headers(): Object;
  compile(): string;
  length(): number;
  toJSON(): Object;
  
  // Setter methods (return this for chaining)
  setPayload(value: any): this;
  setExpiry(value: number): this;
  setPriority(value: number): this;
  setAlert(value: string | NotificationAlertOptions): this;
  setBody(value: string): this;
  setTitle(value: string): this;
  setSubtitle(value: string): this;
  setBadge(value: number): this;
  setSound(value: string): this;
  setContentAvailable(value: boolean): this;
  setMutableContent(value: boolean): this;
  setMdm(value: string | Object): this;
  setUrlArgs(value: string[]): this;
  setCategory(value: string): this;
  setThreadId(value: string): this;
  setLocKey(value: string): this;
  setLocArgs(value: string[]): this;
  setTitleLocKey(value: string): this;
  setTitleLocArgs(value: string[]): this;
  setAction(value: string): this;
  setActionLocKey(value: string): this;
  setLaunchImage(value: string): this;
}

interface NotificationAlertOptions {
  title?: string;
  subtitle?: string;
  body: string;
  "title-loc-key"?: string;
  "title-loc-args"?: string[];
  "action-loc-key"?: string;
  "loc-key"?: string;
  "loc-args"?: string[];
  "launch-image"?: string;
}

interface Aps {
  alert?: string | ApsAlert;
  "launch-image"?: string;
  badge?: number;
  sound?: string;
  "content-available"?: undefined | 1;
  "mutable-content"?: undefined | 1;
  "url-args"?: string[];
  category?: string;
  "thread-id"?: string;
}

interface ApsAlert {
  body?: string;
  "loc-key"?: string;
  "loc-args"?: any[];
  title?: string;
  "title-loc-key"?: string;
  "title-loc-args"?: any[];
  action?: string;
  "action-loc-key"?: string;
}

Usage Examples:

// Basic notification
const notification = new apn.Notification();
notification.topic = "com.example.MyApp";
notification.alert = "Hello World!";
notification.badge = 1;
notification.sound = "default";

// Complex alert with localization
notification.alert = {
  title: "New Message",
  subtitle: "From John",
  body: "Hey there! How are you?",
  "loc-key": "MESSAGE_BODY",
  "loc-args": ["John", "Hey there! How are you?"]
};

// Method chaining
const notification = new apn.Notification()
  .setTopic("com.example.MyApp")
  .setAlert("Hello World!")
  .setBadge(1)
  .setSound("default")
  .setPayload({ customData: "value" });

// Silent notification for background refresh
const silentNotification = new apn.Notification();
silentNotification.topic = "com.example.MyApp";
silentNotification.contentAvailable = true;
silentNotification.payload = { updateType: "background-sync" };

// Notification with expiry and priority
notification.expiry = Math.floor(Date.now() / 1000) + (24 * 60 * 60); // 24 hours
notification.priority = 5; // Power-conserving delivery

// Custom payload with raw JSON
notification.rawPayload = {
  aps: {
    alert: "Custom notification",
    badge: 1
  },
  customField: "customValue",
  deepLink: "myapp://profile/123"
};

Device Token Validation

Utility function for validating and normalizing device tokens received from iOS devices.

/**
 * Validates and normalizes a device token
 * @param input - Device token as string or Buffer
 * @returns Normalized hex string token
 * @throws Error if token has invalid length after cleanup
 */
function token(input: string | Buffer): string;

Usage Examples:

const apn = require("apn");

// Validate string token
const validToken = apn.token("a9d0ed10e9cfd022a61cb08753f49c5a0b0dfb383697bf9f9d750a1003da19c7");
console.log(validToken); // Clean hex string

// Validate Buffer token
const bufferToken = Buffer.from("a9d0ed10e9cfd022a61cb08753f49c5a0b0dfb383697bf9f9d750a1003da19c7", "hex");
const validatedToken = apn.token(bufferToken);

// Handle validation errors
try {
  const cleanToken = apn.token("invalid!token@with#special$chars");
} catch (error) {
  console.error("Invalid token:", error.message);
}

// Clean up token with spaces and other characters
const messyToken = "a9d0 ed10 e9cf-d022 a61c b087";
const cleanToken = apn.token(messyToken); // Returns clean hex string

Error Handling

The APN package provides comprehensive error handling for common scenarios:

// Handle provider send failures
provider.send(notification, deviceTokens).then((result) => {
  result.failed.forEach((failure) => {
    console.error(`Failed to send to ${failure.device}:`);
    
    if (failure.error) {
      // Network or connection error
      console.error("Error:", failure.error.message);
    }
    
    if (failure.response) {
      // APNS returned an error
      console.error("APNS Error:", failure.response.reason);
      
      // Handle specific error reasons
      switch (failure.response.reason) {
        case "BadDeviceToken":
          // Remove invalid token from database
          break;
        case "Unregistered":
          // Device uninstalled app, remove token
          break;
        case "PayloadTooLarge":
          // Notification payload exceeds 4KB limit
          break;
        case "TooManyProviderTokenUpdates":
          // Rate limited, retry later
          break;
      }
    }
  });
});

// Handle token validation errors
try {
  const validToken = apn.token(userInputToken);
} catch (error) {
  console.error("Invalid device token format");
}

Common Configuration Patterns

// Development configuration
const devProvider = new apn.Provider({
  token: {
    key: process.env.APN_KEY_PATH,
    keyId: process.env.APN_KEY_ID,
    teamId: process.env.APN_TEAM_ID
  },
  production: false
});

// Production configuration
const prodProvider = new apn.Provider({
  token: {
    key: fs.readFileSync(process.env.APN_KEY_PATH),
    keyId: process.env.APN_KEY_ID,
    teamId: process.env.APN_TEAM_ID
  },
  production: true
});

// Enterprise configuration with custom CA
const enterpriseProvider = new apn.Provider({
  cert: process.env.APN_CERT_PATH,
  key: process.env.APN_KEY_PATH,
  ca: [fs.readFileSync("corporate-ca.pem")],
  production: true,
  connectionRetryLimit: 5
});