@unchainedshop/ticketing 2.10.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 (36) hide show
  1. package/jest.config.js +17 -0
  2. package/lib/index.d.ts +38 -0
  3. package/lib/index.js +38 -0
  4. package/lib/index.js.map +1 -0
  5. package/lib/mobile-tickets/apple-wallet.d.ts +14 -0
  6. package/lib/mobile-tickets/apple-wallet.js +23 -0
  7. package/lib/mobile-tickets/apple-wallet.js.map +1 -0
  8. package/lib/mobile-tickets/apple-webservice.d.ts +6 -0
  9. package/lib/mobile-tickets/apple-webservice.js +177 -0
  10. package/lib/mobile-tickets/apple-webservice.js.map +1 -0
  11. package/lib/mobile-tickets/google-webservice.d.ts +6 -0
  12. package/lib/mobile-tickets/google-webservice.js +52 -0
  13. package/lib/mobile-tickets/google-webservice.js.map +1 -0
  14. package/lib/module.d.ts +26 -0
  15. package/lib/module.js +143 -0
  16. package/lib/module.js.map +1 -0
  17. package/lib/pdf-tickets/print-webservice.d.ts +6 -0
  18. package/lib/pdf-tickets/print-webservice.js +31 -0
  19. package/lib/pdf-tickets/print-webservice.js.map +1 -0
  20. package/lib/template-registry.d.ts +25 -0
  21. package/lib/template-registry.js +14 -0
  22. package/lib/template-registry.js.map +1 -0
  23. package/lib/types.d.ts +7 -0
  24. package/lib/types.js +2 -0
  25. package/lib/types.js.map +1 -0
  26. package/package.json +57 -0
  27. package/readme.md +93 -0
  28. package/src/index.ts +78 -0
  29. package/src/mobile-tickets/apple-wallet.ts +31 -0
  30. package/src/mobile-tickets/apple-webservice.ts +245 -0
  31. package/src/mobile-tickets/google-webservice.ts +70 -0
  32. package/src/module.ts +192 -0
  33. package/src/pdf-tickets/print-webservice.ts +43 -0
  34. package/src/template-registry.ts +49 -0
  35. package/src/types.ts +6 -0
  36. package/tsconfig.json +9 -0
