From 70dd6afd1891ce5b51c05b668809a21ee0132328 Mon Sep 17 00:00:00 2001 From: jack perkins Date: Thu, 4 Dec 2025 16:22:05 +0100 Subject: [PATCH 1/6] Add a drop updates button per instance for testing eventual consistency --- frontend/Instance.tsx | 15 +++++++++++++-- frontend/InstanceHeader.tsx | 9 ++++++++- sim/create.ts | 1 - sim/webxdc.ts | 18 ++++++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/frontend/Instance.tsx b/frontend/Instance.tsx index b464418..7aa3eaf 100644 --- a/frontend/Instance.tsx +++ b/frontend/Instance.tsx @@ -1,4 +1,4 @@ -import { Component, Show } from "solid-js"; +import { Component, Show, createSignal } from "solid-js"; import { Flex, createDisclosure, notificationService } from "@hope-ui/solid"; import { Instance as InstanceData } from "../types/instance"; @@ -12,12 +12,13 @@ const Instance: Component<{ setSearch: (search: Search) => void; }> = (props) => { let iframeRef: HTMLIFrameElement | undefined = undefined; + let [dropUpdates, setDropUpdates] = createSignal(false); const handleReload = () => { if (iframeRef == null) { return; } - + setDropUpdates(false) // reset our state because inner sim/webxdc state is reset in reload iframeRef.contentWindow?.postMessage("reload", props.instance.url); notificationService.show({ @@ -29,6 +30,14 @@ const Instance: Component<{ defaultIsOpen: false, }); + const toggleDropUpdates = () => { + if (iframeRef == null) { + return; + } + setDropUpdates(!dropUpdates()) + iframeRef.contentWindow?.postMessage({ name: "dropUpdates", value: dropUpdates()}, props.instance.url) + } + const getStyle = () => { return { // XXX these dimensions should be configurable somehow @@ -49,6 +58,8 @@ const Instance: Component<{ onStart={onOpen} onStop={onClose} isStarted={isOpen} + dropUpdates={dropUpdates()} + onToggleDropUpdates={toggleDropUpdates} /> void; onStop: () => void; isStarted: Accessor; + dropUpdates: boolean; + onToggleDropUpdates: () => void; }> = (props) => { const sentCount = createMemo(() => { return sent(props.instance.id); @@ -113,6 +115,11 @@ const InstanceHeader: Component<{ onClick={() => handleRemoveInstance(props.instance.id)} icon={} /> + } + /> ); diff --git a/sim/create.ts b/sim/create.ts index d7a6307..6bb4e40 100644 --- a/sim/create.ts +++ b/sim/create.ts @@ -112,7 +112,6 @@ export function createWebXdc( setUpdateListener: (listener, serial = 0): Promise => { transport.onMessage((message) => { if (isUpdatesMessage(message)) { - log("recv update", message.updates); for (const update of message.updates) { listener(update); } diff --git a/sim/webxdc.ts b/sim/webxdc.ts index a8d8b02..a833f52 100644 --- a/sim/webxdc.ts +++ b/sim/webxdc.ts @@ -17,12 +17,27 @@ export class DevServerTransport implements Transport { messageListener: SocketMessageListener | null = null; promise: Promise; resolveInfo!: (info: Info) => void; + dropUpdates: boolean = false; constructor(url: string) { this.socket = new WebSocket(url); this.promise = new Promise((resolve) => { this.resolveInfo = resolve; }); + window.addEventListener("message", (event) => { + const isAllowed = + event.origin.includes("localhost:") || + (location.host.endsWith(".webcontainer.io") && + event.origin.includes(".webcontainer.io")); + if (!isAllowed) { + return; + } + if (typeof event.data === 'object') { + if (event.data.name === 'dropUpdates') { + this.dropUpdates = event.data.value + } + } + }); } send(data: any): void { @@ -34,6 +49,9 @@ export class DevServerTransport implements Transport { this.socket.removeEventListener("message", this.messageListener); } const listener = (event: Event): void => { + if (this.dropUpdates) { + return + } callback(JSON.parse((event as any).data)); }; this.messageListener = listener; From 174fd9eeafd08d42519798601a928957325d778f Mon Sep 17 00:00:00 2001 From: jack perkins Date: Thu, 4 Dec 2025 17:28:18 +0100 Subject: [PATCH 2/6] swap dropUpdates envelop icon for Wifi/offline icons --- frontend/InstanceHeader.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/InstanceHeader.tsx b/frontend/InstanceHeader.tsx index af9f170..adbd8eb 100644 --- a/frontend/InstanceHeader.tsx +++ b/frontend/InstanceHeader.tsx @@ -8,7 +8,8 @@ import { notificationService, } from "@hope-ui/solid"; import { IoRefreshOutline, IoStop, IoPlay } from "solid-icons/io"; -import { FiExternalLink, FiMail, FiTrash } from "solid-icons/fi"; +import { FiExternalLink, FiTrash } from "solid-icons/fi"; +import { RiDeviceWifiLine, RiDeviceWifiOffLine } from 'solid-icons/ri' import type { Instance as InstanceData } from "../types/instance"; import { sent, received, mutateInstances } from "./store"; @@ -118,7 +119,7 @@ const InstanceHeader: Component<{ } + icon={props.dropUpdates ? : } /> From 126d356c81f14fb5ce1cd231eb04d9672f94429f Mon Sep 17 00:00:00 2001 From: jack perkins Date: Thu, 11 Dec 2025 16:49:41 +0100 Subject: [PATCH 3/6] add mention of drop Updates feature to readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 285f832..6fa0adf 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,10 @@ are running. The dev tool console shows messages with the same color prefix as the instance UIs. +You can simulate the host losing messages by toggling the "Drop Updates" button +on a specific instance. While enabled the instance will not recieve any updates +as if the transport has failed to deliver them. + #### Clean state Instances start with a clean slate: empty `localStorage` and `sessionStorage`. From 51351c625b5a3b977fdabed3f82e26982e648d06 Mon Sep 17 00:00:00 2001 From: jack perkins Date: Fri, 12 Dec 2025 21:15:06 +0100 Subject: [PATCH 4/6] explain quirk around drop updates feature and replaying of messages --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6fa0adf..839cb3a 100644 --- a/README.md +++ b/README.md @@ -97,9 +97,11 @@ are running. The dev tool console shows messages with the same color prefix as the instance UIs. -You can simulate the host losing messages by toggling the "Drop Updates" button +You can simulate the host losing messages by toggling the `Drop Updates` button on a specific instance. While enabled the instance will not recieve any updates -as if the transport has failed to deliver them. +as if the transport has failed to deliver them. On +later reloads of the page the instance will load all updates as this state is +shared identically between all insances. #### Clean state From 415d6ebfe96aac37b497c4143515e7d669ae09b8 Mon Sep 17 00:00:00 2001 From: jack perkins Date: Thu, 8 Jan 2026 11:11:05 +0100 Subject: [PATCH 5/6] improve readme for drop updates feature, deduplicate security check code --- README.md | 8 ++++---- sim/webxdc.ts | 22 ++++++++++------------ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 839cb3a..af242bc 100644 --- a/README.md +++ b/README.md @@ -98,10 +98,10 @@ The dev tool console shows messages with the same color prefix as the instance UIs. You can simulate the host losing messages by toggling the `Drop Updates` button -on a specific instance. While enabled the instance will not recieve any updates -as if the transport has failed to deliver them. On -later reloads of the page the instance will load all updates as this state is -shared identically between all insances. +on a specific instance. While enabled the instance will not receive any updates +as if the transport has failed to deliver them. Note: on later reloads of the +page the instance will load all updates again because the state is shared between +all instances in the webxdc-dev backend. #### Clean state diff --git a/sim/webxdc.ts b/sim/webxdc.ts index a833f52..c24d8b9 100644 --- a/sim/webxdc.ts +++ b/sim/webxdc.ts @@ -25,12 +25,8 @@ export class DevServerTransport implements Transport { this.resolveInfo = resolve; }); window.addEventListener("message", (event) => { - const isAllowed = - event.origin.includes("localhost:") || - (location.host.endsWith(".webcontainer.io") && - event.origin.includes(".webcontainer.io")); - if (!isAllowed) { - return; + if (!eventIsAllowed(event)) { + return } if (typeof event.data === 'object') { if (event.data.name === 'dropUpdates') { @@ -152,14 +148,16 @@ window.addEventListener("load", () => alterUi(getWebXdc().selfName, transport)); // listen to messages coming into iframe window.addEventListener("message", (event) => { - const isAllowed = - event.origin.includes("localhost:") || - (location.host.endsWith(".webcontainer.io") && - event.origin.includes(".webcontainer.io")); - if (!isAllowed) { - return; + if (!eventIsAllowed(event)) { + return } if (event.data === "reload") { window.location.reload(); } }); + +function eventIsAllowed(event: MessageEvent) { + return event.origin.includes("localhost:") || + (location.host.endsWith(".webcontainer.io") && + event.origin.includes(".webcontainer.io")); +} From 63e2fcea4325bce0a79edf811af2c1b930d0575c Mon Sep 17 00:00:00 2001 From: jack perkins Date: Thu, 8 Jan 2026 11:12:22 +0100 Subject: [PATCH 6/6] prettier cleanup webxdc.ts --- sim/webxdc.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sim/webxdc.ts b/sim/webxdc.ts index c24d8b9..9dffb35 100644 --- a/sim/webxdc.ts +++ b/sim/webxdc.ts @@ -26,11 +26,11 @@ export class DevServerTransport implements Transport { }); window.addEventListener("message", (event) => { if (!eventIsAllowed(event)) { - return + return; } - if (typeof event.data === 'object') { - if (event.data.name === 'dropUpdates') { - this.dropUpdates = event.data.value + if (typeof event.data === "object") { + if (event.data.name === "dropUpdates") { + this.dropUpdates = event.data.value; } } }); @@ -46,7 +46,7 @@ export class DevServerTransport implements Transport { } const listener = (event: Event): void => { if (this.dropUpdates) { - return + return; } callback(JSON.parse((event as any).data)); }; @@ -149,7 +149,7 @@ window.addEventListener("load", () => alterUi(getWebXdc().selfName, transport)); // listen to messages coming into iframe window.addEventListener("message", (event) => { if (!eventIsAllowed(event)) { - return + return; } if (event.data === "reload") { window.location.reload(); @@ -157,7 +157,9 @@ window.addEventListener("message", (event) => { }); function eventIsAllowed(event: MessageEvent) { - return event.origin.includes("localhost:") || + return ( + event.origin.includes("localhost:") || (location.host.endsWith(".webcontainer.io") && - event.origin.includes(".webcontainer.io")); + event.origin.includes(".webcontainer.io")) + ); }