diff --git a/kiosk/src/bundle.rs b/kiosk/src/bundle.rs index 55a6eea..a4d5eb5 100644 --- a/kiosk/src/bundle.rs +++ b/kiosk/src/bundle.rs @@ -138,6 +138,8 @@ pub struct BundleCell { pub fit: String, #[serde(default)] pub smart_url: Option, + #[serde(default)] + pub local_storage: Option>, } fn default_fit() -> String { "cover".to_string() } diff --git a/kiosk/src/ui.rs b/kiosk/src/ui.rs index c8a0165..76734bb 100644 --- a/kiosk/src/ui.rs +++ b/kiosk/src/ui.rs @@ -1193,7 +1193,7 @@ fn render_layout(display_id: &str, layout_id: &str) { none_cell() } else { let key = html_key(html); - ensure_web(key, WebSource::Html(html), server_url, kiosk_key).upcast() + ensure_web(key, WebSource::Html(html), server_url, kiosk_key, None).upcast() } } "web" => { @@ -1202,7 +1202,7 @@ fn render_layout(display_id: &str, layout_id: &str) { none_cell() } else { let key = format!("web:{url}"); - let wv = ensure_web(key, WebSource::Url(url), server_url, kiosk_key); + let wv = ensure_web(key, WebSource::Url(url), server_url, kiosk_key, cell.local_storage.as_ref()); // Smart URL: execute login/navigation steps after page loads. if let Some(ref smart) = cell.smart_url { let decrypt_key = server::load_encrypt_key() @@ -1924,6 +1924,7 @@ fn ensure_web( source: WebSource<'_>, server_url: &str, kiosk_key: &str, + local_storage: Option<&std::collections::HashMap>, ) -> webkit6::WebView { let cached = WARM_WEBVIEWS.with(|m| m.borrow().get(&key).map(|e| e.webview.clone())); if let Some(wv) = cached { @@ -1967,6 +1968,26 @@ fn ensure_web( } } + if let Some(ls) = local_storage { + if !ls.is_empty() { + let mut js = String::from("(function(){"); + for (k, v) in ls { + js.push_str(&format!("localStorage.setItem({},{});", js_string_lit(k), js_string_lit(v))); + } + js.push_str("})();"); + let script = webkit6::UserScript::new( + &js, + webkit6::UserContentInjectedFrames::TopFrame, + webkit6::UserScriptInjectionTime::Start, + &[], + &[], + ); + if let Some(ucm) = webkit6::prelude::WebViewExt::user_content_manager(&wv) { + ucm.add_script(&script); + } + } + } + match source { WebSource::Html(html) => { webkit6::prelude::WebViewExt::load_html(&wv, html, None); diff --git a/server/src/shared/bundle.ts b/server/src/shared/bundle.ts index 0b9c3d9..583fcbf 100644 --- a/server/src/shared/bundle.ts +++ b/server/src/shared/bundle.ts @@ -91,6 +91,8 @@ export interface BundleCell { login_detect_url?: string; session_check_interval_ms?: number; }; + /** Key→value pairs injected into WebView localStorage before page load. */ + local_storage?: Record; } export interface BundleLayout { @@ -220,6 +222,7 @@ export async function generateBundle( // bundle still ships the legacy camera_id/web_url/html_content shape // so the existing Rust kiosk consumes it unchanged. let contentType = c.content_type; + let cellLocalStorage: Record | undefined; let cameraId = c.camera_id; let webUrl = c.web_url; let htmlContent = c.html_content; @@ -237,6 +240,21 @@ export async function generateBundle( ent.type === "dashboard" && ent.dashboard_id ? `/dash/${ent.dashboard_id}` : null; htmlContent = ent.type === "html" ? ent.html_content : null; + // AbleSign: inject screenToken + screenId into localStorage + if (ent.type === "ablesign" && ent.ablesign_screen_id) { + const screen = await repo.getAbleSignScreen(ent.ablesign_screen_id); + if (screen) { + const ls: Record = { + screenId: screen.ablesign_screen_id, + }; + if (screen.ablesign_screen_token_encrypted) { + try { + ls["screenToken"] = secrets.decryptString(screen.ablesign_screen_token_encrypted, "ablesign-token"); + } catch { /* token decrypt failed — player will show pairing */ } + } + cellLocalStorage = ls; + } + } } } bundleCells.push({ @@ -271,6 +289,7 @@ export async function generateBundle( session_check_interval_ms: raw.session_check_interval_ms, }; })() : undefined, + local_storage: cellLocalStorage, }); } result.push({