diff --git a/README.md b/README.md index 285f832..af242bc 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,12 @@ 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 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 Instances start with a clean slate: empty `localStorage` and `sessionStorage`. 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 +116,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..9dffb35 100644 --- a/sim/webxdc.ts +++ b/sim/webxdc.ts @@ -17,12 +17,23 @@ 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) => { + if (!eventIsAllowed(event)) { + return; + } + if (typeof event.data === "object") { + if (event.data.name === "dropUpdates") { + this.dropUpdates = event.data.value; + } + } + }); } send(data: any): void { @@ -34,6 +45,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; @@ -134,14 +148,18 @@ 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) { + 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")) + ); +}