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 }