diff --git a/API.md b/API.md index c2099f3..2e4341f 100644 --- a/API.md +++ b/API.md @@ -2,190 +2,300 @@ The flow of execution is really easy (once everything is set up): -* You get your data from somewhere -* You set the needed data in the pass through methods, overrides and data in fields -* You generate the pass stream through `.generate()` method -* Hooray 😄🎉 +- You get your data from somewhere +- You set the needed data in the pass through methods, props and data in fields +- You generate the pass stream or buffer through respectively `.getAsStream()` or `.getAsBuffer()` methods +- Hooray 😄🎉 Some details: -* Properties will be checked against schemas, which are built to reflect Apple's specifications, that can be found on Apple Developer Portal, at [PassKit Package Format Reference](https://apple.co/2MUHsm0). +- Properties will be checked against schemas, which are built to reflect Apple's specifications, that can be found on Apple Developer Portal, at [PassKit Package Format Reference](https://apple.co/2MUHsm0). -* In case of troubleshooting, you can refer to the [Self-help](https://github.com/alexandercerutti/passkit-generator/wiki/Self-help) guide in Wiki or open an issue. +- In case of troubleshooting, you can refer to the [Self-help](https://github.com/alexandercerutti/passkit-generator/wiki/Self-help) guide in Wiki or open an issue. -* Keep this as it is always valid for the reference: +- Keep this as it is always valid for the reference: ```javascript -const { createPass } = require("passkit-generator"); +const { PKPass } = require("passkit-generator"); ``` -___ -### Index: +--- -* [Create a Pass](#pass_class_constructor) - * [Localizing Passes](#localizing_passes) - * [.localize()](#method_localize) - * Setting barcode - * [.barcodes()](#method_barcodes) - * [.barcode()](#method_barcode) - * Setting expiration / voiding the pass - * [.expiration()](#method_expiration) - * [.void()](#method_void) - * Setting relevance - * [.beacons()](#method_beacons) - * [.locations()](#method_locations) - * [.relevantDate()](#method_revdate) - * Setting NFC - * [.nfc()](#method_nfc) - * [Personalization](#personalization) - * Getting the current information - * [.props](#getter_props) - * [Setting Pass Structure Keys (primaryFields, secondaryFields, ...)](#prop_fields) - * [TransitType](#prop_transitType) - * Generating the compiled pass. - * [.generate()](#method_generate) -* [Create an Abstract Models](#abs_class_constructor) - * [.bundle](#getter_abmbundle) - * [.certificates](#getter_abmcertificates) - * [.overrides](#getter_abmoverrides) +## Creating a Pass -

-___ +--- -## Create a Pass -___ - - - -#### constructor() +### constructor() ```typescript -const pass = await createPass({ ... }, Buffer.from([ ... ], { ... })); +const pass = new PKPass({ ... }, { ... }, { ... }); ``` **Returns**: -`Promise` +`PKPass` + +**Description**: + +PKPass extends an internal class `Bundle`, which gives it some props. Only the exposed ones will be listed below. + +**Arguments**: + +| Key | Type | Description | Optional | Default Value | +| -------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- | :------: | :-----------: | +| buffers | object | The initial files buffers to compose a pass. They must be in form `path: Buffer`. Set to empty object if none is available yet. | false | - | +| certificates | object | The certificates object. | false | - | +| certificates.wwdr | string \| Buffer | The content of Apple WWDR certificate. | false | - | +| certificates.signerCert | string \| Buffer | The content of Developer certificate file. | false | - | +| certificates.signerKey | string \| Buffer | The content of developer certificate's key. | false | - | +| certificates.signerKeyPassphrase | string | The passphrase to decrypt the developer signerKey | true | - | +| props | object | Some pass props that will get merged with the ones in pass.json | true | `{ }` | + +

+ +--- + +### Creating a pass from another pass or from a template + +A pass can be created through its constructor (creating from scratch) or from another pass or from a template. A "template" here is a model saved on disk. Both can be created by using the static method `PKPass.from()`. + +```typescript +PKPass.from(source: PKPass | PKPass.Template, props?: Schemas.OverridableProps): Promise; +``` + +Throws if pass source is not valid. + +In both case, additional props can be passed as second parameter through an object. + +Here below you can see few example functions that will be used in the following ways to use `PKPass.from`. + +```typescript +function getAdditionalPropsForThisPassSomehow() { + // get your additional props for THIS pass + + return { + serialNumber: "12356222", + /** moar props ... */ + }; +} + +function getSomeProps() { + /** Set your base props for all your passes **/ + + return { + description: "Pass for some business activity", + webServiceURL: "https://example.com/passkit", + /** moar props ... */ + }; +} + +function getCertificatesSomehow() { + return { + signerCert: "" /** string or Buffer **/, + signerKey: "" /** string or Buffer **/, + wwdr: "" /** string or Buffer **/, + signerKeyPassphrase: "" /** string **/, + }; +} +``` + +### Another Pass + +**Description**: + +Pass another `PKPass` as source of `PKPass.from` to make it clone every property and file in the source object (except manifest and signature). + +This is useful if you want to create a runtime template with your buffers and then use always it as base. + +**Example**: + +```typescript +const passRuntimeTemplate = new PKPass( + getBuffersSomehow(), + getCertificatesSomehow(), + getSomeProps(), +); + +// later... + +const passToBeServed = await PKPass.from( + passRuntimeTemplate, + getAdditionalPropsForThisPassSomehow(), +); +``` + +### A template + +**Description**: + +Use this is you have a saved model on your File System and want to read it and use it as source for your pass. + +**Arguments**: + +The following are the arguments to be passed in the object in first position: + +| Key | Type | Description | Optional | Default Value | +| model | string (path) | The path to your model on File System | `false` | - | +| certificates | object | The object containing certificates data. Refer to PKPass's constructor for all the keys | `false` | - | + +**Example**: + +```typescript +const passFromDisk = await PKPass.from( + { + model: "../../path/to/your/disk/model", + certificates: getCertificatesSomehow(), + }, + getAdditionalPropsForThisPassSomehow(), +); +``` + +
+ +--- + +### Adding files to your pass + +--- + +#### .addBuffer() + +```typescript +pass.addBuffer(filePath: string, buffer: Buffer): void; +``` + +**Description**: + +This method allows later additions of files to your pass. It has no filters about the type of file you may want to add but for: + +- `pass.json` +- `personalization.json` +- `\.lproj/pass.strings` +- `manifest.json` +- `signature` + +In these three cases, `addBuffer` will perform additional checks and might not add the file as is. + +- `pass.json` will be parsed and merged with current props **only if not already availble**. Otherwise it gets ignored. In case of merging, it might overwrite props you set first, through constructor or methods; +- personalization.json will be parsed, validated and then added (only if valid); +- `\.lproj/pass.strings` will be parsed and, if valid, will be merged within the current translations; +- `manifest` will be ignored; +- `signature` will be ignored; + +Throws if pass is frozen due to a previous export. **Arguments**: | Key | Type | Description | Optional | Default Value | -|-----|------|---------------|:-------------:|:-----------:| -| options | Object \| [Abstract Model](#abs_class_constructor) | The options to create the pass. It can also be an instance of an [Abstract Model](#abs_class_constructor). If instance, below `options` keys are not valid (obv.) and both `abstractMissingData` and `additionalBuffers` can be used. `additionalBuffers` usage is **NOT** restricted to Abstract Models. | false | - -| options.model | String \| Path \| Buffer Object | The model path or a Buffer Object with path as key and Buffer as content | false | - -| options.certificates | Object | The certificate object containing the paths to certs files. | false | - -| options.certificates.wwdr | String \| Path | The path to Apple WWDR certificate or its content. | false | - -| options.certificates.signerCert | String \| Path | The path to Developer certificate file or its content. | false | - -| options.certificates.signerKey | Object/String | The object containing developer certificate's key and passphrase. If string, it can be the path to the key file or its content. If object, must have `keyFile` key and might need `passphrase`. | false | - -| options.certificates.signerKey.keyFile | String \| Path | The path to developer certificate key or its content. | false | - -| options.certificates.signerKey.passphrase | String \| Number | The passphrase to use to unlock the key. | false | - -| options.overrides | Object | Dictionary containing all the keys you can override in the pass.json file and does not have a method to get overridden. | true | `{ }` -| additionalBuffers | Buffer Object | Dictionary with path as key and Buffer as a content. Each will represent a file to be added to the final model. These will have priority on model ones | true | - -| abstractMissingData | Object | An object containing missing datas. It will be used only if `options` is an [Abstract Model](#abs_class_constructor). | true | - -| abstractMissingData.certificates | Object | The same as `options.certificates` and its keys. | true | - -| abstractMissingData.overrides | Object | The same as `options.overrides`. | true | - +| filePath | string (path) | The path, local to your pass.json, where the file should be added (if it is a localization file, it will be like `en.lproj/pass.strings`, otherwise just the name, like `personalization.json`) | `false` | - | +| buffer | Buffer | The content of your file | `false` | - | -

- -___ +--- -**Localizing Passes**: -___ +### Localizing Passes -Following Apple Developer Documentation, localization (L10N) is done by creating a `.lproj` folder for each language you want to translate your pass, each named with the relative [ISO-3166-1 alpha-2](https://it.wikipedia.org/wiki/ISO_3166-1_alpha-2) code (e.g. `en.lproj`). +--- -In this library, localization can be done in three ways: **media-only** (images), **translations-only** or both. -The only difference stands in the way the only method below is used and how the model is designed. -If this method is used for translations and the model already contains a `pass.strings` for the specified language, the translations will be appended to that file. +According to Apple Developer Documentation, localization (L10N) is done by creating a `.lproj` folder for each language you want to translate your pass, each named with the relative [ISO-3166-1 alpha-2](https://it.wikipedia.org/wiki/ISO_3166-1_alpha-2) code (e.g. `en.lproj`). + +There are three ways to include a language in this package: + +- By importing a localized buffer or a localized model when creating the instance; +- By adding a file (media or pass.strings) for that specific language through `.addBuffer` method (like `.addBuffer("en.lproj/thumbnail@2x.png", Buffer.from([...]))`); +- By specifying translations for a language through `.localize` method below; > If you are designing your pass for a language only, you can directly replace the placeholders in `pass.json` with translation.
- #### .localize() ```typescript -pass.localize(lang: string, options = {}); +pass.localize(lang: string, translations: { [key: string]: string }): void; ``` **Returns**: -`Object (this)` +`void` **Description**: -You may want to create the folder and add translated media with no translations; else you may want to add only translations without different media or maybe both. +Use this method to add some translations. +Existing translations will be merged with the newly added. -In the first case, create the `.lproj` folder in the model root folder and add the translated medias inside. Then use the method by passing only the first parameters, the code. +Calling `.localize` is the same as calling `.addBuffer` for a `pass.strings` file, except parsing phase. +Pass `null` to translations param to delete **everything** of a language (translations and files). -In the other two cases, you'll need to specify also the second argument (the translations to be added to `pass.strings` file, which will be added later). +Throws if pass is frozen due to a previous export, if the last is not a string or if no translations are provided. **Arguments**: -| Key | Type | Description | Optional | Default Value | -|-----|------|-------------|----------|:-------------:| -| lang | String | The ISO-3166-1 language code | false | - -| options | Object | Translations in format `{ : "TRANSLATED-VALUE"}`. | true | undefined \| { } +| Key | Type | Description | Optional | Default Value | +| ------------ | -------------- | ------------------------------------------------------------------------------------------------------------- | -------- | :-----------: | +| lang | String | The ISO-3166-1 language code | false | - | +| translations | Object \| null | Translations in format `{ : "TRANSLATED-VALUE"}`. Pass `null` to delete everything of a language | false | - | **Example**: -```javascript -pass - .localize("it", { - "EVENT": "Evento", - "LOCATION": "Posizione" - }) - .localize("de", { - "EVENT": "Ereignis", - "LOCATION": "Ort" - }) - .localize("en") +```typescript +pass.localize("it", { + EVENT: "Evento", + LOCATION: "Posizione", +}); + +pass.localize("de", { + EVENT: "Ereignis", + LOCATION: "Ort", +}); ``` -

-___ +--- -**Setting barcodes**: -___ +### Setting barcodes - +--- -#### .barcodes() +#### .setBarcodes() ```typescript -pass.barcodes(first: string | schema.Barcode, ...data: schema.Barcodes[]) : this; +pass.setBarcodes(message: string): void; +pass.setBarcodes(...barcodes: schema.Barcodes[]): void; ``` **Returns**: -`Object (this)` +`void` **Description**: -Setting barcodes can happen in two ways: `controlled` and `uncontrolled` (autogenerated), which mean how many [barcode structures](https://apple.co/2myAbst) you will have in your pass. +Setting barcodes can happen in two ways: `controlled` and `uncontrolled` (autogenerated), which means how many [barcode structures](https://apple.co/2myAbst) you will have in your pass. Passing a `string` to the method, will lead to an `uncontrolled` way: starting from the message (content), all the structures will be generated. Any further parameter will be ignored. -Passing *N* barcode structures (see below), will only validate them and push only the valid ones. +Passing _N_ barcode structures (see below), will only validate them and push only the valid ones. -This method will not take take of setting retro-compatibility, of which responsability is assigned to `.barcode()`. +Setting barcodes _will overwrite_ previously setted barcodes (no matter the source). +Pass `null` to delete all the barcodes. + +Throws if pass is frozen due to a previous export. + +> Please note that, as `barcode` property is deprecated, this is the only method available for setting barcodes. **Arguments**: -| Key | Type | Description | Optional | -|-------|------|-------------|----------| -| first | `String` \| `schema.Barcode` | first value of barcodes | false -| ...data | `schema.Barcode[]` | the other barcode values | true +| Key | Type | Description | Optional | +| ------- | --------------------- | ------------------------ | -------- | +| message | `String` \| `Barcode` | first value of barcodes | false | +| ...rest | `Barcode[]` | the other barcode values | true | **Examples**: ```typescript -pass.barcodes("11424771526"); +pass.setBarcodes("11424771526"); // or -pass.barcodes({ +pass.setBarcodes({ message: "11424771526", format: "PKBarcodeFormatCode128" altText: "11424771526" @@ -200,256 +310,349 @@ pass.barcodes({ }); ``` -**See**: [PassKit Package Format Reference # Barcode Dictionary](https://apple.co/2myAbst) +**See**: [PassKit Package Format Reference # Barcode Dictionary](https://developer.apple.com/documentation/walletpasses/pass/barcodes) +

- -#### .barcode() +--- -```javascript -pass.barcode(chosenFormat: string): this; -``` +### Setting expiration -**Returns**: +--- -`Object (this)` - -**Description**: - -It will let you choose the format to be used in barcode property as backward compatibility. -Also it will work only if `barcodes()` method has already been called or if the current properties already have at least one barcode structure in it and if it matches with the specified one. - -`PKBarcodeFormatCode128` is not supported in barcode. Therefore any attempt to set it, will fail. - -**Arguments**: - -| Key | Type | Description | Optional | Default Value | -|-----|------|-------------|----------|:-------------:| -| format | String | Format to be used. Must be one of these types: `PKBarcodeFormatQR`, `PKBarcodeFormatPDF417`, `PKBarcodeFormatAztec` | false | - - -**Example**: - -```javascript -// Based on the previous (barcodes) example -pass - .barcodes(...) - .barcode("PKBarcodeFormatQR"); - -// This won't set the property since not found. -pass - .barcodes(...) - .barcode("PKBarcodeFormatAztec"); -``` - - -

-___ - -**Setting expiration / void the pass**: -___ - - - -#### .expiration() +#### .setExpirationDate() ```typescript -pass.expiration(date: Date) : this; +pass.setExpirationDate(date: Date): void; ``` **Returns**: -`Object (this)` +`void` **Description**: It sets the date of expiration to the passed argument. -If the parsing fails, the error will be emitted only in debug mode and the property won't be set. -Passing `null` as the parameter, will remove the value. +Pass `null` as the parameter to remove the value from props. +If date parsing fails, an error will be thrown. + +Throws if pass is frozen due to a previous export. **Arguments**: -| Key | Type | Description | Optional | -|-----|------|-------------|----------| -| date | String/date | The date on which the pass will expire | false +| Key | Type | Description | Optional | +| ---- | ----------- | -------------------------------------- | -------- | +| date | String/date | The date on which the pass will expire | false | -
-
+--- - +### Setting relevance -#### .void() +--- -```javascript -pass.void(); -``` - -**Returns**: - -`Object (this)` - -**Description**: - -It sets directly the pass as voided. - -

-___ - -**Setting relevance**: -___ - - - -#### .beacons() +#### .setBeacons() ```typescript -pass.beacons(...data: schema.Beacons[]): this; +pass.setBeacons(...data: schema.Beacons[]): void; ``` **Returns**: -`Object (this)` +`void` **Description**: Sets the beacons information in the passes. -If other beacons structures are available in the structure, they will be overwritten. -Passing `null` as parameter, will remove the content. +Setting beacons _will overwrite_ previously setted beacons. +Pass `null` to delete all the beacons. + +Throws if pass is frozen due to a previous export. **Arguments**: -| Key | Type | Description | Optional | Default Value | -|-----|------|-------------|----------|:-------------:| -| ...data | [schema.Beacons[]](https://apple.co/2XPDoYX) \| `null` | The beacons structures | false | - +| Key | Type | Description | Optional | Default Value | +| ---------- | ------------------------------------------------------------------------------------------ | ---------------------- | -------- | :-----------: | +| ...beacons | [Beacons[]](https://developer.apple.com/documentation/walletpasses/pass/beacons) \| `null` | The beacons structures | false | - | **Example**: ```typescript -pass.beacons({ - "major": 55, - "minor": 0, - "proximityUUID": "59da0f96-3fb5-43aa-9028-2bc796c3d0c5" -}, { - "major": 65, - "minor": 46, - "proximityUUID": "fdcbbf48-a4ae-4ffb-9200-f8a373c5c18e", -}); +pass.setBeacons( + { + major: 55, + minor: 0, + proximityUUID: "59da0f96-3fb5-43aa-9028-2bc796c3d0c5", + }, + { + major: 65, + minor: 46, + proximityUUID: "fdcbbf48-a4ae-4ffb-9200-f8a373c5c18e", + }, +); ``` -
-
+--- - - -#### .locations() +#### .setLocations() ```typescript -pass.locations(...data: schema.Locations[]): this; +pass.setLocations(...data: schema.Locations[]): void; ``` **Returns**: -`Object (this)` +`void` **Description**: Sets the location-relevance information in the passes. -If other location structures are available in the structure, they will be overwritten. -Passing `null` as parameter, will remove its content; +Setting locations _will overwrite_ previously setted locations (no matter the source). +Pass `null` to delete all the locations. + +Throws if pass is frozen due to a previous export. **Arguments**: -| Key | Type | Description | Optional | Default Value | -|-----|------|-------------|----------|:-------------:| -| ...data | [schema.Locations[]](https://apple.co/2LE00VZ) \| `null` | The location structures | false | - +| Key | Type | Description | Optional | Default Value | +| ------------ | ----------------------------------------------------------------------------------------------------- | ----------------------- | -------- | :-----------: | +| ...locations | [schema.Locations[]](https://developer.apple.com/documentation/walletpasses/pass/locations) \| `null` | The location structures | false | - | **Example**: ```typescript -pass.locations({ - "latitude": 66.45725212, - "longitude": 33.010004420 -}, { - "longitude": 4.42634523, - "latitude": 5.344233323352 -}); +pass.setLocations( + { + latitude: 66.45725212, + longitude: 33.01000442, + }, + { + longitude: 4.42634523, + latitude: 5.344233323352, + }, +); ``` -
-
- - - -#### .relevantDate() - -```typescript -pass.relevantDate(date: Date): this; -``` - -**Returns**: - -`Object (this)` - -**Description**: - -Sets the relevant date for the current pass. Passing `null` to the parameter, will remove its content. - -**Arguments**: - -| Key | Type | Description | Optional | Default Value | -|-----|------|-------------|----------|:-------------:| -| date | Date \| `null` | The relevant date | false | - - -

-___ - -**NFC Support**: -___ - - - -#### .nfc() - -```typescript -pass.nfc(data: schema.NFC): this -``` - -**Returns**: - -`Object (this)` - -**Description**: - -It sets NFC info for the current pass. Passing `null` as parameter, will remove its value. - -**Arguments**: - -| Key | Type | Description | Optional | -|-----|------|-------------|----------| -| data | [schema.NFC](https://apple.co/2XrXwMr) \| `null` | NFC structure | false - -**See**: [PassKit Package Format Reference # NFC](https://apple.co/2wTxiaC)

- +#### .setRelevantDate() + +```typescript +pass.setRelevantDate(date: Date): void; +``` + +**Returns**: + +`void` + +**Description**: + +It sets the date of relevance to the passed argument. +Pass `null` as the parameter to remove the value from props. +If date parsing fails, an error will be thrown. + +Throws if pass is frozen due to a previous export. + +**Arguments**: + +| Key | Type | Description | Optional | Default Value | +| ---- | -------------- | ----------------- | -------- | :-----------: | +| date | Date \| `null` | The relevant date | false | - | + +--- + +### Setting NFC Support + +--- + +#### .setNFC() + +```typescript +pass.setNFC(data: schema.NFC): void +``` + +**Returns**: + +`void` + +**Description**: + +It sets NFC info for the current pass. +Pass `null` as parameter to remove its value from props. + +Throws if pass is frozen due to a previous export or if parameter validation fails. + +**Arguments**: + +| Key | Type | Description | Optional | +| ---- | -------------------------------------------------------------------------------- | ------------- | -------- | +| data | [NFC](https://developer.apple.com/documentation/walletpasses/pass/nfc) \| `null` | NFC structure | false | + +--- #### Personalization / Reward Enrollment passes -Personalization (or [Reward Enrollment](https://apple.co/2YkS12N) passes) is supported only if `personalization.json` is available and it's a valid json file (checked against a schema), `personalizationLogo@XX.png` (with 'XX' => x2, x3) is available and NFC is setted. -If these conditions are not met, the personalization gets removed from the output pass. +Personalization (or [Reward Enrollment](https://apple.co/2YkS12N) passes) is supported only if `personalization.json` is available and has it's a valid json file with valid (recognized) props, `personalizationLogo@XX.png` (where 'XX' => x2, x3) is available, and if NFC is setted. ->*Notice*: **I had the possibility to test in no way this feature on any real pass. If you need it and this won't work, feel free to contact me and we will investigate together 😄** +If these conditions are not met, the personalization will get removed from the output pass or not accepted as input file. -

-
+> _Notice_: **I had the possibility to test in no way this feature on any real pass 'cause Apple would never give me an encryptionKey for NFC. Also I don't have an NFC reader. If you need it and this won't work, feel free to contact me and we will investigate together 😄** - +--- -#### [Getter] .props() +### Setting Pass Fields (primaryFields, secondaryFields, headerFields, auxiliaryFields, backFields) + +--- + +Unlike method-set properties or initialization props, to set fields inside the right property, some getters have been created, one per property. Each extends native Arrays, to let you perform all the operations you need on the fields. Fields already available in pass.json, will be automatically loaded in props. + +Please note that they are strictly and directly linked to the pass type property (boardingPass, storeCard, etc...). +This means that **if pass type is not available, accessing them will throw an error**. +Changing the type on runtime will clean all them up. + +All the fields are linked together through a keys pool: each key must be unique among **all** the fields of the whole pass. + +Each getter will throw if pass is frozen due to a previous export, when a new element is attempted to be added to the related array. + +**Examples:** + +```javascript +pass.headerFields.push( + { + key: "header1", + label: "Data", + value: "25 mag", + textAlignment: "PKTextAlignmentCenter", + }, + { + key: "header2", + label: "Volo", + value: "EZY997", + textAlignment: "PKTextAlignmentCenter", + }, +); +pass.primaryFields.pop(); +pass.auxiliaryFields.push(/*...*/); +pass.secondaryFields.push(/*...*/); +pass.backFields.push(/*...*/); +``` + +**See**: [Passkit Package Format Reference # Field Dictionary Keys](https://developer.apple.com/documentation/walletpasses/passfields) + +--- + +#### .transitType (getter + setter) ```typescript -pass.props; +pass.transitType = "PKTransitTypeAir"; +``` + +**Description**: + +Since this property belongs to the "Field Keys" but is not an "array of field dictionaries" like the sibling keys, a setter and a getter got included to select it. + +Allowed values: **PKTransitTypeAir**, **PKTransitTypeBoat**, **PKTransitTypeBus**, **PKTransitTypeGeneric**, **PKTransitTypeTrain**", as described in Passkit Package Format Reference. + +Please note that it is strictly and directly linked to the pass type property (only boardingPass). + +This means that **if pass type is not available or is not a boardingPass, accessing or setting it will throw an error**. +Changing the type on runtime will clean it up. + +Pass exporting will throw an error if a boardingPass is exported without `transitType`. + +Setter throws if pass is frozen due to a previous export, if an invalid pass type is invalid, or if current type is not a `boardingPass`. + +--- + +### Getting the signed Pass + +--- + +Generating the pass is the last step of the process (before enjoying 🎉). +Generating might happen in three ways: getting a Buffer, a Stream or Raw files. + +**All the three ways available, will lock the pass instance to not accept anymore files or props.** + +--- + +#### .getAsBuffer() + +```typescript +pass.getAsBuffer(): Buffer; +``` + +**Description**: + +Creates a buffer of the zipped pass. This is useful when passkit-generator is used in contexts where using Streams is not possible, like cloud functions. + +**Examples**: + +```typescript +const passBuffer = pass.getAsBuffer(); +doSomethingWithPassBuffer(Buffer); +``` + +--- + +#### .getAsStream() + +```typescript +pass.getAsStream(): Stream; +``` + +**Description**: + +Creates a stream for the zipped pass. + +**Examples**: + +```typescript +const passStream = pass.getAsStream(); +doSomethingWithPassStream(stream); +``` + +--- + +#### .getAsRaw() + +```typescript +pass.getAsRaw(): Readonly<{ [path: string]: Buffer }>; +``` + +**Description**: + +Returns a frozen object containing all the files along with their buffers (with compiled signature and manifest). + +The purpose of this stands in how zips are created. In passkit-generator v2.x.x, a different and asyncronous library was being used to create zips. Due to a worse API, it was replaced by do-not-zip, which acts more like a buffers concatenator instead of a compressor. This compromise improved the creation of zips by making zips generation synchronous, through a very-lightweight package, but incremented final archives size. + +For this reason, if the developer is already using a different zip library and is providing quite large files, better result in terms of pass weight might get obtained by manipulating the list of files provided by this method and feeding them to the already-available zip library. + +**Examples**: + +```typescript +import { toBuffer as doNotZip } from "do-not-zip"; + +const passFiles = pass.getAsRaw(); + +const crunchedData = Object.entries(passFiles).map(([path, data]) => ({ + path, + data, +})); + +doSomethingWithCustomZippedPass(doNotZip(chunchedData)); +``` + +--- + +### Getters / setters + +--- + +#### .props (getter only) + +```typescript +const currentProps = pass.props; ``` **Returns**: @@ -458,170 +661,119 @@ An object containing all the current props; **Description**: -This is a getter: a way to access to the current props before generating a pass. In here are available the props set both from pass.json reading and this package methods usage, along with the valid overrides passed to `createPass`. The keys are the same used in pass.json. - -It does not contain fields content (`primaryFields`, `secondaryFields`...) and `transitType`, which are still accessible through their own props. +This is a way to access to the current props. +Querying this getter, will return a nested-clone of the props. +Props are sorted like you are navigating in pass.json; **Example**: ```typescript const currentLocations = pass.props["locations"]; -pass.locations({ - "latitude": 66.45725212, - "longitude": 33.010004420 -}, { - "longitude": 4.42634523, - "latitude": 5.344233323352 -}, -...currentLocations); +pass.locations( + { + latitude: 66.45725212, + longitude: 33.01000442, + }, + { + longitude: 4.42634523, + latitude: 5.344233323352, + }, + ...currentLocations, +); ``` -

+--- - -___ - -**Setting Pass Structure Keys (primaryFields, secondaryFields, ...)**: -___ - -Unlike method-set properties or overrides, to set fields inside _areas_ (*primaryFields*, *secondaryFields*, *auxiliaryFields*, *headerFields*, *backFields*), this library make available a dedicated interface that extends native Array, to let you perform all the operations you need on the fields. Fields already available in pass.json, will be automatically loaded in the library. Therefore, reading one of the _areas_, will also show those that were loaded. - -**Examples:** - -```javascript -pass.headerFields.push({ - key: "header1", - label: "Data", - value: "25 mag", - textAlignment: "PKTextAlignmentCenter" -}, { - key: "header2", - label: "Volo", - value: "EZY997", - textAlignment: "PKTextAlignmentCenter" -}); - -pass.primaryFields.pop(); -``` - -**See**: [Passkit Package Format Reference # Field Dictionary Keys](https://apple.co/2NuDrUM) - -
- - - -#### .transitType +#### .certificates (setter only) ```typescript -pass.transitType = "PKTransitTypeAir"; +pass.certificates = { ... }; ``` **Description**: -Since this property belongs to the "Structure Keys" but is not an "array of field dictionaries" like the other keys on the same level, a setter (and obv. also a getter) got included in this package to check it against a schema (which is like, "is a string and contains one of the following values: **PKTransitTypeAir**, **PKTransitTypeBoat**, **PKTransitTypeBus**, **PKTransitTypeGeneric**, **PKTransitTypeTrain**", as described in Passkit Package Format Reference). +Provides a later way to specify or change the used certificates. -

-___ +It accepts an object which has the same type signature as the one requested in `constructor` and `PKPass.from`. -**Generating the compiled Pass** -___ +Throws if pass is frozen due to a previous export or if schema validation on provided object fails. -Generating the pass is the last step of the process (before enjoying 🎉). -Since the file format is `.pkpass` (which is a `.zip` file with changed MIME), this method will return a `Stream`, which can be used to be piped to a webserver response or to be written in the server. -As you can see in [examples folder](/examples), to send a .pkpass file, a basic webserver uses MIME-type `application/vnd.apple.pkpass`. +--- -
- - - -#### .generate() +#### .type (setter + getter) ```typescript -pass.generate(): Stream; +pass.type = "coupon"; +pass.type !== undefined; ``` -**Returns**: `Stream` - **Description**: -Creates a pass zip as Stream. +Provides a later way to specify a pass type. The availability of this setter, allows developers to create a PKPass without the need to have or create a pass.json first and provide it as file, or provide a generic one. -**Examples**: +Please note that using this setter, will reset all your fields (primaryFields, secondaryFields, ...) and transitType, as they are strictly linked to the type. + +Setter throws if pass is frozen due to a previous export or if and invalid type is provided. + +**Example**: ```typescript -const passStream = pass.generate(); -doSomethingWithPassStream(stream); +const pass = new PKPass({ ... }, { ... }, { ... }); + +// Assuming a pass.json with a type inside has been added first and it has 5 valid primaryFields. +console.log(pass.primaryFields.length); // 5 + +pass.type = "storeCard"; + +console.log(pass.primaryFields.length); // 0 +console.log("Pass type is", pass.type); // "Pass type is storeCard" ``` -

+--- -___ - -## Create an Abstract Model -___ - - - -#### constructor() +#### .mimeType (getter only) - Inherited from Bundle ```typescript -const abstractModel = await createAbstractModel({ ... }); +const passMIME = pass.mimeType; ``` -**Returns**: - -`Promise` - **Description**: -The purpose of this class, is to create a model to be kept in memory during the application runtime. It contains a processed version of the passed `model` (already read and splitted) and, if passed, a processed version of the `certificates`, along with the chosen overrides. -Since `certificates` and `overrides` might differ time to time or not available at the moment of the abstract model creation, an additional attribute has been added to [`createPass`](#pass_class_constructor) function. It is an object that accepts `overrides` and raw `certificates` +This getter allows to abstract the kind of mimeType is being served when generating a PKPass archive or a PKPasses archive (see below). This property is automatically set when a PKPass is created (`application/vnd.apple.pkpass`) or when `PKPass.pack` is invoked (`application/vnd.apple.pkpasses`). -**Arguments**: +--- -It accepts only one argument: an `options` object, which is identical to the first parameter of [`createPass`](#pass_class_constructor). You can refer to that method to compile it correctly. +## Bundling multiple passes together -
-
+Starting from iOS 15, [developers can serve multiple passes through Safari](https://developer.apple.com/videos/play/wwdc2021/10092/?time=99) by zipping multiple passes together in a single Bundle with extension `.pkpasses` and serve it with mimeType `application/vnd.apple.pkpasses`. - - -#### [Getter] .bundle() +#### PKPass.pack() ```typescript -abstractModel.bundle +PKPass.pack(...args: PKPass[]): Bundle; ``` -**Returns**: +**Description** -An object containing processed model. +This method accepts a series of PKPass instances and returns a new Bundle instance (the one from which PKPass inherits some properties), and allows you to serve all the passes together. -
- +**Please note that, internally, this method invokes `.getAsBuffer()` on each pass instance**. This method assumes the passes provided won't be edited again and are ready to get distributed. PKPass instances will be, therefore, locked. -#### [Getter] .certificates() +Throws if not all the args are instance of PKPass. + +**Example**: ```typescript -abstractModel.certificates +const [passInstance1, passInstance2, passInstance3] = getThreePassesSomehow(); + +const pkpassesBundle = PKPass.pack(passInstance1, passInstance2, passInstance3); + +serveBufferWithMimeTypeSomehow( + pkPassesBundle.getAsBuffer(), + pkPassesBundle.mimeType /** -> application/vnd.apple.pkpasses **/, +); ``` -**Returns**: - -An object containing processed certificates. - -
- - -#### [Getter] .overrides() - -```typescript -abstractModel.overrides -``` - -**Returns**: - -An object containing passed overrides. - -___ +--- Thanks for using this library. ❤️ Every contribution is welcome.