mirror of
https://github.com/NebulaServices/Nebula.git
synced 2025-05-13 03:50:02 -04:00
Add UV
This commit is contained in:
parent
5ae0aad1d6
commit
3b00a749b5
13 changed files with 5887 additions and 2913 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -25,3 +25,6 @@ pnpm-debug.log*
|
|||
|
||||
# nebula catalog database
|
||||
database.sqlite
|
||||
|
||||
# Goofy PNPM problem
|
||||
~/
|
||||
|
|
|
@ -2,13 +2,36 @@ import { defineConfig } from "astro/config";
|
|||
import tailwind from "@astrojs/tailwind";
|
||||
import icon from "astro-icon";
|
||||
import svelte from "@astrojs/svelte";
|
||||
|
||||
import node from "@astrojs/node";
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
import { baremuxPath } from '@mercuryworkshop/bare-mux';
|
||||
import { epoxyPath } from '@mercuryworkshop/epoxy-transport';
|
||||
import { uvPath } from '@titaniumnetwork-dev/ultraviolet';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [tailwind(), icon(), svelte()],
|
||||
vite: {
|
||||
plugins: [
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: `${uvPath}/**/*`.replace(/\\/g, '/'),
|
||||
dest: 'uv',
|
||||
overwrite: false
|
||||
},
|
||||
{
|
||||
src: `${epoxyPath}/**/*`.replace(/\\/g, '/'),
|
||||
dest: 'epoxy',
|
||||
overwrite: false
|
||||
},
|
||||
{
|
||||
src: `${baremuxPath}/**/*`.replace(/\\/g, '/'),
|
||||
dest: 'baremux',
|
||||
overwrite: false
|
||||
}
|
||||
]
|
||||
})
|
||||
],
|
||||
server: {
|
||||
proxy: {
|
||||
"/api/catalog-assets": {
|
||||
|
@ -24,6 +47,12 @@ export default defineConfig({
|
|||
target: "http://localhost:8080",
|
||||
changeOrigin: true,
|
||||
},
|
||||
"/wisp/" : {
|
||||
target: "ws://localhost:8080/wisp/",
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: (path) => path.replace(/^\/wisp\//, '')
|
||||
},
|
||||
"/styles": {
|
||||
target: "http://localhost:8080",
|
||||
changeOrigin: true,
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
"@fastify/compress": "^7.0.3",
|
||||
"@fastify/static": "^7.0.4",
|
||||
"@iconify-json/ph": "^1.1.13",
|
||||
"@mercuryworkshop/bare-mux": "1.1.1",
|
||||
"@mercuryworkshop/epoxy-transport": "2.0.1",
|
||||
"@titaniumnetwork-dev/ultraviolet": "3.1.2",
|
||||
"astro": "^4.12.2",
|
||||
"astro-icon": "^1.1.0",
|
||||
"concurrently": "^8.2.2",
|
||||
|
@ -28,6 +31,8 @@
|
|||
"sqlite3": "^5.1.7",
|
||||
"svelte": "^4.2.18",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"typescript": "^5.5.4"
|
||||
"typescript": "^5.5.4",
|
||||
"vite-plugin-static-copy": "^1.0.6",
|
||||
"wisp-server-node": "^1.1.3"
|
||||
}
|
||||
}
|
||||
|
|
8475
pnpm-lock.yaml
generated
8475
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
20
public/sw.js
Normal file
20
public/sw.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
importScripts('/epoxy/index.js');
|
||||
importScripts('/uv/uv.bundle.js');
|
||||
importScripts('/uv/uv.config.js');
|
||||
importScripts(__uv$config.sw || '/uv/uv.sw.js');
|
||||
const uv = new UVServiceWorker();
|
||||
self.addEventListener('fetch', function (event) {
|
||||
if (event.request.url.startsWith(location.origin + __uv$config.prefix)) {
|
||||
event.respondWith(
|
||||
(async function () {
|
||||
return await uv.fetch(event);
|
||||
})()
|
||||
);
|
||||
} else {
|
||||
event.respondWith(
|
||||
(async function () {
|
||||
return await fetch(event.request);
|
||||
})()
|
||||
);
|
||||
}
|
||||
});
|
30
public/uv/uv.config.js
Normal file
30
public/uv/uv.config.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
self.__uv$config = {
|
||||
prefix: '/~/uv/',
|
||||
bare: '/bare/',
|
||||
encodeUrl: function encode(str) {
|
||||
if (!str) return str;
|
||||
return encodeURIComponent(
|
||||
str
|
||||
.toString()
|
||||
.split('')
|
||||
.map((char, ind) => (ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 3) : char))
|
||||
.join('')
|
||||
);
|
||||
},
|
||||
decodeUrl: function decode(str) {
|
||||
if (!str) return str;
|
||||
let [input, ...search] = str.split('?');
|
||||
|
||||
return (
|
||||
decodeURIComponent(input)
|
||||
.split('')
|
||||
.map((char, ind) => (ind % 2 ? String.fromCharCode(char.charCodeAt(0) ^ 3) : char))
|
||||
.join('') + (search.length ? '?' + search.join('?') : '')
|
||||
);
|
||||
},
|
||||
handler: '/uv/uv.handler.js',
|
||||
client: '/uv/uv.client.js',
|
||||
bundle: '/uv/uv.bundle.js',
|
||||
config: '/uv/uv.config.js',
|
||||
sw: '/uv/uv.sw.js'
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
import express from "express";
|
||||
import { createServer } from "node:http";
|
||||
import path from "path";
|
||||
import wisp from "wisp-server-node";
|
||||
import { Sequelize, DataTypes } from "sequelize";
|
||||
import { fileURLToPath } from "url";
|
||||
import { handler as ssrHandler } from "./dist/server/entry.mjs";
|
||||
|
@ -176,6 +177,13 @@ server.on("request", (req, res) => {
|
|||
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
||||
app(req, res);
|
||||
});
|
||||
|
||||
server.on("upgrade", (req, socket, head) => {
|
||||
if (req.url.endsWith("/wisp/")) {
|
||||
wisp.routeRequest(req, socket, head);
|
||||
}
|
||||
})
|
||||
|
||||
server.listen({
|
||||
port: 8080,
|
||||
});
|
||||
|
|
45
src/components/Scripts.astro
Normal file
45
src/components/Scripts.astro
Normal file
|
@ -0,0 +1,45 @@
|
|||
<script>
|
||||
//load all of the scripts required to use uv/rh (this is not loaded by default due to the size)
|
||||
//Usage: await window.loadProxyScripts or window.loadProxyScript.then(() => {})
|
||||
window.loadProxyScripts = function() {
|
||||
//wrap everything in a promise to avoid race conditions
|
||||
return new Promise<void>((resolve) => {
|
||||
//create and append then scripts tags to the body (this is how we lazy load things)
|
||||
const epoxyScript = document.createElement('script');
|
||||
epoxyScript.src = '/epoxy/index.js';
|
||||
epoxyScript.defer = true;
|
||||
document.body.appendChild(epoxyScript);
|
||||
const uvBundle = document.createElement('script');
|
||||
uvBundle.src = '/uv/uv.bundle.js';
|
||||
uvBundle.defer = true;
|
||||
document.body.appendChild(uvBundle);
|
||||
const uvConfig = document.createElement('script');
|
||||
uvConfig.src = '/uv/uv.config.js';
|
||||
uvConfig.defer = true;
|
||||
document.body.appendChild(uvConfig);
|
||||
const bareMux = document.createElement('script');
|
||||
bareMux.src = '/baremux/bare.cjs';
|
||||
bareMux.defer = true;
|
||||
document.body.appendChild(bareMux);
|
||||
const checkScripts = setInterval(() => {
|
||||
//If both of these aren't defined this will repeat until they are
|
||||
//this allows use to wait for all of the scripts to be ready *before* we setup the serviceworker
|
||||
if (typeof EpxMod !== 'undefined' && typeof BareMux !== 'undefined') {
|
||||
clearInterval(checkScripts);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
})
|
||||
}
|
||||
|
||||
//function to set a transport between the *defined* transports
|
||||
//Usage: await window.setTransport("epoxy") or window.setTransport("epoxy").then(() => {})
|
||||
window.setTransport = function(transport?: string) {
|
||||
//wrap in a promise so we don't register sw until a transport is set.
|
||||
return new Promise<void>((resolve) => {
|
||||
const wispUrl = (location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/wisp/";
|
||||
BareMux.SetTransport("EpxMod.EpoxyClient", { wisp: wispUrl });
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
</script>
|
4
src/env.d.ts
vendored
4
src/env.d.ts
vendored
|
@ -1 +1,5 @@
|
|||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
||||
/// <reference types="@titaniumnetwork-dev/ultraviolet/client" />
|
||||
declare var BareMux: any;
|
||||
declare var EpxMod: any;
|
||||
|
|
|
@ -29,8 +29,36 @@ const t = useTranslations(lang);
|
|||
</h1>
|
||||
</div>
|
||||
<input
|
||||
id="nebula-input"
|
||||
class="font-roboto h-14 rounded-t-2xl w-10/12 rounded-b-2xl border border-input-border-color bg-input p-2 text-center text-xl text-input-text placeholder:text-input-text roboto focus:outline-none md:w-3/12"
|
||||
placeholder={t("home.placeholder")}
|
||||
/>
|
||||
<iframe id="neb-iframe" class="hidden z-100 w-full h-full absolute top-0 bottom-0 bg-primary"></iframe>
|
||||
</div>
|
||||
</Layout>
|
||||
<script>
|
||||
import { initSw } from "@utils/registerSW.ts"; //../../utils/registerSW.ts
|
||||
import { search } from "@utils/search.ts"; //../../utils/search.ts
|
||||
function proxy(term: string) {
|
||||
return __uv$config!.prefix + __uv$config.encodeUrl!(search(term, "https://www.google.com/search?q=%s"));
|
||||
}
|
||||
//we need to rerun this on every page load
|
||||
document.addEventListener("astro:page-load", function () {
|
||||
//wrap this in a try catch as sometimes this element will not be available on the page
|
||||
try {
|
||||
const input = document.getElementById("nebula-input") as HTMLInputElement;
|
||||
const iframe = document.getElementById("neb-iframe") as HTMLIframeElement;
|
||||
input?.addEventListener("keypress", function (event: any) {
|
||||
if (event.key === "Enter") {
|
||||
initSw().then(() => {
|
||||
iframe.classList.remove("hidden");
|
||||
iframe.src = proxy(input?.value);
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
catch (_) {
|
||||
//we purposely don't return anything
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
56
src/utils/registerSW.ts
Normal file
56
src/utils/registerSW.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
function loadProxyScripts() {
|
||||
//wrap everything in a promise to avoid race conditions
|
||||
return new Promise<void>((resolve) => {
|
||||
//create and append then scripts tags to the body (this is how we lazy load things)
|
||||
const epoxyScript = document.createElement('script');
|
||||
epoxyScript.src = '/epoxy/index.js';
|
||||
epoxyScript.defer = true;
|
||||
document.body.appendChild(epoxyScript);
|
||||
const uvBundle = document.createElement('script');
|
||||
uvBundle.src = '/uv/uv.bundle.js';
|
||||
uvBundle.defer = true;
|
||||
document.body.appendChild(uvBundle);
|
||||
const uvConfig = document.createElement('script');
|
||||
uvConfig.src = '/uv/uv.config.js';
|
||||
uvConfig.defer = true;
|
||||
document.body.appendChild(uvConfig);
|
||||
const bareMux = document.createElement('script');
|
||||
bareMux.src = '/baremux/bare.cjs';
|
||||
bareMux.defer = true;
|
||||
document.body.appendChild(bareMux);
|
||||
const checkScripts = setInterval(() => {
|
||||
//If both of these aren't defined this will repeat until they are
|
||||
//this allows use to wait for all of the scripts to be ready *before* we setup the serviceworker
|
||||
if (typeof EpxMod !== 'undefined' && typeof BareMux !== 'undefined') {
|
||||
clearInterval(checkScripts);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
})
|
||||
}
|
||||
|
||||
function setTransport(transport?: string) {
|
||||
//wrap in a promise so we don't register sw until a transport is set.
|
||||
return new Promise<void>((resolve) => {
|
||||
const wispUrl = (location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/wisp/";
|
||||
BareMux.SetTransport("EpxMod.EpoxyClient", { wisp: wispUrl });
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function initSw() {
|
||||
//this is wrapped in a promise to mostly solve the bare-mux v1 problems
|
||||
return new Promise<void>((resolve) => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(async () => {
|
||||
console.debug('Service worker ready!');
|
||||
await loadProxyScripts();
|
||||
await setTransport();
|
||||
resolve();
|
||||
});
|
||||
navigator.serviceWorker.register('/sw.js', { scope: '/' });
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export { initSw, setTransport }
|
27
src/utils/search.ts
Normal file
27
src/utils/search.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
function search(input: string, template: string) {
|
||||
try {
|
||||
// input is a valid URL:
|
||||
// eg: https://example.com, https://example.com/test?q=param
|
||||
return new URL(input).toString();
|
||||
} catch (err) {
|
||||
// input was not a valid URL
|
||||
}
|
||||
|
||||
try {
|
||||
// input is a valid URL when http:// is added to the start:
|
||||
// eg: example.com, https://example.com/test?q=param
|
||||
const url = new URL(`http://${input}`);
|
||||
// only if the hostname has a TLD/subdomain
|
||||
if (url.hostname.includes('.')) return url.toString();
|
||||
} catch (err) {
|
||||
// input was not valid URL
|
||||
}
|
||||
|
||||
// input may have been a valid URL, however the hostname was invalid
|
||||
|
||||
// Attempts to convert the input to a fully qualified URL have failed
|
||||
// Treat the input as a search query
|
||||
return template.replace('%s', encodeURIComponent(input));
|
||||
}
|
||||
|
||||
export { search }
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": ["src"]
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@utils/*" : ["src/utils/*"]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue