@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.
- package/jest.config.js +17 -0
- package/lib/index.d.ts +38 -0
- package/lib/index.js +38 -0
- package/lib/index.js.map +1 -0
- package/lib/mobile-tickets/apple-wallet.d.ts +14 -0
- package/lib/mobile-tickets/apple-wallet.js +23 -0
- package/lib/mobile-tickets/apple-wallet.js.map +1 -0
- package/lib/mobile-tickets/apple-webservice.d.ts +6 -0
- package/lib/mobile-tickets/apple-webservice.js +177 -0
- package/lib/mobile-tickets/apple-webservice.js.map +1 -0
- package/lib/mobile-tickets/google-webservice.d.ts +6 -0
- package/lib/mobile-tickets/google-webservice.js +52 -0
- package/lib/mobile-tickets/google-webservice.js.map +1 -0
- package/lib/module.d.ts +26 -0
- package/lib/module.js +143 -0
- package/lib/module.js.map +1 -0
- package/lib/pdf-tickets/print-webservice.d.ts +6 -0
- package/lib/pdf-tickets/print-webservice.js +31 -0
- package/lib/pdf-tickets/print-webservice.js.map +1 -0
- package/lib/template-registry.d.ts +25 -0
- package/lib/template-registry.js +14 -0
- package/lib/template-registry.js.map +1 -0
- package/lib/types.d.ts +7 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/package.json +57 -0
- package/readme.md +93 -0
- package/src/index.ts +78 -0
- package/src/mobile-tickets/apple-wallet.ts +31 -0
- package/src/mobile-tickets/apple-webservice.ts +245 -0
- package/src/mobile-tickets/google-webservice.ts +70 -0
- package/src/module.ts +192 -0
- package/src/pdf-tickets/print-webservice.ts +43 -0
- package/src/template-registry.ts +49 -0
- package/src/types.ts +6 -0
- 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
package/lib/types.js
ADDED
package/lib/types.js.map
ADDED
|
@@ -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
|
+
}
|