@unchainedshop/ticketing 4.0.0-rc.2 → 4.0.0-rc.21

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 (57) hide show
  1. package/lib/fastify.js +2 -2
  2. package/lib/fastify.js.map +1 -1
  3. package/lib/magic-key.js +4 -4
  4. package/lib/magic-key.js.map +1 -1
  5. package/lib/mobile-tickets/apple-handler-express.js +63 -64
  6. package/lib/mobile-tickets/apple-handler-express.js.map +1 -1
  7. package/lib/mobile-tickets/apple-handler-fastify.d.ts.map +1 -1
  8. package/lib/mobile-tickets/apple-handler-fastify.js +93 -42
  9. package/lib/mobile-tickets/apple-handler-fastify.js.map +1 -1
  10. package/lib/mobile-tickets/apple-wallet.d.ts +3 -3
  11. package/lib/mobile-tickets/apple-wallet.d.ts.map +1 -1
  12. package/lib/mobile-tickets/apple-wallet.js +3 -3
  13. package/lib/mobile-tickets/apple-wallet.js.map +1 -1
  14. package/lib/mobile-tickets/apple-webservice.d.ts +7 -0
  15. package/lib/mobile-tickets/apple-webservice.d.ts.map +1 -0
  16. package/lib/mobile-tickets/apple-webservice.js +177 -0
  17. package/lib/mobile-tickets/apple-webservice.js.map +1 -0
  18. package/lib/mobile-tickets/google-handler-express.d.ts.map +1 -1
  19. package/lib/mobile-tickets/google-handler-express.js +10 -12
  20. package/lib/mobile-tickets/google-handler-express.js.map +1 -1
  21. package/lib/mobile-tickets/google-handler-fastify.d.ts.map +1 -1
  22. package/lib/mobile-tickets/google-handler-fastify.js +19 -6
  23. package/lib/mobile-tickets/google-handler-fastify.js.map +1 -1
  24. package/lib/mobile-tickets/google-webservice.d.ts +7 -0
  25. package/lib/mobile-tickets/google-webservice.d.ts.map +1 -0
  26. package/lib/mobile-tickets/google-webservice.js +52 -0
  27. package/lib/mobile-tickets/google-webservice.js.map +1 -0
  28. package/lib/module.d.ts +8 -8
  29. package/lib/module.d.ts.map +1 -1
  30. package/lib/module.js +6 -3
  31. package/lib/module.js.map +1 -1
  32. package/lib/pdf-tickets/print-handler-express.d.ts.map +1 -1
  33. package/lib/pdf-tickets/print-handler-express.js +4 -3
  34. package/lib/pdf-tickets/print-handler-express.js.map +1 -1
  35. package/lib/pdf-tickets/print-handler-fastify.d.ts.map +1 -1
  36. package/lib/pdf-tickets/print-handler-fastify.js +8 -2
  37. package/lib/pdf-tickets/print-handler-fastify.js.map +1 -1
  38. package/lib/pdf-tickets/print-webservice.d.ts +7 -0
  39. package/lib/pdf-tickets/print-webservice.d.ts.map +1 -0
  40. package/lib/pdf-tickets/print-webservice.js +31 -0
  41. package/lib/pdf-tickets/print-webservice.js.map +1 -0
  42. package/lib/template-registry.d.ts +2 -2
  43. package/lib/template-registry.d.ts.map +1 -1
  44. package/package.json +14 -14
  45. package/readme.md +1 -1
  46. package/src/fastify.ts +2 -2
  47. package/src/magic-key.ts +4 -4
  48. package/src/mobile-tickets/apple-handler-express.ts +71 -71
  49. package/src/mobile-tickets/apple-handler-fastify.ts +103 -51
  50. package/src/mobile-tickets/apple-wallet.ts +3 -3
  51. package/src/mobile-tickets/google-handler-express.ts +11 -12
  52. package/src/mobile-tickets/google-handler-fastify.ts +21 -6
  53. package/src/module.ts +7 -4
  54. package/src/pdf-tickets/print-handler-express.ts +6 -3
  55. package/src/pdf-tickets/print-handler-fastify.ts +9 -2
  56. package/src/template-registry.ts +2 -2
  57. package/tsconfig.json +2 -1
