zaphyra's git: oeffisearch

fast and simple tripplanner

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 
91 
92 
93 
94 
95 
96 
97 
98 
99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
import { HafasError, byErrorCode } from 'hafas-client/lib/errors.js';

const
	randomBytesHexString = length => [...Array(length)].map(
		() => Math.floor(Math.random() * 16).toString(16)
	).join('')

export const
	checkIfHafasResponseIsOk = (_) => {
		const {
			body,
			errProps: baseErrProps,
		} = _;

		const errProps = { ...baseErrProps };

		if (body.id) errProps.hafasResponseId = body.id;

		// Because we want more accurate stack traces, we don't construct the error here,
		// but only return the constructor & error message.
		const getError = (_) => {
			// mutating here is ugly but pragmatic
			if (_.errTxt) errProps.hafasMessage = _.errTxt;
			if (_.errTxtOut) errProps.hafasDescription = _.errTxtOut;
			if (_.err in byErrorCode) return byErrorCode[_.err];

			return {
				Error: HafasError,
				message: body.errTxt || 'unknown error',
				props: {},
			};
		};

		if (body.err && body.err !== 'OK') {
			const { Error: HafasError, message, props } = getError(body);
			throw new HafasError(message, body.err, { ...errProps, ...props });
		}

		if (!body.svcResL || !body.svcResL[0])
			throw new HafasError('invalid/unsupported response structure', null, errProps);

		if (body.svcResL[0].err !== 'OK') {
			const {Error: HafasError, message, props} = getError(body.svcResL[0]);
			throw new HafasError(message, body.svcResL[0].err, { ...errProps, ...props });
		}
	},

	hafasRequest = async (ctx, userAgent, reqData) => {
		const {profile, opt} = ctx;

		if (profile.addChecksum)
			throw new Error('profile.addChecksum set but not implemented!');
		if (profile.addMicMac)
			throw new Error('profile.addMicMac set but not implemented!');

		const
			rawReqBody = profile.transformReqBody(ctx, {
				// todo: is it `eng` actually?
				// RSAG has `deu` instead of `de`
				lang: opt.language || profile.defaultLanguage || 'en',
				svcReqL: [reqData],

				client: profile.client, // client identification
				ext: profile.ext, // ?
				ver: profile.ver, // HAFAS protocol version
				auth: profile.auth, // static authentication
			}),

			reqId = randomBytesHexString(3).toString('hex'),
			req = profile.transformReq(ctx, {
				agent: null,
				method: 'post',
				// todo: CORS? referrer policy?
				body: JSON.stringify(rawReqBody),
				headers: {
					'Content-Type': 'application/json',
					'Accept-Encoding': 'gzip, br, deflate',
					'Accept': 'application/json',
					'user-agent': userAgent,
					'connection': 'keep-alive', // prevent excessive re-connecting
				},
				redirect: 'follow',
				query: {},
			}),

			url = `/hafas/${profile.client.id}?${(new URLSearchParams(req.query)).toString()}`,
			fetchReq = new Request(url, req),
			res = await fetch(url, req),
			errProps = {
				// todo [breaking]: assign as non-enumerable property
				request: fetchReq,
				// todo [breaking]: assign as non-enumerable property
				response: res,
				url,
			};

		if (!res.ok) {
			// todo [breaking]: make this a FetchError or a HafasClientError?
			const err = new Error(res.statusText);
			Object.assign(err, errProps);
			throw err;
		}

		let cType = res.headers.get('content-type');
		if (!cType.startsWith('application/json'))
			throw new HafasError('invalid/unsupported response content-type: ' + cType, null, errProps);

		const body = JSON.parse(await res.text())

		checkIfHafasResponseIsOk({ body, errProps });

		const svcRes = body.svcResL[0].res;

		return {
			res: svcRes,
			common: profile.parseCommon({...ctx, res: svcRes}),
		};
	};