feat: initial port from prototype

This commit is contained in:
Derrick Hammer 2023-10-11 09:46:46 -04:00
parent 1dcf53b92b
commit cbb7414932
Signed by: pcfreak30
GPG Key ID: C997C339BE476FF2
25 changed files with 22484 additions and 25753 deletions

30
.presetterrc.json Normal file
View File

@ -0,0 +1,30 @@
{
"preset": [
"@lumeweb/node-library-preset"
],
"config": {
"tsconfig.build": {
"include": {
"0": "{buildSource}/backend"
},
"compilerOptions": {
"outDir": "{source}"
}
}
},
"variable": {
"source": "build",
"output": "dist",
"buildSource": "src"
},
"scripts": {
"build:vite": "vite build -c vite.config.backend.js",
"build:astro": "astro build",
"build": "run-s clean build:astro build:typescript:* build:vite",
"clean:buildOutput": "shx rm -rf {source}",
"build:typescript:mjs:tsc": "tsc -p tsconfig.backend.json",
"build:typescript:mjs:alias:lib": "tsc-alias --resolve-full-paths --dir {output} -p tsconfig.backend.json",
"build:typescript:mjs:alias:root": "tsc-alias --resolve-full-paths --dir . -p tsconfig.backend.json",
"build:typescript:mjs:fix": "tsc-esm-fix --sourceMap --target {output} -p tsconfig.backend.json"
}
}

1
.releaserc.json Symbolic link
View File

@ -0,0 +1 @@
node_modules/presetter/generated/app/.releaserc.json

View File

@ -2,8 +2,17 @@ import { defineConfig } from 'astro/config'
import react from '@astrojs/react'
import tailwind from '@astrojs/tailwind'
import optimizer from 'vite-plugin-optimizer'
// https://astro.build/config
export default defineConfig({
integrations: [react(), tailwind({ applyBaseStyles: false, })]
integrations: [react(), tailwind({ applyBaseStyles: false, })],
vite: {
plugins: [
optimizer({
'node-fetch':
'const e = undefined; export default e;export {e as Response, e as FormData, e as Blob};',
}),
]
}
})

View File

@ -5,7 +5,7 @@
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"css": "src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true
},

47308
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,75 @@
{
"name": "app",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/react": "^3.0.3",
"@astrojs/tailwind": "^5.0.1",
"@lumeweb/sdk": "^0.1.0-develop.5",
"@types/react": "^18.2.25",
"@types/react-dom": "^18.2.11",
"astro": "^3.2.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"lucide-react": "^0.284.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^1.14.0",
"tailwindcss": "^3.3.3",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"prettier": "^3.0.3",
"prettier-plugin-astro": "^0.12.0",
"sass": "^1.69.0"
}
"name": "app",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"prepare": "presetter bootstrap",
"build": "run build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/react": "^3.0.3",
"@astrojs/tailwind": "^5.0.1",
"@helia/unixfs": "^1.4.2",
"@lumeweb/kernel-dns-client": "^0.1.0-develop.7",
"@lumeweb/kernel-eth-client": "^0.1.0-develop.16",
"@lumeweb/kernel-handshake-client": "^0.1.0-develop.8",
"@lumeweb/kernel-ipfs-client": "^0.1.0-develop.24",
"@lumeweb/kernel-network-registry-client": "^0.1.0-develop.10",
"@lumeweb/kernel-peer-discovery-client": "^0.0.2-develop.16",
"@lumeweb/kernel-swarm-client": "^0.1.0-develop.10",
"@lumeweb/libkernel": "^0.1.0-develop.63",
"@lumeweb/libresolver": "^0.1.0-develop.1",
"@lumeweb/sdk": "^0.1.0-develop.14",
"@lumeweb/tld-enum": "^0.1.0-develop.1",
"@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@types/react": "^18.2.25",
"@types/react-dom": "^18.2.11",
"astro": "^3.2.3",
"cheerio": "^1.0.0-rc.12",
"clsx": "^2.0.0",
"file-type": "^18.5.0",
"is-ipfs": "^8.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "^3.3.3"
},
"devDependencies": {
"@astrojs/react": "^3.0.3",
"@astrojs/tailwind": "^5.0.2",
"@lumeweb/kernel-dns-client": "^0.1.0-develop.7",
"@lumeweb/kernel-eth-client": "^0.1.0-develop.16",
"@lumeweb/kernel-handshake-client": "^0.1.0-develop.8",
"@lumeweb/kernel-ipfs-client": "^0.1.0-develop.24",
"@lumeweb/kernel-peer-discovery-client": "^0.0.2-develop.16",
"@lumeweb/kernel-swarm-client": "^0.1.0-develop.10",
"@lumeweb/libresolver": "^0.1.0-develop.1",
"@lumeweb/node-library-preset": "^0.2.7",
"@lumeweb/sdk": "^0.1.0-develop.14",
"@types/react": "^18.2.28",
"@types/react-dom": "^18.2.13",
"astro": "^3.2.4",
"class-variance-authority": "^0.7.0",
"lucide-react": "^0.284.0",
"presetter": "^4.4.1",
"presetter-preset-esm": "^4.4.1",
"presetter-preset-strict": "^4.4.1",
"prettier": "^3.0.3",
"prettier-plugin-astro": "^0.12.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.69.2",
"shx": "^0.3.4",
"tailwind-merge": "^1.14.0",
"tailwindcss": "^3.3.3",
"tailwindcss-animate": "^1.0.7",
"tsc-alias": "^1.8.8",
"tsc-esm-fix": "^2.20.17",
"typescript": "^5.2.2",
"vite-plugin-optimizer": "^1.4.2"
}
}

