diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app.css | 9 | ||||
-rw-r--r-- | src/app.d.ts | 17 | ||||
-rw-r--r-- | src/app.html | 12 | ||||
-rw-r--r-- | src/background.ts | 67 | ||||
-rw-r--r-- | src/lib/index.ts | 1 | ||||
-rw-r--r-- | src/routes/+layout.svelte | 20 | ||||
-rw-r--r-- | src/routes/+layout.ts | 2 | ||||
-rw-r--r-- | src/routes/+page.svelte | 42 | ||||
-rw-r--r-- | src/routes/Monaco.svelte | 144 | ||||
-rw-r--r-- | src/routes/browser.d.ts | 4 | ||||
-rw-r--r-- | src/routes/userland.d.ts | 4 |
11 files changed, 322 insertions, 0 deletions
diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..3e1f888 --- /dev/null +++ b/src/app.css @@ -0,0 +1,9 @@ +@import 'tailwindcss'; +html { + background: var(--bg, #23222b); + color: var(--fg, #dedede); +} +/* html.light { + background: #dedede; + color: #191919; +} */ diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..c689736 --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,17 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces + +import Browser from 'webextension-polyfill'; +declare const browser: typeof Browser; + +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..77a5ff5 --- /dev/null +++ b/src/app.html @@ -0,0 +1,12 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <link rel="icon" href="%sveltekit.assets%/favicon.png" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + %sveltekit.head% + </head> + <body data-sveltekit-preload-data="hover"> + <div style="display: contents">%sveltekit.body%</div> + </body> +</html> diff --git a/src/background.ts b/src/background.ts new file mode 100644 index 0000000..65d7187 --- /dev/null +++ b/src/background.ts @@ -0,0 +1,67 @@ +import browser from 'webextension-polyfill'; +import type { UserlandBrowser } from './routes/userland'; + +console.debug('Loading Extension'); +setTimeout(async () => { + const { sc } = await browser.storage.local.get('sc'); + if (typeof sc === 'undefined') { + console.warn('No value set!'); + } else if (typeof sc !== 'string') { + throw new Error('typeof sc is not str nor undefined'); + } else { + console.debug('Found Script (length', sc.length, ':3)'); + console.debug('Loading esbuild-wasm'); + const { initialize, transform } = await import('esbuild-wasm').catch( + (e) => { + console.error('Failed to load esbuild-wasm!'); + throw e; + } + ); + console.debug("Loading esbuild's WASM Module"); + const wasmModule = new WebAssembly.Module( + await fetch('./generated/esbuild.wasm') + .catch((e) => { + console.error('Failed to fetch() the esbuild wasm file.'); + throw e; + }) + .then((v) => v.arrayBuffer()) + ); + console.debug('Attempting to initialize esbuild-wasm'); + try { + await initialize({ + wasmModule, + worker: false, + }); + } catch (error) { + console.error(error); + throw new Error('Failed to initialize esbuild - see above error'); + } + console.debug('Transforming Content'); + const built = await transform(sc, { + loader: 'ts', + }); + console.debug('Got result', built); + const b = browser; + const brow: UserlandBrowser = { + get webRequest() { + return b.webRequest; + }, + }; + const f = new Function( + 'browser', + built.code.includes('await') + ? `return (async()=>{${built.code} +})();` + : built.code + ); + console.debug('Environment Information:', { + func: f, + browser: brow, + realBrowser: b, + }); + (globalThis as any).browser = brow; + console.debug('Evaluating...'); + await f(brow); + console.debug('Function Exited'); + } +}, 0); diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..b0e82a6 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,20 @@ +<script lang="ts"> + import { onDestroy, onMount } from 'svelte'; + import '../app.css'; + let { children } = $props(); + + let mounted = $state(false); + onMount(async () => { + (globalThis as any).browser = ( + await import('webextension-polyfill') + ).default; + mounted = true; + }); + onDestroy(() => (mounted = false)); +</script> + +{#if mounted} + {@render children()} +{:else} + <p>Waiting on Mount</p> +{/if} diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts new file mode 100644 index 0000000..748cfd9 --- /dev/null +++ b/src/routes/+layout.ts @@ -0,0 +1,2 @@ +export const prerender = true; +export const csr = true; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..7320c2a --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,42 @@ +<script lang="ts"> + import { onMount } from 'svelte'; + import Monaco from './Monaco.svelte'; + let content_script = $state(''); + let default_value = $state(`// Do not put untrusted content here! +// See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Intercept_HTTP_requests and https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest for a tutorial on how to handle things. +// Content saves on every keystroke due to laziness. +// Reload (disable & re-enable) extension or restart browser to apply changes. +`); + let mounted = $state(false); + onMount(async () => { + const s = (await browser.storage.local.get('sc')).sc; + default_value = s && `${s}`.length ? `${s}` : default_value; + content_script = default_value ?? ''; + setTimeout(() => { + mounted = true; + setTimeout(() => { + content_script = default_value; + }, 0); + }, 0); + }); + $effect(() => { + (async (cs: string) => { + if (mounted) { + await browser.storage.local.set({ sc: cs }); + if (browser.runtime.lastError) { + console.warn('Last Error is ', browser.runtime.lastError.message); + browser.runtime.lastError = undefined; + } + } + })(content_script); + }); +</script> + +{#if mounted} + <Monaco + defaultValue={default_value} + bind:value={content_script} + /> +{:else} + Getting Storage +{/if} diff --git a/src/routes/Monaco.svelte b/src/routes/Monaco.svelte new file mode 100644 index 0000000..efdd67a --- /dev/null +++ b/src/routes/Monaco.svelte @@ -0,0 +1,144 @@ +<script lang="ts"> + import type monaco from 'monaco-editor'; + import { onDestroy, onMount } from 'svelte'; + import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'; + import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'; + import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'; + import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'; + import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'; + // @ts-ignore + import wrqTypes from '@types/webextension-polyfill/namespaces/webRequest.d.ts?raw'; + // @ts-ignore + import evTypes from '@types/webextension-polyfill/namespaces/events.d.ts?raw'; + import userland from './userland.d.ts?raw'; + + let divEl: HTMLDivElement | null = $state(null); + let editor: monaco.editor.IStandaloneCodeEditor = $state(null as any); + let Monaco: typeof monaco; + let { + defaultValue = `processRequest = (rq) => rq; +processResponse = (rs) => rs; +`, + typeDefs = `import type { UserlandBrowser } from './userland'; +declare global { + /** + * The subset of the host extension's browser type available to the extension + * Note: We don't properly sandbox anything. You can likely easily get access to shit outside of here from the browser global. + */ + declare const browser: UserlandBrowser; +} +`, + value = $bindable(''), + }: { + value?: string; + defaultValue?: string; + typeDefs?: string; + } = $props(); + let writeDebounce = false; + + onMount(async () => { + // @ts-ignore + globalThis.MonacoEnvironment = { + getWorker: function (_moduleId: any, label: string) { + if (label === 'json') { + return new jsonWorker(); + } + if (label === 'css' || label === 'scss' || label === 'less') { + return new cssWorker(); + } + if (label === 'html' || label === 'handlebars' || label === 'razor') { + return new htmlWorker(); + } + if (label === 'typescript' || label === 'javascript') { + return new tsWorker(); + } + return new editorWorker(); + }, + }; + + Monaco = await import('monaco-editor'); + Monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ + allowNonTsExtensions: true, + moduleResolution: Monaco.languages.typescript.ModuleResolutionKind.NodeJs, + module: Monaco.languages.typescript.ModuleKind.ESNext, + noEmit: true, + typeRoots: ['node_modules/@types'], + }); + Monaco.editor.defineTheme('redirext', { + base: 'vs-dark', + inherit: true, + rules: [], + colors: { + 'editor.foreground': '#dedede', + 'editor.background': '#23222b', + 'editor.selectionBackground': '#4c2889', + 'editor.inactiveSelectionBackground': '#444d56', + 'editor.lineHighlightBackground': '#444d56', + 'editorCursor.foreground': '#ffffff', + 'editorWhitespace.foreground': '#6a737d', + 'editorIndentGuide.background': '#6a737d', + 'editorIndentGuide.activeBackground': '#f6f8fa', + 'editor.selectionHighlightBorder': '#444d56', + }, + }); + if (!divEl) while (!divEl) await new Promise((rs) => setTimeout(rs, 100)); + Monaco.languages.typescript.typescriptDefaults.addExtraLib( + evTypes, + 'node_modules/@types/webextension-polyfill/namespaces/events.d.ts' + ); + Monaco.languages.typescript.typescriptDefaults.addExtraLib( + wrqTypes, + 'node_modules/@types/webextension-polyfill/namespaces/webRequest.d.ts' + ); + Monaco.languages.typescript.typescriptDefaults.addExtraLib( + `export * from './namespaces/webRequest';`, + 'node_modules/@types/webextension-polyfill/index.d.ts' + ); + Monaco.languages.typescript.typescriptDefaults.addExtraLib( + userland, + 'node_modules/@types/redirext/userland.d.ts' + ); + Monaco.languages.typescript.typescriptDefaults.addExtraLib( + typeDefs, + 'node_modules/@types/redirext/index.d.ts' + ); + editor = Monaco.editor.create(divEl, { + value: defaultValue, + language: 'typescript', + theme: 'redirext', + autoDetectHighContrast: false, + }); + editor.getModel()?.onDidChangeContent((e) => { + writeDebounce = true; + const upd = () => { + try { + value = + editor.getValue({ lineEnding: '\n', preserveBOM: false }).trim() + + '\n'; + // console.debug('Updated value from editor to:', value); + } catch (error) { + writeDebounce = false; + throw error; + } + setTimeout(() => { + writeDebounce = false; + }, 0); + }; + setTimeout(upd, 0); + }); + }); + $effect(() => { + if (editor && !writeDebounce) { + // console.debug('Updating editor with value', value); + editor.setValue(value.trim() + '\n'); + } + }); + onDestroy(() => { + if (editor) editor.dispose(); + }); +</script> + +<div + bind:this={divEl} + class="h-screen overflow-hidden bg-card box-border max-h-full" +></div> diff --git a/src/routes/browser.d.ts b/src/routes/browser.d.ts new file mode 100644 index 0000000..ed54792 --- /dev/null +++ b/src/routes/browser.d.ts @@ -0,0 +1,4 @@ +import Browser from 'webextension-polyfill'; +declare global { + declare const browser: typeof Browser; +} diff --git a/src/routes/userland.d.ts b/src/routes/userland.d.ts new file mode 100644 index 0000000..7b659b8 --- /dev/null +++ b/src/routes/userland.d.ts @@ -0,0 +1,4 @@ +import type { WebRequest } from 'webextension-polyfill'; +export type UserlandBrowser = { + get webRequest(): WebRequest.Static; +}; |