Merge pull request #254 from alexandercerutti/feature/iOS26

Support for iOS 26 Changes
This commit is contained in:
Alexander Cerutti
2025-10-07 22:44:55 +02:00
committed by GitHub
30 changed files with 1433 additions and 85 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,156 @@
{
"formatVersion": 1,
"passTypeIdentifier": "pass.com.passkitgenerator",
"teamIdentifier": "F53WB8AE67",
"groupingIdentifier": "ticket-demo-upcoming-events",
"description": "Description",
"organizationName": "A some kind of event happening tomorrow",
"backgroundColor": "#ffffff",
"foregroundColor": "#000000",
"labelColor": "#FF0000",
"logoText": "Demo",
"preferredStyleSchemes": ["posterEventTicket", "eventTicket"],
"eventTicket": {
"headerFields": [
{
"key": "event_date",
"label": "event-date",
"value": "26.09.2024"
}
],
"primaryFields": [
{ "key": "event_name", "label": "event-name", "value": "Dune" }
],
"additionalInfoFields": [
{
"key": "additionalInfo-1",
"label": "Additional Info 1",
"value": "The text to show"
},
{
"key": "additionalInfo-2",
"label": "Additional Info 2",
"value": "The text to show 2"
},
{
"key": "lineItem3",
"label": "Emergency Contact",
"value": "+1 8716 12736131",
"dataDetectorTypes": ["PKDataDetectorTypePhoneNumber"]
},
{
"key": "lineItem4",
"label": "Test link",
"value": "https://apple.com",
"dataDetectorTypes": ["PKDataDetectorTypeLink"],
"attributedValue": "<a href=\"https://apple.com\">Used literally on iPhone, used correctly on Watch</a>"
}
]
},
"semantics": {
"venueParkingLotsOpenDate": "2025-10-09T04:00:00+00:00",
"venueGatesOpenDate": "2025-10-09T06:00:00+00:00",
"eventLiveMessage": "This event is going to start soon! Try to relax your anus (cit.)",
"eventType": "PKEventTypeLivePerformance",
"eventName": "South Bay Jazz Festival",
"entranceDescription": "Event at The Stadium",
"venueLocation": {
"latitude": 51.555557,
"longitude": 0.238041
},
"venueName": "The Stadium",
"performerNames": ["Lady Gaga"],
"eventStartDate": "2025-10-08T22:00:00+00:00",
"eventEndDate": "2025-10-09T23:59:59+00:00",
"tailgatingAllowed": true,
"seats": [
{
"seatNumber": "5",
"seatRow": "3",
"seatSection": "100",
"seatSectionColor": "#FFD700"
}
],
"artistIDs": ["984117861"]
},
"directionsInformationURL": "https://www.displaysomeinfoexample.com",
"contactVenueWebsite": "https://www.venueexample.com",
"relevantDates": [
{
"startDate": "2025-10-09T17:00:00+01:00",
"endDate": "2025-10-09T23:59:59+01:00"
},
{
"startDate": "2025-10-10T17:00:00+00:00",
"endDate": "2025-10-10T19:00:00+00:00"
},
{
"startDate": "2025-10-11T17:00:00+00:00",
"endDate": "2025-10-11T19:00:00+00:00"
}
],
"nfc": {
"message": "message",
"encryptionPublicKey": "MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADwKMBv29ByaSLiGF0FctuyB+Hs2oZ1kDIYhTVllPexNE="
},
"upcomingPassInformation": [
{
"type": "event",
"identifier": "dnb-event",
"isActive": true,
"name": "Drum'n'Bass Night",
"dateInformation": {
"date": "2025-09-18T00:00:00+01:00",
"timeZone": "Europe/Rome",
"isAllDay": true
},
"semantics": {
"venueName": "The Stadium",
"venuePlaceID": "IB452E0A3979253B0",
"venueLocation": {
"latitude": 37.334859,
"longitude": -122.00904
},
"seats": [
{
"seatNumber": "1",
"seatRow": "A",
"seatSection": "100"
}
],
"performerNames": ["Maduk"],
"venueParkingLotsOpenDate": "2025-11-09T04:00:00+00:00",
"venueGatesOpenDate": "2025-11-09T06:00:00+00:00",
"eventType": "PKEventTypeLivePerformance",
"eventName": "South Bay Jazz Festival",
"entranceDescription": "Event at The Stadium",
"eventStartDate": "2025-11-08T22:00:00+00:00",
"eventEndDate": "2025-11-09T23:59:59+00:00"
},
"additionalInfoFields": [
{
"key": "huehueheu",
"label": "Line Item 1",
"value": "Value 1"
}
],
"backFields": [
{
"key": "huehueheu1",
"label": "Line Item 1",
"value": "Value 1"
}
],
"URLs": {
"merchandiseURL": "https://www.example.com/merchandise",
"parkingInformationURL": "https://www.example.com/parking",
"transferURL": "https://www.example.com/transfer"
},
"images": {
"venueMap": {
"reuseExisting": true
}
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,43 @@
{
"formatVersion": 1,
"description": "iOS 26 Semantic Boarding Pass",
"serialNumber": "2w5zzretyg7n168c7c4053e961",
"teamIdentifier": "F53WB8AE67",
"passTypeIdentifier": "pass.com.passkitgenerator",
"foregroundColor": "rgb(0, 0, 0)",
"backgroundColor": "rgb(255, 255, 255)",
"labelColor": "rgb(0, 0, 0)",
"preferredStyleSchemes": ["semanticBoardingPass", "boardingPass"],
"organizationName": "A Generic Random Organization",
"boardingPass": {},
"barcodes": [
{
"messageEncoding": "utf-8",
"message": "Passkit-generator generated this",
"format": "PKBarcodeFormatQR",
"altText": "Passkit-generator generated this"
}
],
"semantics": {
"internationalDocumentsAreVerified": true,
"internationalDocumentsVerifiedDeclarationName": "Travel Ready (custom tag)",
"airlineCode": "FI",
"flightNumber": 533,
"originalDepartureDate": "2025-09-30T14:00:00Z",
"originalArrivalDate": "2025-09-30T16:00:00Z",
"originalBoardingDate": "2025-09-30T13:30:00Z",
"departureAirportCode": "MUC",
"departureCityName": "Munich",
"departureLocationTimeZone": "Europe/Berlin",
"departureGate": "A12",
"destinationGate": "B34",
"destinationAirportCode": "KEF",
"destinationCityName": "Reykjavik",
"destinationLocationTimeZone": "Atlantic/Reykjavik",
"passengerName": { "givenName": "John", "familyName": "Doe" },
"loungePlaceIDs": ["IB452E0A3979253B0"]
},
"upgradeURL": "https://www.example.com",
"entertainmentURL": "https://www.example.com"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -29,11 +29,14 @@
* A feedback to Apple have been sent for this. * A feedback to Apple have been sent for this.
*/ */
import { fileURLToPath } from "node:url";
import path from "node:path";
import { promises as fs } from "node:fs";
import { PKPass } from "passkit-generator";
import { app } from "./webserver.js"; import { app } from "./webserver.js";
import { getCertificates } from "./shared.js"; import { getCertificates } from "./shared.js";
import { promises as fs } from "node:fs";
import path from "node:path"; const __dirname = path.dirname(fileURLToPath(import.meta.url));
import { PKPass } from "passkit-generator";
// *************************** // // *************************** //
// *** EXAMPLE FROM NOW ON *** // // *** EXAMPLE FROM NOW ON *** //

View File

@@ -13,6 +13,9 @@ import path from "node:path";
import { PKPass } from "passkit-generator"; import { PKPass } from "passkit-generator";
import { app } from "./webserver.js"; import { app } from "./webserver.js";
import { getCertificates } from "./shared.js"; import { getCertificates } from "./shared.js";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
app.route("/fields/:modelName").get(async (request, response) => { app.route("/fields/:modelName").get(async (request, response) => {
const passName = const passName =

View File

@@ -5,10 +5,13 @@
*/ */
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url";
import { PKPass } from "passkit-generator"; import { PKPass } from "passkit-generator";
import { app } from "./webserver.js"; import { app } from "./webserver.js";
import { getCertificates } from "./shared.js"; import { getCertificates } from "./shared.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
app.route("/localize/:modelName").get(async (request, response) => { app.route("/localize/:modelName").get(async (request, response) => {
const passName = const passName =
request.params.modelName + request.params.modelName +
@@ -31,7 +34,12 @@ app.route("/localize/:modelName").get(async (request, response) => {
signerKeyPassphrase: certificates.signerKeyPassphrase, signerKeyPassphrase: certificates.signerKeyPassphrase,
}, },
}, },
request.body || request.params || request.query, Object.assign(
{
serialNumber: Math.random().toString(36).substring(2, 15),
},
request.body || request.query || {},
),
); );
// Italian, already has an .lproj which gets included... // Italian, already has an .lproj which gets included...

View File

@@ -5,10 +5,13 @@
import path from "node:path"; import path from "node:path";
import { promises as fs } from "node:fs"; import { promises as fs } from "node:fs";
import { fileURLToPath } from "node:url";
import { PKPass } from "passkit-generator"; import { PKPass } from "passkit-generator";
import { app } from "./webserver.js"; import { app } from "./webserver.js";
import { getCertificates } from "./shared.js"; import { getCertificates } from "./shared.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
function getRandomColorPart() { function getRandomColorPart() {
return Math.floor(Math.random() * 255); return Math.floor(Math.random() * 255);
} }

View File

@@ -10,9 +10,12 @@
import { PKPass } from "passkit-generator"; import { PKPass } from "passkit-generator";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url";
import { app } from "./webserver.js"; import { app } from "./webserver.js";
import { getCertificates } from "./shared.js"; import { getCertificates } from "./shared.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
app.route("/barcodes/:modelName").get(async (request, response) => { app.route("/barcodes/:modelName").get(async (request, response) => {
const passName = const passName =
request.params.modelName + request.params.modelName +

View File

@@ -8,10 +8,13 @@
*/ */
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url";
import { PKPass } from "passkit-generator"; import { PKPass } from "passkit-generator";
import { app } from "./webserver.js"; import { app } from "./webserver.js";
import { getCertificates } from "./shared.js"; import { getCertificates } from "./shared.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
app.route("/expirationDate/:modelName").get(async (request, response) => { app.route("/expirationDate/:modelName").get(async (request, response) => {
if (!request.query.fn) { if (!request.query.fn) {
response.send( response.send(

View File

@@ -10,5 +10,5 @@ export const app = express();
app.use(express.json()); app.use(express.json());
app.listen(8080, "0.0.0.0", () => { app.listen(8080, "0.0.0.0", () => {
console.log("Webserver started."); console.log("Webserver started on port 8080");
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "passkit-generator", "name": "passkit-generator",
"version": "3.4.0", "version": "3.5.0-alpha.1",
"description": "The easiest way to generate custom Apple Wallet passes in Node.js", "description": "The easiest way to generate custom Apple Wallet passes in Node.js",
"main": "lib/cjs/index.js", "main": "lib/cjs/index.js",
"types": "lib/types/index.d.ts", "types": "lib/types/index.d.ts",

View File

@@ -1276,7 +1276,7 @@ describe("PKPass", () => {
}); });
}); });
describe("eventTicket new layout", () => { describe("iOS 18 / iOS 26 new layouts", () => {
it("should contain preferredStyleSchemes if coming from an imported pass json", () => { it("should contain preferredStyleSchemes if coming from an imported pass json", () => {
const passjson = modelFiles["pass.json"]; const passjson = modelFiles["pass.json"];
const changedPassJson = Buffer.from( const changedPassJson = Buffer.from(
@@ -1355,16 +1355,16 @@ describe("PKPass", () => {
}); });
}); });
it("preferredStyleSchemes setter should throw if pass is not an eventTicket", () => { it("preferredStyleSchemes setter should throw if pass is not an eventTicket or boardingPass", () => {
pkpass.type = "boardingPass"; pkpass.type = "storeCard";
expect(() => { expect(() => {
pkpass.preferredStyleSchemes = ["posterEventTicket", "eventTicket"]; pkpass.preferredStyleSchemes = ["posterEventTicket", "eventTicket"];
}).toThrowError(); }).toThrowError();
}); });
it("preferredStyleSchemes getter should throw if pass is not an eventTicket", () => { it("preferredStyleSchemes getter should throw if pass is not an eventTicket or boardingPass", () => {
pkpass.type = "boardingPass"; pkpass.type = "storeCard";
expect(() => { expect(() => {
pkpass.preferredStyleSchemes; pkpass.preferredStyleSchemes;

View File

@@ -12,15 +12,17 @@ const passInstanceSymbol = Symbol("passInstance");
const sharedKeysPoolSymbol = Symbol("keysPool"); const sharedKeysPoolSymbol = Symbol("keysPool");
const fieldSchemaSymbol = Symbol("fieldSchema"); const fieldSchemaSymbol = Symbol("fieldSchema");
export default class FieldsArray extends Array<Schemas.Field> { export default class FieldsArray extends Array<Schemas.PassFieldContent> {
private [passInstanceSymbol]: InstanceType<typeof PKPass>; private [passInstanceSymbol]: InstanceType<typeof PKPass>;
private [sharedKeysPoolSymbol]: Set<string>; private [sharedKeysPoolSymbol]: Set<string>;
constructor( constructor(
passInstance: InstanceType<typeof PKPass>, passInstance: InstanceType<typeof PKPass>,
keysPool: Set<string>, keysPool: Set<string>,
fieldSchema: typeof Schemas.Field | typeof Schemas.FieldWithRow, fieldSchema:
...args: Schemas.Field[] | typeof Schemas.PassFieldContent
| typeof Schemas.PassFieldContentWithRow,
...args: Schemas.PassFieldContent[]
) { ) {
super(...args); super(...args);
this[fieldSchemaSymbol] = fieldSchema; this[fieldSchemaSymbol] = fieldSchema;
@@ -28,20 +30,20 @@ export default class FieldsArray extends Array<Schemas.Field> {
this[sharedKeysPoolSymbol] = keysPool; this[sharedKeysPoolSymbol] = keysPool;
} }
push(...items: Schemas.Field[]): number { push(...items: Schemas.PassFieldContent[]): number {
const validItems = registerWithValidation(this, ...items); const validItems = registerWithValidation(this, ...items);
return super.push(...validItems); return super.push(...validItems);
} }
pop(): Schemas.Field { pop(): Schemas.PassFieldContent {
return unregisterItems(this, () => super.pop()); return unregisterItems(this, () => super.pop());
} }
splice( splice(
start: number, start: number,
deleteCount: number, deleteCount: number,
...items: Schemas.Field[] ...items: Schemas.PassFieldContent[]
): Schemas.Field[] { ): Schemas.PassFieldContent[] {
// Perfoming frozen check, validation and getting valid items // Perfoming frozen check, validation and getting valid items
const validItems = registerWithValidation(this, ...items); const validItems = registerWithValidation(this, ...items);
@@ -56,7 +58,7 @@ export default class FieldsArray extends Array<Schemas.Field> {
return unregisterItems(this, () => super.shift()); return unregisterItems(this, () => super.shift());
} }
unshift(...items: Schemas.Field[]) { unshift(...items: Schemas.PassFieldContent[]) {
const validItems = registerWithValidation(this, ...items); const validItems = registerWithValidation(this, ...items);
return super.unshift(...validItems); return super.unshift(...validItems);
} }
@@ -64,11 +66,11 @@ export default class FieldsArray extends Array<Schemas.Field> {
function registerWithValidation( function registerWithValidation(
instance: InstanceType<typeof FieldsArray>, instance: InstanceType<typeof FieldsArray>,
...items: Schemas.Field[] ...items: Schemas.PassFieldContent[]
) { ) {
Utils.assertUnfrozen(instance[passInstanceSymbol]); Utils.assertUnfrozen(instance[passInstanceSymbol]);
let validItems: Schemas.Field[] = []; let validItems: Schemas.PassFieldContent[] = [];
for (const field of items) { for (const field of items) {
if (!field) { if (!field) {
@@ -109,7 +111,7 @@ function unregisterItems(
) { ) {
Utils.assertUnfrozen(instance[passInstanceSymbol]); Utils.assertUnfrozen(instance[passInstanceSymbol]);
const element: Schemas.Field = removeFn(); const element: Schemas.PassFieldContent = removeFn();
instance[sharedKeysPoolSymbol].delete(element.key); instance[sharedKeysPoolSymbol].delete(element.key);
return element; return element;
} }

View File

@@ -217,14 +217,14 @@ export default class PKPass extends Bundle {
} }
/** /**
* Allows accessing to iOS 18 new Event Ticket * Allows accessing to iOS 18 new property
* property `preferredStyleSchemes`. * `preferredStyleSchemes`.
* *
* @throws if current type is not "eventTicket". * @throws if current type is not "eventTicket" and is not "boardingPass".
*/ */
public get preferredStyleSchemes(): Schemas.PreferredStyleSchemes { public get preferredStyleSchemes(): Schemas.PreferredStyleSchemes {
if (this.type !== "eventTicket") { if (this.type !== "eventTicket" && this.type !== "boardingPass") {
throw new TypeError( throw new TypeError(
Messages.PREFERRED_STYLE_SCHEMES.UNEXPECTED_PASS_TYPE_GET, Messages.PREFERRED_STYLE_SCHEMES.UNEXPECTED_PASS_TYPE_GET,
); );
@@ -235,7 +235,9 @@ export default class PKPass extends Bundle {
/** /**
* Allows setting a preferredStyleSchemes property * Allows setting a preferredStyleSchemes property
* for a eventTicket. * for a eventTicket. Use this to select
* either the Poster Event Tickets (iOS 18+) or the Semantic
* Boarding passes (iOS 26+).
* *
* @throws if current type is not "eventTicket". * @throws if current type is not "eventTicket".
* @param value * @param value
@@ -244,7 +246,7 @@ export default class PKPass extends Bundle {
public set preferredStyleSchemes(value: Schemas.PreferredStyleSchemes) { public set preferredStyleSchemes(value: Schemas.PreferredStyleSchemes) {
Utils.assertUnfrozen(this); Utils.assertUnfrozen(this);
if (this.type !== "eventTicket") { if (this.type !== "eventTicket" && this.type !== "boardingPass") {
throw new TypeError( throw new TypeError(
Messages.PREFERRED_STYLE_SCHEMES.UNEXPECTED_PASS_TYPE_SET, Messages.PREFERRED_STYLE_SCHEMES.UNEXPECTED_PASS_TYPE_SET,
); );
@@ -259,6 +261,52 @@ export default class PKPass extends Bundle {
this[propsSymbol].preferredStyleSchemes = value; this[propsSymbol].preferredStyleSchemes = value;
} }
/**
* Allows setting UpcomingPassInformation for poster event tickets
* (iOS 26+).
*
* @throws if current type is not "eventTicket"
* @throws if preferredStyleSchemes is not set or does not include "posterEventTicket"
*/
public set upcomingPassInformation(
value: Schemas.UpcomingPassInformationEntry[],
) {
Utils.assertUnfrozen(this);
if (this.type !== "eventTicket") {
throw new TypeError(
Messages.UPCOMING_PASS_INFORMATION.UNEXPECTED_PASS_TYPE_SET,
);
}
if (!this.preferredStyleSchemes?.includes("posterEventTicket")) {
throw new TypeError(
Messages.UPCOMING_PASS_INFORMATION.UNEXPECTED_STYLE_SCHEME,
);
}
for (const entry of value) {
Schemas.assertValidity(
Schemas.UpcomingPassInformationEntry,
entry,
Messages.UPCOMING_PASS_INFORMATION.INVALID,
);
}
this[propsSymbol].upcomingPassInformation = value;
}
public get upcomingPassInformation(): Schemas.UpcomingPassInformationEntry[] {
if (this.type !== "eventTicket") {
throw new TypeError(
Messages.UPCOMING_PASS_INFORMATION.UNEXPECTED_PASS_TYPE_GET,
);
}
return this[propsSymbol].upcomingPassInformation || [];
}
/** /**
* Allows setting a transitType property * Allows setting a transitType property
* for a boardingPass. * for a boardingPass.
@@ -302,7 +350,7 @@ export default class PKPass extends Bundle {
* instance has not a valid type set yet. * instance has not a valid type set yet.
*/ */
public get primaryFields(): Schemas.Field[] { public get primaryFields(): Schemas.PassFieldContent[] {
return this[propsSymbol][this.type].primaryFields; return this[propsSymbol][this.type].primaryFields;
} }
@@ -314,7 +362,7 @@ export default class PKPass extends Bundle {
* instance has not a valid type set yet. * instance has not a valid type set yet.
*/ */
public get secondaryFields(): Schemas.Field[] { public get secondaryFields(): Schemas.PassFieldContent[] {
return this[propsSymbol][this.type].secondaryFields; return this[propsSymbol][this.type].secondaryFields;
} }
@@ -331,7 +379,7 @@ export default class PKPass extends Bundle {
* instance has not a valid type set yet. * instance has not a valid type set yet.
*/ */
public get auxiliaryFields(): Schemas.FieldWithRow[] { public get auxiliaryFields(): Schemas.PassFieldContentWithRow[] {
return this[propsSymbol][this.type].auxiliaryFields; return this[propsSymbol][this.type].auxiliaryFields;
} }
@@ -343,7 +391,7 @@ export default class PKPass extends Bundle {
* instance has not a valid type set yet. * instance has not a valid type set yet.
*/ */
public get headerFields(): Schemas.Field[] { public get headerFields(): Schemas.PassFieldContent[] {
return this[propsSymbol][this.type].headerFields; return this[propsSymbol][this.type].headerFields;
} }
@@ -355,7 +403,7 @@ export default class PKPass extends Bundle {
* instance has not a valid type set yet. * instance has not a valid type set yet.
*/ */
public get backFields(): Schemas.Field[] { public get backFields(): Schemas.PassFieldContent[] {
return this[propsSymbol][this.type].backFields; return this[propsSymbol][this.type].backFields;
} }
@@ -368,7 +416,7 @@ export default class PKPass extends Bundle {
* type is not "eventTicket". * type is not "eventTicket".
*/ */
public get additionalInfoFields(): Schemas.Field[] { public get additionalInfoFields(): Schemas.PassFieldContent[] {
return this[propsSymbol]["eventTicket"].additionalInfoFields; return this[propsSymbol]["eventTicket"].additionalInfoFields;
} }
@@ -411,32 +459,34 @@ export default class PKPass extends Bundle {
headerFields /******/: new FieldsArray( headerFields /******/: new FieldsArray(
this, this,
sharedKeysPool, sharedKeysPool,
Schemas.Field, Schemas.PassFieldContent,
), ),
primaryFields /*****/: new FieldsArray( primaryFields /*****/: new FieldsArray(
this, this,
sharedKeysPool, sharedKeysPool,
Schemas.Field, Schemas.PassFieldContent,
), ),
secondaryFields /***/: new FieldsArray( secondaryFields /***/: new FieldsArray(
this, this,
sharedKeysPool, sharedKeysPool,
Schemas.Field, Schemas.PassFieldContent,
), ),
auxiliaryFields /***/: new FieldsArray( auxiliaryFields /***/: new FieldsArray(
this, this,
sharedKeysPool, sharedKeysPool,
type === "eventTicket" ? Schemas.FieldWithRow : Schemas.Field, type === "eventTicket"
? Schemas.PassFieldContentWithRow
: Schemas.PassFieldContent,
), ),
backFields /********/: new FieldsArray( backFields /********/: new FieldsArray(
this, this,
sharedKeysPool, sharedKeysPool,
Schemas.Field, Schemas.PassFieldContent,
), ),
additionalInfoFields: new FieldsArray( additionalInfoFields: new FieldsArray(
this, this,
sharedKeysPool, sharedKeysPool,
Schemas.Field, Schemas.PassFieldContent,
), ),
transitType: undefined, transitType: undefined,
}; };
@@ -960,6 +1010,11 @@ export default class PKPass extends Bundle {
* Allows setting a series of relevancy intervals or * Allows setting a series of relevancy intervals or
* relevancy entries for the pass. * relevancy entries for the pass.
* *
* Please note that `RelevantDate[]` `relevantDate` property was renamed
* in "date" since iOS 26. Since retro-compatibility is not ensured,
* this methods takes `date`, and fallbacks to `relevantDate`, and use
* the first value found for both properties.
*
* @param {Schemas.RelevantDate[] | null} relevancyEntries * @param {Schemas.RelevantDate[] | null} relevancyEntries
* @returns {void} * @returns {void}
*/ */
@@ -981,10 +1036,13 @@ export default class PKPass extends Bundle {
Schemas.validate(Schemas.RelevantDate, entry); Schemas.validate(Schemas.RelevantDate, entry);
if (isRelevantEntry(entry)) { if (isRelevantEntry(entry)) {
const date = Utils.processDate(
new Date(entry.date || entry.relevantDate),
);
acc.push({ acc.push({
relevantDate: Utils.processDate( relevantDate: date,
new Date(entry.relevantDate), date,
),
}); });
return acc; return acc;
@@ -1143,5 +1201,12 @@ function validateJSONBuffer(
function isRelevantEntry( function isRelevantEntry(
entry: Schemas.RelevantDate, entry: Schemas.RelevantDate,
): entry is Schemas.RelevancyEntry { ): entry is Schemas.RelevancyEntry {
return Object.prototype.hasOwnProperty.call(entry, "relevantDate"); const isRelevantDateAvailable: boolean =
Object.prototype.hasOwnProperty.call(entry, "relevantDate") &&
"relevantDate" in entry;
const isDateAvailable: boolean =
Object.prototype.hasOwnProperty.call(entry, "date") && "date" in entry;
return isRelevantDateAvailable || isDateAvailable;
} }

View File

@@ -17,13 +17,24 @@ export const TRANSIT_TYPE = {
export const PREFERRED_STYLE_SCHEMES = { export const PREFERRED_STYLE_SCHEMES = {
UNEXPECTED_PASS_TYPE_SET: UNEXPECTED_PASS_TYPE_SET:
"Cannot set preferredStyleSchemes on a pass with type different from eventTicket.", "Cannot set preferredStyleSchemes on a pass with type different from eventTicket or boardingPass.",
UNEXPECTED_PASS_TYPE_GET: UNEXPECTED_PASS_TYPE_GET:
"Cannot get preferredStyleSchemes on a pass with type different from eventTicket.", "Cannot get preferredStyleSchemes on a pass with type different from eventTicket or boardingPass.",
INVALID: INVALID:
"Cannot set preferredStyleSchemes because not compliant with Apple specifications - %s", "Cannot set preferredStyleSchemes because not compliant with Apple specifications - %s",
} as const; } as const;
export const UPCOMING_PASS_INFORMATION = {
UNEXPECTED_PASS_TYPE_SET:
"Cannot set upcomingPassInformation on a pass with type different from eventTicket.",
UNEXPECTED_PASS_TYPE_GET:
"Cannot get upcomingPassInformation on a pass with type different from eventTicket.",
UNEXPECTED_STYLE_SCHEME:
"Cannot set upcomingPassInformation because 'preferredStyleSchemes' does not include 'posterEventTicket' style.",
INVALID:
"Cannot set upcomingPassInformation: validation failed. Be sure to follow the Apple specifications. - %s",
} as const;
export const PASS_TYPE = { export const PASS_TYPE = {
INVALID: INVALID:
"Cannot set type because not compliant with Apple specifications. Refer to https://apple.co/3aFpSfg for a list of valid props - %s", "Cannot set type because not compliant with Apple specifications. Refer to https://apple.co/3aFpSfg for a list of valid props - %s",

View File

@@ -30,7 +30,7 @@ export type PKNumberStyleType =
* @see https://developer.apple.com/documentation/walletpasses/passfieldcontent * @see https://developer.apple.com/documentation/walletpasses/passfieldcontent
*/ */
export interface Field { export interface PassFieldContent {
attributedValue?: string | number | Date; attributedValue?: string | number | Date;
changeMessage?: string; changeMessage?: string;
dataDetectorTypes?: PKDataDetectorType[]; dataDetectorTypes?: PKDataDetectorType[];
@@ -47,11 +47,23 @@ export interface Field {
numberStyle?: PKNumberStyleType; numberStyle?: PKNumberStyleType;
} }
export interface FieldWithRow extends Field { /**
* @deprecated Use `PassFieldContent` instead,
* which is the right Apple name.
*/
export type Field = PassFieldContent;
export interface PassFieldContentWithRow extends PassFieldContent {
row?: 0 | 1; row?: 0 | 1;
} }
export const Field = Joi.object<Field>().keys({ /**
* @deprecated Use `PassFieldContentWithRow` instead,
* which is the right Apple name.
*/
export type FieldWithRow = PassFieldContentWithRow;
export const PassFieldContent = Joi.object<PassFieldContent>().keys({
attributedValue: Joi.alternatives( attributedValue: Joi.alternatives(
Joi.string().allow(""), Joi.string().allow(""),
Joi.number(), Joi.number(),
@@ -102,8 +114,20 @@ export const Field = Joi.object<Field>().keys({
}), }),
}); });
export const FieldWithRow = Field.concat( /**
Joi.object<FieldWithRow>().keys({ * @deprecated Use `PassFieldContent` instead,
* which is the right Apple name.
*/
export const Field = PassFieldContent;
export const PassFieldContentWithRow = PassFieldContent.concat(
Joi.object<PassFieldContentWithRow>().keys({
row: Joi.number().min(0).max(1), row: Joi.number().min(0).max(1),
}), }),
); );
/**
* @deprecated Use `PassFieldContentWithRow` instead,
* which is the right Apple name.
*/
export const FieldWithRow = PassFieldContentWithRow;

View File

@@ -1,5 +1,8 @@
import Joi from "joi"; import Joi from "joi";
import { Field, FieldWithRow } from "./Field.js"; import {
PassFieldContent,
PassFieldContentWithRow,
} from "./PassFieldContent.js";
export type TransitType = export type TransitType =
| "PKTransitTypeAir" | "PKTransitTypeAir"
@@ -13,11 +16,11 @@ export const TransitType = Joi.string().regex(
); );
export interface PassFields { export interface PassFields {
auxiliaryFields: FieldWithRow[]; auxiliaryFields: PassFieldContentWithRow[];
backFields: Field[]; backFields: PassFieldContent[];
headerFields: Field[]; headerFields: PassFieldContent[];
primaryFields: Field[]; primaryFields: PassFieldContent[];
secondaryFields: Field[]; secondaryFields: PassFieldContent[];
transitType?: TransitType; transitType?: TransitType;
/** /**
@@ -27,15 +30,15 @@ export interface PassFields {
* *
* @see \<undiclosed> * @see \<undiclosed>
*/ */
additionalInfoFields?: Field[]; additionalInfoFields?: PassFieldContent[];
} }
export const PassFields = Joi.object<PassFields>().keys({ export const PassFields = Joi.object<PassFields>().keys({
auxiliaryFields: Joi.array().items(FieldWithRow), auxiliaryFields: Joi.array().items(PassFieldContentWithRow),
backFields: Joi.array().items(Field), backFields: Joi.array().items(PassFieldContent),
headerFields: Joi.array().items(Field), headerFields: Joi.array().items(PassFieldContent),
primaryFields: Joi.array().items(Field), primaryFields: Joi.array().items(PassFieldContent),
secondaryFields: Joi.array().items(Field), secondaryFields: Joi.array().items(PassFieldContent),
transitType: TransitType, transitType: TransitType,
/** /**
@@ -45,5 +48,5 @@ export const PassFields = Joi.object<PassFields>().keys({
* *
* @see \<undiclosed> * @see \<undiclosed>
*/ */
additionalInfoFields: Joi.array().items(Field), additionalInfoFields: Joi.array().items(PassFieldContent),
}); });

View File

@@ -55,36 +55,148 @@ export interface Semantics {
/** /**
* @iOSVersion 18 * @iOSVersion 18
* @passStyle eventTicket (new layout) *
* Additional ticket attributes that other tags or keys in the pass dont include.
* Use this key for any type of event ticket.
*/ */
additionalTicketAttributes?: string; additionalTicketAttributes?: string;
balance?: SemanticTagType.CurrencyAmount; balance?: SemanticTagType.CurrencyAmount;
/**
* A group number for boarding.
* Use this key for any type of boarding pass.
*/
boardingGroup?: string; boardingGroup?: string;
/**
* A sequence number for boarding.
* Use this key for any type of boarding pass.
*/
boardingSequenceNumber?: string; boardingSequenceNumber?: string;
/**
* @iOSVersion 26
*
* A zone number for boarding. Don't include the word _zone_.
*/
boardingZone?: string;
/**
* The number of the passenger car.
* A train car is also called a carriage, wagon, coach, or bogie in some countries.
* Use this key only for a train or other rail boarding pass.
*/
carNumber?: string; carNumber?: string;
confirmationNumber?: string; confirmationNumber?: string;
currentArrivalDate?: string; currentArrivalDate?: string;
currentBoardingDate?: string; currentBoardingDate?: string;
currentDepartureDate?: string; currentDepartureDate?: string;
/**
* The IATA airport code for the departure airport, such as `MPM` or `LHR`.
* Use this key only for airline boarding passes.
*/
departureAirportCode?: string; departureAirportCode?: string;
/**
* The full name of the departure airport, such as Maputo International Airport. Use this key only for airline boarding passes.
*/
departureAirportName?: string; departureAirportName?: string;
/**
* @iOSVersion 26
*
* The name of the departure city to display on the boarding pass, such as `London` or `Shanghai`.
*/
departureCityName?: string;
/**
* The gate number or letters of the departure gate, such as 1A. Dont include the word gate.
*/
departureGate?: string; departureGate?: string;
/**
* An object that represents the geographic coordinates of the transit departure location,
* suitable for display on a map.
* If possible, use precise locations, which are more useful to travelers;
* for example, the specific location of an airport gate.
*
* Use this key for any type of boarding pass.
*/
departureLocation?: SemanticTagType.Location; departureLocation?: SemanticTagType.Location;
departureLocationDescription?: string; departureLocationDescription?: string;
departurePlatform?: string; departurePlatform?: string;
departureStationName?: string; departureStationName?: string;
departureTerminal?: string; departureTerminal?: string;
/**
* @iOSVersion 26
*
* A list of security programs that exist at the departure location.
* This only shows in the UI if a program is in `passengerEligibleSecurityPrograms`
* and at least one of `departureLocationSecurityPrograms` or `destinationLocationSecurityPrograms`
*/
departureLocationSecurityPrograms?: (
| "PKTransitSecurityProgramTSAPreCheck"
| "PKTransitSecurityProgramTSAPreCheckTouchlessID"
| "PKTransitSecurityProgramOSS"
| "PKTransitSecurityProgramITI"
| "PKTransitSecurityProgramITD"
| "PKTransitSecurityProgramGlobalEntry"
| "PKTransitSecurityProgramCLEAR"
)[];
/**
* @iOSVersion 26
*
* The time zone of the departure location, such as `America/Chicago`.
* See the [IANA Time Zone Database](https://www.iana.org/time-zones) for the full list of supported time zones.
*/
departureLocationTimeZone?: string;
destinationAirportCode?: string; destinationAirportCode?: string;
destinationAirportName?: string; destinationAirportName?: string;
/**
* @iOSVersion 26
*
* The name of the destination city to display on the boarding pass, such as `London` or `Shanghai`.
*/
destinationCityName?: string;
destinationGate?: string; destinationGate?: string;
destinationLocation?: SemanticTagType.Location; destinationLocation?: SemanticTagType.Location;
destinationLocationDescription?: string; destinationLocationDescription?: string;
destinationPlatform?: string; destinationPlatform?: string;
destinationStationName?: string; destinationStationName?: string;
destinationTerminal?: string; destinationTerminal?: string;
/**
* @iOSVersion 26
*
* A list of security programs the passenger is eligible for. This only shows in the UI if a program is in `passengerEligibleSecurityPrograms` and at least one of `departureLocationSecurityPrograms` or `destinationLocationSecurityPrograms`.
*/
destinationLocationSecurityPrograms?: (
| "PKTransitSecurityProgramTSAPreCheck"
| "PKTransitSecurityProgramTSAPreCheckTouchlessID"
| "PKTransitSecurityProgramOSS"
| "PKTransitSecurityProgramITI"
| "PKTransitSecurityProgramITD"
| "PKTransitSecurityProgramGlobalEntry"
| "PKTransitSecurityProgramCLEAR"
)[];
/**
* @iOSVersion 26
*
* The time zone of the destination location, such as `America/Los_Angeles`.
* See the [IANA Time Zone Database](https://www.iana.org/time-zones) for the full list of supported time zones.
*/
destinationLocationTimeZone?: string;
duration?: number; duration?: number;
/** /**
@@ -155,18 +267,126 @@ export interface Semantics {
homeTeamAbbreviation?: string; homeTeamAbbreviation?: string;
homeTeamLocation?: string; homeTeamLocation?: string;
homeTeamName?: string; homeTeamName?: string;
/**
* @iOSVersion 26
*
* An optional boolean that indicates whether the passenger's international documents are verified. If set to `true` Wallet displays the badge on the boarding pass with the value from `internationalDocumentsVerifiedDeclarationName`.
*/
internationalDocumentsAreVerified?: boolean;
/**
* @iOSVersion 26
*
* The name of the declaration given once the passenger's international documents are verified.
* Examples include `DOCS OK` or `Travel Ready`.
* If `internationalDocumentsAreVerified` is true, Wallet displays a badge on the boarding pass with this value.
*/
internationalDocumentsVerifiedDeclarationName?: string;
/**
* The abbreviated league name for a sports event. Use this key only for a sports event ticket.
*/
leagueAbbreviation?: string; leagueAbbreviation?: string;
/**
* The unabbreviated league name for a sports event.
* Use this key only for a sports event ticket.
*/
leagueName?: string; leagueName?: string;
/**
* @iOSVersion 26
*
* The MapKit Place IDs that reference the transit provider lounge locations.
* For more information, see [Identifying unique locations with Place IDs](https://developer.apple.com/documentation/MapKit/identifying-unique-locations-with-place-ids)
*/
loungePlaceIDs?: string[];
/**
* The name of a frequent flyer or loyalty program.
* Use this key for any type of boarding pass.
*/
membershipProgramName?: string; membershipProgramName?: string;
/**
* The ticketed passengers frequent flyer or loyalty number.
* Use this key for any type of boarding pass.
*/
membershipProgramNumber?: string; membershipProgramNumber?: string;
/**
* @iOSVersion 26
*
* The ticketed passengers frequent flyer or loyalty program status.
* Use this key for any type of boarding pass.
*/
membershipProgramStatus?: string;
originalArrivalDate?: string; originalArrivalDate?: string;
originalBoardingDate?: string; originalBoardingDate?: string;
originalDepartureDate?: string; originalDepartureDate?: string;
/**
* An object that represents the name of the passenger.
* Use this key for any type of boarding pass.
*/
passengerName?: SemanticTagType.PersonNameComponents; passengerName?: SemanticTagType.PersonNameComponents;
/**
* @iOSVersion 26
*
* An array of airline-specific SSRs (Special Service Requests) that apply to the ticketed passenger.
*/
passengerAirlineSSRs?: string[];
/**
* @iOSVersion 26
*
* A list of capabilities the passenger has. Only use this key for airline boarding passes.
*/
passengerCapabilities?: (
| "PKPassengerCapabilityPreboarding"
| "PKPassengerCapabilityPriorityBoarding"
| "PKPassengerCapabilityCarryon"
| "PKPassengerCapabilityPersonalItem"
)[];
/**
* @iOSVersion 26
*
* A list of security programs the passenger is eligible for. This only shows in the UI if a program is in `passengerEligibleSecurityPrograms` and at least one of `departureLocationSecurityPrograms` or `destinationLocationSecurityPrograms`.
*/
passengerEligibleSecurityPrograms?: (
| "PKTransitSecurityProgramTSAPreCheck"
| "PKTransitSecurityProgramTSAPreCheckTouchlessID"
| "PKTransitSecurityProgramOSS"
| "PKTransitSecurityProgramITI"
| "PKTransitSecurityProgramITD"
| "PKTransitSecurityProgramGlobalEntry"
| "PKTransitSecurityProgramCLEAR"
)[];
/**
* @iOSVersion 26
*
* An array of IATA information SSRs that apply to the ticketed passenger. A comprehensive list of service SSRs can be found in the [IATA Airlines Developer Guide](https://guides.developer.iata.org/docs/21-1_ImplementationGuide.pdf) under A List of Information SSRs.
*/
passengerInformationSSRs?: string[];
/**
* @iOSVersion 26
*
* An array of IATA SSRs that apply to the ticketed passenger. A comprehensive list of service SSRs can be found in the [IATA Airlines Developer Guide](https://guides.developer.iata.org/docs/21-1_ImplementationGuide.pdf) under A List of Service SSRs.
*/
passengerServiceSSRs?: string[];
performerNames?: string[]; performerNames?: string[];
/**
* The priority status the ticketed passenger holds, such as `Gold` or `Silver`.
* Use this key for any type of boarding pass.
*/
priorityStatus?: string; priorityStatus?: string;
/** /**
@@ -186,9 +406,31 @@ export interface Semantics {
*/ */
tailgatingAllowed?: boolean; tailgatingAllowed?: boolean;
/**
* @iOSVersion 26
*
* A localizable string that denotes the ticket class, such as `Saver`, `Economy`, `First`. This value displays as a badge on the boarding pass.
*/
ticketFareClass?: string;
totalPrice?: SemanticTagType.CurrencyAmount; totalPrice?: SemanticTagType.CurrencyAmount;
/**
* The name of the transit company. Use this key for any type of boarding pass.
*/
transitProvider?: string; transitProvider?: string;
/**
* A brief description of the current boarding status for the vessel, such as `On Time` or `Delayed`.
* For delayed status, provide `currentBoardingDate`, `currentDepartureDate`, and `currentArrivalDate` where available.
* Use this key for any type of boarding pass.
*/
transitStatus?: string; transitStatus?: string;
/**
* A brief description that explains the reason for the current transitStatus, such as `Thunderstorms`.
* Use this key for any type of boarding pass.
*/
transitStatusReason?: string; transitStatusReason?: string;
vehicleName?: string; vehicleName?: string;
@@ -312,34 +554,137 @@ export const Semantics = Joi.object<Semantics>().keys({
awayTeamLocation: Joi.string(), awayTeamLocation: Joi.string(),
awayTeamName: Joi.string(), awayTeamName: Joi.string(),
/**
* @iOSVersion 18
*
* Additional ticket attributes that other tags or keys in the pass dont include.
* Use this key for any type of event ticket.
*/
additionalTicketAttributes: Joi.string(), additionalTicketAttributes: Joi.string(),
balance: SemanticTagType.CurrencyAmount, balance: SemanticTagType.CurrencyAmount,
/**
* A group number for boarding.
* Use this key for any type of boarding pass.
*/
boardingGroup: Joi.string(), boardingGroup: Joi.string(),
/**
* A sequence number for boarding.
* Use this key for any type of boarding pass.
*/
boardingSequenceNumber: Joi.string(), boardingSequenceNumber: Joi.string(),
/**
* @iOSVersion 26
*
* A zone number for boarding. Don't include the word _zone_.
*/
boardingZone: Joi.string(),
/**
* The number of the passenger car.
* A train car is also called a carriage, wagon, coach, or bogie in some countries.
* Use this key only for a train or other rail boarding pass.
*/
carNumber: Joi.string(), carNumber: Joi.string(),
confirmationNumber: Joi.string(), confirmationNumber: Joi.string(),
currentArrivalDate: Joi.string(), currentArrivalDate: Joi.string(),
currentBoardingDate: Joi.string(), currentBoardingDate: Joi.string(),
currentDepartureDate: Joi.string(), currentDepartureDate: Joi.string(),
/**
* The IATA airport code for the departure airport, such as `MPM` or `LHR`.
* Use this key only for airline boarding passes.
*/
departureAirportCode: Joi.string(), departureAirportCode: Joi.string(),
/**
* The full name of the departure airport, such as `Maputo International Airport`.
* Use this key only for airline boarding passes.
*/
departureAirportName: Joi.string(), departureAirportName: Joi.string(),
/**
* @iOSVersion 26
*
* The name of the departure city to display on the boarding pass, such as London or Shanghai.
*/
departureCityName: Joi.string(),
/**
* The gate number or letters of the departure gate, such as 1A. Dont include the word gate.
*/
departureGate: Joi.string(), departureGate: Joi.string(),
/**
* An object that represents the geographic coordinates of the transit departure location,
* suitable for display on a map.
* If possible, use precise locations, which are more useful to travelers;
* for example, the specific location of an airport gate.
*
* Use this key for any type of boarding pass.
*/
departureLocation: SemanticTagType.Location, departureLocation: SemanticTagType.Location,
departureLocationDescription: Joi.string(), departureLocationDescription: Joi.string(),
departurePlatform: Joi.string(), departurePlatform: Joi.string(),
departureStationName: Joi.string(), departureStationName: Joi.string(),
departureTerminal: Joi.string(), departureTerminal: Joi.string(),
/**
* @iOSVersion 26
*
* A list of security programs that exist at the departure location. This only shows in the UI if a program is in `passengerEligibleSecurityPrograms` and at least one of `departureLocationSecurityPrograms` or `destinationLocationSecurityPrograms`
*/
departureLocationSecurityPrograms: Joi.array().items(
Joi.string().regex(
/(PKTransitSecurityProgramTSAPreCheck|PKTransitSecurityProgramTSAPreCheckTouchlessID|PKTransitSecurityProgramOSS|PKTransitSecurityProgramITI|PKTransitSecurityProgramITD|PKTransitSecurityProgramGlobalEntry|PKTransitSecurityProgramCLEAR)/,
),
),
/**
* @iOSVersion 26
*
* The time zone of the departure location, such as America/Chicago. See the IANA Time Zone Database for the full list of supported time zones.
*/
departureLocationTimeZone: Joi.string(),
destinationAirportCode: Joi.string(), destinationAirportCode: Joi.string(),
destinationAirportName: Joi.string(), destinationAirportName: Joi.string(),
/**
* @iOSVersion 26
*
* The name of the destination city to display on the boarding pass, such as London or Shanghai.
*/
destinationCityName: Joi.string(),
destinationGate: Joi.string(), destinationGate: Joi.string(),
destinationLocation: SemanticTagType.Location, destinationLocation: SemanticTagType.Location,
destinationLocationDescription: Joi.string(), destinationLocationDescription: Joi.string(),
destinationPlatform: Joi.string(), destinationPlatform: Joi.string(),
destinationStationName: Joi.string(), destinationStationName: Joi.string(),
destinationTerminal: Joi.string(), destinationTerminal: Joi.string(),
/**
* @iOSVersion 26
*
* A list of security programs the passenger is eligible for. This only shows in the UI if a program is in passengerEligibleSecurityPrograms and at least one of departureLocationSecurityPrograms or destinationLocationSecurityPrograms.
*/
destinationLocationSecurityPrograms: Joi.array().items(
Joi.string().regex(
/(PKTransitSecurityProgramTSAPreCheck|PKTransitSecurityProgramTSAPreCheckTouchlessID|PKTransitSecurityProgramOSS|PKTransitSecurityProgramITI|PKTransitSecurityProgramITD|PKTransitSecurityProgramGlobalEntry|PKTransitSecurityProgramCLEAR)/,
),
),
/**
* @iOSVersion 26
*
* The time zone of the destination location, such as America/Los_Angeles. See the IANA Time Zone Database for the full list of supported time zones.
*/
destinationLocationTimeZone: Joi.string(),
duration: Joi.number(), duration: Joi.number(),
/** /**
@@ -385,18 +730,118 @@ export const Semantics = Joi.object<Semantics>().keys({
homeTeamAbbreviation: Joi.string(), homeTeamAbbreviation: Joi.string(),
homeTeamLocation: Joi.string(), homeTeamLocation: Joi.string(),
homeTeamName: Joi.string(), homeTeamName: Joi.string(),
/**
* @iOSVersion 26
*
* An optional boolean that indicates whether the passenger's international documents are verified. If set to `true` Wallet displays the badge on the boarding pass with the value from `internationalDocumentsVerifiedDeclarationName`.
*/
internationalDocumentsAreVerified: Joi.boolean(),
/**
* @iOSVersion 26
*
* The name of the declaration given once the passenger's international documents are verified. Examples include `DOCS OK` or `Travel Ready`. If `internationalDocumentsAreVerified` is true, Wallet displays a badge on the boarding pass with this value.
*/
internationalDocumentsVerifiedDeclarationName: Joi.string(),
/**
* The abbreviated league name for a sports event. Use this key only for a sports event ticket.
*/
leagueAbbreviation: Joi.string(), leagueAbbreviation: Joi.string(),
/**
* The unabbreviated league name for a sports event. Use this key only for a sports event ticket.
*/
leagueName: Joi.string(), leagueName: Joi.string(),
/**
* @iOSVersion 26
*
* The MapKit Place IDs that reference the transit provider lounge locations. For more information, see [Identifying unique locations with Place IDs](https://developer.apple.com/documentation/MapKit/identifying-unique-locations-with-place-ids)
*/
loungePlaceIDs: Joi.array().items(Joi.string()),
/**
* The name of a frequent flyer or loyalty program.
* Use this key for any type of boarding pass.
*/
membershipProgramName: Joi.string(), membershipProgramName: Joi.string(),
/**
* The ticketed passengers frequent flyer or loyalty number.
* Use this key for any type of boarding pass.
*/
membershipProgramNumber: Joi.string(), membershipProgramNumber: Joi.string(),
/**
* @iOSVersion 26
*
* The ticketed passengers frequent flyer or loyalty program status.
* Use this key for any type of boarding pass.
*/
membershipProgramStatus: Joi.string(),
originalArrivalDate: Joi.string(), originalArrivalDate: Joi.string(),
originalBoardingDate: Joi.string(), originalBoardingDate: Joi.string(),
originalDepartureDate: Joi.string(), originalDepartureDate: Joi.string(),
/**
* An object that represents the name of the passenger.
* Use this key for any type of boarding pass.
*/
passengerName: SemanticTagType.PersonNameComponents, passengerName: SemanticTagType.PersonNameComponents,
/**
* @iOSVersion 26
*
* An array of airline-specific SSRs (Special Service Requests) that apply to the ticketed passenger.
*/
passengerAirlineSSRs: Joi.array().items(Joi.string()),
/**
* @iOSVersion 26
*
* A list of capabilities the passenger has. Only use this key for airline boarding passes.
*/
passengerCapabilities: Joi.array().items(
Joi.string().regex(
/(PKPassengerCapabilityPreboarding|PKPassengerCapabilityPriorityBoarding|PKPassengerCapabilityCarryon|PKPassengerCapabilityPersonalItem)/,
),
),
/**
* @iOSVersion 26
*
* A list of security programs the passenger is eligible for. This only shows in the UI if a program is in `passengerEligibleSecurityPrograms` and at least one of `departureLocationSecurityPrograms` or `destinationLocationSecurityPrograms`.
*/
passengerEligibleSecurityPrograms: Joi.array().items(
Joi.string().regex(
/(PKTransitSecurityProgramTSAPreCheck|PKTransitSecurityProgramTSAPreCheckTouchlessID|PKTransitSecurityProgramOSS|PKTransitSecurityProgramITI|PKTransitSecurityProgramITD|PKTransitSecurityProgramGlobalEntry|PKTransitSecurityProgramCLEAR)/,
),
),
/**
* @iOSVersion 26
*
* An array of IATA information SSRs that apply to the ticketed passenger. A comprehensive list of service SSRs can be found in the [IATA Airlines Developer Guide](https://guides.developer.iata.org/docs/21-1_ImplementationGuide.pdf) under A List of Information SSRs.
*/
passengerInformationSSRs: Joi.array().items(Joi.string()),
/**
* @iOSVersion 26
*
* An array of IATA SSRs that apply to the ticketed passenger. A comprehensive list of service SSRs can be found in the [IATA Airlines Developer Guide](https://guides.developer.iata.org/docs/21-1_ImplementationGuide.pdf) under A List of Service SSRs.
*/
passengerServiceSSRs: Joi.array().items(Joi.string()),
performerNames: Joi.array().items(Joi.string()), performerNames: Joi.array().items(Joi.string()),
/**
* The priority status the ticketed passenger holds, such as `Gold` or `Silver`.
* Use this key for any type of boarding pass.
*/
priorityStatus: Joi.string(), priorityStatus: Joi.string(),
playlistIDs: Joi.array().items(Joi.string()), playlistIDs: Joi.array().items(Joi.string()),
@@ -408,9 +853,31 @@ export const Semantics = Joi.object<Semantics>().keys({
tailgatingAllowed: Joi.boolean(), tailgatingAllowed: Joi.boolean(),
/**
* @iOSVersion 26
*
* A localizable string that denotes the ticket class, such as `Saver`, `Economy`, `First`. This value displays as a badge on the boarding pass.
*/
ticketFareClass: Joi.string(),
totalPrice: SemanticTagType.CurrencyAmount, totalPrice: SemanticTagType.CurrencyAmount,
/**
* The name of the transit company. Use this key for any type of boarding pass.
*/
transitProvider: Joi.string(), transitProvider: Joi.string(),
/**
* A brief description of the current boarding status for the vessel, such as `On Time` or `Delayed`.
* For delayed status, provide `currentBoardingDate`, `currentDepartureDate`, and `currentArrivalDate` where available.
* Use this key for any type of boarding pass.
*/
transitStatus: Joi.string(), transitStatus: Joi.string(),
/**
* A brief description that explains the reason for the current transitStatus, such as `Thunderstorms`.
* Use this key for any type of boarding pass.
*/
transitStatusReason: Joi.string(), transitStatusReason: Joi.string(),
vehicleName: Joi.string(), vehicleName: Joi.string(),

View File

@@ -0,0 +1,256 @@
import Joi from "joi";
import { PassFieldContent } from "./PassFieldContent.js";
import { Semantics } from "./Semantics.js";
import { URL_REGEX } from "./regexps.js";
/**
* @iOSVersion 26
* @see https://developer.apple.com/documentation/walletpasses/upcomingpassinformationentrytype/imageurlentry-data.dictionary
*/
interface ImageURLEntry {
/** The SHA256 hash of the image. */
SHA256: string;
/** The URL that points to the image asset to be downloaded. This must be an https link. */
URL: string;
/** The scale of the image. If unspecified, defaults to 1. */
scale?: number;
/** Size of the image asset in bytes. The maximum allowed size is 2 megabytes. */
size?: number;
}
const ImageURLEntry = Joi.object<ImageURLEntry>({
SHA256: Joi.string().required(),
URL: Joi.string().regex(URL_REGEX).required(),
scale: Joi.number().default(1),
size: Joi.number().max(2 * 1024 * 1024), // 2 megabytes max
});
/**
* @iOSVersion 26
* @see https://developer.apple.com/documentation/walletpasses/upcomingpassinformationentrytype/image-data.dictionary
*/
interface Image {
/** A list of URLs used to retrieve an image. The upcoming pass information entry uses the item that best matches the device's scale. */
URLs?: ImageURLEntry[];
/** Indicates whether to use the local equivalent image instead of the image specified by URLs. */
reuseExisting?: boolean;
}
const Image = Joi.object<Image>({
URLs: Joi.array().items(ImageURLEntry),
reuseExisting: Joi.boolean(),
});
/**
* @iOSVersion 26
* @see https://developer.apple.com/documentation/walletpasses/upcomingpassinformationentry/images-data.dictionary
*/
interface Images {
/** The name of the image file used for the header image on the details screen. This can be a remote asset. */
headerImage?: Image;
/** The name of the image file used for the venue map in the event guide for each upcoming pass information entry. This can be a remote asset and is available for event entries. */
venueMap?: Image;
}
const Images = Joi.object<Images>({
headerImage: Image,
venueMap: Image,
});
/**
* @iOSVersion 26
* @see https://developer.apple.com/documentation/walletpasses/upcomingpassinformationentry/urls-data.dictionary
*/
interface URLs {
/** A URL that links to your or the venue's accessibility content. */
accessibilityURL?: string;
/** A URL that links to experiences that you can add on to your ticket or that allows you to access your existing prepurchased or preloaded add-on experiences, including any necessary QR or barcode links to access the experience. For example, loaded value or upgrades for an experience. */
addOnURL?: string;
/** A URL that links out to the bag policy of the venue. */
bagPolicyURL?: string;
/** The preferred email address to contact the venue, event, or issuer. */
contactVenueEmail?: string;
/** The preferred phone number to contact the venue, event, or issuer. */
contactVenuePhoneNumber?: string;
/** A URL that links the user to the website of the venue, event, or issuer. */
contactVenueWebsite?: string;
/** A URL that links to content you have about getting to the venue. */
directionsInformationURL?: string;
/** A URL that links to order merchandise for the specific event. This can be a ship-to-home ecommerce site, a pre-order to pickup at the venue, or other appropriate merchandise flow. This link can also be updated throughout the user's journey to provide more accurately tailored links at certain times. For example, before versus after a user enters an event. This can be done through a pass update. For more information on updating a pass, see Distributing and updating a pass. */
merchandiseURL?: string;
/** A URL that links out to the food-ordering page for the venue. This can be in-seat food delivery, pre-order for pickup at a vendor, or other appropriate food-ordering service. */
orderFoodURL?: string;
/** A URL that links to any information you have about parking. */
parkingInformationURL?: string;
/** A URL that links to your experience to buy or access prepaid parking or general parking information. */
purchaseParkingURL?: string;
/** A URL that launches the user into the issuer's flow for selling their current ticket. Provide as deep a link as possible into the sale flow. */
sellURL?: string;
/** A URL that launches the user into the issuer's flow for transferring the current ticket. Provide as deep a link as possible into the transfer flow. */
transferURL?: string;
/** A URL that links to documentation you have about public or private transit to the venue. */
transitInformationURL?: string;
}
const URLs = Joi.object<URLs>({
accessibilityURL: Joi.string().regex(URL_REGEX),
addOnURL: Joi.string().regex(URL_REGEX),
bagPolicyURL: Joi.string().regex(URL_REGEX),
contactVenueEmail: Joi.string().email(),
contactVenuePhoneNumber: Joi.string(),
contactVenueWebsite: Joi.string().regex(URL_REGEX),
directionsInformationURL: Joi.string().regex(URL_REGEX),
merchandiseURL: Joi.string().regex(URL_REGEX),
orderFoodURL: Joi.string().regex(URL_REGEX),
parkingInformationURL: Joi.string().regex(URL_REGEX),
purchaseParkingURL: Joi.string().regex(URL_REGEX),
sellURL: Joi.string().regex(URL_REGEX),
transferURL: Joi.string().regex(URL_REGEX),
transitInformationURL: Joi.string().regex(URL_REGEX),
});
/**
* @iOSVersion 26
* @see https://developer.apple.com/documentation/walletpasses/upcomingpassinformationentry/dateinformation-data.dictionary
*/
interface DateInformation {
/**
* A string containing an ISO 8601 date and time.
* The date and time when the event is scheduled.
*/
date?: string | Date;
/**
* A Boolean value that controls whether the time appears on the pass.
* When true, the pass displays only the date, not the time.
*/
ignoreTimeComponents?: boolean;
/**
* A Boolean value that indicates whether the event lasts all day.
* When true, the system ignores the time portion of the date.
*/
isAllDay?: boolean;
/**
* A Boolean value that indicates whether the event time is unannounced.
* When true, the pass displays "Time TBA" instead of the actual time.
*/
isUnannounced?: boolean;
/**
* A Boolean value that indicates whether the event time is undetermined.
* When true, the pass may display the date differently to indicate uncertainty.
*/
isUndetermined?: boolean;
/**
* The time zone for the event.
* Use IANA time zone database names (e.g., "America/New_York").
*/
timeZone?: string;
}
const DateInformation = Joi.object<DateInformation>({
date: Joi.alternatives(Joi.string().isoDate(), Joi.date().iso()).required(),
ignoreTimeComponents: Joi.boolean(),
isAllDay: Joi.boolean(),
isUnannounced: Joi.boolean(),
isUndetermined: Joi.boolean(),
timeZone: Joi.string(),
});
/**
* @iOSVersion 26
* @see https://developer.apple.com/documentation/walletpasses/upcomingpassinformationentry
*/
export interface UpcomingPassInformationEntry {
/** A collection of URLs used to populate UI elements in the details view. */
URLs?: URLs;
/** The fields of information displayed on the Additional Info section below a pass. */
additionalInfoFields?: PassFieldContent[];
/**
* An array of App Store identifiers for apps associated with the upcoming pass information entry.
* The associated app on a device is the first item in the array that's compatible with that device.
* This key works only for upcoming pass information entries for an event. A link to launch the app
* is in the event guide of the entry details view. If the app isn't installed, the link opens to the App Store.
*/
auxiliaryStoreIdentifiers?: number[];
/** The fields of information displayed on the details view of the upcoming pass information entry. */
backfields?: PassFieldContent[];
/**
* Information about the start and end time of the upcoming pass information entry.
* If omitted, the entry is labeled as TBD.
*/
dateInformation?: DateInformation;
/**
* A string that uniquely identifies the upcoming pass information entry.
* The identifier needs to be unique for each upcoming information entry.
*/
identifier: string;
/** A collection of image names used to populate images in the details view. */
images?: Images;
/**
* Indicates whether the upcoming pass information entry is currently active.
* The default value is false.
*/
isActive?: boolean;
/** The name of the upcoming pass information entry. */
name: string;
/** The semantic, machine-readable metadata about the upcoming pass information entry. */
semantics?: Semantics & {
venuePlaceID: string;
};
/**
* The type of upcoming pass information entry.
* Value: event
*/
type: "event";
}
export const UpcomingPassInformationEntry =
Joi.object<UpcomingPassInformationEntry>({
URLs: URLs,
additionalInfoFields: Joi.array().items(PassFieldContent),
auxiliaryStoreIdentifiers: Joi.array().items(Joi.number()),
backfields: Joi.array().items(PassFieldContent),
dateInformation: DateInformation,
identifier: Joi.string().required(),
images: Images,
isActive: Joi.boolean(),
name: Joi.string().required(),
semantics: Semantics.concat(
Joi.object({
venuePlaceID: Joi.string(),
}),
),
type: Joi.string().valid("event").required(),
});

View File

@@ -1,12 +1,13 @@
export * from "./Barcode.js"; export * from "./Barcode.js";
export * from "./Beacon.js"; export * from "./Beacon.js";
export * from "./Location.js"; export * from "./Location.js";
export * from "./Field.js"; export * from "./PassFieldContent.js";
export * from "./NFC.js"; export * from "./NFC.js";
export * from "./Semantics.js"; export * from "./Semantics.js";
export * from "./PassFields.js"; export * from "./PassFields.js";
export * from "./Personalize.js"; export * from "./Personalize.js";
export * from "./Certificates.js"; export * from "./Certificates.js";
export * from "./UpcomingPassInformation.js";
import Joi from "joi"; import Joi from "joi";
import type { Buffer } from "node:buffer"; import type { Buffer } from "node:buffer";
@@ -18,15 +19,22 @@ import { NFC } from "./NFC.js";
import { PassFields, TransitType } from "./PassFields.js"; import { PassFields, TransitType } from "./PassFields.js";
import { Semantics } from "./Semantics.js"; import { Semantics } from "./Semantics.js";
import { CertificatesSchema } from "./Certificates.js"; import { CertificatesSchema } from "./Certificates.js";
import { UpcomingPassInformationEntry } from "./UpcomingPassInformation.js";
import * as Messages from "../messages.js"; import * as Messages from "../messages.js";
import { RGB_HEX_COLOR_REGEX, URL_REGEX } from "./regexps.js"; import { RGB_HEX_COLOR_REGEX, URL_REGEX } from "./regexps.js";
export type PreferredStyleSchemes = ("posterEventTicket" | "eventTicket")[]; export type PreferredStyleSchemes = (
| ("posterEventTicket" | "eventTicket")
| ("boardingPass" | "semanticBoardingPass")
)[];
export const PreferredStyleSchemes = Joi.array().items( export const PreferredStyleSchemes = Joi.array().items(
"posterEventTicket", "posterEventTicket",
"eventTicket", "eventTicket",
// or, since iOS 26
"boardingPass",
"semanticBoardingPass",
) satisfies Joi.Schema<PreferredStyleSchemes>; ) satisfies Joi.Schema<PreferredStyleSchemes>;
/** /**
@@ -37,9 +45,19 @@ export interface RelevancyInterval {
endDate: string | Date; endDate: string | Date;
} }
export interface RelevancyEntry { /**
* @iOSVersion 18 => "relevantDate"
* @iOSVersion 26 => "date"
*/
export type RelevancyEntry =
| {
date: string | Date;
relevantDate?: string | Date;
}
| {
date?: string | Date;
relevantDate: string | Date; relevantDate: string | Date;
} };
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -65,6 +83,14 @@ export const RelevantDate = Joi.alternatives(
).required(), ).required(),
}), }),
Joi.object<RelevancyEntry>().keys({ Joi.object<RelevancyEntry>().keys({
/**
* Since iOS 26
*/
date: Joi.alternatives(Joi.string().isoDate(), Joi.date().iso()),
/**
* Since iOS 18, then was renamed in
* 'date' in iOS 26 (what a breaking change)
*/
relevantDate: Joi.alternatives( relevantDate: Joi.alternatives(
Joi.string().isoDate(), Joi.string().isoDate(),
Joi.date().iso(), Joi.date().iso(),
@@ -353,7 +379,7 @@ export interface PassProps {
* If enabled, `foregroundColor` and `labelColor` * If enabled, `foregroundColor` and `labelColor`
* are ignored. * are ignored.
*/ */
useAutomaticColor?: boolean; useAutomaticColors?: boolean;
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -372,6 +398,146 @@ export interface PassProps {
* by `associatedStoreIdentifiers`). * by `associatedStoreIdentifiers`).
*/ */
auxiliaryStoreIdentifiers?: number[]; auxiliaryStoreIdentifiers?: number[];
/**
* @iOSVersion 26
*
* @description
*
* Information about upcoming passes related
* to this pass.
*/
upcomingPassInformation?: UpcomingPassInformationEntry[];
/**
* @iOSVersion 26
*
* @description
*
* A URL for changing the seat for the ticket.
* Available only with Enhanced (or semantic) Boarding Passes
*/
changeSeatURL?: string;
/**
* @iOSVersion 26
*
* @description
*
* A URL for in-flight entertainment.
* Available only with Enhanced (or semantic) Boarding Passes
*/
entertainmentURL?: string;
/**
* @iOSVersion 26
*
* @description
*
* A URL for adding checked bags for the ticket.
* Available only with Enhanced (or semantic) Boarding Passes
*/
purchaseAdditionalBaggageURL?: string;
/**
* @iOSVersion 26
*
* @description
*
* A URL that links to information to purchase lounge access.
* Available only with Enhanced (or semantic) Boarding Passes
*/
purchaseLoungeAccessURL?: string;
/**
* @iOSVersion 26
*
* @description
*
* A URL for purchasing in-flight wifi.
* Available only with Enhanced (or semantic) Boarding Passes
*/
purchaseWifiURL?: string;
/**
* @iOSVersion 26
*
* @description
*
* A URL for upgrading the flight.
* Available only with Enhanced (or semantic) Boarding Passes
*/
upgradeURL?: string;
/**
* @iOSVersion 26
*
* @description
*
* A URL for management.
* Available only with Enhanced (or semantic) Boarding Passes
*/
managementURL?: string;
/**
* @iOSVersion 26
*
* @description
*
* A URL for registering a service animal.
* Available only with Enhanced (or semantic) Boarding Passes
*/
registerServiceAnimalURL?: string;
/**
* @iOSVersion 26
*
* @description
*
* A URL to report a lost bag.
* Available only with Enhanced (or semantic) Boarding Passes
*/
reportLostBagURL?: string;
/**
* @iOSVersion 26
*
* @description
*
* A URL to request a wheel chair.
* Available only with Enhanced (or semantic) Boarding Passes
*/
requestWheelchairURL?: string;
/**
* @iOSVersion 26
*
* @description
*
* The email for the transit provider.
* Available only with Enhanced (or semantic) Boarding Passes
*/
transitProviderEmail?: string;
/**
* @iOSVersion 26
*
* @description
*
* The phone number for the transit provider.
* Available only with Enhanced (or semantic) Boarding Passes
*/
transitProviderPhoneNumber?: string;
/**
* @iOSVersion 26
*
* @description
*
* The URL for the transit provider.
* Available only with Enhanced (or semantic) Boarding Passes
*/
transitProviderWebsiteURL?: string;
} }
/** /**
@@ -387,7 +553,8 @@ type PassMethodsProps =
| "relevantDates" | "relevantDates"
| "expirationDate" | "expirationDate"
| "locations" | "locations"
| "preferredStyleSchemes"; | "preferredStyleSchemes"
| "upcomingPassInformation";
export type PassTypesProps = export type PassTypesProps =
| "boardingPass" | "boardingPass"
@@ -417,6 +584,7 @@ export const PassPropsFromMethods = Joi.object<PassPropsFromMethods>({
expirationDate: Joi.string().isoDate(), expirationDate: Joi.string().isoDate(),
locations: Joi.array().items(Location), locations: Joi.array().items(Location),
preferredStyleSchemes: PreferredStyleSchemes, preferredStyleSchemes: PreferredStyleSchemes,
upcomingPassInformation: Joi.array().items(UpcomingPassInformationEntry),
}); });
export const PassKindsProps = Joi.object<PassKindsProps>({ export const PassKindsProps = Joi.object<PassKindsProps>({
@@ -438,7 +606,7 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
logoText: Joi.string(), logoText: Joi.string(),
description: Joi.string(), description: Joi.string(),
serialNumber: Joi.string(), serialNumber: Joi.string(),
appLaunchURL: Joi.string(), appLaunchURL: Joi.string().regex(URL_REGEX),
teamIdentifier: Joi.string(), teamIdentifier: Joi.string(),
organizationName: Joi.string(), organizationName: Joi.string(),
passTypeIdentifier: Joi.string(), passTypeIdentifier: Joi.string(),
@@ -468,8 +636,8 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
/** /**
* @iOSVersion 18 * @iOSVersion 18
* @passStyle eventTicket (new layout) * @passStyle posterEventTicket, semanticBoardingPasses
* @passDomain Event Guide * @passDomain Event Guide, Semantic Boarding Passes
* *
* To show buttons in the event guide, * To show buttons in the event guide,
* at least two among those marked with * at least two among those marked with
@@ -497,7 +665,7 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
* at least two among those marked with * at least two among those marked with
* "@passDomain Event Guide" must be used. * "@passDomain Event Guide" must be used.
*/ */
directionsInformationURL: Joi.string(), directionsInformationURL: Joi.string().regex(URL_REGEX),
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -513,7 +681,7 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
* at least two among those marked with * at least two among those marked with
* "@passDomain Event Guide" must be used. * "@passDomain Event Guide" must be used.
*/ */
purchaseParkingURL: Joi.string(), purchaseParkingURL: Joi.string().regex(URL_REGEX),
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -529,7 +697,7 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
* at least two among those marked with * at least two among those marked with
* "@passDomain Event Guide" must be used. * "@passDomain Event Guide" must be used.
*/ */
merchandiseURL: Joi.string(), merchandiseURL: Joi.string().regex(URL_REGEX),
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -546,7 +714,7 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
* at least two among those marked with * at least two among those marked with
* "@passDomain Event Guide" must be used. * "@passDomain Event Guide" must be used.
*/ */
transitInformationURL: Joi.string(), transitInformationURL: Joi.string().regex(URL_REGEX),
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -562,7 +730,7 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
* at least two among those marked with * at least two among those marked with
* "@passDomain Event Guide" must be used. * "@passDomain Event Guide" must be used.
*/ */
accessibilityURL: Joi.string(), accessibilityURL: Joi.string().regex(URL_REGEX),
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -578,7 +746,7 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
* at least two among those marked with * at least two among those marked with
* "@passDomain Event Guide" must be used. * "@passDomain Event Guide" must be used.
*/ */
addOnURL: Joi.string(), addOnURL: Joi.string().regex(URL_REGEX),
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -617,7 +785,7 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
* at least two among those marked with * at least two among those marked with
* "@passDomain Event Guide" must be used. * "@passDomain Event Guide" must be used.
*/ */
contactVenueWebsite: Joi.string(), contactVenueWebsite: Joi.string().regex(URL_REGEX),
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -627,7 +795,7 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
* *
* Will add a button among options near "share" * Will add a button among options near "share"
*/ */
transferURL: Joi.string(), transferURL: Joi.string().regex(URL_REGEX),
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -637,7 +805,7 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
* *
* Will add a button among options near "share" * Will add a button among options near "share"
*/ */
sellURL: Joi.string(), sellURL: Joi.string().regex(URL_REGEX),
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -677,7 +845,7 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
* If enabled, `foregroundColor` and `labelColor` * If enabled, `foregroundColor` and `labelColor`
* are ignored. * are ignored.
*/ */
useAutomaticColor: Joi.boolean(), useAutomaticColors: Joi.boolean(),
/** /**
* @iOSVersion 18 * @iOSVersion 18
@@ -696,6 +864,136 @@ export const OverridablePassProps = Joi.object<OverridablePassProps>({
* by `associatedStoreIdentifiers`). * by `associatedStoreIdentifiers`).
*/ */
auxiliaryStoreIdentifiers: Joi.array().items(Joi.number()), auxiliaryStoreIdentifiers: Joi.array().items(Joi.number()),
/**
* @iOSVersion 26
*
* @description
*
* A URL for changing the seat for the ticket.
* Available only with Enhanced (or semantic) Boarding Passes
*/
changeSeatURL: Joi.string().regex(URL_REGEX),
/**
* @iOSVersion 26
*
* @description
*
* A URL for in-flight entertainment.
* Available only with Enhanced (or semantic) Boarding Passes
*/
entertainmentURL: Joi.string().regex(URL_REGEX),
/**
* @iOSVersion 26
*
* @description
*
* A URL for adding checked bags for the ticket.
* Available only with Enhanced (or semantic) Boarding Passes
*/
purchaseAdditionalBaggageURL: Joi.string().regex(URL_REGEX),
/**
* @iOSVersion 26
*
* @description
*
* A URL that links to information to purchase lounge access.
* Available only with Enhanced (or semantic) Boarding Passes
*/
purchaseLoungeAccessURL: Joi.string().regex(URL_REGEX),
/**
* @iOSVersion 26
*
* @description
*
* A URL for purchasing in-flight wifi.
* Available only with Enhanced (or semantic) Boarding Passes
*/
purchaseWifiURL: Joi.string().regex(URL_REGEX),
/**
* @iOSVersion 26
*
* @description
*
* A URL for upgrading the flight.
* Available only with Enhanced (or semantic) Boarding Passes
*/
upgradeURL: Joi.string().regex(URL_REGEX),
/**
* @iOSVersion 26
*
* @description
*
* A URL for management.
* Available only with Enhanced (or semantic) Boarding Passes
*/
managementURL: Joi.string().regex(URL_REGEX),
/**
* @iOSVersion 26
*
* @description
*
* A URL for registering a service animal.
* Available only with Enhanced (or semantic) Boarding Passes
*/
registerServiceAnimalURL: Joi.string().regex(URL_REGEX),
/**
* @iOSVersion 26
*
* @description
*
* A URL to report a lost bag.
* Available only with Enhanced (or semantic) Boarding Passes
*/
reportLostBagURL: Joi.string().regex(URL_REGEX),
/**
* @iOSVersion 26
*
* @description
*
* A URL to request a wheel chair.
* Available only with Enhanced (or semantic) Boarding Passes
*/
requestWheelchairURL: Joi.string().regex(URL_REGEX),
/**
* @iOSVersion 26
*
* @description
*
* The email for the transit provider.
* Available only with Enhanced (or semantic) Boarding Passes
*/
transitProviderEmail: Joi.string(),
/**
* @iOSVersion 26
*
* @description
*
* The phone number for the transit provider.
* Available only with Enhanced (or semantic) Boarding Passes
*/
transitProviderPhoneNumber: Joi.string(),
/**
* @iOSVersion 26
*
* @description
*
* The URL for the transit provider.
* Available only with Enhanced (or semantic) Boarding Passes
*/
transitProviderWebsiteURL: Joi.string().regex(URL_REGEX),
}).with("webServiceURL", "authenticationToken"); }).with("webServiceURL", "authenticationToken");
export const PassProps = Joi.object< export const PassProps = Joi.object<