Video hosted by Apple at devstreaming-cdn.apple.com

Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2012-309
$eventId
ID of event: wwdc2012
$eventContentId
ID of session without event part: 309
$eventShortId
Shortened ID of event: wwdc12
$year
Year of session: 2012
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2012] [Session 309] Introducing...

WWDC12 • Session 309

Introducing Passbook, Part 2

App Services • iOS • 58:24

Passbook passes can be updated dynamically, ensuring your users always have the most recent information. Building on the basics covered in Introducing Passbook, come to this session to see how you can add new passes and access your application's passes using the PassKit framework. Learn what you need to do on your server to support updating your passes automatically using push notifications.

Speakers: Eliza Block, Ken Ferry

Unlisted on Apple Developer site

Downloads from Apple

HD Video (338 MB)

Transcript

This transcript was generated using Whisper, it may have transcription errors.

Good afternoon. Welcome to part two of our introduction to Passbook. I'm Ken Ferry, and Eliza Block and I will be speaking to you this afternoon. So this is part two. In part one, what did we do? We talked about this file format that we define for a pass in Passbook that you need to use if you're going to make one. And already from that, you know, that's actually kind of strange, right? Apple doesn't usually define file formats. We usually vend frameworks to let you make things. But in this case, we had to do a file format because a pass is usually rendered out on a server, kind of the same way it gets a request and it renders out a web page. In this case, it renders out a pass. Now, already from this, you can see that the ecosystem sort of for Passbook has more pieces than is usual for an Apple API. And what we're going to do today is talk about the different pieces of that ecosystem, except for the file format, because we did that already. So there are three things we're going to talk about. The first one is the idea of a companion app. So this would be something like if you're a coffee shop and you want to let the user add more money to their pass, that's not something they can do in Passbook. So you're going to have some sort of extended companion. We want to talk about a conduit app, we're calling it. This is something like Safari or Mail, which isn't something that sort of you think of as having much to do with passes, but it can take attachments. It takes arbitrary data of arbitrary types, and it might be a way for a pass to enter the system. And so you can write these, too. On the system, Safari and Mail are both in this category.

And externally, if you had an instant messaging client or if you had something like VoodooPad or just a personal wiki that takes attachments, that's the kind of thing that you might be able to use. And then last, we're going to talk about the server. This is really interesting. I don't think we've ever done something quite like this. We defined a web service API that's almost the same kind of thing as a delegate protocol, except that instead of calling an object, we're calling it on a server. And it's layered over top of push. And Eliza's going to go through this, and she has really good animations, so I'm sure you'll love it. Okay, so let's get started. And first we're going to talk about these companion apps. Okay, so like we said, a companion app is going to be something that offers extended functionality past what Passbook itself does, and usually what that's gonna mean is it's gonna mean modifying things, because Passbook does display and it does browsing, but it doesn't do any user-driven modification.

So let's kind of go through this sort of task by task. In order to write this kind of app, the first thing you're gonna be able to have to do is to show the user what passes are already installed on the device so that they can modify them. There are two classes involved with that. There's the PKPass library, which represents the collection of installed passes, and then there's PKPass, which is relatively obvious. And so let's go through those in turn. So first, you're going to need to be able to grab back the list of installed passes, which you'll do using PK pass library, which is mostly pretty straightforward.

You create it with alloc init. You ask for its passes, which returns an array of passes. And then you probably want to register with the notification center to get notified if those passes change because your UI will have to be updated to account for that. You might think it's impossible for passes to change while the user is in your app without you knowing it. But that's not true. It might, because of the server API, something might change behind the scenes there, or iCloud sync might kick in and something might change that way. And especially iCloud sync, you really can't control, so this always might happen. Now there is one thing on here that I wanna call your attention to that is a little bit different than sometimes. Note the object of that notification. The pass library there, it's not nil.

That's because each pass library issues its own notifications. And you just want to, it's not a singleton. You just want to register to observe a single pass library object. And in fact, if you try to pass nil there, if you don't instantiate any pass libraries, you'll never get any notifications. Or if there happened to be three instantiated because three different areas of the processor are using it, you'd get three. So be sure that you pass that there.

Sort of a little bit more on that line. It's sort of the same model as core data. The PKPath library always represents the same underlying data, but it's sort of at differing interfaces to it, like a managed object context, and they're thread confined, so, or queue confined. So generally you'd just be using one from the main thread and that's fine, and you won't have to worry about it. But be aware, that's the threading model if you want to use it. And Eliza's going to show you this a little bit, and a little bit of a caveat there when she does a demo.

Okay, so you might be wondering about security involving this stuff because it doesn't seem like an arbitrary app should be able to pull back all the passes that are installed in the system because they might be sensitive, right? So one way of doing it is you might expect that there'd be a blue alert that shows up as soon as an app wants to use Passbook that says, "Hey, this app wants to access your passes. "Is that cool?" But that's not how it works.

We think that we have a better user experience for this, which is that it's more granular. Every pass has a pass type identifier. You may remember from the first session. And an app pre-declares which pass type identifiers it should be able to access. So if I'm a coffee shop, then I'm just going to be able to see the passes for my coffee shop. But that's something that can get checked during app store review, that you're not saying anything ridiculous. And so there's no need to bother the user about it. So that'll just work transparently.

