Overhauled XHR API.

This commit is contained in:
flash 2025-01-04 01:45:40 +00:00
parent 12bb564613
commit 8083ad6a20
7 changed files with 135 additions and 139 deletions

View file

@ -1,4 +1,4 @@
#include utility.js
#include xhr.js
#include audio/source.js
const MamiAudioContext = function() {
@ -63,7 +63,7 @@ const MamiAudioContext = function() {
return undefined;
const result = await $x.get(url, { type: 'arraybuffer' });
return await ctx.decodeAudioData(result.body());
return await ctx.decodeAudioData(result.body);
},
createSource: (buffer, reverse) => {

View file

@ -1,4 +1,4 @@
#include utility.js
#include xhr.js
const FutamiCommon = function(vars) {
vars = vars || {};
@ -15,14 +15,14 @@ const FutamiCommon = function(vars) {
if(noCache)
options.headers = { 'Cache-Control': 'no-cache' };
return (await $x.get(get(name), options)).body();
return (await $x.get(get(name), options)).body;
},
getApiJson: async (path, noCache) => {
const options = { type: 'json' };
if(noCache)
options.headers = { 'Cache-Control': 'no-cache' };
return (await $x.get(get('api') + path, options)).body();
return (await $x.get(get('api') + path, options)).body;
},
};
};
@ -31,12 +31,12 @@ FutamiCommon.load = async url => {
if(typeof url !== 'string' && 'FUTAMI_URL' in window)
url = window.FUTAMI_URL + '?t=' + Date.now().toString();
const resp = await $x.get(url, {
const { body } = await $x.get(url, {
type: 'json',
headers: {
'Cache-Control': 'no-cache'
}
});
return new FutamiCommon(resp.body());
return new FutamiCommon(body);
};

View file

@ -1,4 +1,4 @@
#include utility.js
#include xhr.js
const MamiEEPROM = function(endPoint, getAuthLine) {
if(typeof endPoint !== 'string')
@ -47,7 +47,7 @@ const MamiEEPROM = function(endPoint, getAuthLine) {
formData.append('src', appId);
formData.append('file', fileInput);
const result = await $x.post(`${endPoint}/uploads`, {
const { status, body } = await $x.post(`${endPoint}/uploads`, {
type: 'json',
headers: {
Authorization: getAuthLine(),
@ -55,13 +55,11 @@ const MamiEEPROM = function(endPoint, getAuthLine) {
upload: reportProgress,
abort: handler => abortHandler = handler,
}, formData);
const body = result.body();
console.log(body);
if(body === null)
throw "The upload server didn't return the metadata for some reason.";
if(result.status !== 201)
throw body.english ?? body.error ?? `Upload failed with status code ${result.status}`;
if(status !== 201)
throw body.english ?? body.error ?? `Upload failed with status code ${status}`;
body.isImage = () => body.type.startsWith('image/');
body.isVideo = () => body.type.startsWith('video/');
@ -86,19 +84,18 @@ const MamiEEPROM = function(endPoint, getAuthLine) {
if(typeof fileInfo.urlf !== 'string')
throw 'fileInfo.urlf must be a string';
const result = await $x.delete(fileInfo.urlf, {
const { status, body } = await $x.delete(fileInfo.urlf, {
type: 'json',
headers: {
Authorization: getAuthLine(),
},
});
if(result.status !== 204) {
const body = result.body();
if(status !== 204) {
if(body === null)
throw `Delete failed with status code ${result.status}`;
throw `Delete failed with status code ${status}`;
throw body.english ?? body.error ?? `Delete failed with status code ${result.status}`;
throw body.english ?? body.error ?? `Delete failed with status code ${status}`;
}
},
};

View file

@ -1,5 +1,5 @@
#include common.js
#include utility.js
#include xhr.js
const MamiMisuzuAuth = (() => {
let userId = null;
@ -18,9 +18,7 @@ const MamiMisuzuAuth = (() => {
};
},
update: async () => {
const resp = await $x.get(futami.get('token'), { authed: true, type: 'json' });
const body = resp.body();
const { body } = await $x.get(futami.get('token'), { authed: true, type: 'json' });
if(body.ok) {
userId = body.usr.toString();
authToken = body.tkn;

View file

@ -168,118 +168,3 @@ const $as = function(array) {
array[j] = tmp;
}
};
const $x = (function() {
const send = function(method, url, options, body) {
if(options === undefined)
options = {};
else if(typeof options !== 'object')
throw 'options must be undefined or an object';
const xhr = new XMLHttpRequest;
const requestHeaders = new Map;
if('headers' in options && typeof options.headers === 'object')
for(const name in options.headers)
if(options.headers.hasOwnProperty(name))
requestHeaders.set(name.toLowerCase(), options.headers[name]);
if(typeof options.download === 'function') {
xhr.onloadstart = ev => options.download(ev);
xhr.onprogress = ev => options.download(ev);
xhr.onloadend = ev => options.download(ev);
}
if(typeof options.upload === 'function') {
xhr.upload.onloadstart = ev => options.upload(ev);
xhr.upload.onprogress = ev => options.upload(ev);
xhr.upload.onloadend = ev => options.upload(ev);
}
if(options.authed)
xhr.withCredentials = true;
if(typeof options.timeout === 'number')
xhr.timeout = options.timeout;
if(typeof options.type === 'string')
xhr.responseType = options.type;
if(typeof options.abort === 'function')
options.abort(() => xhr.abort());
if(typeof options.xhr === 'function')
options.xhr(() => xhr);
if(typeof body === 'object') {
if(body instanceof URLSearchParams) {
requestHeaders.set('content-type', 'application/x-www-form-urlencoded');
} else if(body instanceof FormData) {
// content-type is implicitly set
} else if(body instanceof Blob || body instanceof ArrayBuffer || body instanceof DataView) {
if(!requestHeaders.has('content-type'))
requestHeaders.set('content-type', 'application/octet-stream');
} else if(!requestHeaders.has('content-type')) {
const bodyParts = [];
for(const name in body)
if(body.hasOwnProperty(name))
bodyParts.push(encodeURIComponent(name) + '=' + encodeURIComponent(body[name]));
body = bodyParts.join('&');
requestHeaders.set('content-type', 'application/x-www-form-urlencoded');
}
}
return new Promise((resolve, reject) => {
let responseHeaders = undefined;
xhr.onload = ev => resolve({
status: xhr.status,
body: () => xhr.response,
text: () => xhr.responseText,
headers: () => {
if(responseHeaders !== undefined)
return responseHeaders;
responseHeaders = new Map;
const raw = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/);
for(const name in raw)
if(raw.hasOwnProperty(name)) {
const parts = raw[name].split(': ');
responseHeaders.set(parts.shift(), parts.join(': '));
}
return responseHeaders;
},
xhr: xhr,
ev: ev,
});
xhr.onabort = ev => reject({
abort: true,
xhr: xhr,
ev: ev,
});
xhr.onerror = ev => reject({
abort: false,
xhr: xhr,
ev: ev,
});
xhr.open(method, url);
for(const [name, value] of requestHeaders)
xhr.setRequestHeader(name, value);
xhr.send(body);
});
};
return {
send: send,
get: (url, options, body) => send('GET', url, options, body),
post: (url, options, body) => send('POST', url, options, body),
delete: (url, options, body) => send('DELETE', url, options, body),
patch: (url, options, body) => send('PATCH', url, options, body),
put: (url, options, body) => send('PUT', url, options, body),
};
})();

View file

@ -1,6 +1,6 @@
#include common.js
#include rng.js
#include utility.js
#include xhr.js
const Weeaboo = (function() {
let kaomoji = [];
@ -17,7 +17,7 @@ const Weeaboo = (function() {
return;
$x.get(futami.get('kaomoji'))
.then(resp => kaomoji = resp.text().split("\n"));
.then(resp => kaomoji = resp.text.split("\n"));
};
pub.getRandomKaomoji = function(allowEmpty, message) {

116
src/mami.js/xhr.js Normal file
View file

@ -0,0 +1,116 @@
const $x = (function() {
const send = function(method, url, options, body) {
if(options === undefined)
options = {};
else if(typeof options !== 'object')
throw 'options must be undefined or an object';
Object.freeze(options);
const xhr = new XMLHttpRequest;
const requestHeaders = new Map;
if('headers' in options && typeof options.headers === 'object')
for(const name in options.headers)
if(options.headers.hasOwnProperty(name))
requestHeaders.set(name.toLowerCase(), options.headers[name]);
if(typeof options.download === 'function') {
xhr.onloadstart = ev => options.download(ev);
xhr.onprogress = ev => options.download(ev);
xhr.onloadend = ev => options.download(ev);
}
if(typeof options.upload === 'function') {
xhr.upload.onloadstart = ev => options.upload(ev);
xhr.upload.onprogress = ev => options.upload(ev);
xhr.upload.onloadend = ev => options.upload(ev);
}
if(options.authed)
xhr.withCredentials = true;
if(typeof options.timeout === 'number')
xhr.timeout = options.timeout;
if(typeof options.type === 'string')
xhr.responseType = options.type;
if(typeof options.abort === 'function')
options.abort(() => xhr.abort());
if(typeof options.xhr === 'function')
options.xhr(() => xhr);
if(typeof body === 'object') {
if(body instanceof URLSearchParams) {
requestHeaders.set('content-type', 'application/x-www-form-urlencoded');
} else if(body instanceof FormData) {
// content-type is implicitly set
} else if(body instanceof Blob || body instanceof ArrayBuffer || body instanceof DataView) {
if(!requestHeaders.has('content-type'))
requestHeaders.set('content-type', 'application/octet-stream');
} else if(!requestHeaders.has('content-type')) {
const bodyParts = [];
for(const name in body)
if(body.hasOwnProperty(name))
bodyParts.push(encodeURIComponent(name) + '=' + encodeURIComponent(body[name]));
body = bodyParts.join('&');
requestHeaders.set('content-type', 'application/x-www-form-urlencoded');
}
}
return new Promise((resolve, reject) => {
xhr.onload = ev => {
const headers = (headersString => {
const headers = new Map;
const raw = headersString.trim().split(/[\r\n]+/);
for(const name in raw)
if(raw.hasOwnProperty(name)) {
const parts = raw[name].split(': ');
headers.set(parts.shift(), parts.join(': '));
}
return headers;
})(xhr.getAllResponseHeaders());
resolve({
get ev() { return ev; },
get xhr() { return xhr; },
get status() { return xhr.status; },
get headers() { return headers; },
get body() { return xhr.response; },
get text() { return xhr.responseText; },
});
};
xhr.onabort = ev => reject({
abort: true,
xhr: xhr,
ev: ev,
});
xhr.onerror = ev => reject({
abort: false,
xhr: xhr,
ev: ev,
});
xhr.open(method, url);
for(const [name, value] of requestHeaders)
xhr.setRequestHeader(name, value);
xhr.send(body);
});
};
return {
send: send,
get: (url, options, body) => send('GET', url, options, body),
post: (url, options, body) => send('POST', url, options, body),
delete: (url, options, body) => send('DELETE', url, options, body),
patch: (url, options, body) => send('PATCH', url, options, body),
put: (url, options, body) => send('PUT', url, options, body),
};
})();