@nimee/wallet-generator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/apple/ApplePassGenerator.d.ts +71 -0
  2. package/dist/apple/ApplePassGenerator.js +262 -0
  3. package/dist/apple/ApplePassGenerator.js.map +1 -0
  4. package/dist/apple/signPassWorker.d.ts +1 -0
  5. package/dist/apple/signPassWorker.js +44 -0
  6. package/dist/apple/signPassWorker.js.map +1 -0
  7. package/dist/apple/template.pass/icon.png +0 -0
  8. package/dist/apple/template.pass/icon@2x.png +0 -0
  9. package/dist/apple/template.pass/logo.png +0 -0
  10. package/dist/apple/template.pass/logo@2x.png +0 -0
  11. package/dist/apple/template.pass/pass.json +16 -0
  12. package/dist/apple/template.pass/template.pass/icon.png +0 -0
  13. package/dist/apple/template.pass/template.pass/icon@2x.png +0 -0
  14. package/dist/apple/template.pass/template.pass/logo.png +0 -0
  15. package/dist/apple/template.pass/template.pass/logo@2x.png +0 -0
  16. package/dist/apple/template.pass/template.pass/pass.json +16 -0
  17. package/dist/google/GooglePassGenerator.d.ts +31 -0
  18. package/dist/google/GooglePassGenerator.js +103 -0
  19. package/dist/google/GooglePassGenerator.js.map +1 -0
  20. package/dist/google/googleAuth.d.ts +11 -0
  21. package/dist/google/googleAuth.js +99 -0
  22. package/dist/google/googleAuth.js.map +1 -0
  23. package/dist/helpers/colorHelpers.d.ts +9 -0
  24. package/dist/helpers/colorHelpers.js +32 -0
  25. package/dist/helpers/colorHelpers.js.map +1 -0
  26. package/dist/helpers/imageHelpers.d.ts +23 -0
  27. package/dist/helpers/imageHelpers.js +94 -0
  28. package/dist/helpers/imageHelpers.js.map +1 -0
  29. package/dist/index.d.ts +5 -0
  30. package/dist/index.js +25 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/types.d.ts +65 -0
  33. package/dist/types.js +3 -0
  34. package/dist/types.js.map +1 -0
  35. package/jest.config.js +18 -0
  36. package/package.json +38 -0
  37. package/src/apple/ApplePassGenerator.ts +291 -0
  38. package/src/apple/signPassWorker.ts +52 -0
  39. package/src/apple/template.pass/icon.png +0 -0
  40. package/src/apple/template.pass/icon@2x.png +0 -0
  41. package/src/apple/template.pass/logo.png +0 -0
  42. package/src/apple/template.pass/logo@2x.png +0 -0
  43. package/src/apple/template.pass/pass.json +16 -0
  44. package/src/google/GooglePassGenerator.ts +104 -0
  45. package/src/google/googleAuth.ts +134 -0
  46. package/src/helpers/colorHelpers.ts +34 -0
  47. package/src/helpers/imageHelpers.ts +87 -0
  48. package/src/index.ts +5 -0
  49. package/src/types.ts +66 -0
  50. package/tests/apple/ApplePassGenerator.test.ts +47 -0
  51. package/tests/google/GooglePassGenerator.test.ts +47 -0
  52. package/tests/helpers/colorHelpers.test.ts +30 -0
  53. package/tests/helpers/imageHelpers.test.ts +19 -0
  54. package/tsconfig.json +28 -0
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.buildGoogleTicketClass = exports.buildGoogleTicketObject = exports.ensureGoogleTicketClass = exports.getGoogleAccessToken = void 0;
16
+ const axios_1 = __importDefault(require("axios"));
17
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
18
+ // Intentional module-level singletons — shared across all Wallet instances
19
+ // (each pod maintains its own in-memory cache).
20
+ const googleTokenCache = new Map();
21
+ const googleClassSyncedAt = new Map(); // classId → epoch ms
22
+ const CLASS_SYNC_TTL_MS = 60 * 60 * 1000; // re-sync class at most once per hour
23
+ // ─── OAuth ────────────────────────────────────────────────────────────────────
24
+ /** Exchanges service account credentials for a Google OAuth2 access token (cached for ~1 hour). */
25
+ function getGoogleAccessToken(config) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ const cached = googleTokenCache.get(config.serviceAccountEmail);
28
+ if (cached && cached.expiresAt - Date.now() > 5 * 60 * 1000) {
29
+ return cached.accessToken;
30
+ }
31
+ const now = Math.floor(Date.now() / 1000);
32
+ const assertion = jsonwebtoken_1.default.sign({
33
+ iss: config.serviceAccountEmail,
34
+ sub: config.serviceAccountEmail,
35
+ aud: "https://oauth2.googleapis.com/token",
36
+ scope: "https://www.googleapis.com/auth/wallet_object.issuer",
37
+ iat: now,
38
+ exp: now + 3600,
39
+ }, config.privateKey, { algorithm: "RS256" });
40
+ const response = yield axios_1.default.post("https://oauth2.googleapis.com/token", new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion }), { headers: { "Content-Type": "application/x-www-form-urlencoded" }, timeout: 5000 });
41
+ const accessToken = response.data.access_token;
42
+ googleTokenCache.set(config.serviceAccountEmail, { accessToken, expiresAt: Date.now() + 3600 * 1000 });
43
+ return accessToken;
44
+ });
45
+ }
46
+ exports.getGoogleAccessToken = getGoogleAccessToken;
47
+ // ─── Class sync ───────────────────────────────────────────────────────────────
48
+ /**
49
+ * Ensures the Google Wallet EventTicketClass exists and is up to date.
50
+ * Skipped if successfully synced within the last hour.
51
+ * Attempts to create; if it already exists (409), patches instead.
52
+ */
53
+ function ensureGoogleTicketClass(config, data) {
54
+ var _a, _b, _c, _d, _e, _f;
55
+ return __awaiter(this, void 0, void 0, function* () {
56
+ const classId = config.classId || `${config.issuerId}.event-${data.classIdSuffix || data.objectId}`;
57
+ if (Date.now() - ((_a = googleClassSyncedAt.get(classId)) !== null && _a !== void 0 ? _a : 0) < CLASS_SYNC_TTL_MS) {
58
+ return;
59
+ }
60
+ const accessToken = yield getGoogleAccessToken(config);
61
+ const classBody = buildGoogleTicketClass(classId, data);
62
+ const baseUrl = "https://walletobjects.googleapis.com/walletobjects/v1/eventTicketClass";
63
+ const headers = { Authorization: `Bearer ${accessToken}` };
64
+ try {
65
+ yield axios_1.default.post(baseUrl, classBody, { headers, timeout: 5000 });
66
+ googleClassSyncedAt.set(classId, Date.now());
67
+ }
68
+ catch (err) {
69
+ if (((_b = err.response) === null || _b === void 0 ? void 0 : _b.status) === 409) {
70
+ yield axios_1.default.patch(`${baseUrl}/${encodeURIComponent(classId)}`, classBody, { headers, timeout: 5000 });
71
+ googleClassSyncedAt.set(classId, Date.now());
72
+ }
73
+ else {
74
+ throw new Error(`failed to sync Google Wallet class: ${(_f = (_e = (_d = (_c = err.response) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.error) === null || _e === void 0 ? void 0 : _e.message) !== null && _f !== void 0 ? _f : err.message}`);
75
+ }
76
+ }
77
+ });
78
+ }
79
+ exports.ensureGoogleTicketClass = ensureGoogleTicketClass;
80
+ // ─── Object / class builders ──────────────────────────────────────────────────
81
+ function buildGoogleTicketObject(objectId, classId, data) {
82
+ const dateDisplay = data.dateDisplay;
83
+ return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ id: objectId, classId, state: data.isCheckedIn ? "COMPLETED" : "ACTIVE", ticketHolderName: data.holderName, issueName: data.marketplaceName || "", ticketNumber: data.orderNumber || "" }, (data.logoUrl && { logo: { sourceUri: { uri: data.logoUrl } } })), { barcode: Object.assign({ type: "QR_CODE", value: data.qrContent }, (data.orderNumber && { alternateText: data.orderNumber })), eventName: {
84
+ defaultValue: { language: "en-US", value: data.passTitle || "" },
85
+ } }), (data.backgroundColorHex && { hexBackgroundColor: data.backgroundColorHex })), ((data.seat) && {
86
+ seatInfo: Object.assign({}, (data.seat && { seat: { defaultValue: { language: "en-US", value: data.seat } } })),
87
+ })), { textModulesData: [
88
+ { header: "TICKET", body: data.passSubtitle || data.passTitle || "" },
89
+ ...(dateDisplay ? [{ header: "DATE", body: dateDisplay }] : []),
90
+ ], validTimeInterval: Object.assign({ start: { date: (data.startDate || new Date()).toISOString() } }, (data.endDate && { end: { date: data.endDate.toISOString() } })) });
91
+ }
92
+ exports.buildGoogleTicketObject = buildGoogleTicketObject;
93
+ function buildGoogleTicketClass(classId, data) {
94
+ return Object.assign(Object.assign({ id: classId, issuerName: data.marketplaceName || "", reviewStatus: "UNDER_REVIEW", eventName: {
95
+ defaultValue: { language: "en-US", value: data.passTitle || "" },
96
+ } }, (data.logoUrl && { logo: { sourceUri: { uri: data.logoUrl } } })), (data.stripImageUrl && { heroImage: { sourceUri: { uri: data.stripImageUrl } } }));
97
+ }
98
+ exports.buildGoogleTicketClass = buildGoogleTicketClass;
99
+ //# sourceMappingURL=googleAuth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"googleAuth.js","sourceRoot":"","sources":["../../src/google/googleAuth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface INormalizedColor {
2
+ rgb: string;
3
+ hex: string;
4
+ }
5
+ /**
6
+ * Normalizes "#3c4150", "3c4150", or "rgb(60, 65, 80)" to both Apple rgb() and hex formats.
7
+ * Returns undefined if the input is missing or invalid.
8
+ */
9
+ export declare function normalizeColor(raw: string): INormalizedColor | undefined;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ // ─── Color normalization ──────────────────────────────────────────────────────
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.normalizeColor = void 0;
5
+ /**
6
+ * Normalizes "#3c4150", "3c4150", or "rgb(60, 65, 80)" to both Apple rgb() and hex formats.
7
+ * Returns undefined if the input is missing or invalid.
8
+ */
9
+ function normalizeColor(raw) {
10
+ if (!raw || !raw.trim())
11
+ return undefined;
12
+ const rgbMatch = raw.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
13
+ if (rgbMatch) {
14
+ const r = parseInt(rgbMatch[1]);
15
+ const g = parseInt(rgbMatch[2]);
16
+ const b = parseInt(rgbMatch[3]);
17
+ if (r > 255 || g > 255 || b > 255)
18
+ return undefined;
19
+ const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
20
+ return { rgb: `rgb(${r},${g},${b})`, hex };
21
+ }
22
+ const cleaned = raw.replace('#', '');
23
+ if (/^[0-9a-fA-F]{6}$/.test(cleaned)) {
24
+ const r = parseInt(cleaned.slice(0, 2), 16);
25
+ const g = parseInt(cleaned.slice(2, 4), 16);
26
+ const b = parseInt(cleaned.slice(4, 6), 16);
27
+ return { rgb: `rgb(${r},${g},${b})`, hex: `#${cleaned.toLowerCase()}` };
28
+ }
29
+ return undefined;
30
+ }
31
+ exports.normalizeColor = normalizeColor;
32
+ //# sourceMappingURL=colorHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colorHelpers.js","sourceRoot":"","sources":["../../src/helpers/colorHelpers.ts"],"names":[],"mappings":";AAAA,iFAAiF;;;AAOjF;;;GAGG;AACH,SAAgB,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,OAAO,SAAS,CAAC;IAE1C,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAC3E,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,GAAG;YAAE,OAAO,SAAS,CAAC;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QACtH,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACrC,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,OAAO,CAAC,WAAW,EAAE,EAAE,EAAE,CAAC;IAC1E,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAtBD,wCAsBC"}
@@ -0,0 +1,23 @@
1
+ /// <reference types="node" />
2
+ export declare const IMAGE_MAX_SIZE: number;
3
+ export declare const PNG_MAGIC: Buffer;
4
+ export declare const JPEG_MAGIC: Buffer;
5
+ export declare const WEBP_RIFF: Buffer;
6
+ export declare const WEBP_WEBP: Buffer;
7
+ /**
8
+ * Returns true if the URL is safe to fetch for wallet images.
9
+ * Requires https:// and optionally restricts to hosts in the allowedHosts list.
10
+ * An empty allowedHosts list means all https hosts are permitted.
11
+ */
12
+ export declare function isAllowedImageUrl(url: string, allowedHosts: string[]): boolean;
13
+ /**
14
+ * Downloads an image from a URL, validates it, and returns its buffer.
15
+ * Returns undefined if the URL is not allowed, the download fails, or the
16
+ * image is invalid/too large.
17
+ */
18
+ export declare function downloadImageBuffer(url: string, allowedHosts: string[], timeoutMs?: number): Promise<Buffer | undefined>;
19
+ /**
20
+ * Resizes an image buffer to the given dimensions using sharp.
21
+ * Returns undefined on error.
22
+ */
23
+ export declare function resizeImageBuffer(buffer: Buffer, width: number, height: number): Promise<Buffer | undefined>;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.resizeImageBuffer = exports.downloadImageBuffer = exports.isAllowedImageUrl = exports.WEBP_WEBP = exports.WEBP_RIFF = exports.JPEG_MAGIC = exports.PNG_MAGIC = exports.IMAGE_MAX_SIZE = void 0;
16
+ const axios_1 = __importDefault(require("axios"));
17
+ const sharp_1 = __importDefault(require("sharp"));
18
+ // ─── Image validation constants ───────────────────────────────────────────────
19
+ exports.IMAGE_MAX_SIZE = 5 * 1024 * 1024; // 5 MB
20
+ exports.PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
21
+ exports.JPEG_MAGIC = Buffer.from([0xff, 0xd8, 0xff]);
22
+ exports.WEBP_RIFF = Buffer.from([0x52, 0x49, 0x46, 0x46]);
23
+ exports.WEBP_WEBP = Buffer.from([0x57, 0x45, 0x42, 0x50]);
24
+ // ─── URL validation ───────────────────────────────────────────────────────────
25
+ /**
26
+ * Returns true if the URL is safe to fetch for wallet images.
27
+ * Requires https:// and optionally restricts to hosts in the allowedHosts list.
28
+ * An empty allowedHosts list means all https hosts are permitted.
29
+ */
30
+ function isAllowedImageUrl(url, allowedHosts) {
31
+ let parsed;
32
+ try {
33
+ parsed = new URL(url);
34
+ }
35
+ catch (_a) {
36
+ return false;
37
+ }
38
+ if (parsed.protocol !== 'https:')
39
+ return false;
40
+ if (allowedHosts.length === 0)
41
+ return true;
42
+ return allowedHosts.some((host) => parsed.hostname === host || parsed.hostname.endsWith(`.${host}`));
43
+ }
44
+ exports.isAllowedImageUrl = isAllowedImageUrl;
45
+ // ─── Image download ───────────────────────────────────────────────────────────
46
+ /**
47
+ * Downloads an image from a URL, validates it, and returns its buffer.
48
+ * Returns undefined if the URL is not allowed, the download fails, or the
49
+ * image is invalid/too large.
50
+ */
51
+ function downloadImageBuffer(url, allowedHosts, timeoutMs = 10000) {
52
+ return __awaiter(this, void 0, void 0, function* () {
53
+ if (!isAllowedImageUrl(url, allowedHosts))
54
+ return undefined;
55
+ try {
56
+ const response = yield axios_1.default.get(url, {
57
+ responseType: 'arraybuffer',
58
+ timeout: timeoutMs,
59
+ maxContentLength: exports.IMAGE_MAX_SIZE,
60
+ });
61
+ const buffer = Buffer.from(response.data);
62
+ if (buffer.length > exports.IMAGE_MAX_SIZE)
63
+ return undefined;
64
+ // Validate image format via magic bytes
65
+ const isPng = buffer.slice(0, 8).equals(exports.PNG_MAGIC);
66
+ const isJpeg = buffer.slice(0, 3).equals(exports.JPEG_MAGIC);
67
+ const isWebp = buffer.slice(0, 4).equals(exports.WEBP_RIFF) && buffer.slice(8, 12).equals(exports.WEBP_WEBP);
68
+ if (!isPng && !isJpeg && !isWebp)
69
+ return undefined;
70
+ return buffer;
71
+ }
72
+ catch (_a) {
73
+ return undefined;
74
+ }
75
+ });
76
+ }
77
+ exports.downloadImageBuffer = downloadImageBuffer;
78
+ // ─── Image resize helper ──────────────────────────────────────────────────────
79
+ /**
80
+ * Resizes an image buffer to the given dimensions using sharp.
81
+ * Returns undefined on error.
82
+ */
83
+ function resizeImageBuffer(buffer, width, height) {
84
+ return __awaiter(this, void 0, void 0, function* () {
85
+ try {
86
+ return yield (0, sharp_1.default)(buffer).resize(width, height, { fit: 'inside' }).png().toBuffer();
87
+ }
88
+ catch (_a) {
89
+ return undefined;
90
+ }
91
+ });
92
+ }
93
+ exports.resizeImageBuffer = resizeImageBuffer;
94
+ //# sourceMappingURL=imageHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageHelpers.js","sourceRoot":"","sources":["../../src/helpers/imageHelpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './helpers/colorHelpers';
3
+ export * from './helpers/imageHelpers';
4
+ export { ApplePassGenerator } from './apple/ApplePassGenerator';
5
+ export { GooglePassGenerator } from './google/GooglePassGenerator';
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.GooglePassGenerator = exports.ApplePassGenerator = void 0;
18
+ __exportStar(require("./types"), exports);
19
+ __exportStar(require("./helpers/colorHelpers"), exports);
20
+ __exportStar(require("./helpers/imageHelpers"), exports);
21
+ var ApplePassGenerator_1 = require("./apple/ApplePassGenerator");
22
+ Object.defineProperty(exports, "ApplePassGenerator", { enumerable: true, get: function () { return ApplePassGenerator_1.ApplePassGenerator; } });
23
+ var GooglePassGenerator_1 = require("./google/GooglePassGenerator");
24
+ Object.defineProperty(exports, "GooglePassGenerator", { enumerable: true, get: function () { return GooglePassGenerator_1.GooglePassGenerator; } });
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,yDAAuC;AACvC,yDAAuC;AACvC,iEAAgE;AAAvD,wHAAA,kBAAkB,OAAA;AAC3B,oEAAmE;AAA1D,0HAAA,mBAAmB,OAAA"}
@@ -0,0 +1,65 @@
1
+ export interface IWalletLayoutField {
2
+ key: string;
3
+ row: 'header' | 'secondary' | 'auxiliary' | 'back' | 'hidden';
4
+ position: number;
5
+ }
6
+ /**
7
+ * Caller-assembled pass data. All design values (colors, URLs) must be
8
+ * pre-resolved by the service before passing in — generators do not touch the DB.
9
+ */
10
+ export interface IWalletPassData {
11
+ /** MongoDB _id of the source document (userEvent or endUserSeasonTicket). Used as Google pass object ID. */
12
+ objectId: string;
13
+ /** Person's display name shown on the pass. */
14
+ holderName: string;
15
+ /** Primary title (event name or plan name). */
16
+ passTitle: string;
17
+ /** Secondary label (ticket type, plan type). Optional. */
18
+ passSubtitle?: string;
19
+ /** Human-readable date string already formatted for display (e.g. "14/05/2026 18:00"). */
20
+ dateDisplay?: string;
21
+ startDate?: Date;
22
+ endDate?: Date;
23
+ seat?: string;
24
+ orderNumber?: string;
25
+ /** True when the QR code has already been scanned/used. Affects Google pass state. */
26
+ isCheckedIn?: boolean;
27
+ /** Raw string encoded in the QR barcode. For events: "userEventId/ticketInfoId". For subscriptions: "sub/{sellerId}/{endUserSeasonTicketId}". */
28
+ qrContent: string;
29
+ marketplaceName?: string;
30
+ /** Used to build the Google Wallet class ID suffix. E.g. "event-<eventId>" or "subscription". */
31
+ classIdSuffix?: string;
32
+ accountId?: string;
33
+ /** Pre-resolved logo URL (HTTPS). */
34
+ logoUrl?: string;
35
+ /** Pre-resolved strip/banner image URL (HTTPS). */
36
+ stripImageUrl?: string;
37
+ /** Background color in rgb() format. */
38
+ backgroundColor?: string;
39
+ /** Background color in #hex format (for Google). */
40
+ backgroundColorHex?: string;
41
+ /** Text color in rgb() format. Apple only. */
42
+ foregroundColor?: string;
43
+ /** Label color in rgb() format. Apple only. */
44
+ labelColor?: string;
45
+ /** Layout from walletDesign. Generators fall back to their own DEFAULT_LAYOUT if absent. */
46
+ layout?: {
47
+ fields: IWalletLayoutField[];
48
+ };
49
+ }
50
+ export interface IAppleWalletConfig {
51
+ cert: string;
52
+ key: string;
53
+ wwdr: string;
54
+ passTypeIdentifier: string;
55
+ teamIdentifier: string;
56
+ /** Absolute path to the `.pass` template directory. Defaults to the bundled template. */
57
+ templatePath?: string;
58
+ }
59
+ export interface IGoogleWalletConfig {
60
+ issuerId: string;
61
+ /** If provided, used as-is. Otherwise built from issuerId + data.classIdSuffix. */
62
+ classId?: string;
63
+ serviceAccountEmail: string;
64
+ privateKey: string;
65
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/jest.config.js ADDED
@@ -0,0 +1,18 @@
1
+ const config = require("@nimee/tests");
2
+
3
+ const { clearMocks, coverageDirectory, collectCoverage, coverageReporters, forceExit, transform, testTimeout } =
4
+ config.jestConfig;
5
+
6
+ module.exports = {
7
+ setupFiles: ["dotenv/config"],
8
+ clearMocks: clearMocks,
9
+ coverageDirectory: coverageDirectory,
10
+ testEnvironment: "node",
11
+ collectCoverage: collectCoverage,
12
+ coverageReporters: coverageReporters,
13
+ forceExit: forceExit,
14
+ transform: { "^.+\\.tsx?$": "ts-jest" },
15
+ testTimeout: testTimeout,
16
+ modulePathIgnorePatterns: ["<rootDir>/test-client/", "<rootDir>/dist/"],
17
+ transformIgnorePatterns: ["node_modules/(?!axios)"],
18
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@nimee/wallet-generator",
3
+ "version": "1.0.0",
4
+ "description": "Apple Wallet and Google Wallet pass generation for Nimi services",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build-ts": "tsc",
9
+ "watch-ts": "tsc -w",
10
+ "build": "npm run build-ts && cp -r src/apple/template.pass dist/apple/template.pass",
11
+ "lint": "eslint -c ../../.eslintrc.js --ext .ts src/**",
12
+ "test": "jest --detectOpenHandles --watch",
13
+ "test:deploy": "jest --ci --coverage",
14
+ "publish-it": "npm run build && npm publish --scope=nimee --access public",
15
+ "incVersionAndPublish": "npm version patch && npm run publish-it && npm run i-all-workspaces",
16
+ "i-all-workspaces": "version=${npm_package_version} npm i @nimee/wallet-generator@$version --workspace=@nimee/payment --workspace=@nimee/user-event"
17
+ },
18
+ "devDependencies": {
19
+ "@nimee/tests": "0.0.39",
20
+ "@types/jest": "^23.3.12",
21
+ "@types/jsonwebtoken": "^8.5.9",
22
+ "@typescript-eslint/eslint-plugin": "^5.18.0",
23
+ "@typescript-eslint/parser": "^5.18.0",
24
+ "eslint": "8.22.0",
25
+ "jest": "^29.0.0",
26
+ "nock": "^13.0.5",
27
+ "sinon": "^9.2.1",
28
+ "ts-jest": "^29.0.0",
29
+ "typescript": "^5.2.2"
30
+ },
31
+ "dependencies": {
32
+ "@nimee/logger": "^1.0.27",
33
+ "axios": "1.2.1",
34
+ "jsonwebtoken": "^8.5.1",
35
+ "passkit-generator": "^3.5.7",
36
+ "sharp": "^0.34.5"
37
+ }
38
+ }