But as a result, when you pull back this passes array, you're not actually getting every single thing that's installed in the device. You're just getting the ones back that you are entitled to see. And if you don't have any entitlements like this, that it'll be an empty array. And Eliza's gonna show a little bit about this too. You don't have to type these things in my hand. Xcode has some integration. Okay, that's it for security. That's it for pulling back the passes. So the next thing you have to be able to do once you've pulled back these PK pass objects is to get enough information to be able to display some stuff, okay? In this case, we're showing where we're coming from and to and the time of the flight for this Oceanic Airlines app. Sorry, I didn't mention that before. And it's just like that. And if you think about this, if we have the class PK pass, there's no way we're gonna have properties that have to do with all of these things that are specific to your individual passes. We're not gonna know about them. So how does this work? Well, there's this method called localized value for field key that can sort of pull text off of your pass kind of arbitrarily. I think of it as being a lot like IB outlets.

In your pass JSON, you may remember from the first session, there are these field dictionaries that have keys, labels, and values. And if you pass the key, which you defined, you got to pick that when you made the pass, then it'll pass you back the value associated with that key, which is how you can get this data. You may also be able to get it by talking to your server, maybe, if you sort of know what these passes represent. But this would let you get it when you're offline as well, just straight out of the pass itself, if that makes sense. And Eliza is going to show you that later in the demo. Okay, so that's it for display. Sorry, display in that form. But you also, you know, obviously another thing you need to be able to do with a pass is display it for scanning.

You know, sort of modally, filling the whole screen. Which you can do, sort of. You can do it in Passbook. So this was a design decision. Should every app be able to display and browse passes like this? Or should that be in one place? should that be in passbook, and apps don't do that. Do apps just switch to passbook? And we went with the latter. So if you have a PK pass, you can ask for its pass URL. And if you open that, then the apps will switch around and show you that pass in passbook. So you don't display, we don't actually vend any view classes at all in Passbook. We vend model level data. So you can do that kind of display in a table, but not the full on pass for that go to Passbook.

So if that's the case, if there's no browsing in your app, and if we're saying that you should just do it in Passbook, you should realize at this point what companion apps are really for. They're not for browsing. They're not for reproducing that same experience. They're for extended, for different things. They're generally for editing. They're for modifying passes. And that's it. So you may find when you think about it, actually, I don't need to do that. I have a simpler case where the user is really just going to need to see their passes and pick one. And if that's the case, you shouldn't write an app.

Because it's a really, really good user experience. It's really the experience we're kind of aiming for that passes should be self-contained and should be useful by themselves. It's very lightweight. The user already understands it if they understand Passbook. It's just nice. And, of course, if you need it, you should write a companion app. But don't just do it for no good reason. So with that said, let's go ahead and actually talk about genuinely modifying a pass. In this case, the example might be for this airline choosing a seat. OK.

This would be easier, perhaps, if PKPass was mutable, and it's not. So you have to be able to sign your pass in order to make a pass, and if your signing certificate was present on the device, that wouldn't be a very good secret, would it? Since anybody would be able to sign with that. So as a result, if you want to modify a pass, the way you're gonna have to do it is you're going to have to talk to a server, your server, tell it what it is you want done, and then the server will send back the full signed zipped PK pass data. And that's ad hoc, that's in your own hands, handle authentication however seems appropriate.

One thing you will have to do in order to be able to do this though is to identify what pass is being talked about to your server. And so for that, we're just basically saying, yes, these methods do exist. Pass type identifier and serial number, if you remember, these two are what passbook uses to uniquely identify a pass together. And so it's available to you and you can tell your server and that way your server should know what it's operating on. Okay, so once you've got that data back from the server, you need to incorporate it into passbook.

How are you going to do that? Well, you can do pkpass alloc init, and then once you have it, you can call this method on pass library, which is replace pass with pass. It has this little bit of a funny name. You might expect it to be add pass, right? But the distinction is that adding a completely new pass to passbook is something that we say requires user interaction. The passbook is theirs.

You can't just kind of stuff stuff in there. They have to hit add. They have to okay it with a button. So what we do let you do is that if you have a new pass and an old pass that both have the same pass type identifier and serial number, then we consider the new one to be a new version of the old one and we'll let you update it in place. The user would not think of it as a new pass. they would think of it as the past changed. Okay, so that's what it is. But since they're immutable, we don't actually modify them, we're just replacing them. Okay, great. So at this point, I would like to invite up Eliza to show you a demo of some of the things we've been talking about in the PassiveHook API.

So I'm going to do a little demo where I'm going to build an app that Ken already showed you some screenshots from. Okay, so this is the app. It doesn't do anything yet. It's showing us a list of our passes, but I haven't written the part that's actually going to go get the passes out of Passbook. So let me do that now. So what I did to create this project was I basically took the stock Xcode master detail view controller template. I made one and I made a few modifications to it just to make the UI look pretty. But the interesting thing here is in this master view controller, which is the view that I just showed you that was empty, what we want to do is instantiate a pass library and we're also going to fill up this array of passes here in order to display the list of the users currently installed passes. So in my init method, I'm gonna create a pass library just by allocating one and anitting it. And then I'm gonna grab the passes out of it by asking it for its passes. Now, one thing to note about this is that these passes are gonna come back in an arbitrary order. We don't actually sort them in any way when giving them back, so it's kind of non-deterministic how they're gonna come out. So you might want to display them to the user in some useful order. In this case, this is an Oceanic app, and it's gonna show a list of your upcoming flights, so we might wanna sort them by date. Now, passes, as you may remember from our first session, have the possibility of having a relevant date, which is the time at which you would most likely wanna use that pass. In this case, Oceanic has chosen to use that relevant date to store the departure time of the flight, so that would be a good property to sort the passes on. So I'm gonna create some sort descriptors, whoa.