@@ -0,0 +1,31 @@
1
+ import { useMiddlewareWithCurrentContext } from '@unchainedshop/api/express/index.js';
2
+ import { checkAction } from '@unchainedshop/api/acl.js';
3
+ import { actions } from '@unchainedshop/api/roles/index.js';
4
+ import express from 'express';
5
+ import { RendererTypes, getRenderer } from '../template-registry.js';
6
+ const { UNCHAINED_PDF_PRINT_HANDLER_PATH = '/rest/print_tickets' } = process.env;
7
+ export async function printTicketsHandler(req, res) {
8
+ const { variant, orderId, otp } = req.query || {};
9
+ try {
10
+ if (typeof orderId !== 'string' ||
11
+ typeof otp !== 'string' ||
12
+ (variant && typeof variant !== 'string'))
13
+ throw new Error('Invalid query parameter');
14
+ await checkAction(req.unchainedContext, actions.viewOrder, [undefined, { orderId, otp }]);
15
+ const render = getRenderer(RendererTypes.ORDER_PDF);
16
+ const pdfStream = await render({ orderId, variant: variant }, req.unchainedContext);
17
+ res.setHeader('Content-Type', 'application/pdf');
18
+ pdfStream.pipe(res);
19
+ }
20
+ catch (error) {
21
+ console.error(error);
22
+ res.status(403);
23
+ res.end();
24
+ }
25
+ }
26
+ export default function connectPrintWebservice(app) {
27
+ useMiddlewareWithCurrentContext(app, UNCHAINED_PDF_PRINT_HANDLER_PATH, express.json({
28
+ type: 'application/json',
29
+ }), printTicketsHandler);
30
+ }
31
+ //# sourceMappingURL=print-webservice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"print-webservice.js","sourceRoot":"","sources":["../../src/pdf-tickets/print-webservice.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,qCAAqC,CAAC;AACtF,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAC;AAC5D,OAAO,OAA8B,MAAM,SAAS,CAAC;AAErD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAErE,MAAM,EAAE,gCAAgC,GAAG,qBAAqB,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;AAEjF,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAA4C,EAAE,GAAa;IACnG,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;IAElD,IAAI,CAAC;QACH,IACE,OAAO,OAAO,KAAK,QAAQ;YAC3B,OAAO,GAAG,KAAK,QAAQ;YACvB,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC;YAExC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAE7C,MAAM,WAAW,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QAE1F,MAAM,MAAM,GAAG,WAAW,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAiB,EAAE,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC9F,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;QACjD,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChB,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,sBAAsB,CAAC,GAAG;IAChD,+BAA+B,CAC7B,GAAG,EACH,gCAAgC,EAChC,OAAO,CAAC,IAAI,CAAC;QACX,IAAI,EAAE,kBAAkB;KACzB,CAAC,EACF,mBAAmB,CACpB,CAAC;AACJ,CAAC"}
@@ -5,8 +5,8 @@ export type PDFRenderer = ({ orderId, variant, }: {
5
5
  variant?: string;
6
6
  }, context: UnchainedCore) => Promise<NodeJS.ReadableStream>;
