river-of-ebooks/assets/js/lib/Ajax.js

163 lines
5.0 KiB
JavaScript

/* global XMLHttpRequest FormData */
'use strict';
let ajaxcfg = {};
class AjaxError extends Error {
constructor (reason, data, xhr) {
super(reason);
this.data = data;
this.xhr = xhr;
}
}
export default class Ajax {
static async get (opts) {
if (!opts) {opts = {};}
opts.method = 'get';
return Ajax.ajax(opts);
}
static async post (opts) {
if (!opts) {opts = {};}
opts.method = 'post';
return Ajax.ajax(opts);
}
static async put (opts) {
if (!opts) {opts = {};}
opts.method = 'put';
return Ajax.ajax(opts);
}
static async patch (opts) {
if (!opts) {opts = {};}
opts.method = 'patch';
return Ajax.ajax(opts);
}
static async delete (opts) {
if (!opts) {opts = {};}
opts.method = 'delete';
return Ajax.ajax(opts);
}
static async head (opts) {
if (!opts) {opts = {};}
opts.method = 'head';
return Ajax.ajax(opts);
}
static async options (opts) {
if (!opts) {opts = {};}
opts.method = 'options';
return Ajax.ajax(opts);
}
static ajax (opts) {
return new Promise((resolve, reject) => {
if (!opts) {reject(new Error('Missing required options parameter.'));}
if (opts.method) {
if (!['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(opts.method.toLowerCase())) {reject(new Error('opts.method must be one of: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.'));}
opts.method = opts.method.toUpperCase();
}
var xhr = opts.xhr || new XMLHttpRequest();
var fd = null;
var qs = '';
if (opts.data && opts.method.toLowerCase() !== 'get') {
fd = new FormData();
for (let key in opts.data) {
fd.append(key, opts.data[key]);
}
} else if (opts.data) {
qs += '?';
let params = [];
for (let key in opts.data) {
params.push([key, opts.data[key]].join('='));
}
qs += params.join('&');
}
xhr.onload = () => {
if (xhr.status !== 200) {return xhr.onerror();}
var data = xhr.response;
resolve({
data,
xhr
});
};
xhr.onerror = () => {
var data = xhr.response;
// method not allowed
if (xhr.status === 405) {
reject(new AjaxError('405 Method Not Allowed', data, xhr));
return;
} else if (xhr.status === 404) {
reject(new AjaxError('404 Not Found', data, xhr));
return;
}
try {
// if the access token is invalid, try to use the refresh token
var json = JSON.parse(data);
if (json.error === 'access_denied' && json.hint.includes('token') && json.hint.includes('invalid') && ajaxcfg.refresh_token) {
return Ajax.refresh(opts);
} else if (json.error === 'access_denied' && json.hint.includes('token') && json.hint.includes('revoked')) {
reject(new AjaxError('token revoked', data, xhr));
}
} catch (e) {
reject(new AjaxError(e.toString(), data, xhr));
} finally {
reject(new AjaxError(data, xhr.status, xhr));
}
};
xhr.open(opts.method || 'GET', opts.url + qs || window.location.href);
if (opts.headers) {
for (let key in opts.headers) {xhr.setRequestHeader(key, opts.headers[key]);}
}
if (ajaxcfg.access_token && !(opts.headers || {}).Authorization) {xhr.setRequestHeader('Authorization', 'Bearer ' + ajaxcfg.access_token);}
xhr.send(fd);
});
}
static refresh (opts) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
var fd = new FormData();
const OAUTH_TOKEN_REQUEST = {
grant_type: 'refresh_token',
refresh_token: ajaxcfg.refresh_token,
client_id: 'foxfile',
client_secret: 1
};
for (var key in OAUTH_TOKEN_REQUEST) {
fd.append(key, OAUTH_TOKEN_REQUEST[key]);
}
// try original request
xhr.onload = () => {
if (xhr.status !== 200) {return xhr.onerror();}
if (ajaxcfg.refresh) {ajaxcfg.refresh(xhr.response);}
var json = JSON.parse(xhr.response);
ajaxcfg.access_token = json.access_token;
ajaxcfg.refresh_token = json.refresh_token;
return Ajax.ajax(opts);
};
// if this fails, dont try again
xhr.onerror = () => {
var data = xhr.response;
reject(new AjaxError(xhr.status, data, xhr));
};
xhr.open('POST', ajaxcfg.refresh_url);
xhr.send(fd);
});
}
static setTokenData (tokens) {
if (!tokens) {throw new Error('Missing tokens.');}
if (!tokens.access_token && !tokens.refresh_token && !tokens.refresh_url) {throw new Error('Missing at least one of: access_token, refresh_token, refresh_url.');}
if (tokens.access_token) {ajaxcfg.access_token = tokens.access_token;}
if (tokens.refresh_token) {ajaxcfg.refresh_token = tokens.refresh_token;}
if (tokens.refresh_url) {ajaxcfg.refresh_url = tokens.refresh_url;}
return true;
}
static onRefresh (func) {
ajaxcfg.refresh = func;
}
}