@walletpass/pass-js
Version:
Apple Wallet Pass generating and pushing updates from Node.js
141 lines • 5.25 kB
JavaScript
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2017-2026 Konstantin Vyatkin <tino@vtkn.io>
import { PASS_STYLES, TRANSIT, STRUCTURE_FIELDS } from '../constants.js';
import { FieldsMap } from './fieldsMap.js';
import { NFCField } from './nfc-fields.js';
export class PassStructure {
fields = {};
constructor(fields = {}) {
for (const style of PASS_STYLES) {
if (!(style in fields))
continue;
this.style = style;
if ('boardingPass' in fields && fields.boardingPass) {
this.transitType = fields.boardingPass.transitType;
}
else if ('storeCard' in this.fields && 'nfc' in fields) {
this.fields.nfc = new NFCField(fields.nfc);
}
const structure = fields[style];
if (!structure)
continue;
for (const prop of STRUCTURE_FIELDS) {
if (!(prop in structure))
continue;
const currentProperty = structure[prop];
const target = this[prop];
if (Array.isArray(currentProperty))
for (const field of currentProperty)
target.add(field);
else if (currentProperty instanceof FieldsMap)
for (const [key, data] of currentProperty)
target.add({ key, ...data });
}
}
}
// Returns the structure container for the current pass style, creating it
// if it doesn't exist. Throws if no style is set.
structure() {
const { style } = this;
if (!style)
throw new ReferenceError(`Pass style is undefined, set the pass style before accessing pass structure fields`);
const s = this.fields[style];
if (s)
return s;
const fresh = {};
this.fields[style] = fresh;
return fresh;
}
fieldMap(name) {
const s = this.structure();
if (!(s[name] instanceof FieldsMap))
s[name] = new FieldsMap();
return s[name];
}
/** Pass type, e.g. boardingPass, coupon, etc. */
get style() {
for (const style of PASS_STYLES) {
if (style in this.fields)
return style;
}
return undefined;
}
set style(v) {
for (const style of PASS_STYLES)
if (style !== v)
delete this.fields[style];
// NFC is a storeCard-only field; drop any carry-over when switching away.
if (v !== 'storeCard')
delete this.fields.nfc;
if (!v)
return;
if (!PASS_STYLES.has(v))
throw new TypeError(`Invalid Pass type "${v}"`);
if (!(v in this.fields))
this.fields[v] =
{};
if ('storeCard' in this.fields && !this.fields.nfc)
this.fields.nfc = new NFCField();
}
get transitType() {
if (this.style !== 'boardingPass')
throw new ReferenceError(`transitType field only allowed in Boarding Passes, current pass is ${this.style}`);
if ('boardingPass' in this.fields && this.fields.boardingPass)
return this.fields.boardingPass.transitType;
return undefined;
}
set transitType(v) {
const { style } = this;
if (!style) {
if (!v)
return;
this.style = 'boardingPass';
}
if (!('boardingPass' in this.fields))
throw new ReferenceError(`transitType field is only allowed at boarding passes`);
if (!v) {
if (this.fields.boardingPass)
delete this.fields.boardingPass
.transitType;
}
else {
if (Object.values(TRANSIT).includes(v)) {
if (this.fields.boardingPass)
this.fields.boardingPass.transitType = v;
else
this.fields.boardingPass = { transitType: v };
}
else
throw new TypeError(`Unknown transit type "${v}"`);
}
}
get nfc() {
if (!('storeCard' in this.fields))
throw new ReferenceError(`NFC fields only available for storeCard passes, current is ${this.style}`);
return this.fields.nfc;
}
get headerFields() {
return this.fieldMap('headerFields');
}
get auxiliaryFields() {
return this.fieldMap('auxiliaryFields');
}
get backFields() {
return this.fieldMap('backFields');
}
get primaryFields() {
return this.fieldMap('primaryFields');
}
get secondaryFields() {
return this.fieldMap('secondaryFields');
}
// iOS 18 event-ticket dashboard fields. Only valid on eventTicket
// passes — mirrors the style gating used by `transitType` (boardingPass)
// and `nfc` (storeCard).
get additionalInfoFields() {
if (this.style !== 'eventTicket')
throw new ReferenceError(`additionalInfoFields only allowed on eventTicket passes, current style is ${this.style}`);
return this.fieldMap('additionalInfoFields');
}
}
//# sourceMappingURL=pass-structure.js.map