mirror of
https://github.com/marcogll/passkit-generator.git
synced 2026-03-15 14:25:17 +00:00
Added first version of serverless-based implementation example
This commit is contained in:
8
examples/serverless/.gitignore
vendored
Normal file
8
examples/serverless/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# package directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
.build
|
||||
!*.js
|
||||
52
examples/serverless/README.md
Normal file
52
examples/serverless/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Serverless Examples
|
||||
|
||||
This is a sample project for showing passkit-generator being used on cloud functions.
|
||||
|
||||
Typescript compilation happens automatically through `serverless-plugin-typescript` when serverless is started.
|
||||
|
||||
## Configuration
|
||||
|
||||
These examples are basically made for being executed locally. In the file `config.json`, some constants can be customized.
|
||||
|
||||
```json
|
||||
/** Passkit signerKey passphrase **/
|
||||
"SIGNER_KEY_PASSPHRASE": "123456",
|
||||
|
||||
/** Bucket name where a pass is saved before being served. */
|
||||
"PASSES_S3_TEMP_BUCKET": "pkge-test",
|
||||
|
||||
/** S3 Access key ID - "S3RVER" is default for `serverless-s3-local`. If this example is run offline, "S3RVER" will always be used. */
|
||||
"ACCESS_KEY_ID": "S3RVER",
|
||||
|
||||
/** S3 Secret - "S3RVER" is default for `serverless-s3-local` */
|
||||
"SECRET_ACCESS_KEY": "S3RVER",
|
||||
|
||||
/** Bucket that contains pass models **/
|
||||
"MODELS_S3_BUCKET": "pkge-mdbk"
|
||||
```
|
||||
|
||||
## Run examples
|
||||
|
||||
Install the dependencies and run serverless. Installing the dependencies will link the latest version of passkit-generator in the parent workspace.
|
||||
|
||||
```sh
|
||||
$ npm install;
|
||||
$ npm run run-offline;
|
||||
```
|
||||
|
||||
This will start `serverless offline` with an additional host option (mainly for WSL environment).
|
||||
Serverless will start, by default, on `0.0.0.0:8080`.
|
||||
|
||||
### Available examples
|
||||
|
||||
All the examples, except fields ones, require a `modelName` to be passed in queryString. The name will be checked against local FS or S3 bucket if example is deployed.
|
||||
Pass in queryString all the pass props you want to apply them to the final result.
|
||||
|
||||
| Example name | Endpoint name | Additional notes |
|
||||
| -------------- | ----------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| localize | `/localize` | - |
|
||||
| fields | `/fields` | - |
|
||||
| expirationDate | `/expirationDate` | - |
|
||||
| scratch | `/scratch` | - |
|
||||
| barcodes | `/barcodes` | Using `?alt=true` query parameter, will lead to barcode string message usage instead of selected ones |
|
||||
| pkpasses | `/pkpasses` | - |
|
||||
7
examples/serverless/config.json
Normal file
7
examples/serverless/config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"SIGNER_KEY_PASSPHRASE": "123456",
|
||||
"PASSES_S3_TEMP_BUCKET": "pkge-test",
|
||||
"ACCESS_KEY_ID": "S3RVER",
|
||||
"SECRET_ACCESS_KEY": "S3RVER",
|
||||
"MODELS_S3_BUCKET": "pkge-mdbk"
|
||||
}
|
||||
21368
examples/serverless/package-lock.json
generated
Normal file
21368
examples/serverless/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
examples/serverless/package.json
Normal file
27
examples/serverless/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "examples-aws-lambda",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "Passkit-generator examples for running in AWS Lambda",
|
||||
"author": "Alexander P. Cerutti <cerutti.alexander@gmail.com>",
|
||||
"license": "ISC",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"preinstall": "npm run clear:deps && npm unlink --no-save passkit-generator",
|
||||
"postinstall": "npm --prefix ../.. run build && npm --prefix ../.. link && npm link passkit-generator",
|
||||
"clear:deps": "rm -rf node_modules",
|
||||
"run-offline": "npx serverless offline --host 0.0.0.0; :'specifying host due to WSL limits'"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.1018.0",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/aws-lambda": "^8.10.84",
|
||||
"@types/express": "4.17.8",
|
||||
"serverless-offline": "^8.2.0",
|
||||
"serverless-plugin-typescript": "^2.1.0",
|
||||
"serverless-s3-local": "^0.6.20",
|
||||
"typescript": "^4.4.4"
|
||||
}
|
||||
}
|
||||
56
examples/serverless/serverless.yml
Normal file
56
examples/serverless/serverless.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
service: passkit-generator-test-lambda
|
||||
frameworkVersion: "2"
|
||||
|
||||
plugins:
|
||||
- serverless-offline
|
||||
- serverless-plugin-typescript
|
||||
- serverless-s3-local
|
||||
|
||||
provider:
|
||||
name: aws
|
||||
runtime: nodejs14.x
|
||||
lambdaHashingVersion: "20201221"
|
||||
|
||||
functions:
|
||||
fields:
|
||||
handler: src/index.fields
|
||||
events:
|
||||
- httpApi:
|
||||
path: /fields
|
||||
method: get
|
||||
expiration:
|
||||
handler: src/index.expirationDate
|
||||
events:
|
||||
- httpApi:
|
||||
path: /expirationDate
|
||||
method: get
|
||||
localize:
|
||||
handler: src/index.localize
|
||||
events:
|
||||
- httpApi:
|
||||
path: /localize
|
||||
method: get
|
||||
barcodes:
|
||||
handler: src/index.barcodes
|
||||
events:
|
||||
- httpApi:
|
||||
path: /barcodes
|
||||
method: get
|
||||
scratch:
|
||||
handler: src/index.scratch
|
||||
events:
|
||||
- httpApi:
|
||||
path: /scratch
|
||||
method: get
|
||||
pkpasses:
|
||||
handler: src/index.pkpasses
|
||||
events:
|
||||
- httpApi:
|
||||
path: /pkpasses
|
||||
method: get
|
||||
|
||||
custom:
|
||||
serverless-offline:
|
||||
httpPort: 8080
|
||||
s3:
|
||||
directory: /tmp
|
||||
43
examples/serverless/src/functions/barcodes.ts
Normal file
43
examples/serverless/src/functions/barcodes.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ALBEvent, ALBResult } from "aws-lambda";
|
||||
import { PKPass } from "../../../../lib";
|
||||
import { finish400WithoutModelName, createPassGenerator } from "../shared";
|
||||
|
||||
/**
|
||||
* Lambda for barcodes example
|
||||
*/
|
||||
|
||||
export async function barcodes(event: ALBEvent) {
|
||||
finish400WithoutModelName(event);
|
||||
|
||||
const { modelName, alt, ...passOptions } = event.queryStringParameters;
|
||||
|
||||
const passGenerator = createPassGenerator(modelName, passOptions);
|
||||
|
||||
const pass = (await passGenerator.next()).value as PKPass;
|
||||
|
||||
if (alt === "true") {
|
||||
// After this, pass.props["barcodes"] will have support for all the formats
|
||||
pass.setBarcodes("Thank you for using this package <3");
|
||||
|
||||
console.log(
|
||||
"Barcodes support is autocompleted:",
|
||||
pass.props["barcodes"],
|
||||
);
|
||||
} else {
|
||||
// After this, pass.props["barcodes"] will have support for just two of three
|
||||
// of the passed format (the valid ones);
|
||||
|
||||
pass.setBarcodes(
|
||||
{
|
||||
message: "Thank you for using this package <3",
|
||||
format: "PKBarcodeFormatCode128",
|
||||
},
|
||||
{
|
||||
message: "Thank you for using this package <3",
|
||||
format: "PKBarcodeFormatPDF417",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (await passGenerator.next(pass as PKPass)).value as ALBResult;
|
||||
}
|
||||
31
examples/serverless/src/functions/expirationDate.ts
Normal file
31
examples/serverless/src/functions/expirationDate.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { ALBEvent, ALBResult } from "aws-lambda";
|
||||
import { Context } from "vm";
|
||||
import { PKPass } from "passkit-generator";
|
||||
import { finish400WithoutModelName, createPassGenerator } from "../shared";
|
||||
|
||||
/**
|
||||
* Lambda for expirationDate example
|
||||
*/
|
||||
|
||||
export async function expirationDate(event: ALBEvent, context: Context) {
|
||||
finish400WithoutModelName(event);
|
||||
|
||||
const { modelName, ...passOptions } = event.queryStringParameters;
|
||||
|
||||
const passGenerator = createPassGenerator(modelName, passOptions);
|
||||
|
||||
const pass = (await passGenerator.next()).value as PKPass;
|
||||
|
||||
// 2 minutes later...
|
||||
const d = new Date();
|
||||
d.setMinutes(d.getMinutes() + 2);
|
||||
|
||||
// setting the expiration
|
||||
(pass as PKPass).setExpirationDate(d);
|
||||
console.log(
|
||||
"EXPIRATION DATE EXPECTED:",
|
||||
(pass as PKPass).props["expirationDate"],
|
||||
);
|
||||
|
||||
return (await passGenerator.next(pass as PKPass)).value as ALBResult;
|
||||
}
|
||||
160
examples/serverless/src/functions/fields.ts
Normal file
160
examples/serverless/src/functions/fields.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { ALBEvent, ALBResult } from "aws-lambda";
|
||||
import { PKPass } from "passkit-generator";
|
||||
import { createPassGenerator } from "../shared";
|
||||
|
||||
/**
|
||||
* Lambda for fields example
|
||||
*/
|
||||
|
||||
export async function fields(event: ALBEvent) {
|
||||
const { modelName, ...passOptions } = event.queryStringParameters;
|
||||
|
||||
const passGenerator = createPassGenerator("exampleBooking", passOptions);
|
||||
|
||||
const pass = (await passGenerator.next()).value as PKPass;
|
||||
|
||||
pass.transitType = "PKTransitTypeAir";
|
||||
|
||||
pass.headerFields.push(
|
||||
{
|
||||
key: "header1",
|
||||
label: "Data",
|
||||
value: "25 mag",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
{
|
||||
key: "header2",
|
||||
label: "Volo",
|
||||
value: "EZY997",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
);
|
||||
|
||||
pass.primaryFields.push(
|
||||
{
|
||||
key: "IATA-source",
|
||||
value: "NAP",
|
||||
label: "Napoli",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "IATA-destination",
|
||||
value: "VCE",
|
||||
label: "Venezia Marco Polo",
|
||||
textAlignment: "PKTextAlignmentRight",
|
||||
},
|
||||
);
|
||||
|
||||
pass.secondaryFields.push(
|
||||
{
|
||||
key: "secondary1",
|
||||
label: "Imbarco chiuso",
|
||||
value: "18:40",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
{
|
||||
key: "sec2",
|
||||
label: "Partenze",
|
||||
value: "19:10",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
{
|
||||
key: "sec3",
|
||||
label: "SB",
|
||||
value: "Sì",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
{
|
||||
key: "sec4",
|
||||
label: "Imbarco",
|
||||
value: "Anteriore",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
);
|
||||
|
||||
pass.auxiliaryFields.push(
|
||||
{
|
||||
key: "aux1",
|
||||
label: "Passeggero",
|
||||
value: "MR. WHO KNOWS",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "aux2",
|
||||
label: "Posto",
|
||||
value: "1A*",
|
||||
textAlignment: "PKTextAlignmentCenter",
|
||||
},
|
||||
);
|
||||
|
||||
pass.backFields.push(
|
||||
{
|
||||
key: "document number",
|
||||
label: "Numero documento:",
|
||||
value: "- -",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "You're checked in, what next",
|
||||
label: "Hai effettuato il check-in, Quali sono le prospettive",
|
||||
value: "",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "Check In",
|
||||
label: "1. check-in✓",
|
||||
value: "",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "checkIn",
|
||||
label: "",
|
||||
value: "Le uscite d'imbarco chiudono 30 minuti prima della partenza, quindi sii puntuale. In questo aeroporto puoi utilizzare la corsia Fast Track ai varchi di sicurezza.",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "2. Bags",
|
||||
label: "2. Bagaglio",
|
||||
value: "",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "Require special assistance",
|
||||
label: "Assistenza speciale",
|
||||
value: "Se hai richiesto assistenza speciale, presentati a un membro del personale nell'area di Consegna bagagli almeno 90 minuti prima del volo.",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "3. Departures",
|
||||
label: "3. Partenze",
|
||||
value: "",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "photoId",
|
||||
label: "Un documento d’identità corredato di fotografia",
|
||||
value: "è obbligatorio su TUTTI i voli. Per un viaggio internazionale è necessario un passaporto valido o, dove consentita, una carta d’identità.",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "yourSeat",
|
||||
label: "Il tuo posto:",
|
||||
value: "verifica il tuo numero di posto nella parte superiore. Durante l’imbarco utilizza le scale anteriori e posteriori: per le file 1-10 imbarcati dalla parte anteriore; per le file 11-31 imbarcati dalla parte posteriore. Colloca le borse di dimensioni ridotte sotto il sedile davanti a te.",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "Pack safely",
|
||||
label: "Bagaglio sicuro",
|
||||
value: "Fai clic http://easyjet.com/it/articoli-pericolosi per maggiori informazioni sulle merci pericolose oppure visita il sito CAA http://www.caa.co.uk/default.aspx?catid=2200",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
{
|
||||
key: "Thank you for travelling easyJet",
|
||||
label: "Grazie per aver viaggiato con easyJet",
|
||||
value: "",
|
||||
textAlignment: "PKTextAlignmentLeft",
|
||||
},
|
||||
);
|
||||
|
||||
return (await passGenerator.next(pass as PKPass)).value as ALBResult;
|
||||
}
|
||||
6
examples/serverless/src/functions/index.ts
Normal file
6
examples/serverless/src/functions/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from "./barcodes";
|
||||
export * from "./expirationDate";
|
||||
export * from "./fields";
|
||||
export * from "./localize";
|
||||
export * from "./pkpasses";
|
||||
export * from "./scratch";
|
||||
45
examples/serverless/src/functions/localize.ts
Normal file
45
examples/serverless/src/functions/localize.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { finish400WithoutModelName, createPassGenerator } from "../shared";
|
||||
import type { ALBEvent, ALBResult } from "aws-lambda";
|
||||
import type { PKPass } from "passkit-generator";
|
||||
import { localizationSymbol } from "passkit-generator/lib/PKPass";
|
||||
|
||||
/**
|
||||
* Lambda for localize example
|
||||
*/
|
||||
|
||||
export async function localize(event: ALBEvent) {
|
||||
finish400WithoutModelName(event);
|
||||
|
||||
const { modelName, ...passOptions } = event.queryStringParameters;
|
||||
|
||||
const passGenerator = createPassGenerator(modelName, passOptions);
|
||||
|
||||
const pass = (await passGenerator.next()).value as PKPass;
|
||||
|
||||
/**
|
||||
* Italian and German already has an .lproj which gets included
|
||||
* but it doesn't have translations
|
||||
*/
|
||||
pass.localize("it", {
|
||||
EVENT: "Evento",
|
||||
LOCATION: "Dove",
|
||||
});
|
||||
|
||||
pass.localize("de", {
|
||||
EVENT: "Ereignis",
|
||||
LOCATION: "Ort",
|
||||
});
|
||||
|
||||
// ...while Norwegian doesn't, so it gets created
|
||||
pass.localize("nn", {
|
||||
EVENT: "Begivenhet",
|
||||
LOCATION: "plassering",
|
||||
});
|
||||
|
||||
console.log(
|
||||
"Added languages",
|
||||
Object.keys(pass[localizationSymbol]).join(", "),
|
||||
);
|
||||
|
||||
return (await passGenerator.next(pass as PKPass)).value as ALBResult;
|
||||
}
|
||||
115
examples/serverless/src/functions/pkpasses.ts
Normal file
115
examples/serverless/src/functions/pkpasses.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { ALBEvent } from "aws-lambda";
|
||||
import { PKPass } from "passkit-generator";
|
||||
import {
|
||||
getCertificates,
|
||||
getSpecificFileInModel,
|
||||
getS3Instance,
|
||||
getRandomColorPart,
|
||||
finish400WithoutModelName,
|
||||
} from "../shared";
|
||||
import config from "../../config.json";
|
||||
|
||||
/**
|
||||
* Lambda for PkPasses example
|
||||
*/
|
||||
|
||||
export async function pkpasses(event: ALBEvent) {
|
||||
finish400WithoutModelName(event);
|
||||
|
||||
const [certificates, iconFromModel, s3] = await Promise.all([
|
||||
getCertificates(),
|
||||
getSpecificFileInModel(
|
||||
"icon.png",
|
||||
event.queryStringParameters.modelName,
|
||||
),
|
||||
getS3Instance(),
|
||||
]);
|
||||
|
||||
function createPass() {
|
||||
const pass = new PKPass({}, certificates, {
|
||||
description: "Example Apple Wallet Pass",
|
||||
passTypeIdentifier: "pass.com.passkitgenerator",
|
||||
serialNumber: "nmyuxofgna",
|
||||
organizationName: `Test Organization ${Math.random()}`,
|
||||
teamIdentifier: "F53WB8AE67",
|
||||
foregroundColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
labelColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
backgroundColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
});
|
||||
|
||||
pass.type = "boardingPass";
|
||||
pass.transitType = "PKTransitTypeAir";
|
||||
|
||||
pass.headerFields.push(
|
||||
{
|
||||
key: "header-field-test-1",
|
||||
value: "Unknown",
|
||||
},
|
||||
{
|
||||
key: "header-field-test-2",
|
||||
value: "unknown",
|
||||
},
|
||||
);
|
||||
|
||||
pass.primaryFields.push(
|
||||
{
|
||||
key: "primaryField-1",
|
||||
value: "NAP",
|
||||
},
|
||||
{
|
||||
key: "primaryField-2",
|
||||
value: "VCE",
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Required by Apple. If one is not available, a
|
||||
* pass might be openable on a Mac but not on a
|
||||
* specific iPhone model
|
||||
*/
|
||||
|
||||
pass.addBuffer("icon.png", iconFromModel);
|
||||
pass.addBuffer("icon@2x.png", iconFromModel);
|
||||
pass.addBuffer("icon@3x.png", iconFromModel);
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
const passes = await Promise.all([
|
||||
Promise.resolve(createPass()),
|
||||
Promise.resolve(createPass()),
|
||||
Promise.resolve(createPass()),
|
||||
Promise.resolve(createPass()),
|
||||
]);
|
||||
|
||||
const pkpasses = PKPass.pack(...passes);
|
||||
|
||||
const buffer = pkpasses.getAsBuffer();
|
||||
const passName = `GeneratedPass-${Math.random()}.pkpass`;
|
||||
|
||||
const { Location } = await s3
|
||||
.upload({
|
||||
Bucket: config.PASSES_S3_TEMP_BUCKET,
|
||||
Key: passName,
|
||||
ContentType: pkpasses.mimeType,
|
||||
/** Marking it as expiring in 5 minutes, because passes should not be stored */
|
||||
Expires: new Date(Date.now() + 5 * 60 * 1000),
|
||||
Body: buffer,
|
||||
})
|
||||
.promise();
|
||||
|
||||
/**
|
||||
* Please note that redirection to `Location` does not work
|
||||
* if you open this code in another device if this is run
|
||||
* offline. This because `Location` is on localhost. Didn't
|
||||
* find yet a way to solve this.
|
||||
*/
|
||||
|
||||
return {
|
||||
statusCode: 302,
|
||||
headers: {
|
||||
"Content-Type": "application/vnd.apple.pkpass",
|
||||
Location: Location,
|
||||
},
|
||||
};
|
||||
}
|
||||
71
examples/serverless/src/functions/scratch.ts
Normal file
71
examples/serverless/src/functions/scratch.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { ALBEvent, ALBResult } from "aws-lambda";
|
||||
import { PKPass } from "passkit-generator";
|
||||
import {
|
||||
createPassGenerator,
|
||||
getRandomColorPart,
|
||||
getSpecificFileInModel,
|
||||
} from "../shared";
|
||||
|
||||
/**
|
||||
* Lambda for scratch example
|
||||
*/
|
||||
|
||||
export async function scratch(event: ALBEvent) {
|
||||
const passGenerator = createPassGenerator(undefined, {
|
||||
description: "Example Apple Wallet Pass",
|
||||
passTypeIdentifier: "pass.com.passkitgenerator",
|
||||
serialNumber: "nmyuxofgna",
|
||||
organizationName: `Test Organization ${Math.random()}`,
|
||||
teamIdentifier: "F53WB8AE67",
|
||||
foregroundColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
labelColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
backgroundColor: `rgb(${getRandomColorPart()}, ${getRandomColorPart()}, ${getRandomColorPart()})`,
|
||||
});
|
||||
|
||||
const [{ value }, iconFromModel] = await Promise.all([
|
||||
passGenerator.next(),
|
||||
getSpecificFileInModel(
|
||||
"icon.png",
|
||||
event.queryStringParameters.modelName,
|
||||
),
|
||||
]);
|
||||
|
||||
const pass = value as PKPass;
|
||||
|
||||
pass.type = "boardingPass";
|
||||
pass.transitType = "PKTransitTypeAir";
|
||||
|
||||
pass.headerFields.push(
|
||||
{
|
||||
key: "header-field-test-1",
|
||||
value: "Unknown",
|
||||
},
|
||||
{
|
||||
key: "header-field-test-2",
|
||||
value: "unknown",
|
||||
},
|
||||
);
|
||||
|
||||
pass.primaryFields.push(
|
||||
{
|
||||
key: "primaryField-1",
|
||||
value: "NAP",
|
||||
},
|
||||
{
|
||||
key: "primaryField-2",
|
||||
value: "VCE",
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Required by Apple. If one is not available, a
|
||||
* pass might be openable on a Mac but not on a
|
||||
* specific iPhone model
|
||||
*/
|
||||
|
||||
pass.addBuffer("icon.png", iconFromModel);
|
||||
pass.addBuffer("icon@2x.png", iconFromModel);
|
||||
pass.addBuffer("icon@3x.png", iconFromModel);
|
||||
|
||||
return (await passGenerator.next(pass as PKPass)).value as ALBResult;
|
||||
}
|
||||
1
examples/serverless/src/index.ts
Normal file
1
examples/serverless/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./functions";
|
||||
176
examples/serverless/src/shared.ts
Normal file
176
examples/serverless/src/shared.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { ALBEvent, ALBResult } from "aws-lambda";
|
||||
import AWS from "aws-sdk";
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import config from "../config.json";
|
||||
import { PKPass } from "passkit-generator";
|
||||
|
||||
const S3: { instance: AWS.S3 } = { instance: undefined };
|
||||
|
||||
export function finish400WithoutModelName(event: ALBEvent) {
|
||||
if (event.queryStringParameters?.modelName) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({
|
||||
message: "modelName is missing in query params",
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function getRandomColorPart() {
|
||||
return Math.floor(Math.random() * 255);
|
||||
}
|
||||
|
||||
export async function getModel(
|
||||
modelName: string,
|
||||
): Promise<string | { [key: string]: Buffer }> {
|
||||
if (process.env.IS_OFFLINE === "true") {
|
||||
console.log("model offline retrieving");
|
||||
return path.resolve(__dirname, `../../models/${modelName}`);
|
||||
}
|
||||
|
||||
const s3 = await getS3Instance();
|
||||
|
||||
const result = await s3
|
||||
.getObject({ Bucket: config.MODELS_S3_BUCKET, Key: modelName })
|
||||
.promise();
|
||||
|
||||
return {}; // @TODO, like when it is run on s3
|
||||
}
|
||||
|
||||
export async function getCertificates(): Promise<{
|
||||
signerCert: string | Buffer;
|
||||
signerKey: string | Buffer;
|
||||
wwdr: string | Buffer;
|
||||
signerKeyPassphrase?: string;
|
||||
}> {
|
||||
let signerCert: string;
|
||||
let signerKey: string;
|
||||
let wwdr: string;
|
||||
let signerKeyPassphrase: string;
|
||||
|
||||
if (process.env.IS_OFFLINE) {
|
||||
console.log("Fetching Certificates locally");
|
||||
|
||||
[signerCert, signerKey, wwdr, signerKeyPassphrase] = await Promise.all([
|
||||
fs.readFile(
|
||||
path.resolve(__dirname, "../../../certificates/signerCert.pem"),
|
||||
"utf-8",
|
||||
),
|
||||
fs.readFile(
|
||||
path.resolve(__dirname, "../../../certificates/signerKey.pem"),
|
||||
"utf-8",
|
||||
),
|
||||
fs.readFile(
|
||||
path.resolve(__dirname, "../../../certificates/WWDR.pem"),
|
||||
"utf-8",
|
||||
),
|
||||
Promise.resolve(config.SIGNER_KEY_PASSPHRASE),
|
||||
]);
|
||||
} else {
|
||||
// @TODO
|
||||
}
|
||||
|
||||
return {
|
||||
signerCert,
|
||||
signerKey,
|
||||
wwdr,
|
||||
signerKeyPassphrase,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getS3Instance() {
|
||||
if (S3.instance) {
|
||||
return S3.instance;
|
||||
}
|
||||
|
||||
const instance = new AWS.S3({
|
||||
s3ForcePathStyle: true,
|
||||
accessKeyId: process.env.IS_OFFLINE ? "S3RVER" : config.ACCESS_KEY_ID, // This specific key is required when working offline
|
||||
secretAccessKey: config.SECRET_ACCESS_KEY,
|
||||
endpoint: new AWS.Endpoint("http://localhost:4569"),
|
||||
});
|
||||
|
||||
S3.instance = instance;
|
||||
|
||||
try {
|
||||
/** Trying to create a new bucket. If it fails, it already exists (at least in theory) */
|
||||
await instance
|
||||
.createBucket({ Bucket: config.PASSES_S3_TEMP_BUCKET })
|
||||
.promise();
|
||||
} catch (err) {}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
export async function getSpecificFileInModel(
|
||||
fileName: string,
|
||||
modelName: string,
|
||||
) {
|
||||
const model = await getModel(modelName);
|
||||
|
||||
if (typeof model === "string") {
|
||||
return fs.readFile(path.resolve(`${model}.pass`, fileName));
|
||||
}
|
||||
|
||||
return model[fileName];
|
||||
}
|
||||
|
||||
export async function* createPassGenerator(
|
||||
modelName?: string,
|
||||
passOptions?: Object,
|
||||
): AsyncGenerator<PKPass, ALBResult, PKPass> {
|
||||
const [template, certificates, s3] = await Promise.all([
|
||||
modelName ? getModel(modelName) : Promise.resolve({}),
|
||||
getCertificates(),
|
||||
getS3Instance(),
|
||||
]);
|
||||
|
||||
let pass: PKPass;
|
||||
|
||||
if (template instanceof Object) {
|
||||
pass = new PKPass(template, certificates, passOptions);
|
||||
} else if (typeof template === "string") {
|
||||
pass = await PKPass.from(
|
||||
{
|
||||
model: template,
|
||||
certificates,
|
||||
},
|
||||
passOptions,
|
||||
);
|
||||
}
|
||||
|
||||
pass = yield pass;
|
||||
|
||||
const buffer = pass.getAsBuffer();
|
||||
const passName = `GeneratedPass-${Math.random()}.pkpass`;
|
||||
|
||||
const { Location } = await s3
|
||||
.upload({
|
||||
Bucket: config.PASSES_S3_TEMP_BUCKET,
|
||||
Key: passName,
|
||||
ContentType: pass.mimeType,
|
||||
/** Marking it as expiring in 5 minutes, because passes should not be stored */
|
||||
Expires: new Date(Date.now() + 5 * 60 * 1000),
|
||||
Body: buffer,
|
||||
})
|
||||
.promise();
|
||||
|
||||
/**
|
||||
* Please note that redirection to `Location` does not work
|
||||
* if you open this code in another device if this is run
|
||||
* offline. This because `Location` is on localhost. Didn't
|
||||
* find yet a way to solve this.
|
||||
*/
|
||||
|
||||
return {
|
||||
statusCode: 302,
|
||||
headers: {
|
||||
"Content-Type": "application/vnd.apple.pkpass",
|
||||
Location: Location,
|
||||
},
|
||||
};
|
||||
}
|
||||
11
examples/serverless/tsconfig.json
Normal file
11
examples/serverless/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "CommonJS",
|
||||
"outDir": "build",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user