diff options
author | Brian Picciano <mediocregopher@gmail.com> | 2022-05-20 13:37:43 -0600 |
---|---|---|
committer | Brian Picciano <mediocregopher@gmail.com> | 2022-05-20 13:37:43 -0600 |
commit | 16cfbd19157df76e7296dddb287412f1099feb33 (patch) | |
tree | e4bbf892066cceeaeeaee4c25e5365152412a1c3 /srv/src/http/static/api.js | |
parent | 3cdee89c961ae9c836234f5aec87174a04a800a8 (diff) |
Move static assets to within srv
Diffstat (limited to 'srv/src/http/static/api.js')
-rw-r--r-- | srv/src/http/static/api.js | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/srv/src/http/static/api.js b/srv/src/http/static/api.js new file mode 100644 index 0000000..a635118 --- /dev/null +++ b/srv/src/http/static/api.js @@ -0,0 +1,132 @@ +import * as utils from "/static/utils.js"; + +const csrfTokenCookie = "csrf_token"; + +const doFetch = async (req) => { + let res, jsonRes; + try { + res = await fetch(req); + jsonRes = await res.json(); + + } catch (e) { + + if (e instanceof SyntaxError) + e = new Error(`status ${res.status}, empty (or invalid) response body`); + + console.error(`api call ${req.method} ${req.url}: unexpected error:`, e); + throw e; + } + + if (jsonRes.error) { + console.error( + `api call ${req.method} ${req.url}: application error:`, + res.status, + jsonRes.error, + ); + + throw jsonRes.error; + } + + return jsonRes; +} + +// may throw +const solvePow = async () => { + + const res = await call('/api/pow/challenge'); + + const worker = new Worker('/static/solvePow.js'); + + const p = new Promise((resolve, reject) => { + worker.postMessage({seedHex: res.seed, target: res.target}); + worker.onmessage = resolve; + }); + + const powSol = (await p).data; + worker.terminate(); + + return {seed: res.seed, solution: powSol}; +} + +const call = async (route, opts = {}) => { + const { + method = 'POST', + body = {}, + requiresPow = false, + } = opts; + + if (!utils.cookies[csrfTokenCookie]) + throw `${csrfTokenCookie} cookie not set, can't make api call`; + + const reqOpts = { + method, + headers: { + "X-CSRF-Token": utils.cookies[csrfTokenCookie], + }, + }; + + if (requiresPow) { + const {seed, solution} = await solvePow(); + body.powSeed = seed; + body.powSolution = solution; + } + + if (Object.keys(body).length > 0) { + const form = new FormData(); + for (const key in body) form.append(key, body[key]); + + reqOpts.body = form; + } + + const req = new Request(route, reqOpts); + return doFetch(req); +} + +const ws = async (route, opts = {}) => { + const { + requiresPow = false, + params = {}, + } = opts; + + const docURL = new URL(document.URL); + const protocol = docURL.protocol == "http:" ? "ws:" : "wss:"; + + const fullParams = new URLSearchParams(params); + const csrfToken = utils.cookies[csrfTokenCookie]; + + if (!csrfToken) + throw `${csrfTokenCookie} cookie not set, can't make api call`; + + fullParams.set("csrfToken", csrfToken); + + if (requiresPow) { + const {seed, solution} = await solvePow(); + fullParams.set("powSeed", seed); + fullParams.set("powSolution", solution); + } + + const rawConn = new WebSocket(`${protocol}//${docURL.host}${route}?${fullParams.toString()}`); + + const conn = { + next: () => new Promise((resolve, reject) => { + rawConn.onmessage = (m) => { + const mj = JSON.parse(m.data); + resolve(mj); + }; + rawConn.onerror = reject; + rawConn.onclose = reject; + }), + + close: rawConn.close, + }; + + return new Promise((resolve, reject) => { + rawConn.onopen = () => resolve(conn); + rawConn.onerror = reject; + }); +} + +export { + call, + ws +} |