Okay, so I'm gonna create these sort descriptors. We're gonna sort by the relevant date property ascending, and then instead of just setting this to the raw result of calling passes, we're gonna sort the result first by that descriptor. So this is the new literal array syntax that's equivalent to calling NSArray, array with object, by date, much more compact, new in iOS 6. Okay, so now we've got our passes. So our model is set up. We need to now configure the table view cells to actually display the data out of the passes. So I'm gonna scroll down here to my table view cell for row at index path method. And again, this is pretty much just the stock method that gets produced with the one exception that I've created a UITableViewCell subclass, which its job is simply to make these cells lay themselves out all pretty. So I'm gonna show you the header for that. It's got one interesting method, configure with departure code, arrival code, flight date, passenger seat. So if we can get all that information out of the pass, we can just configure it on the cell and then the cell will lay itself out. So that's what we're gonna do next. All right, so switching back, we need to grab the pass for this row, which is just the object at index row, and then we're going to call that method you on the cell. And to get all this information out of the pass, we're gonna use the localized value for field key method that Ken discussed. So the departure code is the localized value for the field key depart. And the reason that I know that is that I wrote the pass also, right, so I got to choose these keys. And so I know that I chose to use the key depart for my departure code and so on. So I'm relying, you're relying on the fact that the app writer is also the pass writer. So we can just go ahead and fill these in. Arrive, flight, date, passenger, seat. So now we've grabbed all that data out of the pass and we're now ready to go ahead and display it. So I'm gonna run this again.

And now we actually see some data show up. So here's a list of these flights that Hugo Reyes is going to take from Sydney to Perth, back to Sydney, and then to LA, where he never actually arrives. So now let's suppose that on his way to Perth, he decides that he wants to change his seat. So we can go into the detail view controller now. And the detail view controller, I'm producing all this information up here by just the same method. I'm grabbing that same information out of the PKPass object. So suppose he chooses to change his seat, and maybe he selects 3A. So now, when he is gonna tap the 3A button, what's gonna happen is this app is going to send a request to the Oceanic server and say, "I would like to update this pass with this serial number, "and I wanna update it by changing the seat to 3A." And then the server is going to try to process that request however it does, and it will send back a new PK pass object which will then insert into the pass library. So I'm going to go ahead and do it. Now, to tell the truth, I didn't actually write an Oceanic server, so what I'm really doing is waiting three seconds and then returning a prepackaged pass. So let's see if it works.

It worked, thank goodness. OK, so we've got a new pass, and I updated my UI. And I want to show you what that looks like in the code, even though I'm kind of faking it here. So I'm going to switch over to the detail controller. And the interesting method here is fetch pass from server with updated seat. So that was what got called when Hugo pressed 3A. What it does is I made this little fake server communicator class. And what I asked it to do was assign this new seat to the pass that the detail view controller already had. And that will pull out the serial number from the pass so that we can talk to our server about it. And then if it works, it's going to call this completion with the new pass that it got back from the server. So at that point, if we got a new pass and we were able to successfully push that pass into the pass library, then We're gonna update our own UI and report success and so on, whatever you're gonna do in response to that succeeding. Now, I wanna point out this.

So this method is one of the ones that Ken just was talking about. This could return no, and it might return no if we were unable to replace a pass with the new pass that we got. And that would happen if, for example, the pass that we got back wasn't already in the user's pass library. So if for some reason something got messed up, the server gave you back a new pass, but it was for the wrong serial number, then this method will actually fail because you have to be replacing something that's already there in order for this to work. Okay, so with that in place, I wanna switch back to the app and show you one remaining problem. So our UI got updated, right? It says 3A. If we now go back to the table view in the master view controller, there's this bug, which is that the table view is still showing 19C, which was our previous seed. The reason for this is that I haven't made it so that the table view controller is listening for the updates from the past library that tells it that something changed. So no one's bothering to update its UI. So I want to show you how to add that. So back in the master view controller, in the init method, we're going to go ahead and register to add ourselves as an observer for this notification.

So the observer is gonna be ourself, the master view controller, and I'll write a selector handle library change in a minute. The name of the notification is PK pass library did change notification. It's a single notification which covers all changes to the pass library, and the object is the pass library itself.

As Ken mentioned, you do need to pass the instance of the pass library that you expect to be sending you notifications. So in this case, we're passing the one that we own. Okay, so now we can write the method, handleLibraryChange, and what we wanna do here, well, okay, so what we'd really like to do here would be to peer into the notifications user info and find exactly what changed and make a beautiful animation to our table cell by maybe adding a row or deleting one, but that would take too long, so I'm gonna use a really big hammer.

I'm just gonna reload all my passes, and then I'm gonna reload the table just for now, right? So do this better yourself, do as I say, not as I do. Okay, so what we first have to do though is gonna be a little surprising. So I'm gonna paste it in. We have to dispatch async to the main queue before we do anything. And the reason for this is that is a combination of two factors. So the first is that pass libraries, as Ken mentioned, are thread or queue confined, and we made this pass library on the main queue in our init method, so we need to keep talking to it always on the main queue. Now, the other odd fact is that there's no guarantee about what queue or what thread this notification will be delivered on. So we may be on some arbitrary background queue when we get this notification.

And so we have to dispatch async to the main queue, of course we might be on the main queue, so it wouldn't be safe to dispatch sync, in order to safely talk to our pass library. So now that we're there, We can just do exactly the same thing we did before, which is grab these passes out of the pass library and sort them, so I'll just paste that. And then we also want to call table view reload data. And then we'll have our update. So I'm gonna run it again, and we'll go back in here. And because I restarted it, the app, we got the right seat again.

So let's change this maybe to 22D, which is the only other one that works. And, Okay, so it worked here. If I go back to the table view, now you can see that it did update. So it got the notification from the past library and updated its UI in response. So I'm gonna turn it back over to Ken to talk about Conduit apps.

Thank you, Eliza. So that's everything we had to say about companion apps. So the last thing I want to talk about before I give it to Eliza is conduit apps. Again, these are things that deal with arbitrary attachments in the OS. Mail is a really great example. And it just wants to be able to display a little bit of stuff like this, like this little attachment bubble. And then it wants to let the user add the pass to the pass library.

Aren't you glad I gave you enough time to read the slide? So the first thing you're going to have to do in order to do that is recognize the data. If you're doing it with arbitrary attachments, how do you know if it's a pass or not? So we vend a UTI. The UTI can be converted using core services stuff into a MIME type or an extension. And these are they, which can be used to recognize it. Once you've got the data and you think it's probably a pass, then you can try to instantiate it like so. In this case, I'm having the error, the NSErrorReturn, because of course, in this case, the data might be wrong.

It might not actually manage to instate you to pass, and if it does, I just want to point out that the NSErrorReturn is user presentable. It's something that you should feel comfortable showing the user. It's not gonna have something ridiculous like you wrote your JSON dictionary wrong. It will say in the console that you wrote your JSON dictionary wrong, but not to the user. Okay, once you've got that, then you want to be able to display the thing this little bubbly, well, in case of mail, a little bubbly UI, else for maybe a table view or something. And this is going to have to be a little bit different than before because before we really relied on the fact that the person displaying the pass was the guy who wrote the pass because we were using these keys that we knew about. And that's not going to work here because who knows who wrote this pass? We know nothing about it. So we do have a very small number of methods like icon that return data that all passes should have in common. And you can use those to provide some UI so the user can tell what it is that they're clicking on, tapping on, whatever. Can you tell I came from AppKit? Now once you've done that, then you do want to let the user actually add the thing. And for this, there's one class that we hadn't talked about yet, which is this pkadpass's view controller. But it only has one method on it that's not inherited, which is init with pass. So you instantiate the view controller with a pass object, and then you should present it to the user to let them add it. And it should be presented animated, and it should be presented modally like this. If you try to pop it onto a nav stack, it will look stupid. So don't do that. OK, that is most of what we want to talk about. There are sort of two other little extra bits that are interesting. One is this method contains pass on pass library. So you can ask if a pass is already there. Why is this here? Well, think about something like the App Store. The button in the App Store for a particular app says install or open, I think, depending on whether or not you already have it.

So similarly, if the user already has a pass, you might actually want to present a different UI than otherwise. You might also think this is redundant because you could just pull back the list of passes and run through them to see if the pass is already there. But that wouldn't work because a conduit app like this has no entitlements. It's not allowed to see arbitrary passes out there in the world.

So this method is not redundant, and this method doesn't require any entitlements. We figure if you've already got all of the data in your hand, there's not really that much harm in telling you if it's already there. And then the last bit is that we have this method, is pass library available? And that's because the pass library is only available on the iPhone and on the iPod Touch. It's not on the iPad at the moment. So on the iPad, you can still instantiate a PK pass and get that metadata out of it so you can show the user kind of what it is, but there's no passbook app, and so you can't add to the passbook. So PK pass library will return nil if you try to instantiate it, and so will PK pass add view controller.

That is pretty much what I wanted to talk about. This is PassKit. There are only three classes in it, PK pass, PK pass library, and the add passes view controller. And with that, I will turn it over to Eliza to talk about the last component of the ecosystem. which is the server. - Thanks. Really not working?

  • Maybe.
  • Okay.
  • Hi, so the server. So I want to talk a little bit about why you would actually have any server component at all. I mean, you've seen that you need a server in order to create and sign passes, but one of the coolest parts of the whole passbook feature, in my view, is the fact that these passes can be live. They can update themselves in response to changes. So, for example, the user goes and uses their coffee shop card. and they decrement their balance, that pass can be updated automatically to reflect the new balance. Now, of course, this is not something that we can do by ourselves because we have no idea what their new balance is and what's going on in the real world. We're just displaying these things that you gave us. So in order to make this automatic updating thing work, you actually need to implement a web services API so that the device, that passbook running on a device can talk to your server and pull down a new instance of a pass when it changes. So I'm going to talk to you about how that works. And first, how you opt in. So when you make a pass, if you want to enable it for automatic updates, you need to provide two keys. And the keys are web service URL. These are in your pass.json file. That's the URL at which we can contact you. Otherwise, we would have no way to tell you about the fact that this pass exists on this device. It needs to be an HTTPS URL, although you can disable that for testing. So you can check the documentation to see how that works.

You also need to supply an authentication token, which we're going to use when we contact your server in order to prove that we really have access to the pass that we're talking to you about. So we'll always use that, and I'll talk more about how that plays into the API in a little while. All right, so here's an overview of the communication model. So we've got a device and we have your server, and mediating between them is the Apple Push notification service. So the device, because it has access to the web service URL that you provided, can talk directly to the server. So when a pass is installed for the first time and the user opts in to automatic updates, we will register with your server using that web services URL. Now you know about us and you have the ability to contact the device. So if something changes with the pass that was registered or maybe you have several registered, you can send a push notification to the device through the Apple push service. At which point we will know that something has changed and we need to update. Now at this point we do a little dance with your server. So we know that something has changed. We're not sure what. So we go ahead and ask you, which serial numbers did you want to update? And you respond with some serial numbers that have changed. And again, I'll go into a lot more detail about how you figure that out in a little while. And then we're going to ask you to give me the passes for those serial numbers, and you're going to respond with.pkpass data. So that's the overview of the communication.

And one thing to note about this is that this is kind of a departure from how push usually works because usually the recipient of a push notification ultimately is an app that you wrote. So if you've got an app that's enabled for push, when a push notification arrives, your app may be launched, and at that point you would do all of this communication with your server yourself. And Apple just leaves you to roll your own in the usual case because it's your own code running and your own server. But in this case, there's no app necessarily there to be launched. And in fact, push notifications directed at a PK pass will never cause your app to be launched. They'll only cause the passbook app to do some things. So we had to create a web services API that sort of stands in for what you might have rolled your own in the past. So that's kind of how this plays into the passbook ecosystem. So we're going to be talking to your server. We need a language to talk in. And there's a few different elements of communication that we're gonna be passing back and forth. So from the pass.json file, we're gonna get a pass type identifier, a serial number, and the authentication token, and we're gonna send those around. The pass type identifier and the serial number together serve to uniquely identify one instance of a pass, as you have heard in previous parts of our sessions. All right, from the device, we're going to be providing to you a device identifier and a push token. Now, a quick caveat that I've been asked to provide about the device identifier, this is not the device UDID. It has nothing to do with the device UDID. It is a random number, relatively long number, that we have made up in Passbook that we use to communicate with your web service and no other web services. So it's a shared secret between us and your web service. So there's no way that you could track a user. You couldn't even find out that the same user has accounts on more than one web service, has more than one kind of pass because the device will identify itself with a different ID for every web service that it talks to. All right, that's my caveat. Okay, so there's also the push token. The push token is what you use to actually deliver a push notification to the device. All right, there's one other piece of communication, one other element of communication that I'll go into a lot more detail about in a minute, which is an update tag. Your server's gonna provide it, we're gonna store it, and it's a way for us to keep our state in sync. So I'll talk about that in a little while. All right. So let's look in more detail at what happens when we register. We're gonna send you a big bunch of information. So we're gonna send you the pass type identifier and serial number of the pass being registered, the authentication token for the pass, and then we're also gonna give you the device ID and push token for the device that's doing the registering. Alright, so what should you do now that you've got all this stuff? The first thing you should do is make sure that the authentication token matches the pass. If it doesn't match, you should immediately stop talking to us. We are some malicious third party attempting to steal someone's secrets. So make sure that that matches.

Return 401 if it doesn't. Alright, so once you've checked that, then you need to store the rest of this information. So I want to take a little aside and give a kind of oversimplified version of some of the storage that you might need to have on your server in order to participate in our web services API.

You're going to need a couple tables to store some of this information. One of them is going to be a table of devices. So this is where you would store associations between devices, and these are the device IDs that we're sending you, and push tokens. So you're going to store the device ID and push token that we just sent you in this registration request in that table if you don't already have it. Maybe this might be the second or third pass that's been registered for this device, and if you already have it, you don't need to store it again. So you also are going to need to have a registrations table. And that's where you're going to store associations between devices and individual passes that are registered for that device. So in this case, you're going to make a new row here with the device that we just sent you and the pass that we just sent you. And that stores the fact that this particular pass is now registered with this particular device. All right, pretty straightforward. Okay, so let's look at what that looks like in practice. How are we going to actually send you this stuff?

we're going to send a post to your web service URL, slash v1, which is version one, all of our stuff is going to begin with that. And then we have this long URL after that. It's devices and then the device ID, registrations, and then the pass type identifier and the serial number. And that indicates that this device is registering this pass. In the header for that post, we're going to have an authorization field, and the value for that is going to be Apple Pass and then the auth token. So that's what you should then check against the pass, make sure it matches. And the payload for this post is gonna be a JSON dictionary consisting of the push token.

So that's how we're getting all that information across to you. All right, we have a similar unregistration endpoint. We're going to hit this endpoint if the user deletes a pass that was previously registered to your service or if they actually opt out of receiving push notifications, which they might do. And that just looks like the exact same URL except we're sending a delete this time and we're sending the same authorization field. Okay.