7
7
  export type PassRenderer = (token: TokenSurrogate, context: UnchainedCore) => Promise<{
8
- asURL?: () => Promise<string>;
9
- asBuffer?: () => Promise<Buffer>;
8
+ asURL: () => Promise<string>;
9
+ asBuffer: () => Promise<Buffer>;
10
10
  serialNumber?: string;
11
11
  passTypeIdentifier?: string;
12
12
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"template-registry.d.ts","sourceRoot":"","sources":["../src/template-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,MAAM,MAAM,WAAW,GAAG,CACxB,EACE,OAAO,EACP,OAAO,GACR,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,EACD,OAAO,EAAE,aAAa,KACnB,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;AAEpC,MAAM,MAAM,YAAY,GAAG,CACzB,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,aAAa,KACnB,OAAO,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC,CAAC;AAEH,oBAAY,aAAa;IACvB,aAAa,kBAAkB;IAC/B,YAAY,iBAAiB;IAC7B,SAAS,UAAU;CACpB;AAED,eAAO,MAAM,SAAS,yCAAgD,CAAC;AAEvE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,EAAE,aAAa,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAC,GAC/F,CAAC,CAAC,IAAI,EAAE,aAAa,CAAC,aAAa,GAAG,aAAa,CAAC,YAAY,EAAE,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC,CAAC;AAErG,eAAO,MAAM,gBAAgB,EAAE,kBAK9B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,EAAE,aAAa,CAAC,SAAS,KAAK,WAAW,CAAC,GAC1E,CAAC,CAAC,IAAI,EAAE,aAAa,CAAC,aAAa,GAAG,aAAa,CAAC,YAAY,KAAK,YAAY,CAAC,CAAC;AAErF,eAAO,MAAM,WAAW,EAAE,aAEzB,CAAC"}
1
+ {"version":3,"file":"template-registry.d.ts","sourceRoot":"","sources":["../src/template-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,MAAM,MAAM,WAAW,GAAG,CACxB,EACE,OAAO,EACP,OAAO,GACR,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,EACD,OAAO,EAAE,aAAa,KACnB,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;AAEpC,MAAM,MAAM,YAAY,GAAG,CACzB,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,aAAa,KACnB,OAAO,CAAC;IACX,KAAK,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC,CAAC;AAEH,oBAAY,aAAa;IACvB,aAAa,kBAAkB;IAC/B,YAAY,iBAAiB;IAC7B,SAAS,UAAU;CACpB;AAED,eAAO,MAAM,SAAS,yCAAgD,CAAC;AAEvE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,EAAE,aAAa,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAC,GAC/F,CAAC,CAAC,IAAI,EAAE,aAAa,CAAC,aAAa,GAAG,aAAa,CAAC,YAAY,EAAE,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC,CAAC;AAErG,eAAO,MAAM,gBAAgB,EAAE,kBAK9B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,EAAE,aAAa,CAAC,SAAS,KAAK,WAAW,CAAC,GAC1E,CAAC,CAAC,IAAI,EAAE,aAAa,CAAC,aAAa,GAAG,aAAa,CAAC,YAAY,KAAK,YAAY,CAAC,CAAC;AAErF,eAAO,MAAM,WAAW,EAAE,aAEzB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchainedshop/ticketing",
3
- "version": "4.0.0-rc.2",
3
+ "version": "4.0.0-rc.21",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "type": "module",
@@ -31,22 +31,22 @@
31
31
  },
32
32
  "homepage": "https://github.com/unchainedshop/unchained#readme",
33
33
  "dependencies": {
34
- "@unchainedshop/api": "^4.0.0-rc.2",
35
- "@unchainedshop/core": "^4.0.0-rc.2",
36
- "@unchainedshop/core-files": "^4.0.0-rc.2",
37
- "@unchainedshop/core-warehousing": "^4.0.0-rc.2",
38
- "@unchainedshop/core-worker": "^4.0.0-rc.2",
39
- "@unchainedshop/events": "^4.0.0-rc.2",
40
- "@unchainedshop/logger": "^4.0.0-rc.2",
41
- "@unchainedshop/mongodb": "^4.0.0-rc.2"
34
+ "@unchainedshop/api": "^4.0.0-rc.21",
35
+ "@unchainedshop/core": "^4.0.0-rc.21",
36
+ "@unchainedshop/core-files": "^4.0.0-rc.21",
37
+ "@unchainedshop/core-warehousing": "^4.0.0-rc.21",
38
+ "@unchainedshop/core-worker": "^4.0.0-rc.21",
39
+ "@unchainedshop/events": "^4.0.0-rc.21",
40
+ "@unchainedshop/logger": "^4.0.0-rc.21",
41
+ "@unchainedshop/mongodb": "^4.0.0-rc.21"
42
42
  },
43
43
  "peerDependencies": {
44
- "@hyperlink/node-apn": "^5.1.4",
44
+ "@parse/node-apn": "^6.5.0",
45
45
  "express": "5.x",
46
46
  "fastify": ">= 5.2 < 6"
47
47
  },
48
48
  "peerDependenciesMeta": {
49
- "@hyperlink/node-apn": {
49
+ "@parse/node-apn": {
50
50
  "optional": false
51
51
  },
52
52
  "express": {
@@ -57,10 +57,10 @@
57
57
  }
58
58
  },
59
59
  "devDependencies": {
60
- "@types/express": "^5.0.1",
61
- "@types/node": "^22.15.3",
60
+ "@types/express": "^5.0.3",
61
+ "@types/node": "^24.0.13",
62
62
  "express": "^5.1.0",
63
- "fastify": "^5.3.2",
63
+ "fastify": "^5.4.0",
64
64
  "typescript": "^5.8.3"
65
65
  }
66
66
  }
package/readme.md CHANGED
@@ -105,7 +105,7 @@ export default async (token, unchainedAPI: UnchainedCore) => {
105
105
  // upsert object
106
106
 
107
107
  const asURL = async () =>
108
- createJwtNewObjects(issuerId, product._id, token.chainTokenId);
108
+ createJwtNewObjects(issuerId, product._id, token.tokenSerialNumber);
109
109
 
110
110
  return { asURL };
111
111
  };
package/src/fastify.ts CHANGED
@@ -18,13 +18,13 @@ export default (app: FastifyInstance) => {
18
18
 
19
19
  app.route({
20
20
  url: `${GOOGLE_WALLET_WEBSERVICE_PATH}/*`,
21
- method: 'POST',
21
+ method: 'GET',
22
22
  handler: googleWalletHandler,
23
23
  });
24
24
 
25
25
  app.route({
26
26
  url: `${UNCHAINED_PDF_PRINT_HANDLER_PATH}/*`,
27
- method: 'POST',
27
+ method: 'GET',
28
28
  handler: printTicketsHandler,
29
29
  });
30
30
  };
package/src/magic-key.ts CHANGED
@@ -20,7 +20,7 @@ const isMagicKeyValidForToken = async (
20
20
  params: { tokenId: string } | null,
21
21
  { modules, getHeader }: Context & TicketingAPI,
22
22
  ) => {
23
- const tokenId = params?.tokenId || (root && 'chainTokenId' in root && root._id) || null;
23
+ const tokenId = params?.tokenId || (root && 'tokenSerialNumber' in root && root._id) || null;
24
24
 
25
25
  const token = await modules.warehousing.findToken({ tokenId });
26
26
  // always return true if tokenId leads to nothing to show potentially allowed actions
@@ -37,7 +37,7 @@ const isMagicKeyValidForToken = async (
37
37
  };
38
38
 
39
39
  export default () => {
40
- roles.allRoles.ALL.allow(roles.actions.viewOrder, isMagicKeyValidForOrder);
41
- roles.allRoles.ALL.allow(roles.actions.viewToken, isMagicKeyValidForToken);
42
- roles.allRoles.ALL.allow(roles.actions.updateToken, isMagicKeyValidForToken);
40
+ roles.allRoles?.ALL?.allow(roles.actions.viewOrder, isMagicKeyValidForOrder);
41
+ roles.allRoles?.ALL?.allow(roles.actions.viewToken, isMagicKeyValidForToken);
42
+ roles.allRoles?.ALL?.allow(roles.actions.updateToken, isMagicKeyValidForToken);
43
43
  };
@@ -21,8 +21,7 @@ const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAP
21
21
  const [tokenId] = passFileName.split('.pkpass');
22
22
 
23
23
  if (!tokenId) {
24
- res.writeHead(404);
25
- res.end();
24
+ res.status(404).end();
26
25
  return;
27
26
  }
28
27
 
@@ -31,16 +30,14 @@ const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAP
31
30
  });
32
31
 
33
32
  if (!token) {
34
- res.writeHead(404);
35
- res.end('Token not found');
33
+ res.status(404).send('Token not found');
36
34
  return;
37
35
  }
38
36
 
39
37
  const { hash } = req.query;
40
38
  const correctHash = await modules.warehousing.buildAccessKeyForToken(tokenId);
41
- if (hash !== correctHash) {
42
- res.writeHead(403);
43
- res.end('Token hash invalid for current owner');
39
+ if (!hash || hash !== correctHash) {
40
+ res.status(403).send('Token hash invalid for current owner');
44
41
  return;
45
42
  }
46
43
 
@@ -50,6 +47,16 @@ const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAP
50
47
  const signedUrl = await fileUploadAdapter.createDownloadURL(passFile);
51
48
  const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
52
49
 
50
+ if (!url) {
51
+ res.status(500);
52
+ res.send({
53
+ success: false,
54
+ message: 'Could not create download URL',
55
+ name: 'URL_SIGNING_FAILED',
56
+ });
57
+ return;
58
+ }
59
+
53
60
  const response = await fetch(url);
54
61
  const data = await response.arrayBuffer();
55
62
  const uint8View = new Uint8Array(data);
@@ -57,13 +64,12 @@ const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAP
57
64
  res.status(200);
58
65
  res.setHeader('Content-Type', 'application/vnd.apple.pkpass');
59
66
  res.setHeader('Content-Disposition', `attachment; filename=${tokenId}.pkpass`);
60
- res.end(uint8View);
67
+ res.send(uint8View);
61
68
  return;
62
69
  } catch (e) {
63
- console.error(e);
70
+ logger.error(e);
64
71
  }
65
- res.writeHead(500);
66
- res.end();
72
+ res.status(500).end();
67
73
  return;
68
74
  }
