passkit-generator icon indicating copy to clipboard operation
passkit-generator copied to clipboard

Supporting iOS 18 Changes

Open alexandercerutti opened this issue 1 year ago • 171 comments

iOS 18 have been announced a few days ago and, a few hours ago, the session including the upcoming changes to Apple Wallet have been published: https://developer.apple.com/videos/play/wwdc2024/10108/

Some new fields have been published to support the new UI for events.

That said, within september, a passkit-generator update will be published. If anyone wants to attempt to generate the new passes with an iOS 18 Beta through passkit-generator, I kindly ask you to leave a comment here, so I'll know if I should publish an alpha version with some changes.

This issue will be soon updated with a summary of the changes in the schemas.

Recap from the discussion until now

  • Schema changes are available in this branch,

  • The new eventTicket is a pass that requires NFC Entitlements

  • Show hidden and useless updates
    • We were able to extract the NFC certificate used by PassKit to generate an NFC pass from its signature and found what are the practical differences between a normal one and and NFC one. A certificate cannot be tampered, so we cannot generate one with NFC capabilities.
    • Although RC was released and new model is cited inside the changelog and the web page, with only the details provided in the WWDC video (and NFC certificate), it seems to be impossible yet to generate a valid eventTicket with the new UI. it is possible now to generate one.
    • Apparently not even with 18 or iOS 18.1, it is possible to generate it.
  • Apple made at least one mistake in their video: preferredStyleSchemes is a top level property. All the errors are marked in the second message with a strikethrough style, followed or preceded by the correct property.

  • One of semantics.venueRegionName, semantics.venueName or semantics.venueRoom is a required for the new layouto appear.

  • The new layout applies to all the semantics.eventType but PKEventTypeSports for which two more properties are needed: semantics.awayTeamAbbreviation and semantics.homeTeamAbbreviation

  • The new layout applies to semantics.eventType == PKEventTypeLivePerformance, but semantics.performerNames is required.

  • Each relevantDates entry allows a max time span of 1 day.

  • Found lot more of root properties for event guide, for transfering and selling tickets and for live activies.

  • A new asset venueMap.png (and resolutions, probably) can be added to bundle to show the map of the venue in the event guide

  • iOS 18.1 b6 shows perfectly the live activity, with seat color highlighted, even in case of general admission

  • has been released with all the discussed changes. We are waiting for the stable release of iOS 18.1 to release a stable version of this library, in order to see if an updated documentation will come out (other than the N.D.A/private documents shared with companies) and if something more should be added.