So let's go back for a minute to storage because there's another aspect of storage that I haven't mentioned, which is kind of the most important aspect, which is a table that actually contains all of the data that's backing these passes. So in the case of Oceanic, imagine we're writing the Oceanic server, we've got a boarding passes table. Of course, you would have a different table depending on what data you're vending. So the boarding passes table is gonna just contain the information passes. Here's Hugo's flight from Sydney to Perth and then there's maybe some other flights on here and there's one column that I've left blank because I wanted to draw attention to it. It's going to be the most important column for what's coming next and that's a column for storing the last updated date or tag however you want to do it for each of these rows. So each of these rows was created at some time and then has potentially been updated a few times since and you're going to store the maybe a timestamp for when that happened. Now I say maybe a timestamp, this is actually up to you so we consider this to be a tag, it's opaque to us, we're going to send it back and forth but the only really important thing about it, well there's two really important things about it, one is that they're monotonically increasing so you always you know a new one is always greater than previous ones and you should be able to compare two of them and see which one is later so whatever system works best for you to do that is totally fine. Timestamps are a pretty obvious way to implement it, so that's what I'm gonna go with. Alright, so now suppose that Hugo were to go into the app, for example, or maybe onto the Oceanic website and change his seat as he did in the demo that I just gave. So he's changing the seat for this top row, and he changes it to 3A. So the first thing that you're gonna do is update the update tag to a later date. Maybe he did it at 5:00 PM on the 18th. So we now have a new update tag for that row. And now you want to notify all of the devices that this particular pass is registered with that something changed so that they can go pull down the new seat. So looking over to the registrations table, you're going to find that serial number, JA38, in that table. And we find that, OK, it's registered to two different devices, device A and device C. So now we can go to the devices table, find those two devices, and now we've got the associated push tokens, so those are the push tokens to which we're gonna send a push notification. So that's how that process works. Okay, now watch really carefully, because this is my best animation. Alright, sending a push notification.

Ta-da! Thank you. All right. Okay, so you want to send a push notification. What you're going to do is talk to the Apple Push service. The only two pieces of information you give the Apple Push service are the pass type identifier and the push token that you're sending this push to. The pass type identifier is actually built into the cert that you're going to use to send the push notification. So, Apple Push service delivers that to the device. And so, the device receives the pass type identifier. Now, this is a really small amount of information, and you might wonder why you didn't, for example, tell us what changed in the payload of the push. The reason is that push is not a guaranteed delivery mechanism. So push notifications can be coalesced if the device was offline, for example, at the time that you sent it, and then you sent another one after it. When the device comes back online, it's only going to receive the last push notification for your particular identifier. So we didn't want to take the chance that any information about a crucial update would be lost. So when you give us just the past type identifier, we're basically guaranteed to receive one of those, even if you send a lot of them. So we're now going to go and find out what changed. And the effect of this is that we're guaranteed to always get all of the updates and never miss one. So we've got the past type identifier. We need to go figure out what passes we need to re-download from you.

And the way we're going to do it is by asking you this question, what changed? And we're going to send our device identifier, the pass type identifier that you just gave us, and the update tag that we have stored. Now, the update tag represents kind of a snapshot of the state of the passes for your service that are currently on the device. We got it from you in the past, and I'll explain how that worked. So now your job at this point is to figure out which passes have changed since that snapshot was taken. And you're gonna send us back a list of the serial numbers that have changed, it might be more than one, 'cause we might have missed a bunch of updates in a row if we were offline for a while, going under a tunnel. And then you're gonna send us a new update tag to store.

All right, so let's look at a particular example of this to see how you're going to do this calculation. So suppose that in this case it was device A that received the push notification, and the pass type ID is pass.oceanic, and maybe the update tag we had on file was September 12th at 10 a.m. Okay, so you're going to go look at your registrations table with this information in mind, and you're going to find all of the serial numbers that match the device and the pass type. So in this case, it's the first three rows here. And so these are the serial numbers for the passes that are registered to that device. Now if we go over to our boarding passes table and we look at those rows-- and I've highlighted the rows that we found-- now we look at the last update tag that was sent. And we see, OK, so of those rows, which ones have changed since then? And if you look at those, you can see that the only one that changed since the 12th at 10:00 AM is that first row. And we know that because we actually just changed it, right? So this algorithm worked. And so what we're going to want to respond is, this is what changed. So we're going to take the serial number from that row, and we're going to take the update tag from that row, and we're going to send them back. Now, note that there could have been more than one row that changed, and in that case, you would have taken all of the serial numbers, and you would have taken the latest of the update tags. OK. So back to this situation. You're responding with the list of serial numbers, so So you're gonna send the information that we just computed. At that point, we know, okay, we're supposed to go ask for a pass JA38. So that's how that whole little dance works. Alright, let's look at how it works in practice.

How are we actually asking you for this? We're doing a get to what should now be a familiar endpoint. V1 devices, the device ID slash registrations, and then the pass type identifier. We are also gonna pass this update tag that we have in the form of a query on this URL.