@@ -0,0 +1,25 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ /// <reference types="node" resolution-mode="require"/>
3
+ import { Context } from '@unchainedshop/types/api.js';
4
+ import { UnchainedCore } from '@unchainedshop/types/core.js';
5
+ import { TokenSurrogate } from '@unchainedshop/types/warehousing.js';
6
+ export type PDFRenderer = ({ orderId, variant, }: {
7
+ orderId: string;
8
+ variant?: string;
9
+ }, context: Context) => Promise<NodeJS.ReadableStream>;
10
+ export type PassRenderer = (token: TokenSurrogate, context: UnchainedCore) => Promise<{
11
+ asURL?: () => Promise<string>;
12
+ asBuffer?: () => Promise<Buffer>;
13
+ serialNumber?: string;
14
+ passTypeIdentifier?: string;
15
+ }>;
16
+ export declare enum RendererTypes {
17
+ GOOGLE_WALLET = "google-wallet",
18
+ APPLE_WALLET = "apple-wallet",
19
+ ORDER_PDF = "order"
20
+ }
21
+ export declare const renderers: Map<string, PDFRenderer | PassRenderer>;
22
+ export type RegisterRendererFn = ((type: RendererTypes.ORDER_PDF, renderer: PDFRenderer) => void) & ((type: RendererTypes.GOOGLE_WALLET | RendererTypes.APPLE_WALLET, renderer: PassRenderer) => void);
23
+ export declare const registerRenderer: RegisterRendererFn;
24
+ export type GetRendererFn = ((type: RendererTypes.ORDER_PDF) => PDFRenderer) & ((type: RendererTypes.GOOGLE_WALLET | RendererTypes.APPLE_WALLET) => PassRenderer);
25
+ export declare const getRenderer: GetRendererFn;
@@ -0,0 +1,14 @@
1
+ export var RendererTypes;
2
+ (function (RendererTypes) {
3
+ RendererTypes["GOOGLE_WALLET"] = "google-wallet";
4
+ RendererTypes["APPLE_WALLET"] = "apple-wallet";
5
+ RendererTypes["ORDER_PDF"] = "order";
6
+ })(RendererTypes || (RendererTypes = {}));
7
+ export const renderers = new Map();
8
+ export const registerRenderer = function registerRenderer(type, renderer) {
9
+ renderers.set(type, renderer);
10
+ };
11
+ export const getRenderer = function getRenderer(type) {
12
+ return renderers.get(type);
13
+ };
14
+ //# sourceMappingURL=template-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-registry.js","sourceRoot":"","sources":["../src/template-registry.ts"],"names":[],"mappings":"AAyBA,MAAM,CAAN,IAAY,aAIX;AAJD,WAAY,aAAa;IACvB,gDAA+B,CAAA;IAC/B,8CAA6B,CAAA;IAC7B,oCAAmB,CAAA;AACrB,CAAC,EAJW,aAAa,KAAb,aAAa,QAIxB;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsC,CAAC;AAKvE,MAAM,CAAC,MAAM,gBAAgB,GAAuB,SAAS,gBAAgB,CAC3E,IAAmB,EACnB,QAAoC;IAEpC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAChC,CAAC,CAAC;AAKF,MAAM,CAAC,MAAM,WAAW,GAAkB,SAAS,WAAW,CAAC,IAAmB;IAChF,OAAO,SAAS,CAAC,GAAG,CAAC,IAAI,CAAQ,CAAC;AACpC,CAAC,CAAC"}
package/lib/types.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { UnchainedCore } from '@unchainedshop/types/core.js';
2
+ import type { TicketingModule } from './module.js';
3
+ export type TicketingAPI = UnchainedCore & {
4
+ modules: {
5
+ passes: TicketingModule;
6
+ };
7
+ };
package/lib/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@unchainedshop/ticketing",
3
+ "version": "2.10.0",
4
+ "main": "lib/index.js",
5
+ "types": "lib/index.d.ts",
6
+ "exports": {
7
+ ".": "./lib/index.js",
8
+ "./*": "./lib/*"
9
+ },
10
+ "typesVersions": {
11
+ "*": {
12
+ "mobile-tickets/*": [
13
+ "./lib/mobile-tickets/*"
14
+ ],
15
+ "pdf-tickets/*": [
16
+ "./lib/pdf-tickets/*"
17
+ ]
18
+ }
19
+ },
20
+ "type": "module",
21
+ "scripts": {
22
+ "clean": "rm -rf lib",
23
+ "build": "npm run clean && tsc",
24
+ "watch": "tsc -w",
25
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --forceExit",
26
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch --detectOpenHandles --forceExit"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/unchainedshop/unchained.git"
31
+ },
32
+ "keywords": [
33
+ "unchained",
34
+ "ecommerce",
35
+ "core"
36
+ ],
37
+ "author": "Joël Meiller",
38
+ "license": "EUPL-1.2",
39
+ "bugs": {
40
+ "url": "https://github.com/unchainedshop/unchained/issues"
41
+ },
42
+ "homepage": "https://github.com/unchainedshop/unchained#readme",
43
+ "peerDependencies": {
44
+ "@hyperlink/node-apn": "^5.1.4",
45
+ "express": "^4.19.2"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^20.14.0",
49
+ "@unchainedshop/api": "^2.10.0",
50
+ "@unchainedshop/core-files": "^2.10.0",
51
+ "@unchainedshop/core-worker": "^2.10.0",
52
+ "@unchainedshop/events": "^2.10.0",
53
+ "@unchainedshop/logger": "^2.10.0",
54
+ "@unchainedshop/mongodb": "^2.10.0",
55
+ "@unchainedshop/types": "^2.10.0"
56
+ }
57
+ }
package/readme.md ADDED
@@ -0,0 +1,93 @@
1
+ # Unchained Ticketing Extension
2
+
3
+ ## Setup Example
4
+
5
+ boot.js:
6
+ ```js
7
+ import { modules, setupTicketing } from "@unchainedshop/ticketing";
8
+
9
+ ...
10
+ const app = express();
11
+
12
+ const httpServer = http.createServer(app);
13
+
14
+ const engine = await startPlatform({
15
+ modules,
16
+ options: {},
17
+ });
18
+
19
+ await engine.apolloGraphQLServer.start();
20
+
21
+ connectPlatformToExpress4(app, engine, { corsOrigins: [] });
22
+
23
+ // Unchained Ticketing Extension
24
+ setupTicketing(app, engine.unchainedAPI as TicketingAPI, {
25
+ renderOrderPDF,
26
+ createAppleWalletPass,
27
+ createGoogleWalletPass,
28
+ });
29
+ ...
30
+ ```
31
+
32
+
33
+ renderOrderPDF.ts:
34
+ ```tsx
35
+ import React from "react";
36
+ import ReactPDF, { Document } from "@react-pdf/renderer";
37
+
38
+ const TicketTemplate = ({ tickets }: { tickets: Array<any> }) => {
39
+ return (
40
+ <Document>
41
+ Hello World
42
+ </Document>
43
+ );
44
+ };
45
+
46
+ export default async (
47
+ { orderId, variant }: { orderId: string },
48
+ { modules },
49
+ ) => {
50
+ const order = await modules.orders.findOrder({ orderId });
51
+ ...
52
+ const pdfStream = await ReactPDF.renderToStream(
53
+ <TicketTemplate tickets={tickets} />,
54
+ );
55
+ return pdfStream;
56
+ };
57
+ ```
58
+
59
+ createAppleWalletPass.ts:
60
+ ```ts
61
+ import { UnchainedCore } from "@unchainedshop/types/core.js";
62
+ import { Template, constants } from "@walletpass/pass-js";
63
+
64
+ export default async (token, unchainedAPI: UnchainedCore) => {
65
+ const template = new Template(
66
+ "eventTicket",
67
+ ...
68
+ );
69
+ const pass = await template.createPass(...);
70
+ return pass;
71
+ };
72
+ ```
73
+
74
+ createGoogleWalletPass.ts:
75
+ ```ts
76
+ import { UnchainedCore } from "@unchainedshop/types/core.js";
77
+ import { google } from "googleapis";
78
+ import jwt from "jsonwebtoken";
79
+
80
+ export default async (token, unchainedAPI: UnchainedCore) => {
81
+
82
+ const product = ...;
83
+
84
+ // upsert class
85
+
86
+ // upsert object
87
+
88
+ const asURL = async () =>
89
+ createJwtNewObjects(issuerId, product._id, token.chainTokenId);
90
+
91
+ return { asURL };
92
+ };
93
+ ```
package/src/index.ts ADDED
@@ -0,0 +1,78 @@
1
+ import express from 'express';
2
+ import { subscribe } from '@unchainedshop/events';
3
+ import { RawPayloadType } from '@unchainedshop/events/EventDirector.js';
4
+ import { WorkerEventTypes } from '@unchainedshop/core-worker';
5
+ import { Work } from '@unchainedshop/types/worker.js';
6
+ import { RendererTypes, registerRenderer } from './template-registry.js';
7
+ import loadAppleWalletHandler from './mobile-tickets/apple-webservice.js';
8
+ import loadGoogleWalletHandler from './mobile-tickets/google-webservice.js';
9
+ import loadPDFHandler from './pdf-tickets/print-webservice.js';
10
+ import passes from './module.js';
11
+ import type { TicketingAPI } from './types.js';
12
+
13
+ export type { TicketingAPI, RendererTypes };
14
+
15
+ export const ticketingModules = {
16
+ passes,
17
+ };
18
+
19
+ export function setupPDFTickets(
20
+ app: express.Express,
21
+ {
22
+ renderOrderPDF,
23
+ }: {
24
+ renderOrderPDF: any;
25
+ },
26
+ ) {
27
+ registerRenderer(RendererTypes.ORDER_PDF, renderOrderPDF);
28
+ loadPDFHandler(app);
29
+ }
30
+
31
+ export function setupMobileTickets(
32
+ app: express.Express,
33
+ {
34
+ createGoogleWalletPass,
35
+ createAppleWalletPass,
36
+ }: {
37
+ createGoogleWalletPass: any;
38
+ createAppleWalletPass: any;
39
+ },
40
+ ) {
41
+ registerRenderer(RendererTypes.GOOGLE_WALLET, createGoogleWalletPass);
42
+ registerRenderer(RendererTypes.APPLE_WALLET, createAppleWalletPass);
43
+
44
+ loadAppleWalletHandler(app);
45
+ loadGoogleWalletHandler(app);
46
+ }
47
+
48
+ export default function setupTicketing(
49
+ app: express.Express,
50
+ unchainedAPI: TicketingAPI,
51
+ {
52
+ renderOrderPDF,
53
+ createAppleWalletPass,
54
+ createGoogleWalletPass,
55
+ }: {
56
+ renderOrderPDF: any;
57
+ createAppleWalletPass: any;
58
+ createGoogleWalletPass: any;
59
+ },
60
+ ) {
61
+ setupPDFTickets(app, {
62
+ renderOrderPDF,
63
+ });
64
+ setupMobileTickets(app, {
65
+ createAppleWalletPass,
66
+ createGoogleWalletPass,
67
+ });
68
+
69
+ subscribe('TOKEN_INVALIDATED', async () => {
70
+ await unchainedAPI.modules.passes.invalidateAppleWalletPasses(unchainedAPI);
71
+ });
72
+
73
+ subscribe(WorkerEventTypes.FINISHED, async ({ payload: work }: RawPayloadType<Work>) => {
74
+ if ((work.type === 'EXPORT_TOKEN' || work.type === 'UPDATE_TOKEN_OWNERSHIP') && work.success) {
75
+ await unchainedAPI.modules.passes.invalidateAppleWalletPasses(unchainedAPI);
76
+ }
77
+ });
78
+ }
@@ -0,0 +1,31 @@
1
+ import apn from '@hyperlink/node-apn';
2
+ import { Readable } from 'stream';
3
+
4
+ export const pushToApplePushNotificationService = async (deviceTokens) => {
5
+ const apnProvider = new apn.Provider({
6
+ cert: process.env.PASS_CERTIFICATE_PATH,
7
+ key: process.env.PASS_CERTIFICATE_PATH,
8
+ passphrase: process.env.PASS_CERTIFICATE_SECRET,
9
+ production: true,
10
+ });
11
+
12
+ const note = new apn.Notification({});
13
+ return apnProvider.send(note, deviceTokens);
14
+ };
15
+
16
+ export const buildPassBinary = async (
17
+ chainTokenId: string,
18
+ pass: {
19
+ serialNumber: string;
20
+ asBuffer: () => Promise<Buffer>;
21
+ },
22
+ ) => {
23
+ const passBuffer = await pass.asBuffer();
24
+ const rawFile = {
25
+ _id: pass.serialNumber,
26
+ filename: `${chainTokenId}-${new Date().getTime()}.pkpass`,
27
+ createReadStream: () => Readable.from(passBuffer),
28
+ mimetype: 'application/vnd.apple.pkpass',
29
+ };
30
+ return rawFile;
31
+ };
@@ -0,0 +1,245 @@
1
+ import { createLogger } from '@unchainedshop/logger';
2
+ import { useMiddlewareWithCurrentContext } from '@unchainedshop/api/express/index.js';
3
+ import express, { Request, Response } from 'express';
4
+ import { TicketingAPI } from '../types.js';
5
+
6
+ const logger = createLogger('unchained:apple-wallet-webservice');
7
+
8
+ const { APPLE_WALLET_WEBSERVICE_PATH = '/rest/apple-wallet' } = process.env;
9
+
10
+ const isAuthenticationTokenCorrect = (req, authenticationToken) => {
11
+ const expectedAuthorizationValue = `ApplePass ${authenticationToken}`;
12
+ return req.headers.authorization === expectedAuthorizationValue;
13
+ };
14
+
15
+ export const appleWalletHandler = async (
16
+ req: Request & { unchainedContext: TicketingAPI },
17
+ res: Response,
18
+ ) => {
19
+ const resolvedContext = req.unchainedContext;
20
+ const { modules } = resolvedContext;
21
+ logger.info(`${req.path} (${JSON.stringify(req.query)})`);
22
+
23
+ if (req.path.startsWith('/download/')) {
24
+ try {
25
+ const [, , passFileName] = req.path.split('/');
26
+ const [tokenId] = passFileName.split('.pkpass');
27
+
28
+ if (!tokenId) {
29
+ res.writeHead(404);
30
+ res.end();
31
+ return;
32
+ }
33
+
34
+ const token = await modules.warehousing.findToken({
35
+ tokenId,
36
+ });
37
+
38
+ if (!token) {
39
+ res.writeHead(404);
40
+ res.end('Token not found');
41
+ return;
42
+ }
43
+
44
+ const { hash } = req.query;
45
+ const correctHash = await modules.warehousing.buildAccessKeyForToken(tokenId);
46
+ if (hash !== correctHash) {
47
+ res.writeHead(403);
48
+ res.end('Token hash invalid for current owner');
49
+ return;
50
+ }
51
+
52
+ const passFile = await modules.passes.upsertAppleWalletPass(token, resolvedContext);
53
+
54
+ const url = await modules.files.getUrl(passFile, {});
55
+ const response = await fetch(url);
56
+ const data = await response.arrayBuffer();
57
+ const uint8View = new Uint8Array(data);
58
+
59
+ res.status(200);
60
+ res.setHeader('Content-Type', 'application/vnd.apple.pkpass');
61
+ res.setHeader('Content-Disposition', `attachment; filename=${tokenId}.pkpass`);
62
+ res.end(uint8View);
63
+ return;
64
+ } catch (e) {
65
+ console.error(e);
66
+ }
67
+ res.writeHead(500);
68
+ res.end();
69
+ return;
70
+ }
71
+
72
+ const [, apiVersion, endpoint, ...pathComponents] = req.path.split("/"); /* eslint-disable-line */
73
+ if (endpoint === 'devices') {
74
+ if (req.method === 'POST') {
75
+ // Register Device
76
+ const [deviceLibraryIdentifier, , passTypeIdentifier, serialNumber] = pathComponents;
77
+ const { pushToken } = req.body;
78
+
79
+ const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
80
+
81
+ if (pass) {
82
+ if (
83
+ !isAuthenticationTokenCorrect(
84
+ req,
85
+ (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
86
+ )
87
+ ) {
88
+ res.writeHead(401);
89
+ res.end();
90
+ return;
91
+ }
92
+
93
+ const newRegistration = await modules.passes.registerDeviceForAppleWalletPass(
94
+ passTypeIdentifier,
95
+ serialNumber,
96
+ {
97
+ deviceLibraryIdentifier,
98
+ pushToken,
99
+ },
100
+ );
101
+
102
+ if (!newRegistration) {
103
+ res.writeHead(200);
104
+ res.end();
105
+ return;
106
+ }
107
+
108
+ res.writeHead(201);
109
+ res.end();
110
+ return;
111
+ }
112
+ } else if (req.method === 'GET') {
113
+ // Get the List of Updatable Passes
114
+ const lastUpdated = new Date();
115
+ const [deviceLibraryIdentifier, , passTypeIdentifier] = pathComponents;
116
+ const passesUpdatedSince =
117
+ req.query?.passesUpdatedSince && new Date(req.query.passesUpdatedSince as string);
118
+
119
+ const passes = await modules.passes.findUpdatedAppleWalletPasses(
120
+ passTypeIdentifier,
121
+ deviceLibraryIdentifier,
122
+ passesUpdatedSince,
123
+ );
124
+ const serialNumbers = passes.map((t) => t.meta.serialNumber);
125
+
126
+ if (serialNumbers?.length) {
127
+ res.writeHead(200, {
128
+ 'Content-Type': 'application/json',
129
+ });
130
+ res.write(
131
+ JSON.stringify({
132
+ serialNumbers,
133
+ lastUpdated,
134
+ }),
135
+ );
136
+ res.end();
137
+ return;
138
+ }
139
+
140
+ res.writeHead(204);
141
+ res.end();
142
+ return;
143
+ } else if (req.method === 'DELETE') {
144
+ // Unregister Device
145
+ const [deviceLibraryIdentifier, , passTypeIdentifier, serialNumber] = pathComponents;
146
+
147
+ const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
148
+
149
+ if (pass) {
150
+ if (
151
+ !isAuthenticationTokenCorrect(
152
+ req,
153
+ (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
154
+ )
155
+ ) {
156
+ res.writeHead(401);
157
+ res.end();
158
+ return;
159
+ }
160
+
161
+ await modules.passes.unregisterDeviceForAppleWalletPass(
162
+ passTypeIdentifier,
163
+ serialNumber,
164
+ deviceLibraryIdentifier,
165
+ );
166
+
167
+ // Unregistered
168
+ res.writeHead(200);
169
+ res.end();
170
+ return;
171
+ }
172
+ }
173
+ } else if (endpoint === 'log') {
174
+ // Log Mesage
175
+ const { logs } = req.body;
176
+ logs?.forEach((log) => {
177
+ if (typeof log === 'string') {
178
+ logger.info(log);
179
+ }
180
+ });
181
+ res.writeHead(200);
182
+ res.end();
183
+ return;
184
+ } else if (endpoint === 'passes') {
185
+ if (req.method === 'GET') {
186
+ // Get an updated Pass
187
+ const [passTypeIdentifier, serialNumber] = pathComponents;
188
+
189
+ const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
190
+
191
+ if (pass) {
192
+ if (
193
+ !isAuthenticationTokenCorrect(
194
+ req,
195
+ (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
196
+ )
197
+ ) {
198
+ res.writeHead(401);
199
+ res.end();
200
+ return;
201
+ }
202
+
203
+ const { updated, created } = pass;
204
+
205
+ const lastModifiedDate = new Date(updated || created);
206
+ lastModifiedDate.setMilliseconds(0);
207
+
208
+ const ifModifiedSinceDate = new Date(req.header('if-modified-since'));
209
+ ifModifiedSinceDate.setMilliseconds(0);
210
+
211
+ if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
212
+ res.writeHead(304);
213
+ res.end();
214
+ return;
215
+ }
216
+
217
+ const url = modules.files.getUrl(pass, {});
218
+ const result = await fetch(url);
219
+ const data = await result.arrayBuffer();
220
+ const uint8View = new Uint8Array(data);
221
+
222
+ res.writeHead(200, {
223
+ 'Content-Type': 'application/vnd.apple.pkpass',
224
+ 'Last-Modified': lastModifiedDate.toUTCString(),
225
+ });
226
+ res.end(uint8View);
227
+ return;
228
+ }
229
+ }
230
+ }
231
+
232
+ res.writeHead(404);
233
+ res.end();
234
+ };
235
+
236
+ export default function loadAppleWalletHandler(app) {
237
+ useMiddlewareWithCurrentContext(
238
+ app,
239
+ APPLE_WALLET_WEBSERVICE_PATH,
240
+ express.json({
241
+ type: 'application/json',
242
+ }),
243
+ appleWalletHandler,
244
+ );
245
+ }
@@ -0,0 +1,70 @@
1
+ import { createLogger } from '@unchainedshop/logger';
2
+ import { useMiddlewareWithCurrentContext } from '@unchainedshop/api/express/index.js';
3
+ import express, { Request, Response } from 'express';
4
+ import { TicketingAPI } from '../types.js';
5
+
6
+ const logger = createLogger('unchained:google-wallet-webservice');
7
+
8
+ const { GOOGLE_WALLET_WEBSERVICE_PATH = '/rest/google-wallet' } = process.env;
9
+
10
+ export const googleWalletHandler = async (
11
+ req: Request & { unchainedContext: TicketingAPI },
12
+ res: Response,
13
+ ) => {
14
+ const resolvedContext = req.unchainedContext;
15
+ const { modules } = resolvedContext;
16
+ logger.info(`${req.path} (${JSON.stringify(req.query)})`);
17
+
18
+ if (req.path.startsWith('/download/')) {
19
+ try {
20
+ const [, , tokenId] = req.path.split('/');
21
+
22
+ if (!tokenId) {
23
+ res.writeHead(404);
24
+ res.end();
25
+ return;
26
+ }
27
+
28
+ const token = await modules.warehousing.findToken({
29
+ tokenId,
30
+ });
31
+
32
+ if (!token) {
33
+ res.writeHead(404);
34
+ res.end('Token not found');
35
+ return;
36
+ }
37
+
38
+ const { hash } = req.query;
39
+ const correctHash = await modules.warehousing.buildAccessKeyForToken(tokenId);
40
+ if (hash !== correctHash) {
41
+ res.writeHead(403);
42
+ res.end('Token hash invalid for current owner');
43
+ return;
44
+ }
45
+
46
+ const pass = await modules.passes.upsertGoogleWalletPass(token, resolvedContext);
47
+
48
+ res.redirect(await pass.asURL());
49
+ return;
50
+ } catch (e) {
51
+ console.error(e);
52
+ }
53
+ res.writeHead(500);
54
+ res.end();
55
+ return;
56
+ }
57
+ res.writeHead(404);
58
+ res.end();
59
+ };
60
+
61
+ export default function loadGoogleWalletHandler(app) {
62
+ useMiddlewareWithCurrentContext(
63
+ app,
64
+ GOOGLE_WALLET_WEBSERVICE_PATH,
65
+ express.json({
66
+ type: 'application/json',
67
+ }),
68
+ googleWalletHandler,
69
+ );
70
+ }