mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-22 03:05:32 +02:00
feat: integrate Modal Closer as injected browser script (#27)
Port the modalcloser abx-plugin as a SingleFile --browser-script rather
than a spawned Puppeteer daemon. No external extension download required.
Core behavior (singlefile.rs):
- MODAL_CLOSER_DIALOG_OVERRIDES: main-world <script> bridge (best-effort;
blocked by strict script-src CSP) that overrides window.alert/confirm/
prompt/print, nulls window.onbeforeunload, traps its setter, and wraps
window.addEventListener to no-op 'beforeunload' registrations.
- MODAL_CLOSER_POLLING_SETUP: defines _archivr_mc_run() which runs two
passes on every tick (immediate first run, then setInterval every 500 ms
matching MODALCLOSER_POLL_INTERVAL default):
Pass 1 (best-effort, CSP-sensitive): main-world <script> bridge calls
Bootstrap/jQuery/jQuery UI/SweetAlert teardown APIs.
Pass 2 (always, CSP-immune): isolated-world DOM mutations — Escape-key
dispatch (Radix/Headless UI/Angular Material), backdrop clicks, full
CSS selector hiding for 40+ named consent/overlay vendors, body
scroll-lock reset. Direct DOM mutations need no inline script execution.
- resolve_modal_closer_config(): reads ARCHIVR_MODAL_CLOSER env var
(default true); no external resource required.
- modal_closer_enabled: Option<bool> in CaptureConfig so Default::default()
yields None (follow env var) rather than false.
Schema (database.rs):
- modal_closer_enabled column in auth DB instance_settings (default 1).
- Idempotent ALTER TABLE migration for existing databases.
- get/update_instance_settings updated (SELECT col 6, UPDATE param ?7).
HTTP (routes.rs):
- modal_closer_enabled in CaptureBody and UpdateInstanceSettingsBody.
- Per-capture body overrides global setting which overrides env var.
Frontend:
- SettingsView ExtensionsTab: third ext-card for Modal & Dialog Closer.
- CaptureDialog: per-capture toggle in Advanced Options, state initialized
from server default, included in submitCapture payload.
- api.js: submitCapture forwards modal_closer_enabled to capture body.
Behavior vs original modalcloser daemon:
- Functionally identical on non-strict-CSP pages (the large majority).
- Gap: <script> bridge is subject to page CSP; page.evaluate() is not.
On strict-CSP pages framework teardown and dialog overrides are no-ops;
CSS selector hiding and Escape dispatch (Pass 2) always work.
- Gap: alert/confirm/prompt overridden to instant no-op vs CDP timed
dialog.accept() with 1250 ms delay; irrelevant for archival in practice.
This commit is contained in:
parent
b0d02034ea
commit
278dc928df
10 changed files with 294 additions and 63 deletions
|
|
@ -685,6 +685,7 @@ struct CaptureBody {
|
|||
/// Distil to article content via Readability before archiving. Absent = false.
|
||||
reader_mode: Option<bool>,
|
||||
cookie_ext_enabled: Option<bool>,
|
||||
modal_closer_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
|
|
@ -765,26 +766,29 @@ async fn capture_handler(
|
|||
let job_uid = database::create_capture_job(&conn, &archive_id)?;
|
||||
drop(conn);
|
||||
// Load cookie rules and global uBlock / cookie-ext settings from the auth DB.
|
||||
let (cookie_rules, global_ublock, global_cookie_ext) = {
|
||||
let (cookie_rules, global_ublock, global_cookie_ext, global_modal_closer) = {
|
||||
match database::open_auth_db(&state.auth_db_path) {
|
||||
Ok(conn) => {
|
||||
let rules = database::list_cookie_rules(&conn).unwrap_or_default();
|
||||
let settings = database::get_instance_settings(&conn);
|
||||
let ublock = settings.as_ref().map(|s| s.ublock_enabled).unwrap_or(true);
|
||||
let cookie_ext = settings.map(|s| s.cookie_ext_enabled).unwrap_or(true);
|
||||
(rules, ublock, cookie_ext)
|
||||
let cookie_ext = settings.as_ref().map(|s| s.cookie_ext_enabled).unwrap_or(true);
|
||||
let modal_closer = settings.map(|s| s.modal_closer_enabled).unwrap_or(true);
|
||||
(rules, ublock, cookie_ext, modal_closer)
|
||||
}
|
||||
Err(_) => (vec![], true, true),
|
||||
Err(_) => (vec![], true, true, true),
|
||||
}
|
||||
};
|
||||
// Per-capture body overrides global; if body doesn't specify, use the global setting.
|
||||
// The resolved bool is then passed as Some(_) to singlefile, overriding the env var.
|
||||
let effective_ublock = body.ublock_enabled.unwrap_or(global_ublock);
|
||||
let effective_cookie_ext = body.cookie_ext_enabled.unwrap_or(global_cookie_ext);
|
||||
let effective_modal_closer = body.modal_closer_enabled.unwrap_or(global_modal_closer);
|
||||
let capture_config = capture::CaptureConfig {
|
||||
cookie_rules,
|
||||
ublock_enabled: Some(effective_ublock),
|
||||
cookie_ext_enabled: Some(effective_cookie_ext),
|
||||
modal_closer_enabled: Some(effective_modal_closer),
|
||||
reader_mode: body.reader_mode.unwrap_or(false),
|
||||
};
|
||||
|
||||
|
|
@ -895,6 +899,7 @@ async fn rearchive_handler(
|
|||
cookie_rules,
|
||||
ublock_enabled: None,
|
||||
cookie_ext_enabled: None,
|
||||
modal_closer_enabled: None,
|
||||
reader_mode: false,
|
||||
};
|
||||
|
||||
|
|
@ -1188,6 +1193,7 @@ async fn update_instance_settings_handler(
|
|||
if let Some(v) = body.default_entry_visibility { settings.default_entry_visibility = v; }
|
||||
if let Some(v) = body.ublock_enabled { settings.ublock_enabled = v; }
|
||||
if let Some(v) = body.cookie_ext_enabled { settings.cookie_ext_enabled = v; }
|
||||
if let Some(v) = body.modal_closer_enabled { settings.modal_closer_enabled = v; }
|
||||
database::update_instance_settings(&conn, &settings)?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
|
@ -1519,6 +1525,7 @@ struct UpdateInstanceSettingsBody {
|
|||
default_entry_visibility: Option<u32>,
|
||||
ublock_enabled: Option<bool>,
|
||||
cookie_ext_enabled: Option<bool>,
|
||||
modal_closer_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
async fn admin_list_users(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue