From f252833bcdbe1356bc070815d61cbe08107bd6a9 Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Fri, 14 Jun 2024 22:46:59 +0200 Subject: [PATCH 01/57] Added support to Semantics.Seat[].venueEntranceGate --- src/schemas/Semantics.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/schemas/Semantics.ts b/src/schemas/Semantics.ts index 9a3a79b..e4c146b 100644 --- a/src/schemas/Semantics.ts +++ b/src/schemas/Semantics.ts @@ -39,6 +39,12 @@ declare namespace SemanticTagType { seatIdentifier?: string; seatType?: string; seatDescription?: string; + + /** + * For newly-introduced event tickets + * in iOS 18 + */ + venueEntranceGate?: string; } interface WifiNetwork { @@ -70,6 +76,7 @@ const seat = Joi.object().keys({ seatIdentifier: Joi.string(), seatType: Joi.string(), seatDescription: Joi.string(), + venueEntranceGate: Joi.string(), }); const location = Joi.object().keys({ From b54f994eeae6ab1652b7d8868cfbbe18fa3613a1 Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Fri, 14 Jun 2024 22:51:38 +0200 Subject: [PATCH 02/57] Added support to Semantics.relevantDates --- src/schemas/Semantics.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/schemas/Semantics.ts b/src/schemas/Semantics.ts index e4c146b..8e07be4 100644 --- a/src/schemas/Semantics.ts +++ b/src/schemas/Semantics.ts @@ -32,6 +32,16 @@ declare namespace SemanticTagType { longitude: number; } + /** + * For newly-introduced event tickets + * in iOS 18 + */ + + interface RelevantDate { + startDate: string; + endDate: string; + } + interface Seat { seatSection?: string; seatRow?: string; @@ -69,6 +79,15 @@ const PersonNameComponent = phoneticRepresentation: Joi.string(), }); +/** + * Minimum supported version: iOS 18 + */ + +const RelevantDate = Joi.object().keys({ + startDate: Joi.string().required(), + endDate: Joi.string().required(), +}); + const seat = Joi.object().keys({ seatSection: Joi.string(), seatRow: Joi.string(), @@ -164,6 +183,8 @@ export interface Semantics { performerNames?: string[]; priorityStatus?: string; + relevantDates?: SemanticTagType.RelevantDate[]; + seats?: SemanticTagType.Seat[]; securityScreening?: string; silenceRequested?: boolean; @@ -250,6 +271,8 @@ export const Semantics = Joi.object().keys({ performerNames: Joi.array().items(Joi.string()), priorityStatus: Joi.string(), + relevantDates: Joi.array().items(RelevantDate), + seats: Joi.array().items(seat), securityScreening: Joi.string(), silenceRequested: Joi.boolean(), From f0813d7291a6f970cecf4086dc7169e9366373f4 Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Fri, 14 Jun 2024 22:57:48 +0200 Subject: [PATCH 03/57] Added support to admissionLevel --- src/schemas/Semantics.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/schemas/Semantics.ts b/src/schemas/Semantics.ts index 8e07be4..28aff52 100644 --- a/src/schemas/Semantics.ts +++ b/src/schemas/Semantics.ts @@ -114,6 +114,12 @@ const WifiNetwork = Joi.object().keys({ */ export interface Semantics { + /** + * For newly-introduced event tickets + * in iOS 18 + */ + admissionLevel?: string; + airlineCode?: string; artistIDs?: string[]; awayTeamAbbreviation?: string; @@ -208,6 +214,12 @@ export interface Semantics { } export const Semantics = Joi.object().keys({ + /** + * For newly-introduced event tickets + * in iOS 18 + */ + admissionLevel: Joi.string(), + airlineCode: Joi.string(), artistIDs: Joi.array().items(Joi.string()), awayTeamAbbreviation: Joi.string(), From 6ac24be17bccada6a4e830234de3fba2925c68a0 Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Fri, 14 Jun 2024 22:58:12 +0200 Subject: [PATCH 04/57] Added support to Semantics venueGatesOpenDate, venueParkingLotsOpenDate, venueRegionName --- src/schemas/Semantics.ts | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/schemas/Semantics.ts b/src/schemas/Semantics.ts index 28aff52..9adfa76 100644 --- a/src/schemas/Semantics.ts +++ b/src/schemas/Semantics.ts @@ -206,10 +206,30 @@ export interface Semantics { vehicleType?: string; venueEntrance?: string; venueLocation?: SemanticTagType.Location; + + /** + * For newly-introduced event tickets + * in iOS 18 + */ + venueGatesOpenDate?: string; + venueName?: string; + + /** + * For newly-introduced event tickets + * in iOS 18 + */ + venueParkingLotsOpenDate?: string; + venuePhoneNumber?: string; venueRoom?: string; + /** + * For newly-introduced event tickets + * in iOS 18 + */ + venueRegionName?: string; + wifiAccess?: SemanticTagType.WifiNetwork[]; } @@ -298,10 +318,32 @@ export const Semantics = Joi.object().keys({ vehicleName: Joi.string(), vehicleNumber: Joi.string(), vehicleType: Joi.string(), + venueEntrance: Joi.string(), + + /** + * For newly-introduced event tickets + * in iOS 18 + */ + venueGatesOpenDate: Joi.string(), + venueLocation: location, venueName: Joi.string(), + + /** + * For newly-introduced event tickets + * in iOS 18 + */ + venueParkingLotsOpenDate: Joi.string(), + venuePhoneNumber: Joi.string(), + + /** + * For newly-introduced event tickets + * in iOS 18 + */ + venueRegionName: Joi.string(), + venueRoom: Joi.string(), wifiAccess: Joi.array().items(WifiNetwork), From f74e5241f771b05602e1350c4e8677267f018df5 Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Fri, 14 Jun 2024 23:02:17 +0200 Subject: [PATCH 05/57] Added support to bagPolicyURL, orderFoodURL and parkingInformationURL --- src/schemas/index.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 5b83ee9..10cab32 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -65,6 +65,24 @@ export interface PassProps { coupon?: PassFields; generic?: PassFields; storeCard?: PassFields; + + /** + * New field for iOS 18 + * Event Ticket + */ + bagPolicyURL?: string; + + /** + * New field for iOS 18 + * Event Ticket + */ + orderFoodURL?: string; + + /** + * New field for iOS 18 + * Event Ticket + */ + parkingInformationURL?: string; } /** @@ -145,6 +163,30 @@ export const OverridablePassProps = Joi.object({ webServiceURL: Joi.string().regex( /https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/, ), + + /** + * New field for iOS 18 + * Event Ticket + */ + bagPolicyURL: Joi.string().regex( + /https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/, + ), + + /** + * New field for iOS 18 + * Event Ticket + */ + orderFoodURL: Joi.string().regex( + /https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/, + ), + + /** + * New field for iOS 18 + * Event Ticket + */ + parkingInformationURL: Joi.string().regex( + /https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/, + ), }).with("webServiceURL", "authenticationToken"); export const PassProps = Joi.object< From d82e24ea20fd5f95b66f5675b95783a525a0d819 Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Fri, 14 Jun 2024 23:04:24 +0200 Subject: [PATCH 06/57] Shared URL Regex in schema --- src/schemas/index.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 10cab32..9f45dc5 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -24,6 +24,8 @@ import * as Messages from "../messages"; const RGB_COLOR_REGEX = /rgb\(\s*(?:[01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\s*,\s*(?:[01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\s*,\s*(?:[01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\s*\)/; +const URL_REGEX = /https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/; + export interface FileBuffers { [key: string]: Buffer; } @@ -159,34 +161,25 @@ export const OverridablePassProps = Joi.object({ foregroundColor: Joi.string().regex(RGB_COLOR_REGEX), associatedStoreIdentifiers: Joi.array().items(Joi.number()), userInfo: Joi.alternatives(Joi.object().unknown(), Joi.array()), - // parsing url as set of words and nums followed by dots, optional port and any possible path after - webServiceURL: Joi.string().regex( - /https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/, - ), + webServiceURL: Joi.string().regex(URL_REGEX), /** * New field for iOS 18 * Event Ticket */ - bagPolicyURL: Joi.string().regex( - /https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/, - ), + bagPolicyURL: Joi.string().regex(URL_REGEX), /** * New field for iOS 18 * Event Ticket */ - orderFoodURL: Joi.string().regex( - /https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/, - ), + orderFoodURL: Joi.string().regex(URL_REGEX), /** * New field for iOS 18 * Event Ticket */ - parkingInformationURL: Joi.string().regex( - /https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/, - ), + parkingInformationURL: Joi.string().regex(URL_REGEX), }).with("webServiceURL", "authenticationToken"); export const PassProps = Joi.object< From 9d033bd6d7d5930127be9174bdc93273a36bc6ab Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Fri, 14 Jun 2024 23:12:38 +0200 Subject: [PATCH 07/57] Added support to eventTicket.preferredStyleSchemes --- src/schemas/index.ts | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 9f45dc5..16c6703 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -63,7 +63,20 @@ export interface PassProps { locations?: Location[]; boardingPass?: PassFields & { transitType: TransitType }; - eventTicket?: PassFields; + eventTicket?: PassFields & { + /** + * New field coming in iOS 18 + * `"eventTicket"` is the legacy style. + * + * If used, passkit will try to render following the old style + * first. + * + * Which means that `primaryFields`, `secondaryFields` and + * so on, are not necessary anymore for the new style, + * as semantics are preferred. + */ + preferredStyleSchemes?: ("posterEventTicket" | "eventTicket")[]; + }; coupon?: PassFields; generic?: PassFields; storeCard?: PassFields; @@ -132,7 +145,24 @@ export const PassKindsProps = Joi.object({ coupon: PassFields.disallow("transitType"), generic: PassFields.disallow("transitType"), storeCard: PassFields.disallow("transitType"), - eventTicket: PassFields.disallow("transitType"), + eventTicket: PassFields.disallow("transitType").append( + Joi.object().keys({ + /** + * New field coming in iOS 18 + * `"eventTicket"` is the legacy style. + * + * If used, passkit will try to render following the old style + * first. + * + * Which means that `primaryFields`, `secondaryFields` and + * so on, are not necessary anymore for the new style, + * as semantics are preferred. + */ + preferredStyleSchemes: Joi.array().items( + Joi.string().allow("posterEventTicket", "eventTicket"), + ), + }), + ), boardingPass: PassFields, }); From 593b64a676e111c78f7192521263f7ab1c504248 Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Sat, 15 Jun 2024 23:32:21 +0200 Subject: [PATCH 08/57] Replaced Joi method append with concat --- src/schemas/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 16c6703..fa85c2e 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -145,7 +145,7 @@ export const PassKindsProps = Joi.object({ coupon: PassFields.disallow("transitType"), generic: PassFields.disallow("transitType"), storeCard: PassFields.disallow("transitType"), - eventTicket: PassFields.disallow("transitType").append( + eventTicket: PassFields.disallow("transitType").concat( Joi.object().keys({ /** * New field coming in iOS 18 From 432e3804295253fe786d213ddee192a2af985ee8 Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Sat, 15 Jun 2024 23:48:33 +0200 Subject: [PATCH 09/57] Added a new setter and getter for preferredStyleSchemes to throw if type is not an eventTicket and moved PreferredStyleSchemes schemas --- src/PKPass.ts | 38 ++++++++++++++++++++++++++++++++++++++ src/messages.ts | 7 +++++++ src/schemas/PassFields.ts | 7 +++++++ src/schemas/index.ts | 10 ++++------ 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/PKPass.ts b/src/PKPass.ts index 462fbf9..4f0d53c 100644 --- a/src/PKPass.ts +++ b/src/PKPass.ts @@ -316,6 +316,44 @@ export default class PKPass extends Bundle { return this[propsSymbol][this.type].backFields; } + /** + * Allows accessing to iOS 18 new Event Ticket + * property `preferredStyleSchemes`. + * + * @throws (automatically) if current type is not + * "eventTicket". + */ + + public get preferredStyleSchemes(): Schemas.PreferredStyleSchemes { + return this[propsSymbol]["eventTicket"].preferredStyleSchemes; + } + + /** + * Allows setting a preferredStyleSchemes property + * for a eventTicket. + * + * @throws if current type is not "eventTicket". + * @param value + */ + + public set preferredStyleSchemes(value: Schemas.PreferredStyleSchemes) { + Utils.assertUnfrozen(this); + + if (this.type !== "eventTicket") { + throw new TypeError( + Messages.PREFERRED_STYLE_SCHEMES.UNEXPECTED_PASS_TYPE, + ); + } + + Schemas.assertValidity( + Schemas.PreferredStyleSchemes, + value, + Messages.PREFERRED_STYLE_SCHEMES.INVALID, + ); + + this[propsSymbol]["eventTicket"].preferredStyleSchemes = value; + } + /** * Allows setting a pass type. * diff --git a/src/messages.ts b/src/messages.ts index dd8ca5d..3e48e6c 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -15,6 +15,13 @@ export const TRANSIT_TYPE = { "Cannot set transitType because not compliant with Apple specifications. Refer to https://apple.co/3DHuAG4 for more - %s", } as const; +export const PREFERRED_STYLE_SCHEMES = { + UNEXPECTED_PASS_TYPE: + "Cannot set preferredStyleSchemes on a pass with type different from eventTicket.", + INVALID: + "Cannot set preferredStyleSchemes because not compliant with Apple specifications - %s", +} as const; + export const PASS_TYPE = { INVALID: "Cannot set type because not compliant with Apple specifications. Refer to https://apple.co/3aFpSfg for a list of valid props - %s", diff --git a/src/schemas/PassFields.ts b/src/schemas/PassFields.ts index 41eef7d..58c3b1e 100644 --- a/src/schemas/PassFields.ts +++ b/src/schemas/PassFields.ts @@ -12,6 +12,13 @@ export const TransitType = Joi.string().regex( /(PKTransitTypeAir|PKTransitTypeBoat|PKTransitTypeBus|PKTransitTypeGeneric|PKTransitTypeTrain)/, ); +export type PreferredStyleSchemes = ("posterEventTicket" | "eventTicket")[]; + +export const PreferredStyleSchemes = Joi.array().items( + "posterEventTicket", + "eventTicket", +) satisfies Joi.Schema; + export interface PassFields { auxiliaryFields: FieldWithRow[]; backFields: Field[]; diff --git a/src/schemas/index.ts b/src/schemas/index.ts index fa85c2e..3fe25b7 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -15,7 +15,7 @@ import { Barcode } from "./Barcode"; import { Location } from "./Location"; import { Beacon } from "./Beacon"; import { NFC } from "./NFC"; -import { PassFields, TransitType } from "./PassFields"; +import { PassFields, PreferredStyleSchemes, TransitType } from "./PassFields"; import { Semantics } from "./Semantics"; import { CertificatesSchema } from "./Certificates"; @@ -75,7 +75,7 @@ export interface PassProps { * so on, are not necessary anymore for the new style, * as semantics are preferred. */ - preferredStyleSchemes?: ("posterEventTicket" | "eventTicket")[]; + preferredStyleSchemes?: PreferredStyleSchemes; }; coupon?: PassFields; generic?: PassFields; @@ -158,9 +158,7 @@ export const PassKindsProps = Joi.object({ * so on, are not necessary anymore for the new style, * as semantics are preferred. */ - preferredStyleSchemes: Joi.array().items( - Joi.string().allow("posterEventTicket", "eventTicket"), - ), + preferredStyleSchemes: PreferredStyleSchemes, }), ), boardingPass: PassFields, @@ -240,7 +238,7 @@ export const Template = Joi.object