mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-27 03:56:33 +00:00
feat: localStorage injection for AbleSign WebView cells
Server bundle: - BundleCell gains local_storage: Record<string,string> field - AbleSign entities populate screenId + screenToken (decrypted) into local_storage for the cell Kiosk: - BundleCell.local_storage deserialized from bundle - ensure_web() accepts local_storage param - Injects UserScript at document-start that sets localStorage items before the page loads — AbleSign player reads these and skips the pairing screen Also: getSetupState auto-creates row if missing (handles DELETE) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d58792524d
commit
8b988d7a7d
3 changed files with 44 additions and 2 deletions
|
|
@ -138,6 +138,8 @@ pub struct BundleCell {
|
||||||
pub fit: String,
|
pub fit: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub smart_url: Option<SmartUrlConfig>,
|
pub smart_url: Option<SmartUrlConfig>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub local_storage: Option<std::collections::HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_fit() -> String { "cover".to_string() }
|
fn default_fit() -> String { "cover".to_string() }
|
||||||
|
|
|
||||||
|
|
@ -1193,7 +1193,7 @@ fn render_layout(display_id: &str, layout_id: &str) {
|
||||||
none_cell()
|
none_cell()
|
||||||
} else {
|
} else {
|
||||||
let key = html_key(html);
|
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" => {
|
"web" => {
|
||||||
|
|
@ -1202,7 +1202,7 @@ fn render_layout(display_id: &str, layout_id: &str) {
|
||||||
none_cell()
|
none_cell()
|
||||||
} else {
|
} else {
|
||||||
let key = format!("web:{url}");
|
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.
|
// Smart URL: execute login/navigation steps after page loads.
|
||||||
if let Some(ref smart) = cell.smart_url {
|
if let Some(ref smart) = cell.smart_url {
|
||||||
let decrypt_key = server::load_encrypt_key()
|
let decrypt_key = server::load_encrypt_key()
|
||||||
|
|
@ -1924,6 +1924,7 @@ fn ensure_web(
|
||||||
source: WebSource<'_>,
|
source: WebSource<'_>,
|
||||||
server_url: &str,
|
server_url: &str,
|
||||||
kiosk_key: &str,
|
kiosk_key: &str,
|
||||||
|
local_storage: Option<&std::collections::HashMap<String, String>>,
|
||||||
) -> webkit6::WebView {
|
) -> webkit6::WebView {
|
||||||
let cached = WARM_WEBVIEWS.with(|m| m.borrow().get(&key).map(|e| e.webview.clone()));
|
let cached = WARM_WEBVIEWS.with(|m| m.borrow().get(&key).map(|e| e.webview.clone()));
|
||||||
if let Some(wv) = cached {
|
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 {
|
match source {
|
||||||
WebSource::Html(html) => {
|
WebSource::Html(html) => {
|
||||||
webkit6::prelude::WebViewExt::load_html(&wv, html, None);
|
webkit6::prelude::WebViewExt::load_html(&wv, html, None);
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,8 @@ export interface BundleCell {
|
||||||
login_detect_url?: string;
|
login_detect_url?: string;
|
||||||
session_check_interval_ms?: number;
|
session_check_interval_ms?: number;
|
||||||
};
|
};
|
||||||
|
/** Key→value pairs injected into WebView localStorage before page load. */
|
||||||
|
local_storage?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BundleLayout {
|
export interface BundleLayout {
|
||||||
|
|
@ -220,6 +222,7 @@ export async function generateBundle(
|
||||||
// bundle still ships the legacy camera_id/web_url/html_content shape
|
// bundle still ships the legacy camera_id/web_url/html_content shape
|
||||||
// so the existing Rust kiosk consumes it unchanged.
|
// so the existing Rust kiosk consumes it unchanged.
|
||||||
let contentType = c.content_type;
|
let contentType = c.content_type;
|
||||||
|
let cellLocalStorage: Record<string, string> | undefined;
|
||||||
let cameraId = c.camera_id;
|
let cameraId = c.camera_id;
|
||||||
let webUrl = c.web_url;
|
let webUrl = c.web_url;
|
||||||
let htmlContent = c.html_content;
|
let htmlContent = c.html_content;
|
||||||
|
|
@ -237,6 +240,21 @@ export async function generateBundle(
|
||||||
ent.type === "dashboard" && ent.dashboard_id ? `/dash/${ent.dashboard_id}` :
|
ent.type === "dashboard" && ent.dashboard_id ? `/dash/${ent.dashboard_id}` :
|
||||||
null;
|
null;
|
||||||
htmlContent = ent.type === "html" ? ent.html_content : 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<string, string> = {
|
||||||
|
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({
|
bundleCells.push({
|
||||||
|
|
@ -271,6 +289,7 @@ export async function generateBundle(
|
||||||
session_check_interval_ms: raw.session_check_interval_ms,
|
session_check_interval_ms: raw.session_check_interval_ms,
|
||||||
};
|
};
|
||||||
})() : undefined,
|
})() : undefined,
|
||||||
|
local_storage: cellLocalStorage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
result.push({
|
result.push({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue