-
-
Notifications
You must be signed in to change notification settings - Fork 117
Migrating from v2 to v3
I'm always happy when I can release a new major version of passkit-generator because I know that every time I can learn new things, bring my contribution to open-source community and bring value to those who are trusting passkit-generator to generate their passes. But this value, we must acknowledge, might come with pain. So, after 2 years, here we go again with another migration guide that will help you to transform your code and understand the biggest changes. For any doubts, feel free to open a topic in Discussions.
After several thoughts, I saw that a pass instance should have always been synchronous. Using the factory pattern wasn't suitable for this project. For this reason, we went back to a constructor-based approach.
Also, Pass
class has been renamed to PKPass
, to conform better to Apple's naming.
Overrides, in terms of naming, also have been renamed to a more generic props.
- await createPass({
- model: { <buffers map> },
- certificates: { <certificates },
- overrides: { <overrides> }
- });
+ new PKPass(
+ { <buffers map> },
+ { <certificates> },
+ { <props> }
+ );
Of course, redesigning such an interface would have left us without the possibility to directly read a model from the File System. So, I decided to seize the opportunity and create a "shared" static method PKPass.from
to import a pass from an external source.
These "external sources" at the time of writing, are two:
- Model from the file system (template)
- Another PKPass to be cloned and used as a template
- await createPass({
- model: "path/to/your/model/fs.pass",
- certificates: { ... },
- overrides: { ... }
- });
+ await PKPass.from({
+ model: "path/to/your/model/fs.pass",
+ certificates: { ... }
+ }, { <additional props> });
The introduction of PKPass.from
allowed us also to ditch AbstractModels in favor of just one class.
- const pass = await createPass(aPreviouslyCreatedAbstractModel, { <additional buffers> }, { <additional missing data> });
+ const pass = await PKPass.from(
+ new PKPass(
+ { <base buffers> },
+ { <base certificates> },
+ { <base props> }
+ ),
+ { <additional props> }
+ );
When using an AbstractModel, all the files must have been already known at creation time or added later through createPass
's parameter additionalBuffers
. This is no longer the case, because, as long as the pass is not locked (we'll talk about this later), new files can always be added through a new instance method .addBuffer
, which accepts a file path (e.g. pass.json
or en.lproj/[email protected]
) and file's buffer.
pass.addBuffer("[email protected]", Buffer.alloc(0));
The creation of addBuffer
allowed the library to unlock a very great power: creating a pass without the need of having a pass.json
ready. You can add it whenever you are ready... or not add it at all! In fact, if pass.json is added through this method, internally it will be saved as an empty buffer, and all its props will be validated and merged with current props (accessible through getter .props
). Therefore, if you set all the properties you need without adding a pass.json
first, one will be generated when the pass will get locked. Isn't this great? Who cares about pass.json
? Let libraries handle such aspects! 🚀🚀🚀
Another note (last one in this section, I promise) is the following: what happens if you create a folder model without a pass.json
and import it with PKPass.from
? Well, for that there's (as you can see above) a second optional parameter, that will let you specify all the additional props you will need at that time... at least those should be known when a pass is created from a different source.
For Typescript users (and lovers, like me), now the package exports a few types outside the main class. You are invited to look at the .d.ts files for this.
Previously, we were accepting certificates paths that passkit-generator was going to read or their contents. Since many passkit-generator usages might imply the setup of an APNS server, certificates might be already available on runtime. For Cloud Functions, they are stored in the secrets manager for the used service. For this reason, certificates can now be only the content itself or a Buffer of the content. If you were reading them from the file system, you'll need to read them manually first. Otherwise, you'll be able to read them whenever you want and set them later through the .certificates
setter.
Also, the keys structure changed a bit: signerKey
accepts now only a string
(content) or a Buffer
. A new optional key signerKeyPassphrase
got added.
-{
- wwdr: string;
- signerCert: string;
- signerKey: string | {
- keyFile: string;
- passphrase?: string;
- }
-}
+{
+ wwdr: string | Buffer;
+ signerCert: string | Buffer;
+ signerKey: string | Buffer;
+ signerKeyPassphrase?: string;
+}
Some methods have been renamed to improve their role. Also, from now on, methods chaining is not allowed, due to methods not returning the current pass instance anymore.
.nfc() -> .setNFC();
.barcodes() -> .setBarcodes();
.barcode() -> Removed (see note below);
.locations() -> .setLocations();
.beacons() -> .setBeacons();
.expirationDate() -> .setExpirationDate()
.relevantDate() -> .setRelevantDate()
.void() -> Removed (see note below)
.generate() -> Removed and replaced (see note below)
Since barcode
property is deprecated, the method .barcode
, which allowed to select a fallback value among those set through .barcodes()
, has been removed to encourage moving to the usage of barcodes.
Since voided
property wasn't a property that required any sort of validation (in fact, it is a boolean), the method .void
has been removed.
Now, to create a voided pass, you can provide voided
property when creating a pass, in the props.
.generate()
was one of the most important methods. Yeah, well, it was the core one for exporting. But it had a limit: since the beginning, it always allowed export only a stream. Streams might not be suitable for some cases in which passkit-generator might get integrated (e.g. Cloud Functions).
For this reason, .generate()
was ditched in favor of three separated methods: .getAsStream()
, .getAsBuffer()
and a third, weirder one, .getAsRaw()
.
To accomplish a one-to-one transformation, we can apply the following diff:
- pass.generate()
+ pass.getAsStream()
A note must be expressed about how zip files are now created. Previously, in v2.0, we were using yazl
as a dependency to create a zip file. This library locked us to use only streams and "hacks" to convert streams to buffers.
From now on, we'll be using do-not-zip
. This change comes with some compromises:
-
do-not-zip
is buffer-based. From a buffer, we can easily create a stream; -
do-not-zip
has synchronous and direct API (create a zip when you need, without the need of creating first a zip container); -
do-not-zip
acts as a "buffers concatenator". This means that it does not apply compression on files, but just inlines them;
I acknowledged that this lack of compression, might "hurt" someone. So, here comes in .getAsRaw()
: it will allow you to get the final list of files in a "raw format", so an object with signature { [filePath: string]: Buffer }
. This way, if you are already using a different library for compressing things, you can still get a full-working pass structure and zip it on your own.
This is a new concept that must be thought to when using passkit-generator: when the pass gets exported through one of the three methods above, it gets locked. So no new files and props will be accepted any longer. This means that you should export it only when you are ready for distributing it. You can always use PKPass.from
to clone it... but I think you won't be in need to use it for this specific purpose. 😄
One of the biggest changes in passkit-generator is related to localization. In v2.0.8 and previous, languages could have been selected (among those available) and added without the need of setting translations through this method. This process has now been expanded to allow languages to be added when a localized file is found among buffers, in models, passed to .addBuffer
or if a translation is added.
For this reason, setting translations is now required... if your intention is not to delete everything by passing null.
With the new version of passkit-generator, debug
dependency has been removed for the introduction of more explicit messages. In fact, from now on (as said previously), methods won't return the current pass instance anymore. This happened due to the approach change: methods will, from now on, throw if something is wrong.
Methods that accept an array of data, like .setBarcodes
, .setLocations
, .setBeacons
and some operations on fields (primaryFields, secondaryFields, etc.) will log a warning in the console for all the data that cannot pass successfully the validation and that, therefore, will be omitted.