HAFAS's subscription feature has a flaw: It lets you store arbitrary data. Let's make a cloud storage API from this!
Last active
October 19, 2021 22:10
-
-
Save derhuerst/f60e49de1190c4983f02d01a18128c22 to your computer and use it in GitHub Desktop.
HAFAS-subscription-based cloud storage ✨
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
const {v4: uuidV4} = require('uuid') | |
const createStore = require('./3-fs') | |
;(async () => { | |
const storeId = process.env.STORE_ID || uuidV4() | |
console.log('creating HAFAS-backed blob store') | |
const store = await createStore(storeId) | |
console.log('storing 10 new random 100b blobs') | |
const blobs = new Array(10).fill().map(_ => randomBytes(100)) | |
await Promise.all(blobs.map(store.create)) | |
console.log('retrieving IDs of all stored blobs') | |
const blobIds = await store.list() | |
console.log('stored:', blobIds) | |
console.log('reading 1st blob') | |
console.log('1st blob:', await store.read(blobIds[0])) | |
console.log('deleting 1st blob') | |
await store.delete(blobIds[0]) | |
console.log('removing store and all its data') | |
await store.destroy() | |
console.log('done!') | |
})() | |
.catch((err) => { | |
console.error(err) | |
process.exit(1) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
const https = require('https') | |
const {ok} = require('assert') | |
const {randomBytes} = require('crypto') | |
const ENDPOINT = 'https://reiseauskunft.insa.de/bin/mgate.exe' | |
const AID = process.env.HAFAS_AUTH_AID | |
if (!AID) { | |
console.error('missing HAFAS_AUTH_AID env var') | |
process.exit(1) | |
} | |
// generic HAFAS request helper function | |
const request = async (method, reqData) => { | |
const rawBody = await new Promise((resolve, reject) => { | |
const req = https.request(ENDPOINT, {method: 'POST'}, (res) => { | |
let body = '' | |
res.once('error', reject) | |
res.on('data', d => body += d) | |
res.once('end', () => resolve(body)) | |
}) | |
req.once('error', reject) | |
req.setHeader('content-type', 'application/json') | |
req.setHeader('accept', 'application/json') | |
const reqBody = JSON.stringify({ | |
lang: 'en', | |
client: { | |
type: "IPH", | |
id: "NASA", | |
v: "4000200", | |
name: "nasaPROD" | |
}, | |
ver: "1.44", | |
auth: { | |
type: "AID", | |
aid: AID, | |
}, | |
svcReqL: [{ | |
meth: method, | |
req: reqData, | |
}], | |
}) | |
req.end(reqBody) | |
}) | |
const resBody = JSON.parse(rawBody) | |
if (resBody.err !== 'OK') { | |
const err = new Error(resBody.errTxt) | |
err.res = resBody | |
throw err | |
} | |
const res = resBody.svcResL[0].res | |
if (res.result && res.result.resultCode !== 'OK') { | |
const err = new Error(res.result.internalError) | |
err.res = resBody | |
throw err | |
} | |
return res | |
} | |
const findTripReconCtx = async () => { | |
const res = await request('TripSearch', { | |
depLocL: [{type: 'S', lid: 'A=1@L=90053@'}], | |
arrLocL: [{type: 'S', lid: 'A=1@L=90072@'}], | |
numF: 1, | |
}) | |
return res.outConL[0].recon.ctx | |
} | |
const ensureSubscriptionUser = async (userId) => { | |
await request('SubscrUserCreate', {userId}) | |
} | |
const deleteSubscriptionUser = async (userId) => { | |
await request('SubscrUserDelete', {userId}) | |
} | |
const listSubscriptions = async (userId) => { | |
const res = await request('SubscrSearch', {userId, onlySubIds: true}) | |
if (!Array.isArray(res.idSubscrL)) return [] | |
return res.idSubscrL.map(s => s.subscrId) | |
} | |
const createSubscription = async (userId, ctxRecon, data) => { | |
const formatDate = (d) => ( | |
d.getFullYear() | |
+ ('0' + (d.getMonth() + 1)).slice(-2) | |
+ ('0' + d.getDate()).slice(-2) | |
) | |
const res = await request('SubscrCreate', { | |
userId, | |
conSubscr: { | |
ctxRecon, | |
serviceDays: { | |
beginDate: formatDate(new Date()), | |
endDate: formatDate(new Date(Date.now() + 24 * 60 * 60 * 1000)), // tomorrow | |
}, | |
data, | |
}, | |
}) | |
return res.subscrId | |
} | |
const readSubscription = async (userId, subscrId) => { | |
const res = await request('SubscrDetails', {userId, subscrId}) | |
return { | |
id: res.details.subscrId, | |
ctxRecon: res.details.conSubscr.ctxRecon, | |
data: res.details.conSubscr.data, | |
} | |
} | |
const deleteSubscription = async (userId, subscrId) => { | |
await request('SubscrDelete', {userId, subscrId}) | |
} | |
const createStore = async (userId) => { | |
await ensureSubscriptionUser(userId) | |
const reconCtx = await findTripReconCtx() | |
const create = async (data) => { | |
ok(Buffer.isBuffer(data), 'data must be a buffer') | |
return await createSubscription(userId, reconCtx, data.toString('base64')) | |
} | |
const read = async (subId) => { | |
const sub = await readSubscription(userId, subId) | |
return Buffer.from(sub.data, 'base64') | |
} | |
const del = async (subId) => { | |
await deleteSubscription(userId, subId) | |
} | |
const list = async () => { | |
return await listSubscriptions(userId) | |
} | |
const destroy = async () => { | |
const subIds = await list() | |
await Promise.all(subIds.map(del)) | |
await deleteSubscriptionUser(userId) | |
} | |
return {create, read, delete: del, list, destroy} | |
} | |
module.exports = createStore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "hafas-fs", | |
"version": "1.0.0", | |
"description": "", | |
"main": "index.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"keywords": [], | |
"author": "Jannis R", | |
"license": "ISC", | |
"dependencies": { | |
"uuid": "^8.3.2" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment