www.spaceplanner.app

Web client to the spaceplanner API
git clone git://jacobedwards.org/www.spaceplanner.app
Log | Files | Refs

api.js (3799B)


      1 import * as dev from "/lib/dev.js"
      2 
      3 let config = {
      4 	proto: "https",
      5 	host: "api.spaceplanner.app",
      6 	version: "v0"
      7 }
      8 
      9 if (dev.setting("devapi")) {
     10 	console.warn("Using testing API")
     11 	config.proto = "http"
     12 	let url = new URL(document.URL)
     13 	config.host = url.host
     14 }
     15 
     16 console.log(`Floorplan API: ${config.proto}://${config.host}/${config.version}`)
     17 
     18 export class FetchError extends Error {
     19 	constructor(message, options) {
     20 		super(message, options)
     21 	}
     22 }
     23 
     24 function verify_response(response) {
     25 	let type = response.headers.get("Content-Type")
     26 	if (type != "application/json; charset=utf-8") {
     27 		return Promise.reject(new Error("API returned unacceptable format: " + type))
     28 	} else {
     29 		return Promise.resolve(response)
     30 	}
     31 }
     32 
     33 function parse_response(response) {
     34 	return response.json()
     35 }
     36 
     37 function status(response) {
     38 	// response.code is from appleboy's golang JWT LoginHandler
     39 	// May figure out how to change in the future
     40 	if (response.code >= 200 || response.code < 300) {
     41 		return Promise.resolve(response)
     42 	}
     43 	if (response.status == "ok") {
     44 		return response.body
     45 	}
     46 
     47 	if (response.error) {
     48 		return Promise.reject(new Error(response.error))
     49 	}
     50 	return Promise.reject(new Error("Error undefined"))
     51 }
     52 
     53 function api_fetch(method, endpoint, body) {
     54 	let params = { "method": method, "headers": { "Content-Type": "application/json" } };
     55 	let t = token()
     56 	if (authorized_duration(t) > 0) {
     57 		params["headers"]["Authorization"] = "Bearer " + t
     58 	}
     59 
     60 	if (body) {
     61 		params["body"] = JSON.stringify(body)
     62 	}
     63 	
     64 	return fetch(config.proto + "://" + config.host + "/" + config.version + "/" + requestPath(endpoint), params)
     65 		.catch(function(neterr) {
     66 			console.warn("Fetch error:", neterr)
     67 			throw new FetchError(neterr)
     68 		})
     69 		.then(verify_response)
     70 		.then(parse_response)
     71 		.then(status)
     72 }
     73 
     74 export { api_fetch as fetch }
     75 
     76 export function register(username, password, email, options) {
     77 	options = options ?? {}
     78 
     79 	let req = {
     80 		email: email,
     81 		credentials: { username: username, password: password, email: email }
     82 	}
     83 	if (options.email_policy != null) {
     84 		req.email_policy = options.email_policy
     85 	}
     86 
     87 	return api_fetch("POST", "users", req)
     88 		.then(function(response) {
     89 			console.log("api.register", req)
     90 		})
     91 }
     92 
     93 export function login(username, password) {
     94 	let req = { "username": username, "password": password }
     95 	return api_fetch("POST", "tokens", req)
     96 		.then(function(resp) {
     97 			console.log("api.login", req)
     98 			update_token(resp.token)
     99 		})
    100 }
    101 
    102 export function verifiedEmail() {
    103 	return api_fetch("GET", "users/:user/email/verified")
    104 }
    105 
    106 export function refresh_token() {
    107 	api_fetch("GET", "tokens")
    108 		.then(function(resp) {
    109 			update_token(resp.token)
    110 		})
    111 }
    112 
    113 export function update_token(t) {
    114 	console.log("update_token", t)
    115 	if (!t) {
    116 		localStorage.removeItem("token")
    117 		localStorage.removeItem("token_expires")
    118 		localStorage.removeItem("username")
    119 	} else {
    120 		localStorage.setItem("token", t)
    121 		let p = token_payload(t)
    122 		localStorage.setItem("token_expires", p.exp)
    123 		localStorage.setItem("username", p.id)
    124 	}
    125 }
    126 
    127 export function token() {
    128 	return localStorage.getItem("token")
    129 }
    130 
    131 export function token_payload(t) {
    132 	let a = t.split('.')
    133 	if (a.length != 3) {
    134 		throw new Error("Invalid token")
    135 	}
    136 	return JSON.parse(atob(a[1]))
    137 }
    138 
    139 // Returns seconds until authorization expires, or negative the
    140 // number of seconds it has been expired.
    141 export function authorized_duration(t) {
    142 	let exp = localStorage.getItem("token_expires")
    143 	if (exp == null) {
    144 		return -1
    145 	}
    146 	return Number(exp) - Math.trunc(Date.now() / 1000)
    147 }
    148 
    149 export function authorized() {
    150 	return authorized_duration() > 0
    151 }
    152 
    153 function requestPath(s) {
    154 	let a = s.split("/")
    155 	let subs = {
    156 		":user": localStorage.getItem("username")
    157 	}
    158 
    159 	for (let i in a) {
    160 		if (subs[a[i]] != undefined) {
    161 			a[i] = subs[a[i]]
    162 		}
    163 	}
    164 
    165 	return a.join("/")
    166 }