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
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -24,4 +24,7 @@ pnpm-debug.log*
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
# nebula catalog database
|
# nebula catalog database
|
||||||
database.sqlite
|
database.sqlite
|
||||||
|
|
||||||
|
# Goofy PNPM problem
|
||||||
|
~/
|
||||||
|
|
|
@ -2,13 +2,36 @@ import { defineConfig } from "astro/config";
|
||||||
import tailwind from "@astrojs/tailwind";
|
import tailwind from "@astrojs/tailwind";
|
||||||
import icon from "astro-icon";
|
import icon from "astro-icon";
|
||||||
import svelte from "@astrojs/svelte";
|
import svelte from "@astrojs/svelte";
|
||||||
|
|
||||||
import node from "@astrojs/node";
|
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({
|
export default defineConfig({
|
||||||
integrations: [tailwind(), icon(), svelte()],
|
integrations: [tailwind(), icon(), svelte()],
|
||||||
vite: {
|
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: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
"/api/catalog-assets": {
|
"/api/catalog-assets": {
|
||||||
|
@ -24,6 +47,12 @@ export default defineConfig({
|
||||||
target: "http://localhost:8080",
|
target: "http://localhost:8080",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
|
"/wisp/" : {
|
||||||
|
target: "ws://localhost:8080/wisp/",
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/wisp\//, '')
|
||||||
|
},
|
||||||
"/styles": {
|
"/styles": {
|
||||||
target: "http://localhost:8080",
|
target: "http://localhost:8080",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
"@fastify/compress": "^7.0.3",
|
"@fastify/compress": "^7.0.3",
|
||||||
"@fastify/static": "^7.0.4",
|
"@fastify/static": "^7.0.4",
|
||||||
"@iconify-json/ph": "^1.1.13",
|
"@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": "^4.12.2",
|
||||||
"astro-icon": "^1.1.0",
|
"astro-icon": "^1.1.0",
|
||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
|
@ -28,6 +31,8 @@
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"svelte": "^4.2.18",
|
"svelte": "^4.2.18",
|
||||||
"tailwindcss": "^3.4.6",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
8527
pnpm-lock.yaml
generated
8527
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 express from "express";
|
||||||
import { createServer } from "node:http";
|
import { createServer } from "node:http";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import wisp from "wisp-server-node";
|
||||||
import { Sequelize, DataTypes } from "sequelize";
|
import { Sequelize, DataTypes } from "sequelize";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import { handler as ssrHandler } from "./dist/server/entry.mjs";
|
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");
|
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
||||||
app(req, res);
|
app(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.on("upgrade", (req, socket, head) => {
|
||||||
|
if (req.url.endsWith("/wisp/")) {
|
||||||
|
wisp.routeRequest(req, socket, head);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
server.listen({
|
server.listen({
|
||||||
port: 8080,
|
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="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>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<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"
|
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")}
|
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>
|
</div>
|
||||||
</Layout>
|
</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",
|
"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