linera-io/linera-protocol · error
we need to run in a document context
Error message
we need to run in a document context
What it means
The @linera/client wallet lock is built on the browser Web Locks API, reached through `web_sys::window()`. window() returns Option and is None outside a window/document context: Web Workers (their global object is not `window`), Node.js and other non-browser WASM runtimes, SSR, and DOM-less test runners. `.expect("we need to run in a document context")` panics as soon as try_acquire is called in such a context, before any lock request is made.
Source
Thrown at web/@linera/client/src/lock.rs:106
})
} else {
release.call0(&JsValue::NULL).unwrap_throw();
None
};
resolve
.call1(
&JsValue::NULL,
&serde_wasm_bindgen::to_value(&value).unwrap_throw(),
)
.unwrap_throw();
std::mem::forget(value);
})
});
let _: js_sys::Promise = web_sys::window()
.expect("we need to run in a document context")
.navigator()
.locks()
.request_with_options_and_callback(
name,
&options,
callback.as_ref().unchecked_ref(),
);
callback.forget();
}))
.await
.unwrap_throw();
serde_wasm_bindgen::from_value::<Option<Self>>(value)
.unwrap_throw()
.ok_or_else(|| Error::Contended { name: name.into() })
}
}View on GitHub (pinned to 6c226ddcb3)
Solutions
- Run the locking code on the main browser thread / a real browser environment (a document context)
- Guard the call: check web_sys::window().is_some() (and that navigator.locks exists) and skip or use a fallback when absent
- In tests, run wasm-bindgen-test with a browser target (chrome) instead of node
- If Web Locks is unavailable, fall back to a storage/BroadcastChannel-based mutual exclusion for the wallet
Example fix
// before
let lock = Lock::try_acquire("wallet").await?; // panics in workers/node: no `window`
// after
let lock = if web_locks_available() {
Some(Lock::try_acquire("wallet").await?)
} else {
None // degrade gracefully outside a document context
}; Defensive patterns
Strategy: type-guard
Type guard
fn web_locks_available() -> bool {
web_sys::window().is_some_and(|w| {
js_sys::Reflect::has(&w.navigator(), &wasm_bindgen::JsValue::from_str("locks"))
.unwrap_or(false)
})
} Prevention
- Feature-detect window + navigator.locks before any Web Locks call
- Keep wallet-locking code on the main thread; pass results to workers instead
- Run wasm-bindgen tests with a browser target (chrome) so document APIs exist
When it happens
Trigger: Calling Lock::try_acquire from code running in a Web Worker; executing the client WASM under Node (e.g. wasm-bindgen-test with the nodejs target); SSR/prerendering that imports and runs the wallet module; unit tests under jsdom where window exists but navigator.locks is missing.
Common situations: Moving wallet logic into a worker for background signing; CI unit tests running under node instead of a headless browser; older browsers without navigator.locks (pre-Chrome 69 / Firefox 96 / Safari 15.4) failing the follow-up calls; SSR frameworks pulling in the browser client bundle.
Related errors
- A Wasm runtime is required to load user applications. Please
- BytecodeTooLarge
- MaximumFuelExceeded
- ReentrantCall
- StreamNameTooLong
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/a7646d121e6cfb5c.
Report an issue: GitHub.