use axum::Router;
use axum::http::{HeaderValue, header};
use axum::response::{IntoResponse, Response};
use axum::routing::get;
pub const BRIDGE_PATH: &str = "/__rahti/native.js";
pub fn bridge_script(platform: &str, version: &str) -> String {
let capabilities = json_array(&crate::capabilities::capability_names());
let commands: Vec<&str> = crate::capabilities::commands()
.iter()
.map(|c| c.name)
.collect();
let allowed = json_array(&commands);
let platform = json_string(platform);
let version = json_string(version);
format!(
r#"// Generated by rahti-native. Do not edit.
//
// Attaches `pp.native` to the PulsePoint runtime. The runtime bundle itself is
// untouched: this listens for the moment it publishes `pp` and adds one
// property to it.
(function () {{
"use strict";
var ALLOWED = {allowed};
var CAPABILITIES = {capabilities};
function tauri() {{
var t = window.__TAURI__;
return t && t.core && typeof t.core.invoke === "function" ? t.core : null;
}}
var api = {{
// `true` inside a native package, and absent everywhere else — which is
// why a page checks `pp.native` before it checks anything on it.
available: true,
platform: {platform},
version: {version},
has: function (capability) {{
return CAPABILITIES.indexOf(capability) !== -1 && tauri() !== null;
}},
// Refused here as well as in Rust. This is not the security boundary — the
// shell's allowlist and Tauri's capability file are — but a name that was
// never meant to be callable should fail where the caller can see why.
invoke: function (name, payload) {{
if (ALLOWED.indexOf(name) === -1) {{
return Promise.reject(
new Error("pp.native: `" + name + "` is not a native command this application exposes.")
);
}}
var core = tauri();
if (!core) {{
return Promise.reject(new Error("pp.native: the native bridge is not available."));
}}
return core.invoke(name, payload || {{}});
}},
}};
Object.freeze(api);
// The access point a page uses, and the reason it is a global rather than a
// property of `pp`.
//
// PulsePoint compiles the expressions in a reactive block into a function of
// its own making, and the `pp` visible inside that function is a scoped
// object it supplies — not `window.pp`. So `pp.state(...)` works there and
// `pp.native` cannot, without editing the shipped runtime bundle, which is
// not something Rahti does.
//
// `window` *is* reachable from inside a compiled block, so this is.
window.rahtiNative = api;
// Best-effort, and documented nowhere as the way in: in a context where
// `pp` *is* the global object, `pp.native` also works and reads a little
// better. Nothing depends on it succeeding.
window.__rahtiNativeAttach = "not attempted";
function attach(pp) {{
if (!pp) {{
return false;
}}
if (pp.native) {{
return true;
}}
try {{
pp.native = api;
}} catch (e) {{
window.__rahtiNativeAttach = "assign threw: " + e.message;
}}
if (!pp.native) {{
try {{
Object.defineProperty(pp, "native", {{
value: api,
configurable: true,
enumerable: true,
}});
}} catch (e) {{
window.__rahtiNativeAttach =
"frozen=" + Object.isFrozen(pp) + " extensible=" + Object.isExtensible(pp);
return false;
}}
}}
if (pp.native) {{
window.__rahtiNativeAttach = "attached";
}}
return !!pp.native;
}}
// The bundle assigns `globalThis.pp` when it loads, which is after this
// script runs. Intercepting the assignment is what lets the namespace be
// added without the bundle knowing anything about it.
if (!attach(window.pp)) {{
var stored;
try {{
Object.defineProperty(window, "pp", {{
configurable: true,
get: function () {{
return stored;
}},
set: function (value) {{
stored = value;
attach(value);
}},
}});
}} catch (e) {{
window.__rahtiNativeAttach = "trap threw: " + e.message;
}}
// The trap is not enough on its own, and neither is attaching once.
//
// A runtime that publishes itself with `Object.defineProperty` replaces the
// accessor rather than calling it, so the set never happens. And a runtime
// that *replaces* `window.pp` later — a richer object after mount, say —
// silently drops a `native` that was attached to the object before it. Both
// were observed: the attach reported success and `pp.native` was undefined
// by the time a page read it.
//
// So the attach is not an event, it is a condition that is kept true. The
// check is a property lookup a few times a second and costs nothing
// measurable; being wrong costs the whole native surface, silently.
setInterval(function () {{
if (window.pp && !window.pp.native) {{
attach(window.pp);
}}
}}, 250);
}}
}})();
"#
)
}
pub fn bridge_route(platform: &'static str, version: String) -> Router {
Router::new().route(
BRIDGE_PATH,
get(move || {
let body = bridge_script(platform, &version);
async move {
let mut response = body.into_response();
response.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("text/javascript; charset=utf-8"),
);
response
.headers_mut()
.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
response as Response
}
}),
)
}
fn json_array(values: &[&str]) -> String {
let items: Vec<String> = values.iter().map(|v| json_string(v)).collect();
format!("[{}]", items.join(", "))
}
fn json_string(value: &str) -> String {
serde_json::to_string(value)
.unwrap_or_else(|_| "\"\"".to_string())
.replace('<', "\\u003c")
}