View File

@ -0,0 +1,19 @@
import type { ContentFilter } from "./types.js";
export class ContentProcessor {
private filters: ContentFilter[] = [];
registerFilter(filter: ContentFilter) {
this.filters.push(filter);
}
async process(response: Response, mimeType: string): Promise<Response> {
let processedResponse = response;
for (const filter of this.filters) {
processedResponse = await filter.process(processedResponse, mimeType);
}
return processedResponse;
}
}

View File

@ -0,0 +1,48 @@
import type { ContentFilter } from "../types.js";
import { getTld } from "@lumeweb/libresolver";
import tldEnum from "@lumeweb/tld-enum";
import * as cheerio from "cheerio";
export default class URLRewriteFilter implements ContentFilter {
async process(response: Response, mimeType: string): Promise<Response> {
if (mimeType !== "text/html") {
return response;
}
let html = await response.text();
const $ = cheerio.load(html);
["a", "link", "script", "img"].forEach((tag) => {
$.root()
.find(tag)
.each((index, element) => {
let attrName = ["a", "link"].includes(tag) ? "href" : "src";
let urlValue = $(element).attr(attrName);
if (urlValue) {
if (!isICANN(urlValue)) {
if (urlValue.startsWith("/")) {
$(element).attr(attrName, `/browse${urlValue}`);
} else if (urlValue.startsWith("http")) {
$(element).attr(attrName, `/browse/${urlValue}`);
}
}
}
});
});
return new Response($.html(), {
headers: response.headers,
});
}
}
function isICANN(url: string) {
try {
const parsedUrl = new URL(url);
const domain = parsedUrl.hostname;
return tldEnum.list.includes(getTld(domain));
} catch (e) {
return false;
}
}

10
src/backend/mimes.ts Normal file
View File

@ -0,0 +1,10 @@
const extToMimes = new Map(
Object.entries({
html: "text/html",
xhtml: "application/xhtml+xml",
xml: "application/xml",
})
);
Object.freeze(extToMimes);
export default extToMimes;

View File

@ -0,0 +1,38 @@
import { ContentProcessor } from "./contentProcessor.js";
import type { ContentProvider } from "./types.js";
import type { DNSResult } from "@lumeweb/libresolver";
export class ProviderManager {
private providers: ContentProvider[] = [];
private _processor = new ContentProcessor();
get processor(): ContentProcessor {
return this._processor;
}
register(provider: ContentProvider) {
this.providers.push(provider);
}
async fetch(dnsResult: DNSResult, path: string): Promise<Response> {
for (const record of dnsResult.records) {
for (const provider of this.providers) {
if (provider.supports(record.value)) {
const content = await provider.fetchContent(record.value, path);
if (content.headers.get("Content-Type")) {
return this._processor.process(
content,
content.headers.get("Content-Type")!,
);
}
return content;
}
}
}
throw new Error("No suitable provider found.");
}
}