69
75
 
@@ -80,11 +86,10 @@ const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAP
80
86
  if (
81
87
  !isAuthenticationTokenCorrect(
82
88
  req,
83
- (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
89
+ (pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
84
90
  )
85
91
  ) {
86
- res.writeHead(401);
87
- res.end();
92
+ res.status(401).end();
88
93
  return;
89
94
  }
90
95
 
@@ -98,13 +103,11 @@ const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAP
98
103
  );
99
104
 
100
105
  if (!newRegistration) {
101
- res.writeHead(200);
102
- res.end();
106
+ res.status(200).end();
103
107
  return;
104
108
  }
105
109
 
106
- res.writeHead(201);
107
- res.end();
110
+ res.status(201).end();
108
111
  return;
109
112
  }
110
113
  } else if (req.method === 'GET') {
@@ -119,24 +122,14 @@ const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAP
119
122
  deviceLibraryIdentifier,
120
123
  passesUpdatedSince,
121
124
  );
122
- const serialNumbers = passes.map((t) => t.meta.serialNumber);
125
+ const serialNumbers = passes.map((t) => t.meta?.serialNumber).filter(Boolean) as string[];
123
126
 
124
127
  if (serialNumbers?.length) {
125
- res.writeHead(200, {
126
- 'Content-Type': 'application/json',
127
- });
128
- res.write(
129
- JSON.stringify({
130
- serialNumbers,
131
- lastUpdated,
132
- }),
133
- );
134
- res.end();
128
+ res.status(200).send({ serialNumbers, lastUpdated });
135
129
  return;
136
130
  }
137
131
 
138
- res.writeHead(204);
139
- res.end();
132
+ res.status(204).end();
140
133
  return;
141
134
  } else if (req.method === 'DELETE') {
142
135
  // Unregister Device
@@ -148,11 +141,10 @@ const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAP
148
141
  if (
149
142
  !isAuthenticationTokenCorrect(
150
143
  req,
151
- (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
144
+ (pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
152
145
  )
153
146
  ) {
154
- res.writeHead(401);
155
- res.end();
147
+ res.status(401).end();
156
148
  return;
157
149
  }
158
150
 
@@ -163,8 +155,7 @@ const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAP
163
155
  );
164
156
 
165
157
  // Unregistered
166
- res.writeHead(200);
167
- res.end();
158
+ res.status(200).end();
168
159
  return;
169
160
  }
170
161
  }
@@ -176,8 +167,7 @@ const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAP
176
167
  logger.info(log);
177
168
  }
178
169
  });
179
- res.writeHead(200);
180
- res.end();
170
+ res.status(200).end();
181
171
  return;
182
172
  } else if (endpoint === 'passes') {
183
173
  if (req.method === 'GET') {
@@ -186,52 +176,62 @@ const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAP
186
176
 
187
177
  const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
188
178
 
189
- if (pass) {
190
- if (
191
- !isAuthenticationTokenCorrect(
192
- req,
193
- (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
194
- )
195
- ) {
196
- res.writeHead(401);
197
- res.end();
198
- return;
199
- }
179
+ if (!pass) {
180
+ res.status(404).end();
181
+ return;
182
+ }
200
183
 
201
- const { updated, created } = pass;
184
+ if (
185
+ !isAuthenticationTokenCorrect(
186
+ req,
187
+ (pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
188
+ )
189
+ ) {
190
+ res.status(401).end();
191
+ return;
192
+ }
202
193
 
203
- const lastModifiedDate = new Date(updated || created);
204
- lastModifiedDate.setMilliseconds(0);
194
+ const { updated, created } = pass;
205
195
 
206
- const ifModifiedSinceDate = new Date(req.header('if-modified-since'));
207
- ifModifiedSinceDate.setMilliseconds(0);
196
+ const lastModifiedDate = new Date(updated || created);
197
+ lastModifiedDate.setMilliseconds(0);
208
198
 
209
- if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
210
- res.writeHead(304);
211
- res.end();
212
- return;
213
- }
199
+ const ifModifiedSinceDate = new Date(req.header('if-modified-since')!);
200
+ ifModifiedSinceDate.setMilliseconds(0);
214
201
 
215
- const fileUploadAdapter = getFileAdapter();
216
- const signedUrl = await fileUploadAdapter.createDownloadURL(pass);
217
- const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
202
+ if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
203
+ res.status(304).end();
204
+ return;
205
+ }
218
206
 
219
- const result = await fetch(url);
220
- const data = await result.arrayBuffer();
221
- const uint8View = new Uint8Array(data);
207
+ const fileUploadAdapter = getFileAdapter();
208
+ const signedUrl = await fileUploadAdapter.createDownloadURL(pass);
209
+ const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
222
210
 
223
- res.writeHead(200, {
224
- 'Content-Type': 'application/vnd.apple.pkpass',
225
- 'Last-Modified': lastModifiedDate.toUTCString(),
211
+ if (!url) {
212
+ res.status(500);
213
+ res.send({
214
+ success: false,
215
+ message: 'Could not create download URL',
216
+ name: 'URL_SIGNING_FAILED',
226
217
  });
227
- res.end(uint8View);
228
218
  return;
229
219
  }
220
+
221
+ const result = await fetch(url);
222
+ const data = await result.arrayBuffer();
223
+ const uint8View = new Uint8Array(data);
224
+
225
+ res.writeHead(200, {
226
+ 'Content-Type': 'application/vnd.apple.pkpass',
227
+ 'Last-Modified': lastModifiedDate.toUTCString(),
228
+ });
229
+ res.send(uint8View);
230
+ return;
230
231
  }
231
232
  }
232
233
 
233
- res.writeHead(404);
234
- res.end();
234
+ res.status(404).end();
235
235
  };
236
236
 
237
237
  export default appleWalletHandler;
@@ -38,15 +38,21 @@ const appleWalletHandler: RouteHandlerMethod = async (
38
38
  });
39
39
 
40
40
  if (!token) {
41
+ logger.error('Token not found', { tokenId });
41
42
  reply.status(404);
42
- return reply.send('Token not found');
43
+ return reply.send();
43
44
  }
44
45
 
45
46
  const { hash } = req.query as Record<string, string>;
46
47
  const correctHash = await modules.warehousing.buildAccessKeyForToken(tokenId);
47
- if (hash !== correctHash) {
48
+ if (!hash || hash !== correctHash) {
49
+ logger.error('Token hash invalid for current owner', { tokenId });
48
50
  reply.status(403);
49
- return reply.send('Token hash invalid for current owner');
51
+ return reply.send({
52
+ success: false,
53
+ message: 'Token hash invalid for current owner',
54
+ name: 'HASH_MISMATCH',
55
+ });
50
56
  }
51
57
 
52
58
  const passFile = await modules.passes.upsertAppleWalletPass(token, resolvedContext);
@@ -55,6 +61,15 @@ const appleWalletHandler: RouteHandlerMethod = async (
55
61
  const signedUrl = await fileUploadAdapter.createDownloadURL(passFile);
56
62
  const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
57
63
 
64
+ if (!url) {
65
+ reply.status(500);
66
+ return reply.send({
67
+ success: false,
68
+ message: 'Could not create download URL',
69
+ name: 'URL_SIGNING_FAILED',
70
+ });
71
+ }
72
+
58
73
  const response = await fetch(url);
59
74
  const data = await response.arrayBuffer();
60
75
  const uint8View = new Uint8Array(data);
@@ -64,10 +79,14 @@ const appleWalletHandler: RouteHandlerMethod = async (
64
79
  reply.header('Content-Disposition', `attachment; filename=${tokenId}.pkpass`);
65
80
  return reply.send(uint8View);
66
81
  } catch (e) {
67
- console.error(e);
82
+ logger.error(e);
83
+ reply.status(500);
84
+ return reply.send({
85
+ success: false,
86
+ message: 'Error generating pass',
87
+ name: 'PASS_GENERATION_ERROR',
88
+ });
68
89
  }
69
- reply.status(500);
70
- return reply.send();
71
90
  }
72
91
 
73
92
  const [, apiVersion, endpoint, ...pathComponents] = path.split("/"); /* eslint-disable-line */
@@ -83,9 +102,10 @@ const appleWalletHandler: RouteHandlerMethod = async (
83
102
  if (
84
103
  !isAuthenticationTokenCorrect(
85
104
  req,
86
- (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
105
+ (pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
87
106
  )
88
107
  ) {
108
+ logger.error('Unauthorized', { passTypeIdentifier, serialNumber });
89
109
  reply.status(401);
90
110
  return reply.send();
91
111
  }
@@ -101,11 +121,15 @@ const appleWalletHandler: RouteHandlerMethod = async (
101
121
 
102
122
  if (!newRegistration) {
103
123
  reply.status(200);
104
- return reply.send();
124
+ return reply.send({
125
+ success: true,
126
+ });
105
127
  }
106
128
 
107
129
  reply.status(201);
108
- return reply.send();
130
+ return reply.send({
131
+ success: true,
132
+ });
109
133
  }
110
134
  } else if (req.method === 'GET') {
111
135
  // Get the List of Updatable Passes
@@ -120,21 +144,19 @@ const appleWalletHandler: RouteHandlerMethod = async (
120
144
  deviceLibraryIdentifier,
121
145
  passesUpdatedSince,
122
146
  );
123
- const serialNumbers = passes.map((t) => t.meta.serialNumber);
147
+ const serialNumbers = passes.map((t) => t.meta?.serialNumber).filter(Boolean) as string[];
124
148
 
125
149
  if (serialNumbers?.length) {
126
150
  reply.status(200);
127
- reply.header('content-type', 'application/json');
128
- return reply.send(
129
- JSON.stringify({
130
- serialNumbers,
131
- lastUpdated,
132
- }),
133
- );
151
+ return reply.send({
152
+ success: true,
153
+ serialNumbers,
154
+ lastUpdated,
155
+ });
134
156
  }
135
157
 
136
158
  reply.status(204);
137
- return reply.send();
159
+ return reply.send({ success: true });
138
160
  } else if (req.method === 'DELETE') {
139
161
  // Unregister Device
140
162
  const [deviceLibraryIdentifier, , passTypeIdentifier, serialNumber] = pathComponents;
@@ -145,11 +167,16 @@ const appleWalletHandler: RouteHandlerMethod = async (
145
167
  if (
146
168
  !isAuthenticationTokenCorrect(
147
169
  req,
148
- (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
170
+ (pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
149
171
  )
150
172
  ) {
173
+ logger.error('Unauthorized', { passTypeIdentifier, serialNumber });
151
174
  reply.status(401);
152
- return reply.send();
175
+ return reply.send({
176
+ success: false,
177
+ message: 'Unauthorized',
178
+ name: 'UNAUTHORIZED',
179
+ });
153
180
  }
154
181
 
155
182
  await modules.passes.unregisterDeviceForAppleWalletPass(
@@ -160,7 +187,9 @@ const appleWalletHandler: RouteHandlerMethod = async (
160
187
 
161
188
  // Unregistered
162
189
  reply.status(200);
163
- return reply.send();
190
+ return reply.send({
191
+ success: true,
192
+ });
164
193
  }
165
194
  }
166
195
  } else if (endpoint === 'log') {
@@ -172,7 +201,9 @@ const appleWalletHandler: RouteHandlerMethod = async (
172
201
  }
173
202
  });
174
203
  reply.status(200);
175
- return reply.send();
204
+ return reply.send({
205
+ success: true,
206
+ });
176
207
  } else if (endpoint === 'passes') {
177
208
  if (req.method === 'GET') {
178
209
  // Get an updated Pass
@@ -180,43 +211,64 @@ const appleWalletHandler: RouteHandlerMethod = async (
180
211
 
181
212
  const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
182
213
 
183
- if (pass) {
184
- if (
185
- !isAuthenticationTokenCorrect(
186
- req,
187
- (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
188
- )
189
- ) {
190
- reply.status(401);
191
- return reply.send();
192
- }
214
+ if (!pass) {
215
+ reply.status(404);
216
+ return reply.send();
217
+ }
193
218
 
194
- const { updated, created } = pass;
219
+ if (
220
+ !isAuthenticationTokenCorrect(
221
+ req,
222
+ (pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
223
+ )
224
+ ) {
225
+ logger.error('Unauthorized', { passTypeIdentifier, serialNumber });
226
+ reply.status(401);
227
+ return reply.send({
228
+ success: false,
229
+ message: 'Unauthorized',
230
+ name: 'UNAUTHORIZED',
231
+ });
232
+ }
195
233
 
196
- const lastModifiedDate = new Date(updated || created);
197
- lastModifiedDate.setMilliseconds(0);
234
+ const { updated, created } = pass;
198
235
 
199
- const ifModifiedSinceDate = new Date(req.headers['if-modified-since']);
200
- ifModifiedSinceDate.setMilliseconds(0);
236
+ const lastModifiedDate = new Date(updated || created);
237
+ lastModifiedDate.setMilliseconds(0);
201
238
 
202
- if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
203
- reply.status(304);
204
- return reply.send();
205
- }
239
+ const ifModifiedSinceDate = new Date(req.headers['if-modified-since']!);
240
+ ifModifiedSinceDate.setMilliseconds(0);
206
241
 
207
- const fileUploadAdapter = getFileAdapter();
208
- const signedUrl = await fileUploadAdapter.createDownloadURL(pass);
209
- const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
242
+ if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
243
+ reply.status(304);
244
+ return reply.send({
245
+ success: true,
246
+ message: 'Not modified',
247
+ name: 'NOT_MODIFIED',
248
+ });
249
+ }
210
250
 
211
- const result = await fetch(url);
212
- const data = await result.arrayBuffer();
213
- const uint8View = new Uint8Array(data);
251
+ const fileUploadAdapter = getFileAdapter();
252
+ const signedUrl = await fileUploadAdapter.createDownloadURL(pass);
253
+ const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
214
254
 
215
- reply.status(200);
216
- reply.header('content-type', 'application/vnd.apple.pkpass');
217
- reply.header('last-modified', lastModifiedDate.toUTCString());
218
- return reply.send(uint8View);
255
+ if (!url) {
256
+ reply.status(500);
257
+ return reply.send({
258
+ success: false,
259
+ message: 'Could not create download URL',
260
+ name: 'URL_SIGNING_FAILED',
261
+ });
219
262
  }
263
+
264
+ const result = await fetch(url);
265
+ const data = await result.arrayBuffer();
266
+ const uint8View = new Uint8Array(data);
267
+
268
+ reply.status(200);
269
+ reply.header('content-type', 'application/vnd.apple.pkpass');
270
+ reply.header('last-modified', lastModifiedDate.toUTCString());
271
+ return reply.send(uint8View);
220
272
  }
221
273
  }
222
274