So you respond with a list of serial numbers in a JSON dictionary as an array, and then a new last updated tag for us to store. OK, so of course, this whole process requires that we already have a tag stored. And the very first time that we do this, we're not going to have one. So you also have to be prepared to be able to answer this get request with no query attached. And in that case, you basically have no choice but to respond with all of the registered serial numbers. and then you give us a tag for the last update, at which point the whole process gets started. Hopefully this will happen the first time, and there will only be one or two, and so it won't be that much wasted work. All right, so just looking again at this passes updated since last updated business, it may remind you of the standard HTTP if modified since last modified optimization that you use to kind of gate access to a resource on a server. So I want to talk about why, first of all, Why are we not just using the standard HTTP mechanism? So there's actually a pretty important difference between this optimization for preventing downloading extra stuff and the standard if-modified-since one.

So if-modified-since is designed to optimize access to a single monolithic resource on a server. So basically you've got a timestamp, and if I say give me this resource, if-modified-since this timestamp, Either the resource as a whole has changed since then or it hasn't, and so you're gonna either return the whole resource or you're gonna return 304 not modified. But in our case, we care about gating access to individual elements of a collection. We don't want the whole collection if it's changed or nothing if it hasn't. We want the individual elements of the collection that have changed. And so if modified since is actually not appropriate for our use case, which is why we've invented this similar mechanism. That was actually the hard part. The rest of the stuff is pretty easy. So getting the up-to-date pass is really straightforward.

We're going to hit you with the pass we want and the auth token, and you're going to respond with the zipped data. And what that looks like in practice is this is a new looking URL, so it's v1/passes now. Up until now, we've always been talking about registrations for a device. So we've been talking in terms of serial numbers and device IDs. Now we're actually going to be talking in terms of pass data. So we hit v1/passes. We pass you the pass type ID and the serial number. We're going to use the standard authorization Apple Pass auth token in the header. And you're going to respond with the signed zip data for the pass that we asked for if the auth token matches. All right.

Now there's one extra thing here. Since this actually is a single resource, we're actually going to support-- in fact, we're going to require that you implement the if modified since caching, because we don't want you to give us back this data if in fact we have the latest version. And so you're gonna either respond with the zipped pass data or 304 if it hasn't been modified, and then in the response header, of course, you'll give us a new timestamp to store the standard HTTP mechanism. So I said that you have to respond with zipped signed PK pass data. So you might be wondering how to produce that. I want to talk about the algorithm for pass signing. Now, we actually have given you in the session materials associated with this session two different implementations of this algorithm that you can go check out. One of them is in the sign pass tool that you can actually build in Xcode. And the other one is in our server, a reference server implementation, and it's a Ruby implementation of the pass signing algorithm. But this is what the algorithm is. You may have to go off and implement this in some other language. so it's good to understand what you're doing. You're gonna create, so you've got your pass directory with all of your resources and your pass.json file. So in that directory, you're gonna create a JSON dictionary and you're gonna save it at the top level of the directory as manifest.json. So now this is a dictionary. The keys are relative paths in your pass directory. So if you have a background.ping at the top level, then you'll have one key that says background.ping. The values are the SHA1 hash of the data at that path and you're gonna hex encode that data 'cause you can't stick data raw into a JSON. So you, yeah, so you're creating, you're building up this dictionary of all the stuff in your directory. You save it as manifest.json. Then you take the data of that manifest file and you sign it with your cert. So this has to be done on a server because you can't do signing with a cert on a device since it wouldn't be secure. You're gonna save the result as signature and then you're gonna take the contents of that directory, including the manifest and the signature, and you're gonna zip them up. Don't zip up the directory itself, zip up the contents of the directory. And what you get out of that process is a.pkpass object. That's what you send around. Okay, so I'm gonna bring Ken back up to just show you how to set up this reference server to send a push notification and so on. - Thank you, Eliza.

So if you go into this material, you'll see this past server directory. You'll see this readme that I'm not gonna read because we're hardcore, I guess. You should, by the way, the readme pretty much covers every single thing I'm talking about up here, so really it's more like it would steal my thunder. Okay, now in order to be able to use the server, we need to put our past data into the server so we can see something that's ours. And in order to do that, that's going to be in this data directory. And there are two bits of data we have to give it. We have to give it our actual pass instead of this sample pass that's here. And we have to give it a certificate to sign the pass.

Since here, actually, you see this is not a signed zip pass. This is the contents of a pass, so it still has to be done. So in order to do that, we'll take the pass that we built in part one. And we'll just dump that in there. So do be aware that the serial number actually has to match. It has to be sample in this case. So we dump that in there. And then we need to put our certificate for that in here. Now, in the first part of our session, we added our certificate to the keychain. Here it is. It was this pass.com.oceanic.boardingpass. So what we want to do is choose to export this.

as a P12, okay? We saw some people on the forums who are having a little trouble with this, so this is how you do this. In this case, it doesn't matter what you call it, so I'm just gonna call it, well, I'll call it this. And you have to give it a password. I'm gonna have to type the password out plain text in a minute, so it's just gonna have to be 123. Okay, and now we should see that's in there.

Okay. So now I wanna start the server up, okay? So we go to the terminal, we go into the server reference, we go into pass server, and actually I'm sorry, see I didn't read the readme. I'm not gonna start the server. I'm going to first import my pass, set up the server, ask for setup, I believe. Okay, I need to give it my host name, which I'm gonna use the bonjour name of this computer, which is nepheli.local. I'm going to give the pass type identifier out of that pass, which is pass.com.oceanic.boardingpass. And that needs to match. And that has now set up the server. Okay, having set up the server, this is a rack server, so we run it with -- as soon as I check that we're in the right directory -- we run it with rack up and then dash p for port. It's going to be running on port 4567.

