Skip to content

Commit 676cbbd

Browse files
authored
Add .loadUrl, support for headers, support for userAgent (#75)
* Add the ability to load a URL * Add the ability to set a user agent * Prepare 0.0.17 (0.1.14)
1 parent 20643ba commit 676cbbd

File tree

11 files changed

+209
-16
lines changed

11 files changed

+209
-16
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 0.0.17 (binary 0.1.14) -- 2024-10-02
4+
5+
- Add `webview.loadUrl` to load a new URL after the webview is initialized
6+
- Add the ability to specify headers when instantiating a webview with a URL
7+
- Add `userAgent` to `WebViewOptions` to construct a new webview with a custom user agent.
8+
39
## 0.0.16 (binary 0.1.13) -- 2024-09-29
410

511
- Add `initializationScript` to `WebViewOptions`. Allows providing JS that runs before `onLoad`.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "deno-webview"
3-
version = "0.1.13"
3+
version = "0.1.14"
44
edition = "2021"
55

66
[profile.release]

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@justbe/webview",
33
"exports": "./src/lib.ts",
4-
"version": "0.0.16",
4+
"version": "0.0.17",
55
"tasks": {
66
"dev": "deno run --watch main.ts",
77
"gen": "deno task gen:rust && deno task gen:deno",

examples/load-url.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { createWebView } from "../src/lib.ts";
2+
3+
using webview = await createWebView({
4+
title: "Load Url Example",
5+
url: "https://example.com",
6+
headers: {
7+
"Content-Type": "text/html",
8+
},
9+
userAgent: "curl/7.81.0",
10+
devtools: true,
11+
});
12+
13+
webview.on("started", async () => {
14+
await webview.openDevTools();
15+
await sleep(2000);
16+
await webview.loadUrl("https://val.town/", {
17+
"Content-Type": "text/html",
18+
});
19+
});
20+
21+
await webview.waitUntilClosed();
22+
23+
function sleep(ms: number) {
24+
return new Promise((resolve) => setTimeout(resolve, ms));
25+
}

schemas/WebViewOptions.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

schemas/WebViewRequest.json

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/generate-schema.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type NodeIR =
3434
value: NodeIR;
3535
}[];
3636
}
37+
| { type: "record"; valueType: string }
3738
| { type: "boolean"; optional?: boolean }
3839
| { type: "string"; optional?: boolean }
3940
| { type: "literal"; value: string }
@@ -170,20 +171,34 @@ function jsonSchemaToIR(schema: JSONSchema): DocIR {
170171
},
171172
)
172173
.with(
173-
{ type: "object" },
174-
() => ({
175-
type: "object" as const,
176-
properties: Object.entries(node.properties ?? {}).map((
177-
[key, value],
178-
) => {
179-
return ({
174+
{ type: P.union("object", P.when(isOptionalType("object"))) },
175+
() => {
176+
if (Object.keys(node.properties ?? {}).length === 0) {
177+
if (
178+
typeof node.additionalProperties === "object" &&
179+
"type" in node.additionalProperties
180+
) {
181+
return {
182+
type: "record" as const,
183+
valueType: typeof node.additionalProperties.type === "string"
184+
? node.additionalProperties.type
185+
: "unknown",
186+
};
187+
}
188+
return { type: "record" as const, valueType: "unknown" };
189+
}
190+
return ({
191+
type: "object" as const,
192+
properties: Object.entries(node.properties ?? {}).map((
193+
[key, value],
194+
) => ({
180195
key,
181196
required: node.required?.includes(key) ?? false,
182197
description: (value as JSONSchema).description,
183198
value: nodeToIR(value as JSONSchema),
184-
});
185-
}),
186-
}),
199+
})),
200+
});
201+
},
187202
)
188203
.otherwise(() => ({ type: "unknown" }));
189204
};
@@ -215,6 +230,9 @@ function generateTypes(ir: DocIR) {
215230
.with({ type: "boolean" }, () => w("boolean"))
216231
.with({ type: "string" }, () => w("string"))
217232
.with({ type: "literal" }, (node) => w(`"${node.value}"`))
233+
.with({ type: "record" }, (node) => {
234+
w(`Record<string, ${node.valueType}>`);
235+
})
218236
.with({ type: "object" }, (node) => {
219237
wn("{");
220238
for (const { key, required, description, value } of node.properties) {
@@ -300,6 +318,9 @@ function generateZodSchema(ir: DocIR) {
300318
{ type: "literal" },
301319
(node) => w(`z.literal("${node.value}")`),
302320
)
321+
.with({ type: "record" }, (node) => {
322+
w(`z.record(z.string(), z.${node.valueType}())`);
323+
})
303324
.with({ type: "object" }, (node) => {
304325
w("z.object({");
305326
for (const { key, required, value } of node.properties) {

src/lib.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export type { WebViewOptions } from "./schemas.ts";
3838

3939
// Should match the cargo package version
4040
/** The version of the webview binary that's expected */
41-
export const BIN_VERSION = "0.1.13";
41+
export const BIN_VERSION = "0.1.14";
4242

4343
type WebViewNotification = Extract<
4444
WebViewMessage,
@@ -463,6 +463,14 @@ export class WebView implements Disposable {
463463
return returnAck(result);
464464
}
465465

466+
/**
467+
* Loads a URL in the webview.
468+
*/
469+
async loadUrl(url: string, headers?: Record<string, string>): Promise<void> {
470+
const result = await this.#send({ $type: "loadUrl", url, headers });
471+
return returnAck(result);
472+
}
473+
466474
/**
467475
* Destroys the webview and cleans up resources.
468476
*

src/main.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use std::borrow::Cow;
22
use std::cell::RefCell;
3+
use std::collections::HashMap;
34
use std::env;
45
use std::io::{self, BufRead, Write};
6+
use std::str::FromStr;
57
use std::sync::mpsc;
68
use std::sync::Arc;
79

@@ -16,6 +18,7 @@ use tao::{
1618
event_loop::{ControlFlow, EventLoop},
1719
window::WindowBuilder,
1820
};
21+
use wry::http::header::{HeaderName, HeaderValue};
1922
use wry::http::Response as HttpResponse;
2023
use wry::WebViewBuilder;
2124

@@ -92,6 +95,9 @@ struct WebViewOptions {
9295
#[serde(default)]
9396
/// Run JavaScript code when loading new pages. When the webview loads a new page, this code will be executed. It is guaranteed that the code is executed before window.onload.
9497
initialization_script: Option<String>,
98+
/// Sets the user agent to use when loading pages.
99+
#[serde(default)]
100+
user_agent: Option<String>,
95101
}
96102

97103
fn default_true() -> bool {
@@ -105,6 +111,8 @@ enum WebViewTarget {
105111
Url {
106112
/// Url to load in the webview. Note: Don't use data URLs here, as they are not supported. Use the `html` field instead.
107113
url: String,
114+
/// Optional headers to send with the request.
115+
headers: Option<HashMap<String, String>>,
108116
},
109117
Html {
110118
/// Html to load in the webview.
@@ -232,6 +240,14 @@ enum Request {
232240
/// If not specified, the origin will be set to the value of the `origin` field when the webview was created.
233241
origin: Option<String>,
234242
},
243+
LoadUrl {
244+
/// The id of the request.
245+
id: String,
246+
/// URL to load in the webview.
247+
url: String,
248+
/// Optional headers to send with the request.
249+
headers: Option<HashMap<String, String>>,
250+
},
235251
}
236252

237253
/// Responses from the webview to the client.
@@ -310,7 +326,22 @@ fn main() -> wry::Result<()> {
310326

311327
let html_cell_init = html_cell.clone();
312328
let mut webview_builder = match webview_options.target {
313-
WebViewTarget::Url { url } => WebViewBuilder::new(&window).with_url(url),
329+
WebViewTarget::Url { url, headers } => {
330+
let mut webview_builder = WebViewBuilder::new(&window).with_url(url);
331+
if let Some(headers) = headers {
332+
let headers = headers
333+
.into_iter()
334+
.map(|(k, v)| {
335+
(
336+
HeaderName::from_str(&k).unwrap(),
337+
HeaderValue::from_str(&v).unwrap(),
338+
)
339+
})
340+
.collect();
341+
webview_builder = webview_builder.with_headers(headers);
342+
}
343+
webview_builder
344+
}
314345
WebViewTarget::Html { html, origin } => {
315346
origin_cell.replace(origin.clone());
316347
html_cell.replace(html);
@@ -346,6 +377,9 @@ fn main() -> wry::Result<()> {
346377
webview_builder =
347378
webview_builder.with_initialization_script(initialization_script.as_str());
348379
}
380+
if let Some(user_agent) = webview_options.user_agent {
381+
webview_builder = webview_builder.with_user_agent(user_agent.as_str());
382+
}
349383
let webview = webview_builder.build()?;
350384

351385
let notify_tx = tx.clone();
@@ -527,6 +561,30 @@ fn main() -> wry::Result<()> {
527561
.unwrap();
528562
res(Response::Ack { id });
529563
}
564+
Request::LoadUrl { id, url, headers } => {
565+
let resp = match headers {
566+
Some(headers) => {
567+
let headers = headers
568+
.into_iter()
569+
.map(|(k, v)| {
570+
(
571+
HeaderName::from_str(&k).unwrap(),
572+
HeaderValue::from_str(&v).unwrap(),
573+
)
574+
})
575+
.collect();
576+
webview.load_url_with_headers(&url, headers)
577+
}
578+
None => webview.load_url(&url),
579+
};
580+
match resp {
581+
Ok(_) => res(Response::Ack { id }),
582+
Err(err) => res(Response::Err {
583+
id,
584+
message: err.to_string(),
585+
}),
586+
}
587+
}
530588
}
531589
}
532590
}

0 commit comments

Comments
 (0)