Skip to content

Instantly share code, notes, and snippets.

@sstur
Last active January 9, 2023 08:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sstur/9055f74baa3159d0ee7ba30a06c6b7b0 to your computer and use it in GitHub Desktop.
Save sstur/9055f74baa3159d0ee7ba30a06c6b7b0 to your computer and use it in GitHub Desktop.
type ReqInit = {
method?: 'PUT' | 'POST';
headers?: HeadersInit;
body: Blob | BufferSource | FormData | URLSearchParams | string;
signal?: AbortSignal;
onProgress?: (bytesLoaded: number, bytesTotal: number) => void;
};
export function upload(url: string, init: ReqInit) {
const { signal, onProgress, body } = init;
const method = init.method ?? 'POST';
const requestHeaders = new Headers(init.headers);
return new Promise<Response>((resolve, reject) => {
const request = new XMLHttpRequest();
request.open(method, url);
request.responseType = 'blob';
requestHeaders.forEach((value: string, key: string) => {
request.setRequestHeader(key, value);
});
if (signal) {
const onAbort = () => {
request.abort();
reject(new AbortError());
};
signal.addEventListener('abort', onAbort);
request.addEventListener('load', () => {
signal.removeEventListener('abort', onAbort);
});
}
if (onProgress) {
request.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
onProgress(event.loaded, event.total);
}
});
}
request.addEventListener('load', () => {
const { status, statusText } = request;
const response = new Response(request.response, {
status,
statusText,
headers: parseHeaders(request),
});
resolve(response);
});
request.send(body);
});
}
function parseHeaders(request: XMLHttpRequest) {
const headers = new Headers();
const lines = request
.getAllResponseHeaders()
.trim()
.split(/[\r\n]+/);
for (const line of lines) {
const key = line.split(':')[0] ?? '';
const value = request.getResponseHeader(key) ?? '';
headers.set(key, value);
}
return headers;
}
class AbortError extends Error {
constructor() {
// This matches that of Chromium
const message = t('The user aborted a request.');
super(message);
}
get name() {
return this.constructor.name;
}
get [Symbol.toStringTag]() {
return this.constructor.name;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment