// authentication model const _make_auth = api_root => { // members let _login_name = '', _password = '' let _token_payload = {} // methods let _token = value => { if (value) localStorage.setItem('token', value) return localStorage.getItem('token') } let _load_token_payload = _ => { try { _token_payload = JSON.parse(atob(_token().split('.')[1])) } catch (e) { console.log('failed parsing token: ', e) } } let _login = _ => m.request({ method: 'POST', url: `${api_root}/rpc/login`, body: { login_name: _login_name, password: _password }}).then(data => { _token(data) _load_token_payload() // TODO set to origin page m.route.set('/') }).catch(e => alert(e.response.message)) // initialize _load_token_payload() // public methods return { login: _login, signed_in: _ => _token() ? true : false, payload: _ => _token_payload, token: _token, login_name: value => { if (value) _login_name = value return _login_name }, password: value => { if (value) _password = value return _password } } } // main api const _make_api = (api_root, need_auth=true) => { // private state let _root = api_root // if auth is required let _auth = need_auth ? _make_auth(api_root) : null return { // utilities download: download, // expose authentication module auth: _auth, } } /* api wrapper */ /* * api.request(options): all options will be forwarded to m.request, except a * few: * * 1. options.queries: an array of object '{label: <>, value: <>, op: <>}' to * build postgrest query easily */ export default (api_root, auth={}) => { return { // wrap m.request with authentication validation if demanded request: (options = {}) => { /* normalize arguments */ if (typeof options === 'string') { options = {url: options} } /* normalize url */ if (options.url.startsWith('/')) options.url = api_root + options.url /* normalize headers */ // 1. check if auth is required if (false/*need_auth*/) { // insert auth header if availible if (_auth.token()) { options.headers = { Authorization: 'Bearer ' + _auth.token(), ...options.headers } } // route to login else { m.route.set('/login') } } // 2. return values on Prefer // if (['POST', 'PATCH'].includes(options.method) && options.headers && !options.headers.Prefer) if (['POST', 'PATCH'].includes(options.method)) options.config = xhr => { xhr.setRequestHeader('Prefer', 'return=representation') return xhr } // normalize params: since options.params is an object, it cannot support // duplicated query keys, while postgrest supports that. // // Simply build query string by hand. if (options.queries) { // build the query string let _query_string = options.queries .filter(query => typeof query != 'undefined') .map(query => { // prepare the query kv pair and optional operator let k = query.label let v = query.value let op = query.op // return early if v is empty if (typeof v == 'undefined' || (!v && v !== 0)) return undefined // generate value if v is a function if (typeof v == 'function') v = v() // format v on k & op if (op == 'like') v = `*${v}*` else if (op == 'like.right') { v = `${v}*` op = 'like' } else if (op == 'in') v = `(${v.join(',')})` else if (k == 'and') { v = '(' + v.filter(sub_query => sub_query.value == 0 || sub_query.value).map(sub_query => `${sub_query.label}.${sub_query.op}.${typeof sub_query.value == 'function' ? sub_query.value() : sub_query.value}`).join(',') + ')' } return {k: k, v: op ? `${op}.${v}` : v} }) .map(query => query.k + '=' + encodeURIComponent(query.v)).join('&') // append query string to url if necessary if (_query_string) options.url = options.url + '?' + _query_string delete options.queries } return m.request(options).catch(e => { if (e.code === 401) { localStorage.removeItem('token') m.route.set('/login') } throw e }) } } }