device_envoy_core/wasm/cyd_web.rs
1//! A device abstraction for complete CYD browser applications.
2//!
3//! [`start`] creates the stable browser supervisor and returns [`Handle`]. The
4//! supervisor constructs fresh [`Capabilities`] for each run, allowing the
5//! application to select focused capabilities for unchanged generic core code.
6//! [`Command`] communicates application policy back to the supervisor.
7//!
8//! ## Compiled browser-shell example
9//!
10//! The HTML page supplies a `<canvas id="cyd-canvas">`; Rust supplies the
11//! presentation metadata and an async application function. The returned
12//! [`Handle`] is the stable JavaScript-facing control surface.
13//!
14//! ```rust,no_run
15//! use core::convert::Infallible;
16//! use device_envoy_core::{
17//! button::Button,
18//! cyd::{CydDisplay, display::Orientation},
19//! dns::Dns,
20//! wasm::cyd_web::{
21//! self, Capabilities, Command, Config, Handle, Notice, NoticeSeverity, PageInfo,
22//! },
23//! };
24//! use embedded_graphics::{
25//! mono_font::ascii::FONT_6X10,
26//! pixelcolor::{Rgb888, RgbColor},
27//! };
28//! use wasm_bindgen::JsValue;
29//!
30//! const CONFIG: Config = Config::new(
31//! "counter-demo",
32//! Orientation::Landscape,
33//! Rgb888::BLACK,
34//! Rgb888::WHITE,
35//! &FONT_6X10,
36//! );
37//! const PAGE: PageInfo = PageInfo::new(
38//! "Counter",
39//! "A touch-controlled counter",
40//! "The same application logic used by the hardware builds.",
41//! "Touch the display; BOOT resets.",
42//! "https://example.invalid/counter.rs",
43//! );
44//!
45//! async fn inner_main(
46//! mut capabilities: Capabilities,
47//! ) -> Result<Command, Infallible> {
48//! assert_eq!(capabilities.cyd.display().screen_size().width, 320);
49//! assert!(!capabilities.button.is_pressed());
50//! capabilities.clock_sync.show();
51//! capabilities.wifi_simulator.reset();
52//! let addresses = capabilities.dns_simulator.resolve("example.com").await?;
53//! assert!(!addresses.is_empty());
54//! Ok(Command::Stop)
55//! }
56//!
57//! fn launch() -> Result<(), JsValue> {
58//! assert_eq!(CONFIG.storage_namespace, "counter-demo");
59//! assert_eq!(CONFIG.initial_orientation, Orientation::Landscape);
60//! assert_eq!(CONFIG.background_color, Rgb888::BLACK);
61//! assert_eq!(CONFIG.foreground_color, Rgb888::WHITE);
62//! assert_eq!(CONFIG.font.character_size.width, FONT_6X10.character_size.width);
63//! assert_eq!(PAGE.title, "Counter");
64//! assert!(!PAGE.preview.is_empty());
65//! assert!(!PAGE.description.is_empty());
66//! assert!(!PAGE.controls.is_empty());
67//! assert!(PAGE.core_code_url.ends_with("counter.rs"));
68//!
69//! let handle: Handle = cyd_web::start("cyd-canvas", CONFIG, PAGE, inner_main)?;
70//! handle.touch_down(20.0, 30.0);
71//! handle.touch_move(22.0, 32.0);
72//! handle.touch_up();
73//! handle.boot_down();
74//! handle.boot_up();
75//! assert!(!handle.orientation_is_inverted());
76//! assert_eq!(handle.page_title(), "Counter");
77//! assert_eq!(handle.page_preview(), PAGE.preview);
78//! assert_eq!(handle.page_description(), PAGE.description);
79//! assert_eq!(handle.page_controls(), PAGE.controls);
80//! assert_eq!(handle.page_core_code_url(), PAGE.core_code_url);
81//! handle.set_clock_time_of_day(12 * 60 * 60)?;
82//! handle.use_live_clock();
83//! handle.clock_control_is_visible();
84//! if let Some(notice) = handle.take_notice() {
85//! inspect_notice(notice);
86//! }
87//! handle.request_restart();
88//! handle.clear_storage_and_restart();
89//! Ok(())
90//! }
91//!
92//! fn inspect_notice(notice: Notice) {
93//! assert!(!notice.id().is_empty());
94//! notice.severity();
95//! notice.detail();
96//! }
97//!
98//! const NOTICE_SEVERITIES: [NoticeSeverity; 3] = [
99//! NoticeSeverity::Info,
100//! NoticeSeverity::Warning,
101//! NoticeSeverity::Fatal,
102//! ];
103//!
104//! fn every_command(index: u8) -> Command {
105//! match index {
106//! 0 => Command::Restart,
107//! 1 => Command::CalibrationNotNeeded,
108//! 2 => Command::ResetWifi,
109//! 3 => Command::Reorientate(Orientation::Portrait),
110//! _ => Command::Stop,
111//! }
112//! }
113//! ```
114
115use core::{
116 future::Future,
117 pin::Pin,
118 task::{Context, Poll, Waker},
119};
120use std::{
121 cell::{Cell, RefCell},
122 fmt::Debug,
123 rc::Rc,
124};
125
126use embassy_futures::select::{Either, select};
127use embedded_graphics::{mono_font::MonoFont, pixelcolor::Rgb888};
128use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen};
129use web_sys::{HtmlCanvasElement, window};
130
131use super::{
132 ButtonWasm, ClockSyncWasm, CydSimulatorControlWasm, CydSimulatorWasm, CydWasm,
133 DnsSimulatorWasm, FlashBlockWasm, WifiSimulatorWasm,
134};
135use crate::cyd::display::Orientation;
136use crate::flash_block::FlashBlock as _;
137
138#[derive(Clone, Copy)]
139/// Presentation and persistent-storage settings for a [`Capabilities`]
140/// session.
141/// The compiled browser-shell example on [`crate::wasm::cyd_web`] constructs
142/// and reads every field.
143pub struct Config {
144 /// Namespace used for orientation and simulated Wi-Fi state.
145 pub storage_namespace: &'static str,
146 /// Orientation used when no saved orientation exists.
147 pub initial_orientation: Orientation,
148 /// Canvas background color.
149 pub background_color: Rgb888,
150 /// Canvas foreground color.
151 pub foreground_color: Rgb888,
152 /// Font used by the simulated display.
153 pub font: &'static MonoFont<'static>,
154}
155
156impl Config {
157 /// Construct presentation settings for [`start`].
158 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
159 pub const fn new(
160 storage_namespace: &'static str,
161 initial_orientation: Orientation,
162 background_color: Rgb888,
163 foreground_color: Rgb888,
164 font: &'static MonoFont<'static>,
165 ) -> Self {
166 Self {
167 storage_namespace,
168 initial_orientation,
169 background_color,
170 foreground_color,
171 font,
172 }
173 }
174}
175
176#[derive(Clone, Copy)]
177/// Browser-facing metadata displayed by the shared CYD simulator shell.
178/// The compiled browser-shell example on [`crate::wasm::cyd_web`] constructs
179/// and reads every field.
180pub struct PageInfo {
181 /// Page title.
182 pub title: &'static str,
183 /// Short preview text.
184 pub preview: &'static str,
185 /// Longer application description.
186 pub description: &'static str,
187 /// Human-readable interaction instructions.
188 pub controls: &'static str,
189 /// Link to the platform-neutral application source.
190 pub core_code_url: &'static str,
191}
192
193impl PageInfo {
194 /// Construct page metadata for [`start`].
195 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
196 pub const fn new(
197 title: &'static str,
198 preview: &'static str,
199 description: &'static str,
200 controls: &'static str,
201 core_code_url: &'static str,
202 ) -> Self {
203 Self {
204 title,
205 preview,
206 description,
207 controls,
208 core_code_url,
209 }
210 }
211}
212
213/// Complete capability container supplied to each application run.
214///
215/// A launcher receives this value from [`start`], selects focused capabilities,
216/// and returns a [`Command`].
217/// The compiled browser-shell example on [`crate::wasm::cyd_web`] reads every
218/// field.
219///
220/// ```rust,no_run
221/// # use core::convert::Infallible;
222/// # use device_envoy_core::{cyd::CydDisplay, wasm::cyd_web};
223/// #
224/// async fn inner_main(
225/// mut capabilities: cyd_web::Capabilities,
226/// ) -> Result<cyd_web::Command, Infallible> {
227/// capabilities.clock_sync.show();
228/// assert_eq!(capabilities.cyd.display().screen_size().width, 320);
229/// Ok(cyd_web::Command::Stop)
230/// }
231/// #
232/// # fn receives_capabilities(_capabilities: cyd_web::Capabilities) {}
233/// ```
234pub struct Capabilities {
235 /// CYD display and touch capability.
236 pub cyd: CydWasm,
237 /// BOOT-button capability.
238 pub button: ButtonWasm,
239 /// Browser-backed clock capability.
240 pub clock_sync: ClockSyncWasm,
241 /// Simulated Wi-Fi capability.
242 pub wifi_simulator: WifiSimulatorWasm,
243 /// Deterministic simulated DNS capability.
244 pub dns_simulator: DnsSimulatorWasm,
245}
246
247/// Result requested by an application after one run.
248/// The compiled browser-shell example on [`crate::wasm::cyd_web`] constructs
249/// every variant.
250pub enum Command {
251 /// Restart the current session.
252 Restart,
253 /// Report that physical calibration is unnecessary in the browser.
254 CalibrationNotNeeded,
255 /// Clear simulated Wi-Fi state and restart.
256 ResetWifi,
257 /// Persist and apply a new display orientation.
258 Reorientate(Orientation),
259 /// Stop the supervisor.
260 Stop,
261}
262
263#[wasm_bindgen(js_name = CydWebNoticeSeverity)]
264#[derive(Clone, Copy, Debug, Eq, PartialEq)]
265/// Severity assigned to a framework notice.
266///
267/// The compiled browser-shell example on [`crate::wasm::cyd_web`] constructs
268/// every variant.
269pub enum NoticeSeverity {
270 /// Informational notice.
271 Info,
272 /// Recoverable warning.
273 Warning,
274 /// Terminal runtime failure.
275 Fatal,
276}
277
278#[wasm_bindgen(js_name = CydWebNotice)]
279#[derive(Clone, Debug)]
280/// Typed notice emitted by the framework for the shared browser shell.
281/// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
282pub struct Notice {
283 id: String,
284 severity: NoticeSeverity,
285 detail: Option<String>,
286}
287
288impl Notice {
289 fn new(id: impl Into<String>, severity: NoticeSeverity) -> Self {
290 Self {
291 id: id.into(),
292 severity,
293 detail: None,
294 }
295 }
296 fn fatal(detail: String) -> Self {
297 Self {
298 id: "runtime-error".into(),
299 severity: NoticeSeverity::Fatal,
300 detail: Some(detail),
301 }
302 }
303}
304
305#[wasm_bindgen(js_class = CydWebNotice)]
306impl Notice {
307 /// Return the stable notice identifier.
308 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
309 pub fn id(&self) -> String {
310 self.id.clone()
311 }
312 /// Return the notice severity.
313 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
314 pub fn severity(&self) -> NoticeSeverity {
315 self.severity
316 }
317 /// Return optional diagnostic detail.
318 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
319 pub fn detail(&self) -> Option<String> {
320 self.detail.clone()
321 }
322}
323
324#[derive(Clone, Copy)]
325enum HostRequest {
326 Restart,
327 ClearStorage,
328}
329struct LifecycleState {
330 request: Option<HostRequest>,
331 waker: Option<Waker>,
332}
333#[derive(Clone)]
334struct LifecycleSignal {
335 state: Rc<RefCell<LifecycleState>>,
336}
337
338impl LifecycleSignal {
339 fn new() -> Self {
340 Self {
341 state: Rc::new(RefCell::new(LifecycleState {
342 request: None,
343 waker: None,
344 })),
345 }
346 }
347 fn request(&self, request: HostRequest) {
348 let waker = {
349 let mut state = self.state.borrow_mut();
350 state.request = Some(request);
351 state.waker.take()
352 };
353 if let Some(waker) = waker {
354 waker.wake();
355 }
356 }
357 async fn wait(&self) -> HostRequest {
358 LifecycleRequestFuture {
359 signal: self.clone(),
360 }
361 .await
362 }
363}
364struct LifecycleRequestFuture {
365 signal: LifecycleSignal,
366}
367impl Future for LifecycleRequestFuture {
368 type Output = HostRequest;
369 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
370 let mut state = self.signal.state.borrow_mut();
371 if let Some(request) = state.request.take() {
372 Poll::Ready(request)
373 } else {
374 state.waker = Some(context.waker().clone());
375 Poll::Pending
376 }
377 }
378}
379
380struct SupervisorState {
381 live_control: Option<CydSimulatorControlWasm>,
382 notices: std::collections::VecDeque<Notice>,
383 orientation: Orientation,
384 stopped: bool,
385 page_info: PageInfo,
386 clock_time_of_day: Rc<Cell<Option<u32>>>,
387 clock_control_visible: Rc<Cell<bool>>,
388}
389
390#[wasm_bindgen(js_name = CydWebAppHandle)]
391/// Stable browser control handle returned by [`start`].
392/// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
393pub struct Handle {
394 state: Rc<RefCell<SupervisorState>>,
395 lifecycle_signal: LifecycleSignal,
396}
397
398impl Handle {
399 fn new(state: Rc<RefCell<SupervisorState>>, lifecycle_signal: LifecycleSignal) -> Self {
400 Self {
401 state,
402 lifecycle_signal,
403 }
404 }
405 fn with_control(&self, action: impl FnOnce(&CydSimulatorControlWasm)) {
406 let state = self.state.borrow();
407 if !state.stopped {
408 if let Some(control) = state.live_control.as_ref() {
409 action(control);
410 }
411 }
412 }
413}
414
415#[wasm_bindgen(js_class = CydWebAppHandle)]
416impl Handle {
417 /// Press the simulated touch panel at canvas coordinates.
418 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
419 pub fn touch_down(&self, position_x: f32, position_y: f32) {
420 self.with_control(|control| control.touch_down(position_x, position_y));
421 }
422 /// Move the simulated touch point.
423 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
424 pub fn touch_move(&self, position_x: f32, position_y: f32) {
425 self.with_control(|control| control.touch_move(position_x, position_y));
426 }
427 /// Release the simulated touch panel.
428 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
429 pub fn touch_up(&self) {
430 self.with_control(CydSimulatorControlWasm::touch_up);
431 }
432 /// Press the simulated BOOT button.
433 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
434 pub fn boot_down(&self) {
435 self.with_control(CydSimulatorControlWasm::boot_down);
436 }
437 /// Release the simulated BOOT button.
438 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
439 pub fn boot_up(&self) {
440 self.with_control(CydSimulatorControlWasm::boot_up);
441 }
442 /// Return whether the current orientation is inverted.
443 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
444 pub fn orientation_is_inverted(&self) -> bool {
445 self.state
446 .borrow()
447 .live_control
448 .as_ref()
449 .is_some_and(CydSimulatorControlWasm::orientation_is_inverted)
450 }
451 /// Remove and return the oldest pending framework notice.
452 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
453 pub fn take_notice(&self) -> Option<Notice> {
454 self.state.borrow_mut().notices.pop_front()
455 }
456 /// Request an application restart.
457 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
458 pub fn request_restart(&self) {
459 self.lifecycle_signal.request(HostRequest::Restart);
460 }
461 /// Clear framework storage and restart the application.
462 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
463 pub fn clear_storage_and_restart(&self) {
464 self.lifecycle_signal.request(HostRequest::ClearStorage);
465 }
466 /// Return whether the application has requested the clock control.
467 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
468 pub fn clock_control_is_visible(&self) -> bool {
469 self.state.borrow().clock_control_visible.get()
470 }
471 /// Set the simulated local time, in seconds after midnight.
472 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
473 pub fn set_clock_time_of_day(&self, seconds_of_day: u32) -> Result<(), JsValue> {
474 if seconds_of_day >= 86_400 {
475 return Err(JsValue::from_str(
476 "time of day must be between 0 and 86399 seconds",
477 ));
478 }
479 self.state
480 .borrow()
481 .clock_time_of_day
482 .set(Some(seconds_of_day));
483 Ok(())
484 }
485 /// Restore the browser's live local clock.
486 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
487 pub fn use_live_clock(&self) {
488 self.state.borrow().clock_time_of_day.set(None);
489 }
490 /// Return the configured page title.
491 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
492 pub fn page_title(&self) -> String {
493 self.state.borrow().page_info.title.into()
494 }
495 /// Return the configured preview text.
496 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
497 pub fn page_preview(&self) -> String {
498 self.state.borrow().page_info.preview.into()
499 }
500 /// Return the configured page description.
501 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
502 pub fn page_description(&self) -> String {
503 self.state.borrow().page_info.description.into()
504 }
505 /// Return the configured interaction instructions.
506 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
507 pub fn page_controls(&self) -> String {
508 self.state.borrow().page_info.controls.into()
509 }
510 /// Return the configured platform-neutral source URL.
511 /// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
512 pub fn page_core_code_url(&self) -> String {
513 self.state.borrow().page_info.core_code_url.into()
514 }
515}
516
517/// Start a browser CYD application in the shared simulator shell.
518/// See the compiled browser-shell example on [`crate::wasm::cyd_web`].
519pub fn start<Run, Error>(
520 canvas_id: &str,
521 config: Config,
522 page_info: PageInfo,
523 inner_main: Run,
524) -> Result<Handle, JsValue>
525where
526 Run: AsyncFnMut(Capabilities) -> Result<Command, Error> + 'static,
527 Error: Debug + 'static,
528{
529 let canvas = canvas(canvas_id)?;
530 let mut orientation_flash_block =
531 FlashBlockWasm::new(&format!("{}/orientation", config.storage_namespace))
532 .map_err(|error| JsValue::from_str(&format!("orientation storage: {error:?}")))?;
533 let orientation = orientation_flash_block
534 .load::<Orientation>()
535 .map_err(|error| JsValue::from_str(&format!("orientation load: {error:?}")))?
536 .unwrap_or(config.initial_orientation);
537 let simulator = CydSimulatorWasm::new_with_style(
538 canvas.clone(),
539 orientation,
540 config.background_color,
541 config.foreground_color,
542 config.font,
543 )?;
544 let (cyd, button, control) = simulator.into_parts();
545 let state = Rc::new(RefCell::new(SupervisorState {
546 live_control: Some(control),
547 notices: std::collections::VecDeque::new(),
548 orientation,
549 stopped: false,
550 page_info,
551 clock_time_of_day: Rc::new(Cell::new(None)),
552 clock_control_visible: Rc::new(Cell::new(false)),
553 }));
554 let lifecycle_signal = LifecycleSignal::new();
555 let handle = Handle::new(state.clone(), lifecycle_signal.clone());
556 wasm_bindgen_futures::spawn_local(supervise(
557 canvas,
558 config,
559 orientation_flash_block,
560 state,
561 lifecycle_signal,
562 inner_main,
563 Some((cyd, button)),
564 ));
565 Ok(handle)
566}
567
568fn canvas(canvas_id: &str) -> Result<HtmlCanvasElement, JsValue> {
569 let document = window()
570 .ok_or_else(|| JsValue::from_str("browser window unavailable"))?
571 .document()
572 .ok_or_else(|| JsValue::from_str("document unavailable"))?;
573 document
574 .get_element_by_id(canvas_id)
575 .ok_or_else(|| JsValue::from_str("canvas element unavailable"))?
576 .dyn_into::<HtmlCanvasElement>()
577 .map_err(Into::into)
578}
579
580async fn supervise<Run, Error>(
581 canvas: HtmlCanvasElement,
582 config: Config,
583 mut orientation_flash_block: FlashBlockWasm,
584 state: Rc<RefCell<SupervisorState>>,
585 lifecycle_signal: LifecycleSignal,
586 mut inner_main: Run,
587 initial_session: Option<(CydWasm, ButtonWasm)>,
588) where
589 Run: AsyncFnMut(Capabilities) -> Result<Command, Error> + 'static,
590 Error: Debug + 'static,
591{
592 let mut session = initial_session;
593 loop {
594 let (cyd, button) = match session.take() {
595 Some(session) => session,
596 None => {
597 let orientation = state.borrow().orientation;
598 match CydSimulatorWasm::new_with_style(
599 canvas.clone(),
600 orientation,
601 config.background_color,
602 config.foreground_color,
603 config.font,
604 ) {
605 Ok(simulator) => {
606 let (cyd, button, control) = simulator.into_parts();
607 state.borrow_mut().live_control = Some(control);
608 (cyd, button)
609 }
610 Err(error) => {
611 fatal(&state, format!("simulator construction failed: {error:?}"));
612 break;
613 }
614 }
615 }
616 };
617 let (clock_time_of_day, clock_control_visible) = {
618 let state_ref = state.borrow();
619 (
620 state_ref.clock_time_of_day.clone(),
621 state_ref.clock_control_visible.clone(),
622 )
623 };
624 let clock_sync =
625 ClockSyncWasm::new_with_control_state(clock_time_of_day, clock_control_visible);
626 let application = Capabilities {
627 cyd,
628 button,
629 clock_sync,
630 wifi_simulator: WifiSimulatorWasm::new(config.storage_namespace),
631 dns_simulator: DnsSimulatorWasm::standard(),
632 };
633 let command = match select(inner_main(application), lifecycle_signal.wait()).await {
634 Either::First(result) => match result {
635 Ok(command) => command,
636 Err(error) => {
637 fatal(&state, format!("application failed: {error:?}"));
638 break;
639 }
640 },
641 Either::Second(request) => {
642 match apply_host_request(request, &config, &mut orientation_flash_block, &state) {
643 Ok(()) => Command::Restart,
644 Err(error) => {
645 fatal(&state, error);
646 break;
647 }
648 }
649 }
650 };
651 release_control(&state);
652 match command {
653 Command::Stop => break,
654 Command::Restart => {}
655 Command::ResetWifi => {
656 WifiSimulatorWasm::new(config.storage_namespace).reset();
657 state
658 .borrow_mut()
659 .notices
660 .push_back(Notice::new("wifi-simulated", NoticeSeverity::Info));
661 }
662 Command::CalibrationNotNeeded => state
663 .borrow_mut()
664 .notices
665 .push_back(Notice::new("calibration-not-needed", NoticeSeverity::Info)),
666 Command::Reorientate(orientation) => {
667 if let Err(error) = orientation_flash_block.save(&orientation) {
668 fatal(&state, format!("orientation save failed: {error:?}"));
669 break;
670 }
671 state.borrow_mut().orientation = orientation;
672 }
673 }
674 }
675 release_control(&state);
676 state.borrow_mut().stopped = true;
677}
678
679fn release_control(state: &Rc<RefCell<SupervisorState>>) {
680 let control = state.borrow_mut().live_control.take();
681 if let Some(control) = control {
682 control.reset_transient_state();
683 }
684}
685fn fatal(state: &Rc<RefCell<SupervisorState>>, message: String) {
686 state.borrow_mut().notices.push_back(Notice::fatal(message));
687}
688fn apply_host_request(
689 request: HostRequest,
690 config: &Config,
691 flash: &mut FlashBlockWasm,
692 state: &Rc<RefCell<SupervisorState>>,
693) -> Result<(), String> {
694 if matches!(request, HostRequest::ClearStorage) {
695 flash
696 .clear()
697 .map_err(|error| format!("storage clear failed: {error:?}"))?;
698 state.borrow_mut().orientation = config.initial_orientation;
699 }
700 Ok(())
701}