Okay, and we'll just start that running. Oh, once my password. Like I said, that was 123. Okay, that's that. So now we're going to go switch over to the iPhone, and we need to get that pass. Now, this is now running a web server on that port. So actually, the very first thing I'm going to do is show you in settings here under developer, there's now a pass kit testing area. And we're going to want to do this last one, this allow HTTP services. This is HTTP as opposed to HTTPS. Because normally for passes, it's required that the web service that you use be secure. But that's kind of inconvenient in testing setups like this. So we provide a way to turn it off, but only on developer devices.

Okay, now we are going to go to Safari, and if we had actually read the readme, we would realize that nefaly.local port 4567 at pass.pkpass, and that's part of the server, should get me a pass. Hey, and it worked. It reached it. Okay. We're halfway there. We still have to be able to get a push.

Okay, so I'm going to add that. Great. Okay, now I'm going to look at it in Passbook because that's kind of fun, I guess. And then I'm going to put that away. I'm going to go back to the Mac because we have to do the part where we actually change the pass on the server and get to see it update on the device. You can see here, by the way, that we have these 200-level messages. This was the iPhone just checked in with the server and said, here's my pass token, here's my device. So that was that registration endpoint that Eliza was talking about. And you can see that stuff go by. So now I'm going to leave the server running in that other terminal and go down into the server reference again, into the pass server. There's that version of the signing implemented in Ruby, by the way, is in pass signing directory. Go down here. And then if I do lib, can't type, lib pass server control dash notify, N for notify, and hit enter. Then it's gonna want my certificate password again, but I won't hit enter because I'm gonna switch back to the phone so that you can see the push come through exactly like it's supposed to, right? Over the terrible networking situation. So I'm gonna hit enter right now, that's all I will do. Nothing up my sleeve, bam.

Okay. And we tap it, and it even circles the change. I think at the keynote, people may have thought that was actually part of the keynote template. That was actually real. We can dismiss that, and then we can delete the pass because that's pretty fun. Okay. And that's all I've got. So, back to Liza.

So just to wrap up, a few words about some of the things we are hoping you're going to do if you implement this Web Services API. So as you may realize, when you send a push notification to the device, the device wakes up. Normally, when the device wakes up because of a push notification, the user is notified via a badge, a alert, or a sound. In this case, when the device wakes up, because we're going to just go do some stuff behind the scenes, the user is not notified, which means that the user does not have the to find your service really annoying and delete you, which means that you could actually do some harm to the user's battery life if you misuse this API. So we're really concerned about that. We have a lot of stuff in place to try to avoid it.

The main rule of the road is please do not send unnecessary push notifications. Each one will result in action taken on the device. Only send pushes when something has really changed, and only send it when something has changed for a pass that is registered for the device. So please respect the unregistration requests that we'll send you when passes are deleted or unregistered.

Also, please verify authentication tokens. That's obviously really important because you're gonna actually give back full pass data when we hit you up asking you for a pass. It's really important to only do that if we have the right authentication token and we've proven that we have a legitimate reason to be asking. Okay, so do implement the passes updated since last updated optimization for the what changed end point so that we don't end up pulling down every pass every time one pass changes. And also please do implement the if modified since last modified optimization for the get pass end point. Now you might think this is overkill because we've already got this other optimization in place, but what you may not realize is that the user can flip your pass over and they can pull to refresh and if they want to they can sit there pulling to refresh over and over and over again, and every time they do that, we are going to go pull down the latest version of the pass, which means that these things are quite large with all the images, so it'd be much better to respond with a whole bunch of 304s than with a whole bunch of data every time. So please do implement that and that optimization. Okay, so now how are we going to enforce these rules? So the first thing to note is that because we're using the Apple Push notification service, The standard APNS feedback mechanism applies. You can go look at the documentation to see how that works. It works just the same for us as it does in general. Basically, if you send a push notification to a push token that APNS doesn't think is registered with your topic, then APNS will give you some feedback saying to stop doing that, so you should listen to that. Clear the device from the database if these messages start bouncing. It could be that that device no longer exists or that we've just completely unregistered all of the passes from your service. We also have some specific passbook feedback about the Web Services API, because we think, for the most part, that any sort of violations of these rules are probably a mistake. We want to help you work out these bugs. So what we've done is we've added an additional endpoint that I didn't describe yet, which is the log endpoint. And we're going to post to that, and we'll give you a human-readable description of the problem. And we may rate limit your updates if there's a lot of abuse. And if we really are finding on a lot of devices that there's actually tons of extra wakings up and a lot of stuff going wrong with one particular web service, we're collecting diagnostic information so we can actually, we can sort of aggregate that information and find that out. We may pull out the really big guns and you may receive an angry phone call. So you really don't want that to happen. So listen to the logs that you get. OK, so that's pretty much it for the server side. And just a summing up slide here, the three main classes in PassKit, or the three only classes in PassKit, are PKPass, PKPass Library, and PKAdpass's ViewController. And there you've got all of the end points for our web services API. So more information. You can talk to Paul Marcos. He's the evangelist for PassKit and PassBook. Check out the documentation. And there was another related session earlier. Thank you very much.