diff --git a/.vscode/settings.json b/.vscode/settings.json index 8c59aec..d6dce3e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,8 @@ "editor.semanticHighlighting.enabled": true, "[typescript]": { "editor.defaultFormatter": "denoland.vscode-deno" + }, + "[json]": { + "editor.defaultFormatter": "denoland.vscode-deno" } } diff --git a/Cargo.lock b/Cargo.lock index 9c81611..48ba318 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,7 +278,7 @@ dependencies = [ [[package]] name = "deno-webview" -version = "0.1.3" +version = "0.1.4" dependencies = [ "schemars", "serde", diff --git a/Cargo.toml b/Cargo.toml index 922e11e..6025e5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deno-webview" -version = "0.1.3" +version = "0.1.4" edition = "2021" [profile.release] diff --git a/ClientEvent.json b/ClientEvent.json deleted file mode 100644 index 99d7ed9..0000000 --- a/ClientEvent.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ClientEvent", - "oneOf": [ - { - "type": "object", - "required": [ - "$type", - "data" - ], - "properties": { - "$type": { - "type": "string", - "enum": [ - "eval" - ] - }, - "data": { - "type": "string" - } - } - } - ] -} \ No newline at end of file diff --git a/deno.json b/deno.json index ba72560..5be948d 100644 --- a/deno.json +++ b/deno.json @@ -5,7 +5,7 @@ }, "tasks": { "dev": "deno run --watch main.ts", - "gen": "cargo test && deno run -A scripts/generate-zod.ts", + "gen": "cargo test && deno run -A scripts/generate-zod.ts && deno run -A scripts/sync-versions.ts", "build": "deno task gen && cargo build -F transparent", "example:simple": "deno run -A examples/simple.ts" } diff --git a/deno.lock b/deno.lock index 3cab6f3..b108ca7 100644 --- a/deno.lock +++ b/deno.lock @@ -2,12 +2,18 @@ "version": "3", "packages": { "specifiers": { + "jsr:@denosaurs/plug": "jsr:@denosaurs/plug@1.0.6", "jsr:@denosaurs/plug@^1.0.5": "jsr:@denosaurs/plug@1.0.6", "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", + "jsr:@std/collections@^1.0.4": "jsr:@std/collections@1.0.4", "jsr:@std/encoding@^0.221.0": "jsr:@std/encoding@0.221.0", "jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0", + "jsr:@std/fs": "jsr:@std/fs@0.221.0", "jsr:@std/fs@^0.221.0": "jsr:@std/fs@0.221.0", + "jsr:@std/path": "jsr:@std/path@0.221.0", "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", + "jsr:@std/semver": "jsr:@std/semver@1.0.3", + "jsr:@std/toml": "jsr:@std/toml@1.0.0", "jsr:@std/ulid": "jsr:@std/ulid@0.224.1", "jsr:@valibot/valibot": "jsr:@valibot/valibot@0.42.0", "jsr:@webview/webview": "jsr:@webview/webview@0.8.0", @@ -16,6 +22,7 @@ "npm:effection": "npm:effection@3.0.3", "npm:json-schema-to-zod": "npm:json-schema-to-zod@2.4.1", "npm:ts-pattern": "npm:ts-pattern@5.0.6", + "npm:type-fest": "npm:type-fest@4.25.0", "npm:zod": "npm:zod@3.23.8" }, "jsr": { @@ -31,6 +38,9 @@ "@std/assert@0.221.0": { "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" }, + "@std/collections@1.0.4": { + "integrity": "bcc90800e489dc6bacdf68eb5dc746d6d8a033cb4f3311f0f9cf8094de429ce7" + }, "@std/encoding@0.221.0": { "integrity": "d1dd76ef0dc5d14088411e6dc1dede53bf8308c95d1537df1214c97137208e45" }, @@ -50,6 +60,15 @@ "jsr:@std/assert@^0.221.0" ] }, + "@std/semver@1.0.3": { + "integrity": "7c139c6076a080eeaa4252c78b95ca5302818d7eafab0470d34cafd9930c13c8" + }, + "@std/toml@1.0.0": { + "integrity": "c9e37564eedd84084871c66238e00196ec67aa958e09a7f761b3f36273a7a8a5", + "dependencies": [ + "jsr:@std/collections@^1.0.4" + ] + }, "@std/ulid@0.224.1": { "integrity": "4de06fdb030ff3990b1b0344e330373c11ce6051ac449fc435667a485a6723fa" }, @@ -84,6 +103,10 @@ "integrity": "sha512-Y+jOjihlFriWzcBjncPCf2/am+Hgz7LtsWs77pWg5vQQKLQj07oNrJryo/wK2G0ndNaoVn2ownFMeoeAuReu3Q==", "dependencies": {} }, + "type-fest@4.25.0": { + "integrity": "sha512-bRkIGlXsnGBRBQRAY56UXBm//9qH4bmJfFvq83gSz41N282df+fjy8ofcEgc1sM8geNt5cl6mC2g9Fht1cs8Aw==", + "dependencies": {} + }, "zod@3.23.8": { "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "dependencies": {} diff --git a/examples/simple.ts b/examples/simple.ts index c666c91..08829b1 100644 --- a/examples/simple.ts +++ b/examples/simple.ts @@ -1,6 +1,6 @@ -import { WebView } from "../src/lib.ts"; +import { createWebView } from "../src/lib.ts"; -const webview = new WebView({ +using webview = await createWebView({ title: "Simple", html: "

Hello, World!

", }); diff --git a/scripts/sync-versions.ts b/scripts/sync-versions.ts new file mode 100644 index 0000000..ef17d1c --- /dev/null +++ b/scripts/sync-versions.ts @@ -0,0 +1,25 @@ +/** + * Keeps the version of the WebView binary in sync with the version in Cargo.toml. + */ + +import { parse } from "jsr:@std/toml"; + +const latestVersion = await Deno + .readTextFile("./Cargo.toml").then((text) => + parse(text) as { package: { version: string } } + ).then((config) => config.package.version); + +// Read the content of src/lib.ts +const libPath = "./src/lib.ts"; +const libContent = await Deno.readTextFile(libPath); + +// Replace the version in the URL +const updatedContent = libContent.replace( + /releases\/download\/v\d+\.\d+\.\d+\/deno-webview/, + `releases/download/v${latestVersion}/deno-webview`, +); + +// Write the updated content back to src/lib.ts +await Deno.writeTextFile(libPath, updatedContent); + +console.log(`Updated WebView binary version to ${latestVersion} in src/lib.ts`); diff --git a/src/lib.ts b/src/lib.ts index 2220d1f..e5bfa20 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -7,6 +7,9 @@ import { } from "./schemas.ts"; import { monotonicUlid as ulid } from "jsr:@std/ulid"; import type { Except } from "npm:type-fest"; +import { join } from "jsr:@std/path"; +import { ensureDir } from "jsr:@std/fs"; +import { exists } from "jsr:@std/fs"; type JSON = | string @@ -68,7 +71,82 @@ const returnAck = (result: WebViewResponse) => { } }; -export class WebView implements Disposable { +async function getWebViewBin(options: WebViewOptions) { + if (Deno.permissions.querySync({ name: "env" }).state === "granted") { + const binPath = Deno.env.get("WEBVIEW_BIN"); + if (binPath) return binPath; + } + + const flags = options.devtools + ? "-devtools" + : options.transparent && Deno.build.os === "darwin" + ? "-transparent" + : ""; + + const cacheDir = getCacheDir(); + const fileName = `deno-webview${flags}${ + Deno.build.os === "windows" ? ".exe" : "" + }`; + const filePath = join(cacheDir, fileName); + + // Check if the file already exists in cache + if (await exists(filePath)) { + return filePath; + } + + // If not in cache, download it + let url = + "https://github.com/zephraph/webview/releases/download/v0.1.4/deno-webview"; + switch (Deno.build.os) { + case "darwin": { + url += "-mac" + flags; + break; + } + case "linux": { + url += "-linux" + flags; + break; + } + case "windows": { + url += "-windows" + flags + ".exe"; + break; + } + default: + throw new Error("unsupported OS"); + } + + const res = await fetch(url); + + // Ensure the cache directory exists + await ensureDir(cacheDir); + + // Write the binary to disk + await Deno.writeFile(filePath, new Uint8Array(await res.arrayBuffer()), { + mode: 0o755, + }); + + return filePath; +} + +// Helper function to get the OS-specific cache directory +function getCacheDir(): string { + switch (Deno.build.os) { + case "darwin": + return join(Deno.env.get("HOME")!, "Library", "Caches", "deno-webview"); + case "linux": + return join(Deno.env.get("HOME")!, ".cache", "deno-webview"); + case "windows": + return join(Deno.env.get("LOCALAPPDATA")!, "deno-webview", "Cache"); + default: + throw new Error("Unsupported OS"); + } +} + +export async function createWebView(options: WebViewOptions) { + const binPath = await getWebViewBin(options); + return new WebView(options, binPath); +} + +class WebView implements Disposable { #process: Deno.ChildProcess; #stdin: WritableStreamDefaultWriter; #stdout: ReadableStreamDefaultReader; @@ -77,8 +155,8 @@ export class WebView implements Disposable { #externalEvent = new EventEmitter(); #messageLoop: Promise; - constructor(options: WebViewOptions) { - this.#process = new Deno.Command("./target/debug/deno-webview", { + constructor(options: WebViewOptions, binPath: string) { + this.#process = new Deno.Command(binPath, { args: [JSON.stringify(options)], stdin: "piped", stdout: "piped", diff --git a/src/main.rs b/src/main.rs index edf90a4..510ae70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,8 +137,6 @@ fn main() -> wry::Result<()> { } let window = window_builder.build(&event_loop).unwrap(); - eprintln!("transparent: {:?}", webview_options.transparent); - let webview_builder = match webview_options.target { WebViewTarget::Url(url) => WebViewBuilder::new(&window).with_url(url), WebViewTarget::Html(html) => WebViewBuilder::new(&window).with_html(html), @@ -171,7 +169,6 @@ fn main() -> wry::Result<()> { let mut stdout_lock = stdout.lock(); while let Ok(event) = to_deno.recv() { - eprintln!("Sending event: {:?}", event); match serde_json::to_string(&event) { Ok(json) => { let mut buffer = json.replace("\0", "").into_bytes();