diff --git a/src/uv.sw.js b/src/uv.sw.js
index 6d5aec4..b36d74a 100644
--- a/src/uv.sw.js
+++ b/src/uv.sw.js
@@ -48,6 +48,11 @@ class UVServiceWorker extends Ultraviolet.EventEmitter {
* @returns
*/
async fetch({ request }) {
+ /**
+ * @type {string|void}
+ */
+ let fetchedURL;
+
try {
if (!request.url.startsWith(location.origin + this.config.prefix))
return await fetch(request);
@@ -116,23 +121,22 @@ class UVServiceWorker extends Ultraviolet.EventEmitter {
if (reqEvent.intercepted) return reqEvent.returnValue;
- const response = await this.bareClient.fetch(
- requestCtx.blob
- ? 'blob:' + location.origin + requestCtx.url.pathname
- : requestCtx.url,
- {
- headers: requestCtx.headers,
- method: requestCtx.method,
- body: requestCtx.body,
- credentials: requestCtx.credentials,
- mode:
- location.origin !== requestCtx.address.origin
- ? 'cors'
- : requestCtx.mode,
- cache: requestCtx.cache,
- redirect: requestCtx.redirect,
- }
- );
+ fetchedURL = requestCtx.blob
+ ? 'blob:' + location.origin + requestCtx.url.pathname
+ : requestCtx.url;
+
+ const response = await this.bareClient.fetch(fetchedURL, {
+ headers: requestCtx.headers,
+ method: requestCtx.method,
+ body: requestCtx.body,
+ credentials: requestCtx.credentials,
+ mode:
+ location.origin !== requestCtx.address.origin
+ ? 'cors'
+ : requestCtx.mode,
+ cache: requestCtx.cache,
+ redirect: requestCtx.redirect,
+ });
const responseCtx = new ResponseContext(requestCtx, response);
const resEvent = new HookEvent(responseCtx, null, null);
@@ -254,9 +258,7 @@ class UVServiceWorker extends Ultraviolet.EventEmitter {
console.error(err);
- return new Response(err.toString(), {
- status: 500,
- });
+ return renderError(err, fetchedURL, this.address);
}
}
static Ultraviolet = Ultraviolet;
@@ -358,3 +360,185 @@ class HookEvent {
this.#intercepted = true;
}
}
+
+/**
+ *
+ * @param {string} fetchedURL
+ * @param {string} bareServer
+ * @returns
+ */
+function hostnameErrorTemplate(fetchedURL, bareServer) {
+ const parsedFetchedURL = new URL(fetchedURL);
+ const script =
+ `remoteHostname.textContent = ${JSON.stringify(
+ parsedFetchedURL.hostname
+ )};` +
+ `bareServer.href = ${JSON.stringify(bareServer)};` +
+ `uvHostname.textContent = ${JSON.stringify(location.hostname)};` +
+ `reload.addEventListener("click", () => location.reload())`;
+
+ return (
+ '' +
+ '' +
+ '
' +
+ "" +
+ 'Error' +
+ '' +
+ '' +
+ 'This site can’t be reached
' +
+ '
' +
+ '’s server IP address could not be found.
' +
+ 'Try:
' +
+ '' +
+ '- Make sure you entered the correct address
' +
+ '- Clearing the site data
' +
+ '- Contact the administrator of
' +
+ "- Verify your Bare server isn't censored
" +
+ '
' +
+ '' +
+ `` +
+ '' +
+ ''
+ );
+}
+
+/**
+ *
+ * @param {string} title
+ * @param {string} code
+ * @param {string} id
+ * @param {string} message
+ * @param {string} trace
+ * @param {string} fetchedURL
+ * @param {string} bareServer
+ * @returns
+ */
+function errorTemplate(
+ title,
+ code,
+ id,
+ message,
+ trace,
+ fetchedURL,
+ bareServer
+) {
+ // produced by bare-server-node
+ if (message === 'The specified host could not be resolved.')
+ return hostnameErrorTemplate(fetchedURL, bareServer);
+
+ // turn script into a data URI so we don't have to escape any HTML values
+ const script =
+ `errorTitle.textContent = ${JSON.stringify(title)};` +
+ `errorCode.textContent = ${JSON.stringify(code)};` +
+ (id ? `errorId.textContent = ${JSON.stringify(id)};` : '') +
+ `errorMessage.textContent = ${JSON.stringify(message)};` +
+ `errorTrace.value = ${JSON.stringify(trace)};` +
+ `fetchedURL.textContent = ${JSON.stringify(fetchedURL)};` +
+ `bareServer.href = ${JSON.stringify(bareServer)};` +
+ `uvHostname.textContent = ${JSON.stringify(location.hostname)};` +
+ `reload.addEventListener("click", () => location.reload());`;
+
+ return (
+ '' +
+ '' +
+ '' +
+ "" +
+ 'Error' +
+ '' +
+ '' +
+ "" +
+ '
' +
+ 'Failed to load
' +
+ '' +
+ '' +
+ 'Code: | |
' +
+ (id ? 'ID: | |
' : '') +
+ '
' +
+ '' +
+ 'Try:
' +
+ '' +
+ '- Make sure you entered the correct address
' +
+ '- Clearing the site data
' +
+ '- Contact the administrator of
' +
+ "- Verify your Bare server isn't censored
" +
+ '
' +
+ '' +
+ `` +
+ '' +
+ ''
+ );
+}
+
+/**
+ * @typedef {import("@tomphttp/bare-client").BareError} BareError
+ */
+
+/**
+ *
+ * @param {unknown} err
+ * @returns {err is BareError}
+ */
+function isBareError(err) {
+ return err instanceof Error && typeof err.body === 'object';
+}
+
+/**
+ *
+ * @param {unknown} err
+ * @param {string} fetchedURL
+ * @param {string} bareServer
+ */
+function renderError(err, fetchedURL, bareServer) {
+ /**
+ * @type {number}
+ */
+ let status;
+ /**
+ * @type {string}
+ */
+ let title;
+ /**
+ * @type {string}
+ */
+ let code;
+ let id = '';
+ /**
+ * @type {string}
+ */
+ let message;
+
+ if (isBareError(err)) {
+ status = err.status;
+ title = 'Error communicating with the Bare server';
+ message = err.body.message;
+ code = err.body.code;
+ id = err.body.id;
+ } else {
+ status = 500;
+ title = 'Error processing your request';
+ message = 'Internal Server Error';
+ code = err instanceof Error ? err.name : 'UNKNOWN';
+ }
+
+ return new Response(
+ errorTemplate(
+ title,
+ code,
+ id,
+ message,
+ String(err),
+ fetchedURL,
+ bareServer
+ ),
+ {
+ status,
+ headers: {
+ 'content-type': 'text/html',
+ },
+ }
+ );
+}