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 has known transcription errors. We are working on an improved version.

Good afternoon. Welcome to Part 2 of our Introduction to Passbook. I'm Ken Ferrry, and Eliza Block and I will be speaking to you this afternoon. So this is Part 2. In Part 1, what did we do? We talked about this file format that we defined 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.

So you can write these two. 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.

So we're going to talk about a 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 going to mean is it's going to mean modifying things. Okay. So 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 going to 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.

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 PKPass 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 want to 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 process 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 PK pass library always represents the same underlying data, but it's sort of a differing interfaces to it, like a managed object context. And they're thread confined, 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.

[Transcript missing]

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, you know? I mean, 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. 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. Okay? 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 going to 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 PKPass 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 a 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 see it as a new pass, would not think of it as a new pass. They would think of it as the pass 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 Passbook API. Okay.

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 going to create a pass library just by allocating one, an init.

And then I'm going to grab the passes out of it by asking it for its passes. Now, one thing to note about this is that these passes are going to 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 going to 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 going to show a list of your upcoming flights, so we might want to sort them by date.

Now, if you're going to do this, you're going to want to do it in a way that's going to show you the list of your upcoming flights. So you might want to display them to the user in some useful order. 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 want to 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 going to create some sort descriptors.

Whoa. Okay, so I'm going to create these sort descriptors. We're going to sort by the relevant date property ascending. And then instead of just setting this... To the raw result of calling passes, we're going to 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. So I'm going to 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 UI table view cell subclass, which is... Its job is simply to make these cells lay themselves out all pretty. So I'm going to 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... 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 going to 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 that I just showed you on the cell. And to get all this information out of the pass, we're going to 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. And we're going to, 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 of this information up here by just the same method.

I'm grabbing that same information out of the PK pass object. So suppose he chooses to change his seat, and maybe he selects 3A. So now when he is going to tap the 3A button, what's going to happen is this app is going to send a request to the Oceanic server and say, I would like to update this pass.

I'm going to start with this serial number, and I want to 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.

[Transcript missing]

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 pass library and updated its UI in response. So I'm going to turn it back over to Ken to talk about Conduit apps. Ken Ferrity Thank you, Eliza. Right, 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 sort of deal with arbitrary attachments in the OS. Mail is a really great example. And it just wants to be able to sort of 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.

[Transcript missing]

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. 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 "add_pass". And then 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 going to 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. OK.

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, you know, depending on what data you're vending.

So the boarding passes table is going to just contain the information for one of these 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 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. 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 going to go with. All right. 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 going to do is update the -- the update tag to a later date. Maybe he did it, you know, at 5:00 p.m. 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, okay, it's registered to two different devices, device A and device B. 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 going to send a push notification. All right. So that's how that process works. Okay. Now watch really carefully because this is my best animation. All right. Sending a push notification.

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 pass 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 pass type identifier. We need to go figure out what passes we need to redownload 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 what changes you've made. So the first thing you're going to do is to figure out what passes have changed since that snapshot was taken.

And you're going to send us back a list of the serial numbers that have changed. It might be more than one because 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 going to 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, okay, 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 a.m. 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. So in that case, you would have taken all of the serial numbers, and you would have taken the latest of the update tags.

Okay. So back to this situation, you're responding with the list of serial numbers, so you're going to 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. All right, 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. So we're going to pass the pass type identifier endpoint, V1 devices, the device ID/registrations, and then the pass type identifier. We are also going to 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. Okay, 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 going to either return the whole resource or you're going to 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 slash 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 and device IDs.

Now we're actually going to be talking in terms of past data. So we hit V1 slash 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 going to either respond with the with the zip 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 data. So I'm going 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. So I'm going to go ahead and show you the algorithm. You're going to create -- so you've got your pass directory with all of your resources and your pass.json file.

So in that directory, you're going to create a JSON dictionary. And you're going to 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, you know, a background.ping at the top level, then you'll have one key that says background.ping.

The values -- so you can see that the values are relative paths in your pass directory. So the values are the SHA1 hash of the data at that path. And you're going to hex encode that data because you can't stick data raw into a JSON. So you -- yes, 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 going to save the result as signature.json. And then you're going to take the contents of that directory, including the manifest and the signature, and you're going to 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 .pk pass object. That's what you send around. Okay. So I'm going to 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 pass server directory. You'll see this readme that I'm not going to read because we're hardcore, I guess. 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 pass 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, okay? 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 1, okay? And we'll just dump that in there. Though do be aware that the... The serial number actually has to match, okay? It has to be sample in this case. Okay, so we dump that in there. And then we need to... Oh, 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. Okay, here it is.

It was this pass.com.oceanic.boardingpass. So what we want to do is choose to export this... As a P12, 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 going to call it, well, I'll call it this. And you have to give it a password.

I'm going to have to type the password out plain text in a minute, so it's just going to have to be 123. Okay, and now we should see that's in there. Okay. So now I want to 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 going to start the server. I'm going to first import my pass, set up the server. S for setup, I believe. Okay. I need to give it my host name, which I'm going to use the bonjour name of this computer, which is nefali.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 directory, we run it with rack up and then dash p for port.

for port. It's going to be running on port 4567. And we'll just start that running. Oh, wants 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 passed. 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 [email protected], 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 the 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 going to want my certificate password again. But I won't hit enter because I'm going to 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 going to 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 Eliza.

[Transcript missing]