UNPKG

@walletpass/pass-js

Version:

Apple Wallet Pass generating and pushing updates from Node.js

83 lines 3.95 kB
// SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (C) 2017-2026 Konstantin Vyatkin <tino@vtkn.io> // Generate a pass file. import { getBufferHash } from './lib/get-buffer-hash.js'; import { signManifest } from './lib/sign-manifest.js'; import { PassBase } from './lib/base-pass.js'; import { writeZip } from './lib/zip.js'; import { assertUpcomingPassInformationContext } from './lib/upcoming-pass-information.js'; import { createPersonalizationEntry, isPersonalizationLogoPath, } from './lib/personalization.js'; function hasNfcPayload(pass) { const nfc = pass.nfc; if (!nfc || typeof nfc !== 'object') return false; const message = nfc.message; return typeof message === 'string' && message.length > 0; } // Create a new pass. // // template - The template // fields - Pass fields (description, serialNumber, logoText) export class Pass extends PassBase { template; constructor(template, fields = {}, images, localization, options, personalization) { super(fields, images, localization, options, personalization); this.template = template; Object.preventExtensions(this); } // Validate pass, throws error if missing a mandatory top-level field or image. validate() { for (const requiredField of [ 'description', 'organizationName', 'passTypeIdentifier', 'serialNumber', 'teamIdentifier', ]) { if (!(requiredField in this.fields)) throw new ReferenceError(`${requiredField} is required in a Pass`); } if ('webServiceURL' in this.fields) { if (typeof this.fields.authenticationToken !== 'string') throw new Error('While webServiceURL is present, authenticationToken also required!'); if (this.fields.authenticationToken.length < 16) throw new ReferenceError('authenticationToken must be at least 16 characters long!'); } else if ('authenticationToken' in this.fields) { throw new TypeError('authenticationToken is present in Pass data while webServiceURL is missing!'); } // Cross-field check deferred from the `upcomingPassInformation` // setter — runs here so construction order doesn't matter. assertUpcomingPassInformationContext(this.fields); this.images.validate(); } async asBuffer() { this.validate(); if (!this.template.certificate) throw new ReferenceError(`Set pass certificate in template before producing pass buffers`); if (!this.template.key) throw new ReferenceError(`Set private key in pass template before producing pass buffers`); const zip = []; const passJson = JSON.stringify(this); const passObject = JSON.parse(passJson); const imageEntries = await this.images.toArray(); const personalization = this.personalization; const canPersonalize = Boolean(personalization && hasNfcPayload(passObject) && imageEntries.some(entry => isPersonalizationLogoPath(entry.path))); zip.push({ path: 'pass.json', data: Buffer.from(passJson) }); zip.push(...this.localization.toArray()); zip.push(...imageEntries.filter(entry => canPersonalize || !isPersonalizationLogoPath(entry.path))); if (canPersonalize && personalization) zip.push(createPersonalizationEntry(personalization)); const manifestJson = JSON.stringify(zip.reduce((res, { path, data }) => { res[path] = getBufferHash(data); return res; }, {})); zip.push({ path: 'manifest.json', data: manifestJson }); const signature = signManifest(this.template.certificate, this.template.key, manifestJson); zip.push({ path: 'signature', data: signature }); return writeZip(zip); } } //# sourceMappingURL=pass.js.map