alexandercerutti avatar Jun 13 '24 16:06 alexandercerutti

  • (root).bagPolicyURL (string) (link to message)
  • (root).orderFoodURL (string) (link to message)
  • (root).parkingInformationURL (string) (link to message)
  • (root).contactVenuePhoneNumber (string) (link to message)
  • (root).contactVenueWebsite (string) (link to message)
  • (root).accessibilityURL (string) (link to message)
  • (root).addOnURL (string) (link to message)
  • (root).purchaseParkingURL (string) (link to message)
  • (root).merchandiseURL (string) (link to message)
  • (root).transitInformationURL (string) (link to message)
  • ~(root).eventTicket.preferredStyleSchemes (["posterEventTicket", "eventTicket"])~
  • (root).preferredStyleSchemes (["posterEventTicket", "eventTicket"])
  • (root).sellURL (string) (link to message)
  • (root).transferURL (string) (link to message)
  • (root).suppressHeaderDarkening (boolean) (link to message
  • (root).footerBackgroundColor (boolean) (link to message
  • (root).useAutomaticColor (boolean) (link to message
  • (root).auxiliaryStoreIdentifiers (boolean) (link to message
  • (root).eventTicket.additionalInfoFields (Field[]) (link to message)
  • (root).relevantDates[].startDate (date time string) (link to message, link to message)
  • (root).relevantDates[].endDate (date time string) (link to message, link to message)
  • (root).relevantDates[].relevantDate (date time string) (link to message)
  • ~semantics.relevantDates[].startDate (date time string)~ (link to message)
  • ~semantics.relevantDates[].endDate (date time string)~ (link to message)
  • ~semantics.seats[].venueEntranceGate (string)~
  • semantics.venueEntranceGate (string) (link to message)
  • semantics.admissionLevel (string)
  • semantics.eventStartDateInfo (object) (link to message
  • semantics.venueParkingLotsOpenDate (date time string) (link to message)
  • semantics.venueGatesOpenDate (date time string) (link to message)
  • semantics.venueRegionName (string) (link to message)
  • semantics.entranceDescription (string) (link to message)
  • semantics.attendeeName (string) (link to message)
  • semantics.eventLiveMessage (string) (link to message)
  • semantics.playlistIDs (link to message)
  • semantics.albumIDs (link to message)
  • semantics.venueBoxOfficeOpenDate (link to message)
  • semantics.venueOpenDate (date time string) (link to message
  • semantics.venueCloseDate (link to message)
  • semantics.venueDoorsOpenDate (link to message)
  • semantics.venueFanZoneOpenDate (link to message)
  • semantics.tailgatingAllowed (link to message)
  • semantics.additionalTicketAttributes (link to message)
  • semantics.seats[].seatAisle (link to message)
  • semantics.venueEntranceDoor (link to message)
  • semantics.venueEntrancePortal (link to message)
  • semantics.admissionLevelAbbreviation (link to message)
  • semantics.airplay[].airPlayDeviceGroupToken (link to message)
  • semantics.seats[].seatLevel (link to message)
  • semantics.seats[].seatSectionColor (link to message, link to message)

iOS 18.1 changes (released in v3.5.5)

  • eventLogoText
  • EventDateInfo.unannounced
  • EventDateInfo.undetermined

alexandercerutti avatar Jun 13 '24 16:06 alexandercerutti

I'm able and willing to test if you're publishing an alpha!

nickwelsh avatar Jun 13 '24 22:06 nickwelsh

@nickwelsh Okay great! I'll work on it, probably this weekend.

Before having a stable release, I'll wait for them to publish the updated documentation.

I think these are the new fields but some more could appear possibly.

P.s. Did you leave a 🌟 on the project? 👀

alexandercerutti avatar Jun 13 '24 22:06 alexandercerutti

Schema changes are available in this branch, so you can install the package in NPM from git and build it.

I'll release a version in the next days if I have some time.

If you can successfully test, can you provide me screenshots of the results? I'm very curious!

alexandercerutti avatar Jun 14 '24 21:06 alexandercerutti

I built a minimal version using the stable build to make sure things worked:

Code

// index.ts
import pk from "passkit-generator"
import 'dotenv/config'
import fs from "fs"

const PKPass = pk.PKPass;

try {
    const pass = await PKPass.from({
        model: "./model.pass",
        certificates: {
            wwdr: process.env.WWDR,
            signerCert: process.env.CERT,
            signerKey: process.env.KEY,
            signerKeyPassphrase: process.env.CHALLENGE
        },
    }, {
        // keys to be added or overridden
        serialNumber: "AAGH44625236dddaffbda"
    });

    const buffer = pass.getAsBuffer();

    fs.writeFile('pass.pkpass', buffer, (err) => {
        if (err) throw err;
        console.log('Buffer saved to file');
    });

} catch (err) {
    console.error(err)
}
{
  "organizationName": "My Org",
  "description": "My Event Description",
  "teamIdentifier": "******",
  "passTypeIdentifier": "******",
  "backgroundColor": "rgb(248,252,252)",
  "foregroundColor": "rgb(111,162,163)",
  "labelColor": "rgb(0,52,68)",
  "formatVersion": 1,
  "eventTicket": {
    "preferredStyleSchemes": [
      "posterEventTicket",
      "eventTicket"
    ]
  },
  "semantics": {
    "eventType": "PKEventTypeLivePerformance",
    "eventName": "South Bay Jazz Festival",
    "eventStartDate": "2024-07-15T10:00:00-06:00",
    "seats": [
      {
        "seatDescription": "Normal Seat",
        "seatIdentifier": "112-12-16",
        "seatNumber": "5",
        "seatRow" : "3",
        "seatSection": "100",
        "venueEntranceGate": "3"
      }
    ]
  }
}

Which, indeed, builds a pass that works, obviously using the old UI. Swapping out to the alpha you provided throws an error:

error: Object schema cannot be a joi schema
      at /Users/nick/Downloads/passkit/node_modules/passkit-generator/node_modules/@hapi/hoek/lib/error.js:25:1
      at /Users/nick/Downloads/passkit/node_modules/passkit-generator/node_modules/@hapi/hoek/lib/assert.js:19:30
      at method (/Users/nick/Downloads/passkit/node_modules/passkit-generator/node_modules/joi/lib/types/keys.js:413:21)
      at method (/Users/nick/Downloads/passkit/node_modules/passkit-generator/node_modules/joi/lib/types/keys.js:323:15)
      at /Users/nick/Downloads/passkit/node_modules/passkit-generator/lib/schemas/index.js:17:1
      at require (native:1:1)
      at /Users/nick/Downloads/passkit/node_modules/passkit-generator/lib/FieldsArray.js:48:25
      at require (native:1:1)
      at /Users/nick/Downloads/passkit/node_modules/passkit-generator/lib/PKPass.js:677:50
      at require (native:1:1)

nickwelsh avatar Jun 15 '24 16:06 nickwelsh

@nickwelsh Are you able to see which property failed to validate?

alexandercerutti avatar Jun 15 '24 16:06 alexandercerutti

Seems like it's the new preferredStyleSchemes or rather the newly appended object in the schema. Removing the append() allows the pass to be built, but then preferredStyleSchemes isn't part of the schema and won't be included in the created pass.json.

// schemas/index.ts
// ...
eventTicket: PassFields.disallow("transitType").append(
		Joi.object<PassProps["eventTicket"]>().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"),
			),
		}),
	),
// ...

nickwelsh avatar Jun 15 '24 20:06 nickwelsh

@nickwelsh interesting. Maybe I should have used .concat instead of that .append. What happens if you replace it? I've the commit ready, just in case.

alexandercerutti avatar Jun 15 '24 20:06 alexandercerutti

I tried replace with .concat, and now PKPass are able to build without crashes. Verified with pass.json payload that data actually written.

Although, I have concerns with preferredStyleSchemes field, since it not as a part of PassFields. It means that I have to override it via .props which could make type-checking a bit tricky.

CleanShot 2024-06-16 at 06 19 46

rayriffy avatar Jun 15 '24 21:06 rayriffy

@rayriffy Hey, thanks for testing!

Two things:

  1. Using .props to edit things it not the suggested way to do that as .props returns a deep clone of pass.json;

  2. What Typescript says is tecnically right, eventTicket property might not exist cause your pass might not be of a type eventTicket. In a new version I could try to find a way to make it more safe, but I'm not exactly sure how. I could create and expose some Typescript narrowing guards...

Other than that, I still didn't provide a way to set manually the preferredStyleSchemes just like primaryFields and so on... maybe I should provide a just like the others.

alexandercerutti avatar Jun 15 '24 21:06 alexandercerutti

thanks, although it's not a blocker though. you can make an improvements to an interface later in future version. for now i will have to forcefully mark that eventTicket actually exists.

pass.props.eventTicket!.preferredStyleSchemes = [...]

also i have another suggestion, currently i help testing your library by manually clone a project because referencing package with gtihub: in package.json does not includes any source code nor built code to use. my suggestion is in files also includes src/ directory, and make a script field prepare to run build:src should do the job

rayriffy avatar Jun 15 '24 21:06 rayriffy

I've committed .concat and added a new getter/setter .preferredStyleSchemes to access and to set them with validation. Of course, both will throw if the type is not an eventTicket.

also i have another suggestion, currently i help testing your library by manually clone a project because referencing package with gtihub: in package.json does not includes any source code nor built code to use. my suggestion is in files also includes src/ directory, and make a script field prepare to run build:src should do the job

I never used github: protocol in NPM, but I think you can also get rid of it, as explained here and above in the same page: https://docs.npmjs.com/cli/v10/configuring-npm/package-json#github-urls

It should clone and provide the content from the repository. There, you should have the source.

Once you install the package from github, you can just change the directory to the dependency and use npm run build.

Let me know if it works.

Getting back to the pass, so, are you able to generate with with the new format?

alexandercerutti avatar Jun 15 '24 21:06 alexandercerutti

unfortunately not at this moment, i tried with multiple relevant semantics fields and could not get it to work. lacking of documentation from apple is killing me, my best guess is maybe new event ticket format has not been added to the developer beta 1 yet.

maybe someone else also have a success? i would like to know as well.

CleanShot 2024-06-16 at 07 09 28

rayriffy avatar Jun 15 '24 22:06 rayriffy

Did you try to open it on a real iPhone? Could it be it is not available on the Simulator yet?

BTW I think it is still early for the documentation to come out. It will get surely updated in the next months.

alexandercerutti avatar Jun 15 '24 22:06 alexandercerutti

nah doesn't work either, i will drop my pass.json here if anyone has any insights of what's wrong

Code
{
  "formatVersion": 1,
  "passTypeIdentifier": "",
  "teamIdentifier": "",
  "serialNumber": "ahsdg2",
  "organizationName": "Creatorsgarten",
  "description": "Creatorsgarten Event Ticket",
  "foregroundColor": "rgb(255, 255, 255)",
  "backgroundColor": "rgb(0, 0, 0)",
  "labelColor": "rgb(255, 255, 255)",
  "semantics": {
    "eventName": "The โง่ Hackathon ครั้งที่ 8 แห่งประเทศ Thailand",
    "eventType": "PKEventTypeLivePerformance",
    "eventStartDate": "2024-07-13T00:00+07:00",
    "eventEndDate": "2024-07-14T23:59+07:00",
    "relevantDates": [
      {
        "startDate": "2024-07-13T08:00+07:00",
        "endDate": "2024-07-14T23:59+07:00"
      }
    ],
    "admissionLevel": "ElysiaJS"
  },
  "relevantDate": "2024-07-13T01:00:00.000Z",
  "barcodes": [
    {
      "format": "PKBarcodeFormatQR",
      "message": "QGZRQR",
      "altText": "QGZRQR",
      "messageEncoding": "iso-8859-1"
    }
  ],
  "eventTicket": {
    "headerFields": [
      {
        "key": "date",
        "label": "DATE",
        "value": "13 Jul"
      }
    ],
    "primaryFields": [
      {
        "key": "event",
        "label": "EVENT",
        "value": "The โง่ Hackathon ครั้งที่ 8 แห่งประเทศ Thailand"
      }
    ],
    "secondaryFields": [
      {
        "key": "loc",
        "label": "LOCATION",
        "value": "คณะวิศวกรรมศาสตร์ จุฬาลงกรณ์มหาวิทยาลัย"
      }
    ],
    "auxiliaryFields": [],
    "backFields": [],
    "preferredStyleSchemes": [
      "posterEventTicket",
      "eventTicket"
    ]
  }
}

rayriffy avatar Jun 15 '24 23:06 rayriffy

@rayriffy Did you try to add the venue and seats fields in semantics, like Nick wrote above? For what I understood, they are required to render the new layout...

    "seats": [
      {
        "seatDescription": "Normal Seat",
        "seatIdentifier": "112-12-16",
        "seatNumber": "5",
        "seatRow" : "3",
        "seatSection": "100",
        "venueEntranceGate": "3"
      }
    ]

Also, I can suggest you connecting to Console.app and check if there are any logs about new things.

alexandercerutti avatar Jun 15 '24 23:06 alexandercerutti

Let me add that someone was saying, in Apple Developers Forum, that the video was reporting the availability of some examples as downloadable resources, but it is not available under the video nor in the documentation, as opposed to the changes to Apple Pay.

I wonder if the fact this isn't working and the absence of a resource are due to the same reason, which is the lack of update in the first beta...

alexandercerutti avatar Jun 15 '24 23:06 alexandercerutti

I used the pass.json rayriffy provided to generate a pass, renamed .pkpass to .zip, extracted it, and examined the pass.json in the generated pass. It does not include the preferredStyleSchemes field. It's like it's getting stripped out when the pass is being generated, because everything else from the original pass.json is still there.

nickwelsh avatar Jun 16 '24 00:06 nickwelsh

@nickwelsh Okay interesting. Did you try with the changes of the last commit (432e3804295253fe786d213ddee192a2af985ee8) or the previous one or both?

You can tell NPM to clone a specific commit id.

That's because I changed the schemas a little bit in the last commit.

alexandercerutti avatar Jun 16 '24 00:06 alexandercerutti

@nickwelsh fyi i did make an minor code change to reflect alex's new getter setter update from pass.props.eventTicket!.preferredStyleSchemes = [...] to pass.preferredStyleSchemes = [...]

rayriffy avatar Jun 16 '24 00:06 rayriffy

Ah, I only had the style set in the pass.json in my model.pass. Once I explicitly added pass.preferredStyleSchemes to the js, the generated pass.json had the fields.

nickwelsh avatar Jun 16 '24 01:06 nickwelsh

Ops! I forgot the possibility to import that field 😅 Last commit (dd085152515a6a2b15a84d29ee742fb9b27188ef) should include it

alexandercerutti avatar Jun 16 '24 02:06 alexandercerutti

I proceeded adding unit tests for preferredStyleSchemes. I can confirm it gets now always added to the pass.json, wherever it comes from.

Let me know if you guys are able to generate and show a new layout event ticket.

alexandercerutti avatar Jun 16 '24 12:06 alexandercerutti

Hi everyone! @alexandercerutti great to see you again haha, just thought I’d pop into the repo and leave a message about the iOS 18 Wallet updates but I can see you’re already on it! Great to see :)

@rayriffy mentioned the new event passes may not be on beta 1 - just wanted to confirm through a tweet I saw that they should be there (Apple’s WWDC invite pass updated on the beta), see: https://x.com/frederikriedel/status/1800253419304968439?s=46&t=mbu_2SzVSyE0jhQUv3QWWw

Will definitely be updating my backend and giving these changes a go once I’m free in about a week or so. Fingers crossed someone manages to get one generated and working in the meantime, but thanks a lot Alex, Nick and rayriffy for all of your testing so far :)

Saim-Khan1 avatar Jun 16 '24 16:06 Saim-Khan1

Hey @Saim-Khan1, glad you jumped on here!

So according to the tweet, the update could be already available in the first beta.

Perhaps only Venue data are required along with preferred schemas?

alexandercerutti avatar Jun 16 '24 16:06 alexandercerutti

I tried adding venue information venueName, venueRegionName, venueLocation but doesn't work either. If WWDC pass actually proof that new layout is already added in developer beta I would like to see Apple's approach on .pkpass as well.

rayriffy avatar Jun 17 '24 10:06 rayriffy

@rayriffy I’m asking around to folks who went to WWDC if they can share theirs with us… keep experimenting in the meantime

alexandercerutti avatar Jun 17 '24 10:06 alexandercerutti

@rayriffy

nah doesn't work either, i will drop my pass.json here if anyone has any insights of what's wrong

I was looking again at the differences between your attempt on Simulator and your attempt on real device. On the code you show for real device, I don't see the new semantics.seats[].venueEntrance nor the whole seats structure. Perhaps that's the key?

alexandercerutti avatar Jun 17 '24 10:06 alexandercerutti

I’m asking around to folks who went to WWDC if they can share theirs with us

Awesome, I think that would definitely go a long way – having a working example would be great!

Saim-Khan1 avatar Jun 17 '24 11:06 Saim-Khan1

I feel like the lack of secondaryLogo asset, could be a critical point, just like what happens with icon on different Apple OSs.

alexandercerutti avatar Jun 17 '24 13:06 alexandercerutti