mirror of
https://github.com/marcogll/passkit-generator.git
synced 2026-03-15 18:25:24 +00:00
Refactored all the schemas;
Splitted them in a folder and renamed them to have the same name of typescript type; Added more SemanticKeys; Removed Schema resolution via string;
This commit is contained in:
@@ -1,10 +1,4 @@
|
|||||||
import {
|
import * as Schemas from "./schemas";
|
||||||
Certificates,
|
|
||||||
FinalCertificates,
|
|
||||||
PartitionedBundle,
|
|
||||||
OverridesSupportedOptions,
|
|
||||||
FactoryOptions,
|
|
||||||
} from "./schema";
|
|
||||||
import { getModelContents, readCertificatesFromOptions } from "./parser";
|
import { getModelContents, readCertificatesFromOptions } from "./parser";
|
||||||
import formatMessage from "./messages";
|
import formatMessage from "./messages";
|
||||||
|
|
||||||
@@ -13,14 +7,14 @@ const abmModel = Symbol("model");
|
|||||||
const abmOverrides = Symbol("overrides");
|
const abmOverrides = Symbol("overrides");
|
||||||
|
|
||||||
export interface AbstractFactoryOptions
|
export interface AbstractFactoryOptions
|
||||||
extends Omit<FactoryOptions, "certificates"> {
|
extends Omit<Schemas.FactoryOptions, "certificates"> {
|
||||||
certificates?: Certificates;
|
certificates?: Schemas.Certificates;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AbstractModelOptions {
|
interface AbstractModelOptions {
|
||||||
bundle: PartitionedBundle;
|
bundle: Schemas.PartitionedBundle;
|
||||||
certificates: FinalCertificates;
|
certificates: Schemas.CertificatesSchema;
|
||||||
overrides?: OverridesSupportedOptions;
|
overrides?: Schemas.OverridesSupportedOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,9 +45,9 @@ export async function createAbstractModel(options: AbstractFactoryOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AbstractModel {
|
export class AbstractModel {
|
||||||
private [abmCertificates]: FinalCertificates;
|
private [abmCertificates]: Schemas.CertificatesSchema;
|
||||||
private [abmModel]: PartitionedBundle;
|
private [abmModel]: Schemas.PartitionedBundle;
|
||||||
private [abmOverrides]: OverridesSupportedOptions;
|
private [abmOverrides]: Schemas.OverridesSupportedOptions;
|
||||||
|
|
||||||
constructor(options: AbstractModelOptions) {
|
constructor(options: AbstractModelOptions) {
|
||||||
this[abmModel] = options.bundle;
|
this[abmModel] = options.bundle;
|
||||||
@@ -61,15 +55,15 @@ export class AbstractModel {
|
|||||||
this[abmOverrides] = options.overrides;
|
this[abmOverrides] = options.overrides;
|
||||||
}
|
}
|
||||||
|
|
||||||
get certificates(): FinalCertificates {
|
get certificates(): Schemas.CertificatesSchema {
|
||||||
return this[abmCertificates];
|
return this[abmCertificates];
|
||||||
}
|
}
|
||||||
|
|
||||||
get bundle(): PartitionedBundle {
|
get bundle(): Schemas.PartitionedBundle {
|
||||||
return this[abmModel];
|
return this[abmModel];
|
||||||
}
|
}
|
||||||
|
|
||||||
get overrides(): OverridesSupportedOptions {
|
get overrides(): Schemas.OverridesSupportedOptions {
|
||||||
return this[abmOverrides];
|
return this[abmOverrides];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import { Pass } from "./pass";
|
import { Pass } from "./pass";
|
||||||
import {
|
import * as Schemas from "./schemas";
|
||||||
FactoryOptions,
|
|
||||||
BundleUnit,
|
|
||||||
FinalCertificates,
|
|
||||||
PartitionedBundle,
|
|
||||||
OverridesSupportedOptions,
|
|
||||||
} from "./schema";
|
|
||||||
import formatMessage from "./messages";
|
import formatMessage from "./messages";
|
||||||
import { getModelContents, readCertificatesFromOptions } from "./parser";
|
import { getModelContents, readCertificatesFromOptions } from "./parser";
|
||||||
import { splitBufferBundle } from "./utils";
|
import { splitBufferBundle } from "./utils";
|
||||||
@@ -20,8 +14,8 @@ import { AbstractModel, AbstractFactoryOptions } from "./abstract";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export async function createPass(
|
export async function createPass(
|
||||||
options: FactoryOptions | InstanceType<typeof AbstractModel>,
|
options: Schemas.FactoryOptions | InstanceType<typeof AbstractModel>,
|
||||||
additionalBuffers?: BundleUnit,
|
additionalBuffers?: Schemas.BundleUnit,
|
||||||
abstractMissingData?: Omit<AbstractFactoryOptions, "model">,
|
abstractMissingData?: Omit<AbstractFactoryOptions, "model">,
|
||||||
): Promise<Pass> {
|
): Promise<Pass> {
|
||||||
if (
|
if (
|
||||||
@@ -35,8 +29,8 @@ export async function createPass(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (options instanceof AbstractModel) {
|
if (options instanceof AbstractModel) {
|
||||||
let certificates: FinalCertificates;
|
let certificates: Schemas.CertificatesSchema;
|
||||||
let overrides: OverridesSupportedOptions = {
|
let overrides: Schemas.OverridesSupportedOptions = {
|
||||||
...(options.overrides || {}),
|
...(options.overrides || {}),
|
||||||
...((abstractMissingData && abstractMissingData.overrides) ||
|
...((abstractMissingData && abstractMissingData.overrides) ||
|
||||||
{}),
|
{}),
|
||||||
@@ -85,10 +79,10 @@ export async function createPass(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createPassInstance(
|
function createPassInstance(
|
||||||
model: PartitionedBundle,
|
model: Schemas.PartitionedBundle,
|
||||||
certificates: FinalCertificates,
|
certificates: Schemas.CertificatesSchema,
|
||||||
overrides: OverridesSupportedOptions,
|
overrides: Schemas.OverridesSupportedOptions,
|
||||||
additionalBuffers?: BundleUnit,
|
additionalBuffers?: Schemas.BundleUnit,
|
||||||
) {
|
) {
|
||||||
if (additionalBuffers) {
|
if (additionalBuffers) {
|
||||||
const [additionalL10n, additionalBundle] = splitBufferBundle(
|
const [additionalL10n, additionalBundle] = splitBufferBundle(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as schema from "./schema";
|
import * as Schemas from "./schemas";
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
|
|
||||||
const fieldsDebug = debug("passkit:fields");
|
const fieldsDebug = debug("passkit:fields");
|
||||||
@@ -23,12 +23,12 @@ export default class FieldsArray extends Array {
|
|||||||
* also uniqueKeys set.
|
* also uniqueKeys set.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
push(...fieldsData: schema.Field[]): number {
|
push(...fieldsData: Schemas.Field[]): number {
|
||||||
const validFields = fieldsData.reduce(
|
const validFields = fieldsData.reduce(
|
||||||
(acc: schema.Field[], current: schema.Field) => {
|
(acc: Schemas.Field[], current: Schemas.Field) => {
|
||||||
if (
|
if (
|
||||||
!(typeof current === "object") ||
|
!(typeof current === "object") ||
|
||||||
!schema.isValid(current, "field")
|
!Schemas.isValid(current, Schemas.Field)
|
||||||
) {
|
) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
@@ -55,8 +55,8 @@ export default class FieldsArray extends Array {
|
|||||||
* also uniqueKeys set
|
* also uniqueKeys set
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pop(): schema.Field {
|
pop(): Schemas.Field {
|
||||||
const element: schema.Field = Array.prototype.pop.call(this);
|
const element: Schemas.Field = Array.prototype.pop.call(this);
|
||||||
this[poolSymbol].delete(element.key);
|
this[poolSymbol].delete(element.key);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
@@ -69,8 +69,8 @@ export default class FieldsArray extends Array {
|
|||||||
splice(
|
splice(
|
||||||
start: number,
|
start: number,
|
||||||
deleteCount: number,
|
deleteCount: number,
|
||||||
...items: schema.Field[]
|
...items: Schemas.Field[]
|
||||||
): schema.Field[] {
|
): Schemas.Field[] {
|
||||||
const removeList = this.slice(start, deleteCount + start);
|
const removeList = this.slice(start, deleteCount + start);
|
||||||
removeList.forEach((item) => this[poolSymbol].delete(item.key));
|
removeList.forEach((item) => this[poolSymbol].delete(item.key));
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import forge from "node-forge";
|
import forge from "node-forge";
|
||||||
import formatMessage from "./messages";
|
import formatMessage from "./messages";
|
||||||
import {
|
import * as Schemas from "./schemas";
|
||||||
FactoryOptions,
|
|
||||||
PartitionedBundle,
|
|
||||||
BundleUnit,
|
|
||||||
Certificates,
|
|
||||||
FinalCertificates,
|
|
||||||
isValid,
|
|
||||||
} from "./schema";
|
|
||||||
import {
|
import {
|
||||||
removeHidden,
|
removeHidden,
|
||||||
splitBufferBundle,
|
splitBufferBundle,
|
||||||
@@ -28,8 +21,8 @@ const { readdir: readDir, readFile } = fs.promises;
|
|||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function getModelContents(model: FactoryOptions["model"]) {
|
export async function getModelContents(model: Schemas.FactoryOptions["model"]) {
|
||||||
let modelContents: PartitionedBundle;
|
let modelContents: Schemas.PartitionedBundle;
|
||||||
|
|
||||||
if (typeof model === "string") {
|
if (typeof model === "string") {
|
||||||
modelContents = await getModelFolderContents(model);
|
modelContents = await getModelFolderContents(model);
|
||||||
@@ -77,9 +70,9 @@ export async function getModelContents(model: FactoryOptions["model"]) {
|
|||||||
const parsedPersonalization = JSON.parse(
|
const parsedPersonalization = JSON.parse(
|
||||||
modelContents.bundle[personalizationJsonFile].toString("utf8"),
|
modelContents.bundle[personalizationJsonFile].toString("utf8"),
|
||||||
);
|
);
|
||||||
const isPersonalizationValid = isValid(
|
const isPersonalizationValid = Schemas.isValid(
|
||||||
parsedPersonalization,
|
parsedPersonalization,
|
||||||
"personalizationDict",
|
Schemas.Personalization,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isPersonalizationValid) {
|
if (!isPersonalizationValid) {
|
||||||
@@ -105,7 +98,7 @@ export async function getModelContents(model: FactoryOptions["model"]) {
|
|||||||
|
|
||||||
export async function getModelFolderContents(
|
export async function getModelFolderContents(
|
||||||
model: string,
|
model: string,
|
||||||
): Promise<PartitionedBundle> {
|
): Promise<Schemas.PartitionedBundle> {
|
||||||
try {
|
try {
|
||||||
const modelPath = `${model}${(!path.extname(model) && ".pass") || ""}`;
|
const modelPath = `${model}${(!path.extname(model) && ".pass") || ""}`;
|
||||||
const modelFilesList = await readDir(modelPath);
|
const modelFilesList = await readDir(modelPath);
|
||||||
@@ -142,7 +135,7 @@ export async function getModelFolderContents(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const bundle: BundleUnit = Object.assign(
|
const bundle: Schemas.BundleUnit = Object.assign(
|
||||||
{},
|
{},
|
||||||
...rawBundleFiles.map((fileName, index) => ({
|
...rawBundleFiles.map((fileName, index) => ({
|
||||||
[fileName]: rawBundleBuffers[index],
|
[fileName]: rawBundleBuffers[index],
|
||||||
@@ -151,7 +144,7 @@ export async function getModelFolderContents(
|
|||||||
|
|
||||||
// Reading concurrently localizations folder
|
// Reading concurrently localizations folder
|
||||||
// and their files and their buffers
|
// and their files and their buffers
|
||||||
const L10N_FilesListByFolder: Array<BundleUnit> = await Promise.all(
|
const L10N_FilesListByFolder: Array<Schemas.BundleUnit> = await Promise.all(
|
||||||
l10nFolders.map(async (folderPath) => {
|
l10nFolders.map(async (folderPath) => {
|
||||||
// Reading current folder
|
// Reading current folder
|
||||||
const currentLangPath = path.join(modelPath, folderPath);
|
const currentLangPath = path.join(modelPath, folderPath);
|
||||||
@@ -172,23 +165,27 @@ export async function getModelFolderContents(
|
|||||||
// Assigning each file path to its buffer
|
// Assigning each file path to its buffer
|
||||||
// and discarding the empty ones
|
// and discarding the empty ones
|
||||||
|
|
||||||
return validFiles.reduce<BundleUnit>((acc, file, index) => {
|
return validFiles.reduce<Schemas.BundleUnit>(
|
||||||
if (!buffers[index].length) {
|
(acc, file, index) => {
|
||||||
return acc;
|
if (!buffers[index].length) {
|
||||||
}
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
const fileComponents = file.split(path.sep);
|
const fileComponents = file.split(path.sep);
|
||||||
const fileName = fileComponents[fileComponents.length - 1];
|
const fileName =
|
||||||
|
fileComponents[fileComponents.length - 1];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
[fileName]: buffers[index],
|
[fileName]: buffers[index],
|
||||||
};
|
};
|
||||||
}, {});
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const l10nBundle: PartitionedBundle["l10nBundle"] = Object.assign(
|
const l10nBundle: Schemas.PartitionedBundle["l10nBundle"] = Object.assign(
|
||||||
{},
|
{},
|
||||||
...L10N_FilesListByFolder.map((folder, index) => ({
|
...L10N_FilesListByFolder.map((folder, index) => ({
|
||||||
[l10nFolders[index]]: folder,
|
[l10nFolders[index]]: folder,
|
||||||
@@ -226,20 +223,21 @@ export async function getModelFolderContents(
|
|||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function getModelBufferContents(model: BundleUnit): PartitionedBundle {
|
export function getModelBufferContents(
|
||||||
const rawBundle = removeHidden(Object.keys(model)).reduce<BundleUnit>(
|
model: Schemas.BundleUnit,
|
||||||
(acc, current) => {
|
): Schemas.PartitionedBundle {
|
||||||
// Checking if current file is one of the autogenerated ones or if its
|
const rawBundle = removeHidden(
|
||||||
// content is not available
|
Object.keys(model),
|
||||||
|
).reduce<Schemas.BundleUnit>((acc, current) => {
|
||||||
|
// Checking if current file is one of the autogenerated ones or if its
|
||||||
|
// content is not available
|
||||||
|
|
||||||
if (/(manifest|signature)/.test(current) || !model[current]) {
|
if (/(manifest|signature)/.test(current) || !model[current]) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...acc, [current]: model[current] };
|
return { ...acc, [current]: model[current] };
|
||||||
},
|
}, {});
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
const bundleKeys = Object.keys(rawBundle);
|
const bundleKeys = Object.keys(rawBundle);
|
||||||
|
|
||||||
@@ -266,18 +264,18 @@ export function getModelBufferContents(model: BundleUnit): PartitionedBundle {
|
|||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type flatCertificates = Omit<Certificates, "signerKey"> & {
|
type flatCertificates = Omit<Schemas.Certificates, "signerKey"> & {
|
||||||
signerKey: string;
|
signerKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function readCertificatesFromOptions(
|
export async function readCertificatesFromOptions(
|
||||||
options: Certificates,
|
options: Schemas.Certificates,
|
||||||
): Promise<FinalCertificates> {
|
): Promise<Schemas.CertificatesSchema> {
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
options &&
|
options &&
|
||||||
Object.keys(options).length &&
|
Object.keys(options).length &&
|
||||||
isValid(options, "certificatesSchema")
|
Schemas.isValid(options, Schemas.CertificatesSchema)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new Error(formatMessage("CP_NO_CERTS"));
|
throw new Error(formatMessage("CP_NO_CERTS"));
|
||||||
|
|||||||
120
src/pass.ts
120
src/pass.ts
@@ -4,7 +4,7 @@ import debug from "debug";
|
|||||||
import { Stream } from "stream";
|
import { Stream } from "stream";
|
||||||
import { ZipFile } from "yazl";
|
import { ZipFile } from "yazl";
|
||||||
|
|
||||||
import * as schema from "./schema";
|
import * as Schemas from "./schemas";
|
||||||
import formatMessage from "./messages";
|
import formatMessage from "./messages";
|
||||||
import FieldsArray from "./fieldsArray";
|
import FieldsArray from "./fieldsArray";
|
||||||
import {
|
import {
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
deletePersonalization,
|
deletePersonalization,
|
||||||
getAllFilesWithName,
|
getAllFilesWithName,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
import type Joi from "joi";
|
||||||
|
|
||||||
const barcodeDebug = debug("passkit:barcode");
|
const barcodeDebug = debug("passkit:barcode");
|
||||||
const genericDebug = debug("passkit:generic");
|
const genericDebug = debug("passkit:generic");
|
||||||
@@ -21,22 +22,22 @@ const genericDebug = debug("passkit:generic");
|
|||||||
const transitType = Symbol("transitType");
|
const transitType = Symbol("transitType");
|
||||||
const passProps = Symbol("_props");
|
const passProps = Symbol("_props");
|
||||||
|
|
||||||
const propsSchemaMap = new Map<string, schema.Schema>([
|
const propsSchemaMap = new Map<string, Joi.ObjectSchema<any>>([
|
||||||
["barcodes", "barcode"],
|
["barcodes", Schemas.Barcode],
|
||||||
["barcode", "barcode"],
|
["barcode", Schemas.Barcode],
|
||||||
["beacons", "beaconsDict"],
|
["beacons", Schemas.Beacon],
|
||||||
["locations", "locationsDict"],
|
["locations", Schemas.Location],
|
||||||
["nfc", "nfcDict"],
|
["nfc", Schemas.NFC],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export class Pass {
|
export class Pass {
|
||||||
private bundle: schema.BundleUnit;
|
private bundle: Schemas.BundleUnit;
|
||||||
private l10nBundles: schema.PartitionedBundle["l10nBundle"];
|
private l10nBundles: Schemas.PartitionedBundle["l10nBundle"];
|
||||||
private _fields: (keyof schema.PassFields)[];
|
private _fields: (keyof Schemas.PassFields)[];
|
||||||
private [passProps]: schema.ValidPass = {};
|
private [passProps]: Schemas.ValidPass = {};
|
||||||
private type: keyof schema.ValidPassType;
|
private type: keyof Schemas.ValidPassType;
|
||||||
private fieldsKeys: Set<string> = new Set<string>();
|
private fieldsKeys: Set<string> = new Set<string>();
|
||||||
private passCore: schema.ValidPass;
|
private passCore: Schemas.ValidPass;
|
||||||
|
|
||||||
// Setting these as possibly undefined because we set
|
// Setting these as possibly undefined because we set
|
||||||
// them all in an loop later
|
// them all in an loop later
|
||||||
@@ -46,14 +47,14 @@ export class Pass {
|
|||||||
public auxiliaryFields: FieldsArray | undefined;
|
public auxiliaryFields: FieldsArray | undefined;
|
||||||
public backFields: FieldsArray | undefined;
|
public backFields: FieldsArray | undefined;
|
||||||
|
|
||||||
private Certificates: schema.FinalCertificates;
|
private Certificates: Schemas.CertificatesSchema;
|
||||||
private [transitType]: string = "";
|
private [transitType]: string = "";
|
||||||
private l10nTranslations: {
|
private l10nTranslations: {
|
||||||
[languageCode: string]: { [placeholder: string]: string };
|
[languageCode: string]: { [placeholder: string]: string };
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
constructor(options: schema.PassInstance) {
|
constructor(options: Schemas.PassInstance) {
|
||||||
if (!schema.isValid(options, "instance")) {
|
if (!Schemas.isValid(options, Schemas.PassInstance)) {
|
||||||
throw new Error(formatMessage("REQUIR_VALID_FAILED"));
|
throw new Error(formatMessage("REQUIR_VALID_FAILED"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,10 +71,10 @@ export class Pass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parsing the options and extracting only the valid ones.
|
// Parsing the options and extracting only the valid ones.
|
||||||
const validOverrides = schema.getValidated(
|
const validOverrides = Schemas.getValidated(
|
||||||
options.overrides || {},
|
options.overrides || {},
|
||||||
"supportedOptions",
|
Schemas.OverridesSupportedOptions,
|
||||||
) as schema.OverridesSupportedOptions;
|
);
|
||||||
|
|
||||||
if (validOverrides === null) {
|
if (validOverrides === null) {
|
||||||
throw new Error(formatMessage("OVV_KEYS_BADFORMAT"));
|
throw new Error(formatMessage("OVV_KEYS_BADFORMAT"));
|
||||||
@@ -81,7 +82,7 @@ export class Pass {
|
|||||||
|
|
||||||
this.type = Object.keys(this.passCore).find((key) =>
|
this.type = Object.keys(this.passCore).find((key) =>
|
||||||
/(boardingPass|eventTicket|coupon|generic|storeCard)/.test(key),
|
/(boardingPass|eventTicket|coupon|generic|storeCard)/.test(key),
|
||||||
) as keyof schema.ValidPassType;
|
) as keyof Schemas.ValidPassType;
|
||||||
|
|
||||||
if (!this.type) {
|
if (!this.type) {
|
||||||
throw new Error(formatMessage("NO_PASS_TYPE"));
|
throw new Error(formatMessage("NO_PASS_TYPE"));
|
||||||
@@ -90,8 +91,8 @@ export class Pass {
|
|||||||
// Parsing and validating pass.json keys
|
// Parsing and validating pass.json keys
|
||||||
const passCoreKeys = Object.keys(
|
const passCoreKeys = Object.keys(
|
||||||
this.passCore,
|
this.passCore,
|
||||||
) as (keyof schema.ValidPass)[];
|
) as (keyof Schemas.ValidPass)[];
|
||||||
const validatedPassKeys = passCoreKeys.reduce<schema.ValidPass>(
|
const validatedPassKeys = passCoreKeys.reduce<Schemas.ValidPass>(
|
||||||
(acc, current) => {
|
(acc, current) => {
|
||||||
if (this.type === current) {
|
if (this.type === current) {
|
||||||
// We want to exclude type keys (eventTicket,
|
// We want to exclude type keys (eventTicket,
|
||||||
@@ -109,16 +110,16 @@ export class Pass {
|
|||||||
const currentSchema = propsSchemaMap.get(current)!;
|
const currentSchema = propsSchemaMap.get(current)!;
|
||||||
|
|
||||||
if (Array.isArray(this.passCore[current])) {
|
if (Array.isArray(this.passCore[current])) {
|
||||||
const valid = getValidInArray<schema.ArrayPassSchema>(
|
const valid = getValidInArray<Schemas.ArrayPassSchema>(
|
||||||
currentSchema,
|
currentSchema,
|
||||||
this.passCore[current] as schema.ArrayPassSchema[],
|
this.passCore[current] as Schemas.ArrayPassSchema[],
|
||||||
);
|
);
|
||||||
return { ...acc, [current]: valid };
|
return { ...acc, [current]: valid };
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
[current]:
|
[current]:
|
||||||
(schema.isValid(
|
(Schemas.isValid(
|
||||||
this.passCore[current],
|
this.passCore[current],
|
||||||
currentSchema,
|
currentSchema,
|
||||||
) &&
|
) &&
|
||||||
@@ -155,7 +156,7 @@ export class Pass {
|
|||||||
this[fieldName] = new FieldsArray(
|
this[fieldName] = new FieldsArray(
|
||||||
this.fieldsKeys,
|
this.fieldsKeys,
|
||||||
...(this.passCore[this.type][fieldName] || []).filter((field) =>
|
...(this.passCore[this.type][fieldName] || []).filter((field) =>
|
||||||
schema.isValid(field, "field"),
|
Schemas.isValid(field, Schemas.Field),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -193,7 +194,7 @@ export class Pass {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalBundle = { ...this.bundle } as schema.BundleUnit;
|
const finalBundle = { ...this.bundle } as Schemas.BundleUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterating through languages and generating pass.string file
|
* Iterating through languages and generating pass.string file
|
||||||
@@ -262,7 +263,7 @@ export class Pass {
|
|||||||
* and returning the compiled manifest
|
* and returning the compiled manifest
|
||||||
*/
|
*/
|
||||||
const archive = new ZipFile();
|
const archive = new ZipFile();
|
||||||
const manifest = Object.keys(finalBundle).reduce<schema.Manifest>(
|
const manifest = Object.keys(finalBundle).reduce<Schemas.Manifest>(
|
||||||
(acc, current) => {
|
(acc, current) => {
|
||||||
let hashFlow = forge.md.sha1.create();
|
let hashFlow = forge.md.sha1.create();
|
||||||
|
|
||||||
@@ -360,14 +361,14 @@ export class Pass {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
beacons(resetFlag: null): this;
|
beacons(resetFlag: null): this;
|
||||||
beacons(...data: schema.Beacon[]): this;
|
beacons(...data: Schemas.Beacon[]): this;
|
||||||
beacons(...data: (schema.Beacon | null)[]): this {
|
beacons(...data: (Schemas.Beacon | null)[]): this {
|
||||||
if (data[0] === null) {
|
if (data[0] === null) {
|
||||||
delete this[passProps]["beacons"];
|
delete this[passProps]["beacons"];
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = processRelevancySet("beacons", data as schema.Beacon[]);
|
const valid = processRelevancySet(Schemas.Beacon, data);
|
||||||
|
|
||||||
if (valid.length) {
|
if (valid.length) {
|
||||||
this[passProps]["beacons"] = valid;
|
this[passProps]["beacons"] = valid;
|
||||||
@@ -383,17 +384,14 @@ export class Pass {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
locations(resetFlag: null): this;
|
locations(resetFlag: null): this;
|
||||||
locations(...data: schema.Location[]): this;
|
locations(...data: Schemas.Location[]): this;
|
||||||
locations(...data: (schema.Location | null)[]): this {
|
locations(...data: (Schemas.Location | null)[]): this {
|
||||||
if (data[0] === null) {
|
if (data[0] === null) {
|
||||||
delete this[passProps]["locations"];
|
delete this[passProps]["locations"];
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = processRelevancySet(
|
const valid = processRelevancySet(Schemas.Location, data);
|
||||||
"locations",
|
|
||||||
data as schema.Location[],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (valid.length) {
|
if (valid.length) {
|
||||||
this[passProps]["locations"] = valid;
|
this[passProps]["locations"] = valid;
|
||||||
@@ -436,8 +434,8 @@ export class Pass {
|
|||||||
|
|
||||||
barcodes(resetFlag: null): this;
|
barcodes(resetFlag: null): this;
|
||||||
barcodes(message: string): this;
|
barcodes(message: string): this;
|
||||||
barcodes(...data: schema.Barcode[]): this;
|
barcodes(...data: Schemas.Barcode[]): this;
|
||||||
barcodes(...data: (schema.Barcode | null | string)[]): this {
|
barcodes(...data: (Schemas.Barcode | null | string)[]): this {
|
||||||
if (data[0] === null) {
|
if (data[0] === null) {
|
||||||
delete this[passProps]["barcodes"];
|
delete this[passProps]["barcodes"];
|
||||||
return this;
|
return this;
|
||||||
@@ -461,13 +459,16 @@ export class Pass {
|
|||||||
* Validation assign default value to missing parameters (if any).
|
* Validation assign default value to missing parameters (if any).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const validBarcodes = data.reduce<schema.Barcode[]>(
|
const validBarcodes = data.reduce<Schemas.Barcode[]>(
|
||||||
(acc, current) => {
|
(acc, current) => {
|
||||||
if (!(current && current instanceof Object)) {
|
if (!(current && current instanceof Object)) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validated = schema.getValidated(current, "barcode");
|
const validated = Schemas.getValidated(
|
||||||
|
current,
|
||||||
|
Schemas.Barcode,
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
@@ -479,7 +480,7 @@ export class Pass {
|
|||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...acc, validated] as schema.Barcode[];
|
return [...acc, validated] as Schemas.Barcode[];
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@@ -502,7 +503,7 @@ export class Pass {
|
|||||||
* @return {this}
|
* @return {this}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
barcode(chosenFormat: schema.BarcodeFormat | null): this {
|
barcode(chosenFormat: Schemas.BarcodeFormat | null): this {
|
||||||
const { barcodes } = this[passProps];
|
const { barcodes } = this[passProps];
|
||||||
|
|
||||||
if (chosenFormat === null) {
|
if (chosenFormat === null) {
|
||||||
@@ -548,7 +549,7 @@ export class Pass {
|
|||||||
* @see https://apple.co/2wTxiaC
|
* @see https://apple.co/2wTxiaC
|
||||||
*/
|
*/
|
||||||
|
|
||||||
nfc(data: schema.NFC | null): this {
|
nfc(data: Schemas.NFC | null): this {
|
||||||
if (data === null) {
|
if (data === null) {
|
||||||
delete this[passProps]["nfc"];
|
delete this[passProps]["nfc"];
|
||||||
return this;
|
return this;
|
||||||
@@ -559,7 +560,7 @@ export class Pass {
|
|||||||
data &&
|
data &&
|
||||||
typeof data === "object" &&
|
typeof data === "object" &&
|
||||||
!Array.isArray(data) &&
|
!Array.isArray(data) &&
|
||||||
schema.isValid(data, "nfcDict")
|
Schemas.isValid(data, Schemas.NFC)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
genericDebug(formatMessage("NFC_INVALID"));
|
genericDebug(formatMessage("NFC_INVALID"));
|
||||||
@@ -579,7 +580,7 @@ export class Pass {
|
|||||||
* @returns The properties will be inserted in the pass.
|
* @returns The properties will be inserted in the pass.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
get props(): Readonly<schema.ValidPass> {
|
get props(): Readonly<Schemas.ValidPass> {
|
||||||
return this[passProps];
|
return this[passProps];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,7 +592,7 @@ export class Pass {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private _sign(manifest: schema.Manifest): Buffer {
|
private _sign(manifest: Schemas.Manifest): Buffer {
|
||||||
const signature = forge.pkcs7.createSignedData();
|
const signature = forge.pkcs7.createSignedData();
|
||||||
|
|
||||||
signature.content = forge.util.createBuffer(
|
signature.content = forge.util.createBuffer(
|
||||||
@@ -667,7 +668,7 @@ export class Pass {
|
|||||||
private _patch(passCoreBuffer: Buffer): Buffer {
|
private _patch(passCoreBuffer: Buffer): Buffer {
|
||||||
const passFile = JSON.parse(
|
const passFile = JSON.parse(
|
||||||
passCoreBuffer.toString(),
|
passCoreBuffer.toString(),
|
||||||
) as schema.ValidPass;
|
) as Schemas.ValidPass;
|
||||||
|
|
||||||
if (Object.keys(this[passProps]).length) {
|
if (Object.keys(this[passProps]).length) {
|
||||||
/*
|
/*
|
||||||
@@ -680,7 +681,7 @@ export class Pass {
|
|||||||
"backgroundColor",
|
"backgroundColor",
|
||||||
"foregroundColor",
|
"foregroundColor",
|
||||||
"labelColor",
|
"labelColor",
|
||||||
] as Array<keyof schema.PassColors>;
|
] as Array<keyof Schemas.PassColors>;
|
||||||
passColors
|
passColors
|
||||||
.filter(
|
.filter(
|
||||||
(v) =>
|
(v) =>
|
||||||
@@ -705,7 +706,7 @@ export class Pass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set transitType(value: string) {
|
set transitType(value: string) {
|
||||||
if (!schema.isValid(value, "transitType")) {
|
if (!Schemas.isValid(value, Schemas.TransitType)) {
|
||||||
genericDebug(formatMessage("TRSTYPE_NOT_VALID", value));
|
genericDebug(formatMessage("TRSTYPE_NOT_VALID", value));
|
||||||
this[transitType] = this[transitType] || "";
|
this[transitType] = this[transitType] || "";
|
||||||
return;
|
return;
|
||||||
@@ -727,7 +728,7 @@ export class Pass {
|
|||||||
* @return Array of barcodeDict compliant
|
* @return Array of barcodeDict compliant
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function barcodesFromUncompleteData(message: string): schema.Barcode[] {
|
function barcodesFromUncompleteData(message: string): Schemas.Barcode[] {
|
||||||
if (!(message && typeof message === "string")) {
|
if (!(message && typeof message === "string")) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -739,21 +740,24 @@ function barcodesFromUncompleteData(message: string): schema.Barcode[] {
|
|||||||
"PKBarcodeFormatCode128",
|
"PKBarcodeFormatCode128",
|
||||||
].map(
|
].map(
|
||||||
(format) =>
|
(format) =>
|
||||||
schema.getValidated(
|
Schemas.getValidated(
|
||||||
{ format, message },
|
{ format, message },
|
||||||
"barcode",
|
Schemas.Barcode,
|
||||||
) as schema.Barcode,
|
) as Schemas.Barcode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function processRelevancySet<T>(key: string, data: T[]): T[] {
|
function processRelevancySet<T>(schema: Joi.ObjectSchema<T>, data: T[]): T[] {
|
||||||
return getValidInArray(`${key}Dict` as schema.Schema, data);
|
return getValidInArray(schema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getValidInArray<T>(schemaName: schema.Schema, contents: T[]): T[] {
|
function getValidInArray<T>(
|
||||||
|
schemaName: Joi.ObjectSchema<T>,
|
||||||
|
contents: T[],
|
||||||
|
): T[] {
|
||||||
return contents.filter(
|
return contents.filter(
|
||||||
(current) =>
|
(current) =>
|
||||||
Object.keys(current).length && schema.isValid(current, schemaName),
|
Object.keys(current).length && Schemas.isValid(current, schemaName),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
624
src/schema.ts
624
src/schema.ts
@@ -1,624 +0,0 @@
|
|||||||
import Joi from "joi";
|
|
||||||
import debug from "debug";
|
|
||||||
|
|
||||||
const schemaDebug = debug("Schema");
|
|
||||||
|
|
||||||
export interface Manifest {
|
|
||||||
[key: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Certificates {
|
|
||||||
wwdr?: string;
|
|
||||||
signerCert?: string;
|
|
||||||
signerKey?:
|
|
||||||
| {
|
|
||||||
keyFile: string;
|
|
||||||
passphrase?: string;
|
|
||||||
}
|
|
||||||
| string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FactoryOptions {
|
|
||||||
model: BundleUnit | string;
|
|
||||||
certificates: Certificates;
|
|
||||||
overrides?: OverridesSupportedOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BundleUnit {
|
|
||||||
[key: string]: Buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PartitionedBundle {
|
|
||||||
bundle: BundleUnit;
|
|
||||||
l10nBundle: {
|
|
||||||
[key: string]: BundleUnit;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FinalCertificates {
|
|
||||||
wwdr: string;
|
|
||||||
signerCert: string;
|
|
||||||
signerKey: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PassInstance {
|
|
||||||
model: PartitionedBundle;
|
|
||||||
certificates: FinalCertificates;
|
|
||||||
overrides?: OverridesSupportedOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ************************************ //
|
|
||||||
// * JOI Schemas + Related Interfaces * //
|
|
||||||
// ************************************ //
|
|
||||||
|
|
||||||
const certificatesSchema = Joi.object()
|
|
||||||
.keys({
|
|
||||||
wwdr: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
|
||||||
signerCert: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
|
||||||
signerKey: Joi.alternatives()
|
|
||||||
.try(
|
|
||||||
Joi.object().keys({
|
|
||||||
keyFile: Joi.alternatives(
|
|
||||||
Joi.binary(),
|
|
||||||
Joi.string(),
|
|
||||||
).required(),
|
|
||||||
passphrase: Joi.string().required(),
|
|
||||||
}),
|
|
||||||
Joi.alternatives(Joi.binary(), Joi.string()),
|
|
||||||
)
|
|
||||||
.required(),
|
|
||||||
})
|
|
||||||
.required();
|
|
||||||
|
|
||||||
const instance = Joi.object().keys({
|
|
||||||
model: Joi.alternatives(Joi.object(), Joi.string()).required(),
|
|
||||||
certificates: Joi.object(),
|
|
||||||
overrides: Joi.object(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface OverridesSupportedOptions {
|
|
||||||
serialNumber?: string;
|
|
||||||
description?: string;
|
|
||||||
organizationName?: string;
|
|
||||||
passTypeIdentifier?: string;
|
|
||||||
teamIdentifier?: string;
|
|
||||||
appLaunchURL?: string;
|
|
||||||
associatedStoreIdentifiers?: Array<number>;
|
|
||||||
userInfo?: { [key: string]: any };
|
|
||||||
webServiceURL?: string;
|
|
||||||
authenticationToken?: string;
|
|
||||||
sharingProhibited?: boolean;
|
|
||||||
backgroundColor?: string;
|
|
||||||
foregroundColor?: string;
|
|
||||||
labelColor?: string;
|
|
||||||
groupingIdentifier?: string;
|
|
||||||
suppressStripShine?: boolean;
|
|
||||||
logoText?: string;
|
|
||||||
maxDistance?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const supportedOptions = Joi.object()
|
|
||||||
.keys({
|
|
||||||
serialNumber: Joi.string(),
|
|
||||||
description: Joi.string(),
|
|
||||||
organizationName: Joi.string(),
|
|
||||||
passTypeIdentifier: Joi.string(),
|
|
||||||
teamIdentifier: Joi.string(),
|
|
||||||
appLaunchURL: Joi.string(),
|
|
||||||
associatedStoreIdentifiers: Joi.array().items(Joi.number()),
|
|
||||||
userInfo: Joi.alternatives(Joi.object().unknown(), Joi.array()),
|
|
||||||
// parsing url as set of words and nums followed by dots, optional port and any possible path after
|
|
||||||
webServiceURL: Joi.string().regex(
|
|
||||||
/https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/,
|
|
||||||
),
|
|
||||||
authenticationToken: Joi.string().min(16),
|
|
||||||
sharingProhibited: Joi.boolean(),
|
|
||||||
backgroundColor: Joi.string().min(10).max(16),
|
|
||||||
foregroundColor: Joi.string().min(10).max(16),
|
|
||||||
labelColor: Joi.string().min(10).max(16),
|
|
||||||
groupingIdentifier: Joi.string(),
|
|
||||||
suppressStripShine: Joi.boolean(),
|
|
||||||
logoText: Joi.string(),
|
|
||||||
maxDistance: Joi.number().positive(),
|
|
||||||
})
|
|
||||||
.with("webServiceURL", "authenticationToken");
|
|
||||||
|
|
||||||
/* For a correct usage of semantics, please refer to https://apple.co/2I66Phk */
|
|
||||||
|
|
||||||
interface CurrencyAmount {
|
|
||||||
currencyCode: string;
|
|
||||||
amount: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currencyAmount = Joi.object().keys({
|
|
||||||
currencyCode: Joi.string().required(),
|
|
||||||
amount: Joi.string().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
interface PersonNameComponent {
|
|
||||||
givenName: string;
|
|
||||||
familyName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const personNameComponents = Joi.object().keys({
|
|
||||||
givenName: Joi.string().required(),
|
|
||||||
familyName: Joi.string().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
interface Seat {
|
|
||||||
seatSection?: string;
|
|
||||||
seatRow?: string;
|
|
||||||
seatNumber?: string;
|
|
||||||
seatIdentifier?: string;
|
|
||||||
seatType?: string;
|
|
||||||
seatDescription?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const seat = Joi.object().keys({
|
|
||||||
seatSection: Joi.string(),
|
|
||||||
seatRow: Joi.string(),
|
|
||||||
seatNumber: Joi.string(),
|
|
||||||
seatIdentifier: Joi.string(),
|
|
||||||
seatType: Joi.string(),
|
|
||||||
seatDescription: Joi.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const location = Joi.object().keys({
|
|
||||||
latitude: Joi.number().required(),
|
|
||||||
longitude: Joi.number().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
interface Semantics {
|
|
||||||
totalPrice?: CurrencyAmount;
|
|
||||||
duration?: number;
|
|
||||||
seats?: Seat[];
|
|
||||||
silenceRequested?: boolean;
|
|
||||||
departureLocation?: Location;
|
|
||||||
destinationLocation?: Location;
|
|
||||||
destinationLocationDescription?: Location;
|
|
||||||
transitProvider?: string;
|
|
||||||
vehicleName?: string;
|
|
||||||
vehicleType?: string;
|
|
||||||
originalDepartureDate?: string;
|
|
||||||
currentDepartureDate?: string;
|
|
||||||
originalArrivalDate?: string;
|
|
||||||
currentArrivalDate?: string;
|
|
||||||
originalBoardingDate?: string;
|
|
||||||
currentBoardingDate?: string;
|
|
||||||
boardingGroup?: string;
|
|
||||||
boardingSequenceNumber?: string;
|
|
||||||
confirmationNumber?: string;
|
|
||||||
transitStatus?: string;
|
|
||||||
transitStatuReason?: string;
|
|
||||||
passengetName?: PersonNameComponent;
|
|
||||||
membershipProgramName?: string;
|
|
||||||
membershipProgramNumber?: string;
|
|
||||||
priorityStatus?: string;
|
|
||||||
securityScreening?: string;
|
|
||||||
flightCode?: string;
|
|
||||||
airlineCode?: string;
|
|
||||||
flightNumber?: number;
|
|
||||||
departureAirportCode?: string;
|
|
||||||
departureAirportName?: string;
|
|
||||||
destinationTerminal?: string;
|
|
||||||
destinationGate?: string;
|
|
||||||
departurePlatform?: string;
|
|
||||||
departureStationName?: string;
|
|
||||||
destinationPlatform?: string;
|
|
||||||
destinationStationName?: string;
|
|
||||||
carNumber?: string;
|
|
||||||
eventName?: string;
|
|
||||||
venueName?: string;
|
|
||||||
venueLocation?: Location;
|
|
||||||
venueEntrance?: string;
|
|
||||||
venuePhoneNumber?: string;
|
|
||||||
venueRoom?: string;
|
|
||||||
eventType?:
|
|
||||||
| "PKEventTypeGeneric"
|
|
||||||
| "PKEventTypeLivePerformance"
|
|
||||||
| "PKEventTypeMovie"
|
|
||||||
| "PKEventTypeSports"
|
|
||||||
| "PKEventTypeConference"
|
|
||||||
| "PKEventTypeConvention"
|
|
||||||
| "PKEventTypeWorkshop"
|
|
||||||
| "PKEventTypeSocialGathering";
|
|
||||||
eventStartDate?: string;
|
|
||||||
eventEndDate?: string;
|
|
||||||
artistIDs?: string;
|
|
||||||
performerNames?: string[];
|
|
||||||
genre?: string;
|
|
||||||
leagueName?: string;
|
|
||||||
leagueAbbreviation?: string;
|
|
||||||
homeTeamLocation?: string;
|
|
||||||
homeTeamName?: string;
|
|
||||||
homeTeamAbbreviation?: string;
|
|
||||||
awayTeamLocation?: string;
|
|
||||||
awayTeamName?: string;
|
|
||||||
awayTeamAbbreviation?: string;
|
|
||||||
sportName?: string;
|
|
||||||
balance?: CurrencyAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
const semantics = Joi.object().keys({
|
|
||||||
// All
|
|
||||||
totalPrice: currencyAmount,
|
|
||||||
// boarding Passes and Events
|
|
||||||
duration: Joi.number(),
|
|
||||||
seats: Joi.array().items(seat),
|
|
||||||
silenceRequested: Joi.boolean(),
|
|
||||||
// all boarding passes
|
|
||||||
departureLocation: location,
|
|
||||||
destinationLocation: location,
|
|
||||||
destinationLocationDescription: location,
|
|
||||||
transitProvider: Joi.string(),
|
|
||||||
vehicleName: Joi.string(),
|
|
||||||
vehicleType: Joi.string(),
|
|
||||||
originalDepartureDate: Joi.string(),
|
|
||||||
currentDepartureDate: Joi.string(),
|
|
||||||
originalArrivalDate: Joi.string(),
|
|
||||||
currentArrivalDate: Joi.string(),
|
|
||||||
originalBoardingDate: Joi.string(),
|
|
||||||
currentBoardingDate: Joi.string(),
|
|
||||||
boardingGroup: Joi.string(),
|
|
||||||
boardingSequenceNumber: Joi.string(),
|
|
||||||
confirmationNumber: Joi.string(),
|
|
||||||
transitStatus: Joi.string(),
|
|
||||||
transitStatuReason: Joi.string(),
|
|
||||||
passengetName: personNameComponents,
|
|
||||||
membershipProgramName: Joi.string(),
|
|
||||||
membershipProgramNumber: Joi.string(),
|
|
||||||
priorityStatus: Joi.string(),
|
|
||||||
securityScreening: Joi.string(),
|
|
||||||
// Airline Boarding Passes
|
|
||||||
flightCode: Joi.string(),
|
|
||||||
airlineCode: Joi.string(),
|
|
||||||
flightNumber: Joi.number(),
|
|
||||||
departureAirportCode: Joi.string(),
|
|
||||||
departureAirportName: Joi.string(),
|
|
||||||
destinationTerminal: Joi.string(),
|
|
||||||
destinationGate: Joi.string(),
|
|
||||||
// Train and Other Rail Boarding Passes
|
|
||||||
departurePlatform: Joi.string(),
|
|
||||||
departureStationName: Joi.string(),
|
|
||||||
destinationPlatform: Joi.string(),
|
|
||||||
destinationStationName: Joi.string(),
|
|
||||||
carNumber: Joi.string(),
|
|
||||||
// All Event Tickets
|
|
||||||
eventName: Joi.string(),
|
|
||||||
venueName: Joi.string(),
|
|
||||||
venueLocation: location,
|
|
||||||
venueEntrance: Joi.string(),
|
|
||||||
venuePhoneNumber: Joi.string(),
|
|
||||||
venueRoom: Joi.string(),
|
|
||||||
eventType: Joi.string().regex(
|
|
||||||
/(PKEventTypeGeneric|PKEventTypeLivePerformance|PKEventTypeMovie|PKEventTypeSports|PKEventTypeConference|PKEventTypeConvention|PKEventTypeWorkshop|PKEventTypeSocialGathering)/,
|
|
||||||
),
|
|
||||||
eventStartDate: Joi.string(),
|
|
||||||
eventEndDate: Joi.string(),
|
|
||||||
artistIDs: Joi.string(),
|
|
||||||
performerNames: Joi.array().items(Joi.string()),
|
|
||||||
genre: Joi.string(),
|
|
||||||
// Sport Event Tickets
|
|
||||||
leagueName: Joi.string(),
|
|
||||||
leagueAbbreviation: Joi.string(),
|
|
||||||
homeTeamLocation: Joi.string(),
|
|
||||||
homeTeamName: Joi.string(),
|
|
||||||
homeTeamAbbreviation: Joi.string(),
|
|
||||||
awayTeamLocation: Joi.string(),
|
|
||||||
awayTeamName: Joi.string(),
|
|
||||||
awayTeamAbbreviation: Joi.string(),
|
|
||||||
sportName: Joi.string(),
|
|
||||||
// Store Card Passes
|
|
||||||
balance: currencyAmount,
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface ValidPassType {
|
|
||||||
boardingPass?: PassFields & { transitType: TransitType };
|
|
||||||
eventTicket?: PassFields;
|
|
||||||
coupon?: PassFields;
|
|
||||||
generic?: PassFields;
|
|
||||||
storeCard?: PassFields;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PassInterfacesProps {
|
|
||||||
barcode?: Barcode;
|
|
||||||
barcodes?: Barcode[];
|
|
||||||
beacons?: Beacon[];
|
|
||||||
locations?: Location[];
|
|
||||||
maxDistance?: number;
|
|
||||||
relevantDate?: string;
|
|
||||||
nfc?: NFC;
|
|
||||||
expirationDate?: string;
|
|
||||||
voided?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
type AllPassProps = PassInterfacesProps &
|
|
||||||
ValidPassType &
|
|
||||||
OverridesSupportedOptions;
|
|
||||||
export type ValidPass = {
|
|
||||||
[K in keyof AllPassProps]: AllPassProps[K];
|
|
||||||
};
|
|
||||||
export type PassColors = Pick<
|
|
||||||
OverridesSupportedOptions,
|
|
||||||
"backgroundColor" | "foregroundColor" | "labelColor"
|
|
||||||
>;
|
|
||||||
|
|
||||||
export interface Barcode {
|
|
||||||
altText?: string;
|
|
||||||
messageEncoding?: string;
|
|
||||||
format: string;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BarcodeFormat =
|
|
||||||
| "PKBarcodeFormatQR"
|
|
||||||
| "PKBarcodeFormatPDF417"
|
|
||||||
| "PKBarcodeFormatAztec"
|
|
||||||
| "PKBarcodeFormatCode128";
|
|
||||||
|
|
||||||
const barcode = Joi.object().keys({
|
|
||||||
altText: Joi.string(),
|
|
||||||
messageEncoding: Joi.string().default("iso-8859-1"),
|
|
||||||
format: Joi.string()
|
|
||||||
.required()
|
|
||||||
.regex(
|
|
||||||
/(PKBarcodeFormatQR|PKBarcodeFormatPDF417|PKBarcodeFormatAztec|PKBarcodeFormatCode128)/,
|
|
||||||
"barcodeType",
|
|
||||||
),
|
|
||||||
message: Joi.string().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface Field {
|
|
||||||
attributedValue?: string | number | Date;
|
|
||||||
changeMessage?: string;
|
|
||||||
dataDetectorTypes?: string[];
|
|
||||||
label?: string;
|
|
||||||
textAlignment?: string;
|
|
||||||
key: string;
|
|
||||||
value: string | number | Date;
|
|
||||||
semantics?: Semantics;
|
|
||||||
dateStyle?: string;
|
|
||||||
ignoresTimeZone?: boolean;
|
|
||||||
isRelative?: boolean;
|
|
||||||
timeStyle?: string;
|
|
||||||
currencyCode?: string;
|
|
||||||
numberStyle?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const field = Joi.object().keys({
|
|
||||||
attributedValue: Joi.alternatives(
|
|
||||||
Joi.string().allow(""),
|
|
||||||
Joi.number(),
|
|
||||||
Joi.date().iso(),
|
|
||||||
),
|
|
||||||
changeMessage: Joi.string(),
|
|
||||||
dataDetectorTypes: Joi.array().items(
|
|
||||||
Joi.string().regex(
|
|
||||||
/(PKDataDetectorTypePhoneNumber|PKDataDetectorTypeLink|PKDataDetectorTypeAddress|PKDataDetectorTypeCalendarEvent)/,
|
|
||||||
"dataDetectorType",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
label: Joi.string().allow(""),
|
|
||||||
textAlignment: Joi.string().regex(
|
|
||||||
/(PKTextAlignmentLeft|PKTextAlignmentCenter|PKTextAlignmentRight|PKTextAlignmentNatural)/,
|
|
||||||
"graphic-alignment",
|
|
||||||
),
|
|
||||||
key: Joi.string().required(),
|
|
||||||
value: Joi.alternatives(
|
|
||||||
Joi.string().allow(""),
|
|
||||||
Joi.number(),
|
|
||||||
Joi.date().iso(),
|
|
||||||
).required(),
|
|
||||||
semantics,
|
|
||||||
// date fields formatters, all optionals
|
|
||||||
dateStyle: Joi.string().regex(
|
|
||||||
/(PKDateStyleNone|PKDateStyleShort|PKDateStyleMedium|PKDateStyleLong|PKDateStyleFull)/,
|
|
||||||
"date style",
|
|
||||||
),
|
|
||||||
ignoresTimeZone: Joi.boolean(),
|
|
||||||
isRelative: Joi.boolean(),
|
|
||||||
timeStyle: Joi.string().regex(
|
|
||||||
/(PKDateStyleNone|PKDateStyleShort|PKDateStyleMedium|PKDateStyleLong|PKDateStyleFull)/,
|
|
||||||
"date style",
|
|
||||||
),
|
|
||||||
// number fields formatters, all optionals
|
|
||||||
currencyCode: Joi.string().when("value", {
|
|
||||||
is: Joi.number(),
|
|
||||||
otherwise: Joi.string().forbidden(),
|
|
||||||
}),
|
|
||||||
numberStyle: Joi.string()
|
|
||||||
.regex(
|
|
||||||
/(PKNumberStyleDecimal|PKNumberStylePercent|PKNumberStyleScientific|PKNumberStyleSpellOut)/,
|
|
||||||
)
|
|
||||||
.when("value", {
|
|
||||||
is: Joi.number(),
|
|
||||||
otherwise: Joi.string().forbidden(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface Beacon {
|
|
||||||
major?: number;
|
|
||||||
minor?: number;
|
|
||||||
relevantText?: string;
|
|
||||||
proximityUUID: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const beaconsDict = Joi.object().keys({
|
|
||||||
major: Joi.number()
|
|
||||||
.integer()
|
|
||||||
.positive()
|
|
||||||
.max(65535)
|
|
||||||
.greater(Joi.ref("minor")),
|
|
||||||
minor: Joi.number().integer().min(0).max(65535),
|
|
||||||
proximityUUID: Joi.string().required(),
|
|
||||||
relevantText: Joi.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface Location {
|
|
||||||
relevantText?: string;
|
|
||||||
altitude?: number;
|
|
||||||
latitude: number;
|
|
||||||
longitude: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const locationsDict = Joi.object().keys({
|
|
||||||
altitude: Joi.number(),
|
|
||||||
latitude: Joi.number().required(),
|
|
||||||
longitude: Joi.number().required(),
|
|
||||||
relevantText: Joi.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface PassFields {
|
|
||||||
auxiliaryFields: (Field & { row?: number })[];
|
|
||||||
backFields: Field[];
|
|
||||||
headerFields: Field[];
|
|
||||||
primaryFields: Field[];
|
|
||||||
secondaryFields: Field[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const passDict = Joi.object().keys({
|
|
||||||
auxiliaryFields: Joi.array().items(
|
|
||||||
Joi.object()
|
|
||||||
.keys({
|
|
||||||
row: Joi.number().max(1).min(0),
|
|
||||||
})
|
|
||||||
.concat(field),
|
|
||||||
),
|
|
||||||
backFields: Joi.array().items(field),
|
|
||||||
headerFields: Joi.array().items(field),
|
|
||||||
primaryFields: Joi.array().items(field),
|
|
||||||
secondaryFields: Joi.array().items(field),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TransitType =
|
|
||||||
| "PKTransitTypeAir"
|
|
||||||
| "PKTransitTypeBoat"
|
|
||||||
| "PKTransitTypeBus"
|
|
||||||
| "PKTransitTypeGeneric"
|
|
||||||
| "PKTransitTypeTrain";
|
|
||||||
|
|
||||||
const transitType = Joi.string().regex(
|
|
||||||
/(PKTransitTypeAir|PKTransitTypeBoat|PKTransitTypeBus|PKTransitTypeGeneric|PKTransitTypeTrain)/,
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface NFC {
|
|
||||||
message: string;
|
|
||||||
encryptionPublicKey?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nfcDict = Joi.object().keys({
|
|
||||||
message: Joi.string().required().max(64),
|
|
||||||
encryptionPublicKey: Joi.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// ************************************* //
|
|
||||||
// *** Personalizable Passes Schemas *** //
|
|
||||||
// ************************************* //
|
|
||||||
|
|
||||||
export interface Personalization {
|
|
||||||
requiredPersonalizationFields: PRSField[];
|
|
||||||
description: string;
|
|
||||||
termsAndConditions?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type PRSField =
|
|
||||||
| "PKPassPersonalizationFieldName"
|
|
||||||
| "PKPassPersonalizationFieldPostalCode"
|
|
||||||
| "PKPassPersonalizationFieldEmailAddress"
|
|
||||||
| "PKPassPersonalizationFieldPhoneNumber";
|
|
||||||
|
|
||||||
const personalizationDict = Joi.object().keys({
|
|
||||||
requiredPersonalizationFields: Joi.array()
|
|
||||||
.items(
|
|
||||||
"PKPassPersonalizationFieldName",
|
|
||||||
"PKPassPersonalizationFieldPostalCode",
|
|
||||||
"PKPassPersonalizationFieldEmailAddress",
|
|
||||||
"PKPassPersonalizationFieldPhoneNumber",
|
|
||||||
)
|
|
||||||
.required(),
|
|
||||||
description: Joi.string().required(),
|
|
||||||
termsAndConditions: Joi.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// --------- UTILITIES ---------- //
|
|
||||||
|
|
||||||
const schemas = {
|
|
||||||
instance,
|
|
||||||
certificatesSchema,
|
|
||||||
barcode,
|
|
||||||
field,
|
|
||||||
passDict,
|
|
||||||
beaconsDict,
|
|
||||||
locationsDict,
|
|
||||||
transitType,
|
|
||||||
nfcDict,
|
|
||||||
supportedOptions,
|
|
||||||
personalizationDict,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Schema = keyof typeof schemas;
|
|
||||||
export type ArrayPassSchema = Beacon | Location | Barcode;
|
|
||||||
|
|
||||||
function resolveSchemaName(name: Schema) {
|
|
||||||
return schemas[name] || undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the passed options are compliant with the indicated schema
|
|
||||||
* @param {any} opts - options to be checks
|
|
||||||
* @param {string} schemaName - the indicated schema (will be converted)
|
|
||||||
* @returns {boolean} - result of the check
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function isValid(opts: any, schemaName: Schema): boolean {
|
|
||||||
const resolvedSchema = resolveSchemaName(schemaName);
|
|
||||||
|
|
||||||
if (!resolvedSchema) {
|
|
||||||
schemaDebug(
|
|
||||||
`validation failed due to missing or mispelled schema name`,
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validation = resolvedSchema.validate(opts);
|
|
||||||
|
|
||||||
if (validation.error) {
|
|
||||||
schemaDebug(
|
|
||||||
`validation failed due to error: ${validation.error.message}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return !validation.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the validation in verbose mode, exposing the value or an empty object
|
|
||||||
* @param {object} opts - to be validated
|
|
||||||
* @param {*} schemaName - selected schema
|
|
||||||
* @returns {object} the filtered value or empty object
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function getValidated<T extends Object>(
|
|
||||||
opts: any,
|
|
||||||
schemaName: Schema,
|
|
||||||
): T | null {
|
|
||||||
const resolvedSchema = resolveSchemaName(schemaName);
|
|
||||||
|
|
||||||
if (!resolvedSchema) {
|
|
||||||
schemaDebug(
|
|
||||||
`validation failed due to missing or mispelled schema name`,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validation = resolvedSchema.validate(opts, { stripUnknown: true });
|
|
||||||
|
|
||||||
if (validation.error) {
|
|
||||||
schemaDebug(
|
|
||||||
`Validation failed in getValidated due to error: ${validation.error.message}`,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return validation.value;
|
|
||||||
}
|
|
||||||
26
src/schemas/barcode.ts
Normal file
26
src/schemas/barcode.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
|
||||||
|
export type BarcodeFormat =
|
||||||
|
| "PKBarcodeFormatQR"
|
||||||
|
| "PKBarcodeFormatPDF417"
|
||||||
|
| "PKBarcodeFormatAztec"
|
||||||
|
| "PKBarcodeFormatCode128";
|
||||||
|
|
||||||
|
export interface Barcode {
|
||||||
|
altText?: string;
|
||||||
|
messageEncoding?: string;
|
||||||
|
format: BarcodeFormat;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Barcode = Joi.object<Barcode>().keys({
|
||||||
|
altText: Joi.string(),
|
||||||
|
messageEncoding: Joi.string().default("iso-8859-1"),
|
||||||
|
format: Joi.string()
|
||||||
|
.required()
|
||||||
|
.regex(
|
||||||
|
/(PKBarcodeFormatQR|PKBarcodeFormatPDF417|PKBarcodeFormatAztec|PKBarcodeFormatCode128)/,
|
||||||
|
"barcodeType",
|
||||||
|
),
|
||||||
|
message: Joi.string().required(),
|
||||||
|
});
|
||||||
19
src/schemas/beacon.ts
Normal file
19
src/schemas/beacon.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
|
||||||
|
export interface Beacon {
|
||||||
|
major?: number;
|
||||||
|
minor?: number;
|
||||||
|
relevantText?: string;
|
||||||
|
proximityUUID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Beacon = Joi.object<Beacon>().keys({
|
||||||
|
major: Joi.number()
|
||||||
|
.integer()
|
||||||
|
.positive()
|
||||||
|
.max(65535)
|
||||||
|
.greater(Joi.ref("minor")),
|
||||||
|
minor: Joi.number().integer().min(0).max(65535),
|
||||||
|
proximityUUID: Joi.string().required(),
|
||||||
|
relevantText: Joi.string(),
|
||||||
|
});
|
||||||
70
src/schemas/field.ts
Normal file
70
src/schemas/field.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
import { Semantics } from "./semantics";
|
||||||
|
|
||||||
|
export interface Field {
|
||||||
|
attributedValue?: string | number | Date;
|
||||||
|
changeMessage?: string;
|
||||||
|
dataDetectorTypes?: string[];
|
||||||
|
label?: string;
|
||||||
|
textAlignment?: string;
|
||||||
|
key: string;
|
||||||
|
value: string | number | Date;
|
||||||
|
semantics?: Semantics;
|
||||||
|
dateStyle?: string;
|
||||||
|
ignoresTimeZone?: boolean;
|
||||||
|
isRelative?: boolean;
|
||||||
|
timeStyle?: string;
|
||||||
|
currencyCode?: string;
|
||||||
|
numberStyle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Field = Joi.object<Field>().keys({
|
||||||
|
attributedValue: Joi.alternatives(
|
||||||
|
Joi.string().allow(""),
|
||||||
|
Joi.number(),
|
||||||
|
Joi.date().iso(),
|
||||||
|
),
|
||||||
|
changeMessage: Joi.string(),
|
||||||
|
dataDetectorTypes: Joi.array().items(
|
||||||
|
Joi.string().regex(
|
||||||
|
/(PKDataDetectorTypePhoneNumber|PKDataDetectorTypeLink|PKDataDetectorTypeAddress|PKDataDetectorTypeCalendarEvent)/,
|
||||||
|
"dataDetectorType",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
label: Joi.string().allow(""),
|
||||||
|
textAlignment: Joi.string().regex(
|
||||||
|
/(PKTextAlignmentLeft|PKTextAlignmentCenter|PKTextAlignmentRight|PKTextAlignmentNatural)/,
|
||||||
|
"graphic-alignment",
|
||||||
|
),
|
||||||
|
key: Joi.string().required(),
|
||||||
|
value: Joi.alternatives(
|
||||||
|
Joi.string().allow(""),
|
||||||
|
Joi.number(),
|
||||||
|
Joi.date().iso(),
|
||||||
|
).required(),
|
||||||
|
semantics: Semantics,
|
||||||
|
// date fields formatters, all optionals
|
||||||
|
dateStyle: Joi.string().regex(
|
||||||
|
/(PKDateStyleNone|PKDateStyleShort|PKDateStyleMedium|PKDateStyleLong|PKDateStyleFull)/,
|
||||||
|
"date style",
|
||||||
|
),
|
||||||
|
ignoresTimeZone: Joi.boolean(),
|
||||||
|
isRelative: Joi.boolean(),
|
||||||
|
timeStyle: Joi.string().regex(
|
||||||
|
/(PKDateStyleNone|PKDateStyleShort|PKDateStyleMedium|PKDateStyleLong|PKDateStyleFull)/,
|
||||||
|
"date style",
|
||||||
|
),
|
||||||
|
// number fields formatters, all optionals
|
||||||
|
currencyCode: Joi.string().when("value", {
|
||||||
|
is: Joi.number(),
|
||||||
|
otherwise: Joi.string().forbidden(),
|
||||||
|
}),
|
||||||
|
numberStyle: Joi.string()
|
||||||
|
.regex(
|
||||||
|
/(PKNumberStyleDecimal|PKNumberStylePercent|PKNumberStyleScientific|PKNumberStyleSpellOut)/,
|
||||||
|
)
|
||||||
|
.when("value", {
|
||||||
|
is: Joi.number(),
|
||||||
|
otherwise: Joi.string().forbidden(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
244
src/schemas/index.ts
Normal file
244
src/schemas/index.ts
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
export * from "./barcode";
|
||||||
|
export * from "./beacon";
|
||||||
|
export * from "./location";
|
||||||
|
export * from "./field";
|
||||||
|
export * from "./nfc";
|
||||||
|
export * from "./semantics";
|
||||||
|
export * from "./passFields";
|
||||||
|
export * from "./personalization";
|
||||||
|
|
||||||
|
import Joi from "joi";
|
||||||
|
import debug from "debug";
|
||||||
|
|
||||||
|
import { Barcode } from "./barcode";
|
||||||
|
import { Location } from "./location";
|
||||||
|
import { Beacon } from "./beacon";
|
||||||
|
import { NFC } from "./nfc";
|
||||||
|
import { Field } from "./field";
|
||||||
|
import { PassFields, TransitType } from "./passFields";
|
||||||
|
import { Personalization } from "./personalization";
|
||||||
|
|
||||||
|
const schemaDebug = debug("Schema");
|
||||||
|
|
||||||
|
export interface Manifest {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Certificates {
|
||||||
|
wwdr?: string;
|
||||||
|
signerCert?: string;
|
||||||
|
signerKey?:
|
||||||
|
| {
|
||||||
|
keyFile: string;
|
||||||
|
passphrase?: string;
|
||||||
|
}
|
||||||
|
| string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FactoryOptions {
|
||||||
|
model: BundleUnit | string;
|
||||||
|
certificates: Certificates;
|
||||||
|
overrides?: OverridesSupportedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BundleUnit {
|
||||||
|
[key: string]: Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PartitionedBundle {
|
||||||
|
bundle: BundleUnit;
|
||||||
|
l10nBundle: {
|
||||||
|
[key: string]: BundleUnit;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CertificatesSchema {
|
||||||
|
wwdr: string;
|
||||||
|
signerCert: string;
|
||||||
|
signerKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CertificatesSchema = Joi.object<CertificatesSchema>()
|
||||||
|
.keys({
|
||||||
|
wwdr: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
||||||
|
signerCert: Joi.alternatives(Joi.binary(), Joi.string()).required(),
|
||||||
|
signerKey: Joi.alternatives()
|
||||||
|
.try(
|
||||||
|
Joi.object().keys({
|
||||||
|
keyFile: Joi.alternatives(
|
||||||
|
Joi.binary(),
|
||||||
|
Joi.string(),
|
||||||
|
).required(),
|
||||||
|
passphrase: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
Joi.alternatives(Joi.binary(), Joi.string()),
|
||||||
|
)
|
||||||
|
.required(),
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export interface PassInstance {
|
||||||
|
model: PartitionedBundle;
|
||||||
|
certificates: CertificatesSchema;
|
||||||
|
overrides?: OverridesSupportedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PassInstance = Joi.object<PassInstance>().keys({
|
||||||
|
model: Joi.alternatives(Joi.object(), Joi.string()).required(),
|
||||||
|
certificates: Joi.object(),
|
||||||
|
overrides: Joi.object(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface OverridesSupportedOptions {
|
||||||
|
serialNumber?: string;
|
||||||
|
description?: string;
|
||||||
|
organizationName?: string;
|
||||||
|
passTypeIdentifier?: string;
|
||||||
|
teamIdentifier?: string;
|
||||||
|
appLaunchURL?: string;
|
||||||
|
associatedStoreIdentifiers?: Array<number>;
|
||||||
|
userInfo?: { [key: string]: any };
|
||||||
|
webServiceURL?: string;
|
||||||
|
authenticationToken?: string;
|
||||||
|
sharingProhibited?: boolean;
|
||||||
|
backgroundColor?: string;
|
||||||
|
foregroundColor?: string;
|
||||||
|
labelColor?: string;
|
||||||
|
groupingIdentifier?: string;
|
||||||
|
suppressStripShine?: boolean;
|
||||||
|
logoText?: string;
|
||||||
|
maxDistance?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OverridesSupportedOptions = Joi.object<OverridesSupportedOptions>()
|
||||||
|
.keys({
|
||||||
|
serialNumber: Joi.string(),
|
||||||
|
description: Joi.string(),
|
||||||
|
organizationName: Joi.string(),
|
||||||
|
passTypeIdentifier: Joi.string(),
|
||||||
|
teamIdentifier: Joi.string(),
|
||||||
|
appLaunchURL: Joi.string(),
|
||||||
|
associatedStoreIdentifiers: Joi.array().items(Joi.number()),
|
||||||
|
userInfo: Joi.alternatives(Joi.object().unknown(), Joi.array()),
|
||||||
|
// parsing url as set of words and nums followed by dots, optional port and any possible path after
|
||||||
|
webServiceURL: Joi.string().regex(
|
||||||
|
/https?:\/\/(?:[a-z0-9]+\.?)+(?::\d{2,})?(?:\/[\S]+)*/,
|
||||||
|
),
|
||||||
|
authenticationToken: Joi.string().min(16),
|
||||||
|
sharingProhibited: Joi.boolean(),
|
||||||
|
backgroundColor: Joi.string().min(10).max(16),
|
||||||
|
foregroundColor: Joi.string().min(10).max(16),
|
||||||
|
labelColor: Joi.string().min(10).max(16),
|
||||||
|
groupingIdentifier: Joi.string(),
|
||||||
|
suppressStripShine: Joi.boolean(),
|
||||||
|
logoText: Joi.string(),
|
||||||
|
maxDistance: Joi.number().positive(),
|
||||||
|
})
|
||||||
|
.with("webServiceURL", "authenticationToken");
|
||||||
|
|
||||||
|
export interface ValidPassType {
|
||||||
|
boardingPass?: PassFields & { transitType: TransitType };
|
||||||
|
eventTicket?: PassFields;
|
||||||
|
coupon?: PassFields;
|
||||||
|
generic?: PassFields;
|
||||||
|
storeCard?: PassFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PassInterfacesProps {
|
||||||
|
barcode?: Barcode;
|
||||||
|
barcodes?: Barcode[];
|
||||||
|
beacons?: Beacon[];
|
||||||
|
locations?: Location[];
|
||||||
|
maxDistance?: number;
|
||||||
|
relevantDate?: string;
|
||||||
|
nfc?: NFC;
|
||||||
|
expirationDate?: string;
|
||||||
|
voided?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type AllPassProps = PassInterfacesProps &
|
||||||
|
ValidPassType &
|
||||||
|
OverridesSupportedOptions;
|
||||||
|
export type ValidPass = {
|
||||||
|
[K in keyof AllPassProps]: AllPassProps[K];
|
||||||
|
};
|
||||||
|
export type PassColors = Pick<
|
||||||
|
OverridesSupportedOptions,
|
||||||
|
"backgroundColor" | "foregroundColor" | "labelColor"
|
||||||
|
>;
|
||||||
|
|
||||||
|
// --------- UTILITIES ---------- //
|
||||||
|
|
||||||
|
type AvailableSchemas =
|
||||||
|
| typeof Barcode
|
||||||
|
| typeof Location
|
||||||
|
| typeof Beacon
|
||||||
|
| typeof NFC
|
||||||
|
| typeof Field
|
||||||
|
| typeof PassFields
|
||||||
|
| typeof Personalization
|
||||||
|
| typeof TransitType
|
||||||
|
| typeof PassInstance
|
||||||
|
| typeof CertificatesSchema
|
||||||
|
| typeof OverridesSupportedOptions;
|
||||||
|
|
||||||
|
export type ArrayPassSchema = Beacon | Location | Barcode;
|
||||||
|
|
||||||
|
/* function resolveSchemaName(name: Schema) {
|
||||||
|
return schemas[name] || undefined;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Checks if the passed options are compliant with the indicated schema
|
||||||
|
* @param {any} opts - options to be checks
|
||||||
|
* @param {string} schemaName - the indicated schema (will be converted)
|
||||||
|
* @returns {boolean} - result of the check
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function isValid(opts: any, schema: AvailableSchemas): boolean {
|
||||||
|
if (!schema) {
|
||||||
|
schemaDebug(
|
||||||
|
`validation failed due to missing or mispelled schema name`,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validation = schema.validate(opts);
|
||||||
|
|
||||||
|
if (validation.error) {
|
||||||
|
schemaDebug(
|
||||||
|
`validation failed due to error: ${validation.error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !validation.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the validation in verbose mode, exposing the value or an empty object
|
||||||
|
* @param {object} opts - to be validated
|
||||||
|
* @param {*} schemaName - selected schema
|
||||||
|
* @returns {object} the filtered value or empty object
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getValidated<T extends Object>(
|
||||||
|
opts: T,
|
||||||
|
schema: AvailableSchemas,
|
||||||
|
): T | null {
|
||||||
|
if (!schema) {
|
||||||
|
schemaDebug(`validation failed due to missing schema`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validation = schema.validate(opts, { stripUnknown: true });
|
||||||
|
|
||||||
|
if (validation.error) {
|
||||||
|
schemaDebug(
|
||||||
|
`Validation failed in getValidated due to error: ${validation.error.message}`,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return validation.value;
|
||||||
|
}
|
||||||
15
src/schemas/location.ts
Normal file
15
src/schemas/location.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
|
||||||
|
export interface Location {
|
||||||
|
relevantText?: string;
|
||||||
|
altitude?: number;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Location = Joi.object<Location>().keys({
|
||||||
|
altitude: Joi.number(),
|
||||||
|
latitude: Joi.number().required(),
|
||||||
|
longitude: Joi.number().required(),
|
||||||
|
relevantText: Joi.string(),
|
||||||
|
});
|
||||||
11
src/schemas/nfc.ts
Normal file
11
src/schemas/nfc.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
|
||||||
|
export interface NFC {
|
||||||
|
message: string;
|
||||||
|
encryptionPublicKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NFC = Joi.object<NFC>().keys({
|
||||||
|
message: Joi.string().required().max(64),
|
||||||
|
encryptionPublicKey: Joi.string(),
|
||||||
|
});
|
||||||
35
src/schemas/passFields.ts
Normal file
35
src/schemas/passFields.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
import { Field } from "./field";
|
||||||
|
|
||||||
|
export interface PassFields {
|
||||||
|
auxiliaryFields: (Field & { row?: number })[];
|
||||||
|
backFields: Field[];
|
||||||
|
headerFields: Field[];
|
||||||
|
primaryFields: Field[];
|
||||||
|
secondaryFields: Field[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PassFields = Joi.object<PassFields>().keys({
|
||||||
|
auxiliaryFields: Joi.array().items(
|
||||||
|
Joi.object()
|
||||||
|
.keys({
|
||||||
|
row: Joi.number().max(1).min(0),
|
||||||
|
})
|
||||||
|
.concat(Field),
|
||||||
|
),
|
||||||
|
backFields: Joi.array().items(Field),
|
||||||
|
headerFields: Joi.array().items(Field),
|
||||||
|
primaryFields: Joi.array().items(Field),
|
||||||
|
secondaryFields: Joi.array().items(Field),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TransitType =
|
||||||
|
| "PKTransitTypeAir"
|
||||||
|
| "PKTransitTypeBoat"
|
||||||
|
| "PKTransitTypeBus"
|
||||||
|
| "PKTransitTypeGeneric"
|
||||||
|
| "PKTransitTypeTrain";
|
||||||
|
|
||||||
|
export const TransitType = Joi.string().regex(
|
||||||
|
/(PKTransitTypeAir|PKTransitTypeBoat|PKTransitTypeBus|PKTransitTypeGeneric|PKTransitTypeTrain)/,
|
||||||
|
);
|
||||||
26
src/schemas/personalization.ts
Normal file
26
src/schemas/personalization.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
|
||||||
|
export interface Personalization {
|
||||||
|
description: string;
|
||||||
|
requiredPersonalizationFields: RequiredPersonalizationFields[];
|
||||||
|
termsAndConditions?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequiredPersonalizationFields =
|
||||||
|
| "PKPassPersonalizationFieldName"
|
||||||
|
| "PKPassPersonalizationFieldPostalCode"
|
||||||
|
| "PKPassPersonalizationFieldEmailAddress"
|
||||||
|
| "PKPassPersonalizationFieldPhoneNumber";
|
||||||
|
|
||||||
|
export const Personalization = Joi.object<Personalization>().keys({
|
||||||
|
description: Joi.string().required(),
|
||||||
|
requiredPersonalizationFields: Joi.array()
|
||||||
|
.items(
|
||||||
|
"PKPassPersonalizationFieldName",
|
||||||
|
"PKPassPersonalizationFieldPostalCode",
|
||||||
|
"PKPassPersonalizationFieldEmailAddress",
|
||||||
|
"PKPassPersonalizationFieldPhoneNumber",
|
||||||
|
)
|
||||||
|
.required(),
|
||||||
|
termsAndConditions: Joi.string(),
|
||||||
|
});
|
||||||
267
src/schemas/semantics.ts
Normal file
267
src/schemas/semantics.ts
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a better description of every single field,
|
||||||
|
* please refer to Apple official documentation.
|
||||||
|
*
|
||||||
|
* @see https://developer.apple.com/documentation/walletpasses/semantictags
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://developer.apple.com/documentation/walletpasses/semantictagtype
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare namespace SemanticTagType {
|
||||||
|
interface PersonNameComponents {
|
||||||
|
familyName?: string;
|
||||||
|
givenName?: string;
|
||||||
|
middleName?: string;
|
||||||
|
namePrefix?: string;
|
||||||
|
nameSuffix?: string;
|
||||||
|
nickname?: string;
|
||||||
|
phoneticRepresentation?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CurrencyAmount {
|
||||||
|
currencyCode?: string; // ISO 4217 currency code
|
||||||
|
amount?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Location {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Seat {
|
||||||
|
seatSection?: string;
|
||||||
|
seatRow?: string;
|
||||||
|
seatNumber?: string;
|
||||||
|
seatIdentifier?: string;
|
||||||
|
seatType?: string;
|
||||||
|
seatDescription?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WifiNetwork {
|
||||||
|
password: string;
|
||||||
|
ssid: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CurrencyAmount = Joi.object<SemanticTagType.CurrencyAmount>().keys({
|
||||||
|
currencyCode: Joi.string(),
|
||||||
|
amount: Joi.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const PersonNameComponent = Joi.object<SemanticTagType.PersonNameComponents>().keys(
|
||||||
|
{
|
||||||
|
givenName: Joi.string(),
|
||||||
|
familyName: Joi.string(),
|
||||||
|
middleName: Joi.string(),
|
||||||
|
namePrefix: Joi.string(),
|
||||||
|
nameSuffix: Joi.string(),
|
||||||
|
nickname: Joi.string(),
|
||||||
|
phoneticRepresentation: Joi.string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const seat = Joi.object<SemanticTagType.Seat>().keys({
|
||||||
|
seatSection: Joi.string(),
|
||||||
|
seatRow: Joi.string(),
|
||||||
|
seatNumber: Joi.string(),
|
||||||
|
seatIdentifier: Joi.string(),
|
||||||
|
seatType: Joi.string(),
|
||||||
|
seatDescription: Joi.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const location = Joi.object<SemanticTagType.Location>().keys({
|
||||||
|
latitude: Joi.number().required(),
|
||||||
|
longitude: Joi.number().required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const WifiNetwork = Joi.object<SemanticTagType.WifiNetwork>().keys({
|
||||||
|
password: Joi.string().required(),
|
||||||
|
ssid: Joi.string().required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alphabetical order
|
||||||
|
* @see https://developer.apple.com/documentation/walletpasses/semantictags
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Semantics {
|
||||||
|
airlineCode?: string;
|
||||||
|
artistIDs?: string[];
|
||||||
|
awayTeamAbbreviation?: string;
|
||||||
|
awayTeamLocation?: string;
|
||||||
|
awayTeamName?: string;
|
||||||
|
|
||||||
|
balance?: SemanticTagType.CurrencyAmount;
|
||||||
|
boardingGroup?: string;
|
||||||
|
boardingSequenceNumber?: string;
|
||||||
|
|
||||||
|
carNumber?: string;
|
||||||
|
confirmationNumber?: string;
|
||||||
|
currentArrivalDate?: string;
|
||||||
|
currentBoardingDate?: string;
|
||||||
|
currentDepartureDate?: string;
|
||||||
|
|
||||||
|
departureAirportCode?: string;
|
||||||
|
departureAirportName?: string;
|
||||||
|
departureGate?: string;
|
||||||
|
departureLocation?: SemanticTagType.Location;
|
||||||
|
departureLocationDescription?: string;
|
||||||
|
departurePlatform?: string;
|
||||||
|
departureStationName?: string;
|
||||||
|
departureTerminal?: string;
|
||||||
|
destinationAirportCode?: string;
|
||||||
|
destinationAirportName?: string;
|
||||||
|
destinationGate?: string;
|
||||||
|
destinationLocation?: SemanticTagType.Location;
|
||||||
|
destinationLocationDescription?: string;
|
||||||
|
destinationPlatform?: string;
|
||||||
|
destinationStationName?: string;
|
||||||
|
destinationTerminal?: string;
|
||||||
|
duration?: number;
|
||||||
|
|
||||||
|
eventEndDate?: string;
|
||||||
|
eventName?: string;
|
||||||
|
eventStartDate?: string;
|
||||||
|
eventType?:
|
||||||
|
| "PKEventTypeGeneric"
|
||||||
|
| "PKEventTypeLivePerformance"
|
||||||
|
| "PKEventTypeMovie"
|
||||||
|
| "PKEventTypeSports"
|
||||||
|
| "PKEventTypeConference"
|
||||||
|
| "PKEventTypeConvention"
|
||||||
|
| "PKEventTypeWorkshop"
|
||||||
|
| "PKEventTypeSocialGathering";
|
||||||
|
|
||||||
|
flightCode?: string;
|
||||||
|
flightNumber?: number;
|
||||||
|
|
||||||
|
genre?: string;
|
||||||
|
|
||||||
|
homeTeamAbbreviation?: string;
|
||||||
|
homeTeamLocation?: string;
|
||||||
|
homeTeamName?: string;
|
||||||
|
leagueAbbreviation?: string;
|
||||||
|
leagueName?: string;
|
||||||
|
|
||||||
|
membershipProgramName?: string;
|
||||||
|
membershipProgramNumber?: string;
|
||||||
|
|
||||||
|
originalArrivalDate?: string;
|
||||||
|
originalBoardingDate?: string;
|
||||||
|
originalDepartureDate?: string;
|
||||||
|
|
||||||
|
passengerName?: SemanticTagType.PersonNameComponents;
|
||||||
|
performerNames?: string[];
|
||||||
|
priorityStatus?: string;
|
||||||
|
|
||||||
|
seats?: SemanticTagType.Seat[];
|
||||||
|
securityScreening?: string;
|
||||||
|
silenceRequested?: boolean;
|
||||||
|
sportName?: string;
|
||||||
|
|
||||||
|
totalPrice?: SemanticTagType.CurrencyAmount;
|
||||||
|
transitProvider?: string;
|
||||||
|
transitStatus?: string;
|
||||||
|
transitStatusReason?: string;
|
||||||
|
|
||||||
|
vehicleName?: string;
|
||||||
|
vehicleNumber?: string;
|
||||||
|
vehicleType?: string;
|
||||||
|
venueEntrance?: string;
|
||||||
|
venueLocation?: SemanticTagType.Location;
|
||||||
|
venueName?: string;
|
||||||
|
venuePhoneNumber?: string;
|
||||||
|
venueRoom?: string;
|
||||||
|
|
||||||
|
wifiAccess?: SemanticTagType.WifiNetwork;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Semantics = Joi.object<Semantics>().keys({
|
||||||
|
airlineCode: Joi.string(),
|
||||||
|
artistIDs: Joi.array().items(Joi.string()),
|
||||||
|
awayTeamAbbreviation: Joi.string(),
|
||||||
|
awayTeamLocation: Joi.string(),
|
||||||
|
awayTeamName: Joi.string(),
|
||||||
|
|
||||||
|
balance: CurrencyAmount,
|
||||||
|
boardingGroup: Joi.string(),
|
||||||
|
boardingSequenceNumber: Joi.string(),
|
||||||
|
|
||||||
|
carNumber: Joi.string(),
|
||||||
|
confirmationNumber: Joi.string(),
|
||||||
|
currentArrivalDate: Joi.string(),
|
||||||
|
currentBoardingDate: Joi.string(),
|
||||||
|
currentDepartureDate: Joi.string(),
|
||||||
|
|
||||||
|
departureAirportCode: Joi.string(),
|
||||||
|
departureAirportName: Joi.string(),
|
||||||
|
departureGate: Joi.string(),
|
||||||
|
departureLocation: location,
|
||||||
|
departureLocationDescription: Joi.string(),
|
||||||
|
departurePlatform: Joi.string(),
|
||||||
|
departureStationName: Joi.string(),
|
||||||
|
departureTerminal: Joi.string(),
|
||||||
|
destinationAirportCode: Joi.string(),
|
||||||
|
destinationAirportName: Joi.string(),
|
||||||
|
destinationGate: Joi.string(),
|
||||||
|
destinationLocation: location,
|
||||||
|
destinationLocationDescription: Joi.string(),
|
||||||
|
destinationPlatform: Joi.string(),
|
||||||
|
destinationStationName: Joi.string(),
|
||||||
|
destinationTerminal: Joi.string(),
|
||||||
|
duration: Joi.number(),
|
||||||
|
|
||||||
|
eventEndDate: Joi.string(),
|
||||||
|
eventName: Joi.string(),
|
||||||
|
eventStartDate: Joi.string(),
|
||||||
|
eventType: Joi.string().regex(
|
||||||
|
/(PKEventTypeGeneric|PKEventTypeLivePerformance|PKEventTypeMovie|PKEventTypeSports|PKEventTypeConference|PKEventTypeConvention|PKEventTypeWorkshop|PKEventTypeSocialGathering)/,
|
||||||
|
),
|
||||||
|
|
||||||
|
flightCode: Joi.string(),
|
||||||
|
flightNumber: Joi.number(),
|
||||||
|
|
||||||
|
genre: Joi.string(),
|
||||||
|
|
||||||
|
homeTeamAbbreviation: Joi.string(),
|
||||||
|
homeTeamLocation: Joi.string(),
|
||||||
|
homeTeamName: Joi.string(),
|
||||||
|
leagueAbbreviation: Joi.string(),
|
||||||
|
leagueName: Joi.string(),
|
||||||
|
|
||||||
|
membershipProgramName: Joi.string(),
|
||||||
|
membershipProgramNumber: Joi.string(),
|
||||||
|
|
||||||
|
originalArrivalDate: Joi.string(),
|
||||||
|
originalBoardingDate: Joi.string(),
|
||||||
|
originalDepartureDate: Joi.string(),
|
||||||
|
|
||||||
|
passengerName: PersonNameComponent,
|
||||||
|
performerNames: Joi.array().items(Joi.string()),
|
||||||
|
priorityStatus: Joi.string(),
|
||||||
|
|
||||||
|
seats: Joi.array().items(seat),
|
||||||
|
securityScreening: Joi.string(),
|
||||||
|
silenceRequested: Joi.boolean(),
|
||||||
|
sportName: Joi.string(),
|
||||||
|
|
||||||
|
totalPrice: CurrencyAmount,
|
||||||
|
transitProvider: Joi.string(),
|
||||||
|
transitStatus: Joi.string(),
|
||||||
|
transitStatusReason: Joi.string(),
|
||||||
|
|
||||||
|
vehicleName: Joi.string(),
|
||||||
|
vehicleNumber: Joi.string(),
|
||||||
|
vehicleType: Joi.string(),
|
||||||
|
venueEntrance: Joi.string(),
|
||||||
|
venueLocation: location,
|
||||||
|
venueName: Joi.string(),
|
||||||
|
venuePhoneNumber: Joi.string(),
|
||||||
|
venueRoom: Joi.string(),
|
||||||
|
|
||||||
|
wifiAccess: Joi.array().items(WifiNetwork),
|
||||||
|
});
|
||||||
10
src/utils.ts
10
src/utils.ts
@@ -1,5 +1,5 @@
|
|||||||
import { EOL } from "os";
|
import { EOL } from "os";
|
||||||
import { PartitionedBundle, BundleUnit } from "./schema";
|
import type * as Schemas from "./schemas";
|
||||||
import { sep } from "path";
|
import { sep } from "path";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,12 +118,12 @@ export function generateStringFile(lang: { [index: string]: string }): Buffer {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
type PartitionedBundleElements = [
|
type PartitionedBundleElements = [
|
||||||
PartitionedBundle["l10nBundle"],
|
Schemas.PartitionedBundle["l10nBundle"],
|
||||||
PartitionedBundle["bundle"],
|
Schemas.PartitionedBundle["bundle"],
|
||||||
];
|
];
|
||||||
|
|
||||||
export function splitBufferBundle(
|
export function splitBufferBundle(
|
||||||
origin: BundleUnit,
|
origin: Schemas.BundleUnit,
|
||||||
): PartitionedBundleElements {
|
): PartitionedBundleElements {
|
||||||
const initialValue: PartitionedBundleElements = [{}, {}];
|
const initialValue: PartitionedBundleElements = [{}, {}];
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ export function hasFilesWithName(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function deletePersonalization(
|
export function deletePersonalization(
|
||||||
source: BundleUnit,
|
source: Schemas.BundleUnit,
|
||||||
logosNames: string[] = [],
|
logosNames: string[] = [],
|
||||||
): void {
|
): void {
|
||||||
[...logosNames, "personalization.json"].forEach(
|
[...logosNames, "personalization.json"].forEach(
|
||||||
|
|||||||
Reference in New Issue
Block a user