View File

@ -0,0 +1,145 @@
import type { ContentProvider } from "../types.js";
import { ipfsPath, ipnsPath, path as checkPath } from "is-ipfs";
import { createClient } from "@lumeweb/kernel-ipfs-client";
import { CID } from "multiformats/cid";
import type { UnixFSStats } from "@helia/unixfs";
import * as nodePath from "path";
import { fileTypeFromBuffer } from "file-type";
import extToMimes from "../mimes.js";
export default class IPFSProvider implements ContentProvider {
private _client = createClient();
async fetchContent(
uri: string,
path: string,
query?: string,
): Promise<Response> {
let cid = translatePath(uri);
let stat: UnixFSStats | null = null;
let urlPath = path;
const parsedPath = nodePath.parse(urlPath);
let err;
try {
if (ipnsPath(cid)) {
const cidHash = cid.replace("/ipns/", "");
cid = await this._client.ipns(cidHash);
cid = `/ipfs/${cid}`;
}
if (ipfsPath(cid)) {
cid = CID.parse(cid.replace("/ipfs/", "")).toV1().toString();
stat = await this._client.stat(cid);
}
} catch (e) {
err = (e as Error).message;
}
if (!err && stat?.type === "directory") {
if (!parsedPath.base.length || !parsedPath.ext.length) {
let found = false;
for (const indexFile of ["index.html", "index.htm"]) {
try {
const subPath = nodePath.join(urlPath, indexFile);
await this._client.stat(cid, {
path: subPath,
});
urlPath = subPath;
found = true;
break;
} catch {}
}
if (!found) {
err = "404";
}
} else {
try {
await this._client.stat(cid, {
path: urlPath,
});
} catch {
err = "404";
}
}
if (err) {
throw new Error(err);
}
}
let bufferRead = 0;
const fileTypeBufferLength = 4100;
const mimeBuffer: Uint8Array[] = [];
let reader = await this._client.cat(cid, { path: urlPath });
for await (const chunk of reader.iterable()) {
if (bufferRead < fileTypeBufferLength) {
if (chunk.length >= fileTypeBufferLength) {
mimeBuffer.push(chunk.slice(0, fileTypeBufferLength));
bufferRead += fileTypeBufferLength;
} else {
mimeBuffer.push(chunk);
bufferRead += chunk.length;
}
if (bufferRead >= fileTypeBufferLength) {
reader.abort();
break;
}
} else {
reader.abort();
break;
}
}
let mime;
if (bufferRead >= fileTypeBufferLength) {
const totalLength = mimeBuffer.reduce((acc, val) => acc + val.length, 0);
const concatenated = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of mimeBuffer) {
concatenated.set(chunk, offset);
offset += chunk.length;
}
mime = await fileTypeFromBuffer(concatenated);
if (!mime) {
const ext = nodePath.parse(urlPath).ext.replace(".", "");
if (extToMimes.has(ext)) {
mime = extToMimes.get(ext);
}
}
}
reader = await this._client.cat(cid, { path: urlPath });
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of reader.iterable()) {
controller.enqueue(chunk);
}
controller.close();
},
});
const headers: HeadersInit = {};
if (mime) {
headers["Content-Type"] = mime as string;
}
return new Response(stream, {
headers,
});
}
supports(uri: string): boolean {
return checkPath(translatePath(uri));
}
}
function translatePath(uri: string) {
return uri.replace(/:\/\//, "/").replace(/^/, "/");
}

12
src/backend/types.ts Normal file
View File

@ -0,0 +1,12 @@
export interface ContentProvider {
supports: (uri: string) => boolean;
fetchContent: (
uri: string,
path: string,
query?: string,
) => Promise<Response>;
}
export interface ContentFilter {
process: (response: Response, mineType: string) => Promise<Response>;
}

69
src/backend/worker.ts Normal file
View File

@ -0,0 +1,69 @@
import { createClient as createDnsClient } from "@lumeweb/kernel-dns-client";
import { ProviderManager } from "./providerManager.js";
import IPFSProvider from "./providers/ipfs.js";
import URLRewriteFilter from "./filters/urlRewrite.js";
const dnsClient = createDnsClient();
const providerManager = new ProviderManager();
providerManager.register(new IPFSProvider());
providerManager.processor.registerFilter(new URLRewriteFilter());
globalThis.postMessage = async (...args) => {
// @ts-ignore
let ret = await clients.matchAll({ includeUncontrolled: true });
ret.forEach((item: any) => item.postMessage(...args));
if (!ret.length) {
const cb = (event: any) => {
// @ts-ignore
postMessage(...args);
self.removeEventListener("activate", cb);
};
self.addEventListener("activate", cb);
}
};
self.addEventListener("activate", (event) => {
// @ts-ignore
event.waitUntil(
(async () => {
// @ts-ignore
await clients.claim();
// @ts-ignore
})(),
);
});
addEventListener("fetch", (event: any) => {
event.respondWith(
(async () => {
const req = event.request;
const url = new URL(req.url);
if (
["/index.html", "/index.js", "/"].includes(url.pathname) ||
!url.pathname.startsWith("/browse/")
) {
return fetch(event.request).then((response: any) => {
response.redirectToFinalURL = true;
return response;
});
}
let realUrl = url.pathname.replace(/^\/browse\//, "").replace(/\/$/, "");
if (!realUrl.match(/^https?:\/\//)) {
realUrl = `http://${realUrl}`;
}
// Use your existing communication framework to resolve DNS.
const dnsResult = await dnsClient.resolve(new URL(realUrl).hostname);
if (!dnsResult.error && dnsResult.records.length > 0) {
return providerManager.fetch(dnsResult, new URL(realUrl).pathname);
}
return new Response("Sorry, that is not a valid web3 website.");
})(),
);
});

25
src/clients.ts Normal file
View File

@ -0,0 +1,25 @@
import { createClient as createDnsClient } from "@lumeweb/kernel-dns-client";
import { createClient as createIpfsClient } from "@lumeweb/kernel-ipfs-client";
import { createClient as createSwarmClient } from "@lumeweb/kernel-swarm-client";
import { createClient as createPeerDiscoveryClient } from "@lumeweb/kernel-peer-discovery-client";
import { createClient as createNetworkRegistryClient } from "@lumeweb/kernel-network-registry-client";
import { createClient as createHandshakeClient } from "@lumeweb/kernel-handshake-client";
import { createClient as createEthClient } from "@lumeweb/kernel-eth-client";
const dnsClient = createDnsClient();
const ipfsClient = createIpfsClient();
const swarmClient = createSwarmClient();
const peerDiscoveryClient = createPeerDiscoveryClient();
const networkRegistryClient = createNetworkRegistryClient();
const handshakeClient = createHandshakeClient();
const ethClient = createEthClient();
export {
dnsClient,
ipfsClient,
swarmClient,
peerDiscoveryClient,
networkRegistryClient,
handshakeClient,
ethClient,
};

24
src/components/App.tsx Normal file
View File

@ -0,0 +1,24 @@
import {
Browser,
BrowserStateProvider,
Navigator,
} from "@/components/Browser.tsx";
import Lume from "@/components/Lume.tsx";
export default function () {
return (
<BrowserStateProvider>
<>
<header className="relative h-14 px-2 pl-2 py-2 w-full bg-neutral-900 flex">
<div className="relative h-full w-full rounded-full bg-neutral-800 border border-neutral-700 flex items-center [>input:focus]:ring-2 [>input:focus]:ring-white">
<Navigator />
</div>
<div className="w-32 flex justify-end">
<Lume />
</div>
</header>
<Browser />
</>
</BrowserStateProvider>
);
}

165
src/components/Browser.tsx Normal file
View File

@ -0,0 +1,165 @@
import {
createContext,
createRef,
forwardRef,
useContext,
useEffect,
useState,
} from "react";
import {
dnsClient,
ethClient,
handshakeClient,
ipfsClient,
networkRegistryClient,
peerDiscoveryClient,
swarmClient,
} from "@/clients.ts";
import * as kernel from "@lumeweb/libkernel/kernel";
import { kernelLoaded } from "@lumeweb/libkernel/kernel";
import Arrow from "@/components/Arrow.tsx";
import type React from "react";
import { Input } from "@/components/ui/input.tsx";
import { Button } from "@/components/ui/button.tsx";
let BOOT_FUNCTIONS: (() => Promise<any>)[] = [];
interface BrowserContextType {
url: string;
setUrl: React.Dispatch<React.SetStateAction<string>>;
}
const BrowserStateContext = createContext<BrowserContextType | undefined>(
undefined,
);
export function BrowserStateProvider({
children,
}: {
children: React.ReactElement;
}) {
const [url, setUrl] = useState("about:blank");
return (
<BrowserStateContext.Provider value={{ url, setUrl }}>
{children}
</BrowserStateContext.Provider>
);
}
export function useBrowserState() {
const context = useContext(BrowserStateContext);
if (!context) {
throw new Error(
"useBrowserState must be used within a BrowserStateProvider",
);
}
return context;
}
async function boot() {
const reg = await navigator.serviceWorker.register("/sw.js");
await reg.update();
await kernel.serviceWorkerReady();
await kernelLoaded();
BOOT_FUNCTIONS.push(
async () =>
await swarmClient.addRelay(
"2d7ae1517caf4aae4de73c6d6f400765d2dd00b69d65277a29151437ef1c7d1d",
),
);
// IRC
BOOT_FUNCTIONS.push(
async () =>
await peerDiscoveryClient.register(
"zdiN5eJ3RfHpZHTYorGxBt1GCsrGJYV9GprwVWkj8snGsjWSrptFm8BtQX",
),
);
BOOT_FUNCTIONS.push(
async () => await networkRegistryClient.registerType("content"),
);
BOOT_FUNCTIONS.push(
async () => await networkRegistryClient.registerType("blockchain"),
);
BOOT_FUNCTIONS.push(async () => await handshakeClient.register());
BOOT_FUNCTIONS.push(async () => await ethClient.register());
BOOT_FUNCTIONS.push(async () => await ipfsClient.register());
const resolvers = [
"zdiJdDdBJWAdYFTcRa9So5TQQ9f1pYMiMy4dqYcKp9imomQtR11LJUyJyV", // CID
"zdiKvnZYNjDqXaM8uF3pGEs7Tt6jqGc7t7M4eqbvJwpkTnrZymncfUW9Cj", // ENS
"zrjEH3iojPLr7986o7iCn9THBmJmHiuDWmS1G6oT8DnfuFM", // HNS
];
for (const resolver of resolvers) {
BOOT_FUNCTIONS.push(async () => dnsClient.registerResolver(resolver));
}
await bootup();
await Promise.all([
ethClient.ready(),
handshakeClient.ready(),
ipfsClient.ready(),
]);
}
async function bootup() {
for (const entry of Object.entries(BOOT_FUNCTIONS)) {
await entry[1]();
}
}
export function Navigator() {
const { url, setUrl } = useBrowserState();
const inputRef = createRef<HTMLInputElement>();
const browse = () => {
let input = inputRef.current?.value.trim();
// If the input doesn't contain a protocol, assume it's http
if (!input?.match(/^https?:\/\//)) {
input = `http://${input}`;
}
try {
// Try to parse it as a URL
const url = new URL(input);
setUrl(
`/browse/${url.hostname}${url.pathname}${url.search}${url.hash}` ||
"about:blank",
);
} catch (e) {
// Handle invalid URLs here, if needed
console.error("Invalid URL:", e);
}
};
const NavInput = forwardRef((props: any, ref) => (
<Input ref={ref} {...props}></Input>
));
return (
<>
<NavInput ref={inputRef} />
<Button onClick={browse}>
Navigate
<Arrow />
</Button>
</>
);
}
export function Browser() {
const { url } = useBrowserState();
useEffect(() => {
boot();
}, []);
return <iframe src={url} className="w-full h-full"></iframe>;
}

View File

@ -6,13 +6,12 @@ import {
useLume,
LumeProvider,
} from "@lumeweb/sdk";
const Lume = () => {
const { isLoggedIn } = useLume();
const { isLoggedIn, ready } = useLume();
return (
<>
<LumeIdentity>
<LumeIdentityTrigger asChild>
<LumeIdentityTrigger asChild disabled={!ready}>
{isLoggedIn ? (
<LumeDashboard>
<LumeDashboardTrigger asChild>

View File

@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@ -0,0 +1,25 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

6
src/lib/utils.ts Normal file
View File

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@ -1,8 +1,7 @@
---
import App from "../components/App";
import "@lumeweb/sdk/lib/style.css";
import "@/styles/globals.scss";
import ArrowSvg from "@/components/Arrow";
import Lume from "../components/Lume";
---
<html lang="en">
@ -14,25 +13,10 @@ import Lume from "../components/Lume";
<title>Astro</title>
</head>
<body>
<main class="flex h-screen max-h-screen flex-col items-center justify-between">
<header class="relative h-14 px-2 pl-2 py-2 w-full bg-neutral-900 flex">
<div
class="relative h-full w-full rounded-full bg-neutral-800 border border-neutral-700 flex items-center [>input:focus]:ring-2 [>input:focus]:ring-white"
>
<input class="ml-3 text-gray-300 w-[calc(100%-150px)] h-full bg-transparent focus:ring-0 focus:outline-none focus:border-0"/>
<button
class="absolute bg-neutral-700 text-neutral-400 px-4 py-2 right-0 rounded-r-full"
>
Navigate
<ArrowSvg className="inline-block ml-2 -mt-1 w-5 h-5" />
</button>
</div>
<div class="w-32 flex justify-end">
<Lume client:only="react" />
</div>
</header>
<iframe id="mainIframe" src="https://google.com" class="w-full h-full">
</iframe>
<main
class="flex h-screen max-h-screen flex-col items-center justify-between"
>
<App client:only="react" />
</main>
</body>
</html>

View File

@ -1,7 +1,12 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
container: {
center: true,
@ -68,4 +73,4 @@ module.exports = {
},
},
plugins: [require("tailwindcss-animate")],
};
}

45
tsconfig.backend.json Normal file
View File

@ -0,0 +1,45 @@
{
"compilerOptions": {
"outDir": "build",
"module": "NodeNext",
"target": "ES2022",
"declaration": true,
"inlineSourceMap": true,
"noImplicitUseStrict": false,
"preserveConstEnums": true,
"removeComments": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"isolatedModules": false,
"alwaysStrict": true,
"noImplicitAny": false,
"noImplicitThis": false,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": false,
"skipLibCheck": true,
"moduleResolution": "nodenext",
"resolveJsonModule": true,
"baseUrl": ".",
"lib": [
"ES2020",
"ES2021",
"dom"
]
},
"include": [
"src/backend"
],
"exclude": [
"**/node_modules",
"lib"
]
}

View File

@ -1,13 +1,13 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
}
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
}
}

32
vite.config.backend.js Normal file
View File

@ -0,0 +1,32 @@
import { defineConfig } from "vite";
import optimizer from "vite-plugin-optimizer";
import { nodePolyfills } from "vite-plugin-node-polyfills";
export default defineConfig({
define: { "window.": "globalThis." },
build: {
emptyOutDir: false,
outDir: "dist",
lib: {
entry: "build/worker.js",
name: "sw",
formats: ["cjs"],
fileName: () => "sw.js",
},
minify: false,
rollupOptions: { output: { inlineDynamicImports: true } },
},
resolve: {
dedupe: ["@lumeweb/libportal", "@lumeweb/libweb", "@lumeweb/libkernel"],
},
plugins: [
optimizer({
"node-fetch":
"const e = undefined; export default e;export {e as Response, e as FormData, e as Blob};",
}),
nodePolyfills({
exclude: ["fs"],
globals: { Buffer: true, global: true, process: true },
}),
],
});