Files
moho/scripts/lib/stoat-local.mjs
T

61 lines
3.1 KiB
JavaScript

/**
* The local self-hosted Stoat the Stoat probes talk to (http://localhost:8880, started with
* ~/.local/share/moho-dev/stoat-docker.sh and set up with stoat-local.py): its two test accounts, and calls made as
* either of them - the "other person" in a conversation moho is one side of.
*/
import fs from 'node:fs'
export const API = 'http://localhost:8880/api'
export const accounts = () => JSON.parse(fs.readFileSync('/1tb/stoat-test/accounts.json', 'utf8'))
/** One call as `who` ('alice' or 'bob'). */
export async function as(who, method, path, body) {
return withToken(accounts()[who].token, method, path, body)
}
/** A call with a token, waiting out the instance's rate limit as a client has to (a 429 says how long). */
async function withToken(token, method, path, body) {
for (let attempt = 0; ; attempt++) {
const r = await fetch(API + path, {
method,
headers: { 'X-Session-Token': token, ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}) },
body: body !== undefined ? JSON.stringify(body) : undefined
})
const text = await r.text()
if (r.status === 429 && attempt < 4) {
await new Promise((resolve) => setTimeout(resolve, (JSON.parse(text).retry_after ?? 1000) + 50))
continue
}
if (!r.ok) throw new Error(`${method} ${path}: ${r.status} ${text.slice(0, 200)}`)
return text ? JSON.parse(text) : null
}
}
/** A file put on the file server as `who`, answering its id. `tag` is attachments, emojis, avatars, icons... */
export async function upload(who, name, bytes, type, tag = 'attachments') {
const a = accounts()[who]
const form = new FormData()
form.append('file', new Blob([bytes], { type }), name)
const r = await fetch(`http://localhost:8880/autumn/${tag}`, { method: 'POST', headers: { 'X-Session-Token': a.token }, body: form })
if (!r.ok) throw new Error(`upload: ${r.status} ${await r.text()}`)
return (await r.json()).id
}
/** A new account made on the local instance (never the official one): answers its token and id, for a probe that needs a stranger. */
export async function makeAccount(name) {
const email = `${name}@test.local`
const password = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)
const post = async (path, body, token) => {
const r = await fetch(API + path, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(token ? { 'X-Session-Token': token } : {}) }, body: JSON.stringify(body) })
const text = await r.text()
if (!r.ok) throw new Error(`POST ${path}: ${r.status} ${text.slice(0, 200)}`)
return text ? JSON.parse(text) : null
}
await post('/auth/account/create', { email, password })
const session = await post('/auth/session/login', { email, password, friendly_name: 'probe' })
await post('/onboard/complete', { username: name }, session.token)
const me = await (await fetch(API + '/users/@me', { headers: { 'X-Session-Token': session.token } })).json()
const call = (method, path, body) => withToken(session.token, method, path, body)
return { id: me._id, username: me.username, token: session.token, call }
}