Skip to content

Commit

Permalink
fix: guard against inspector code crashing screens
Browse files Browse the repository at this point in the history
On real Bus E-ink hardware we saw a mysterious "DOM exception 12" crash
that seemed to be caused by the inspector changes. Though the message
sending and listening should have already been a no-op when the screen
is not "framed", this adds a check for whether the screen is "framed"
by the inspector specifically, and otherwise doesn't even attempt to
send/listen. This should sidestep any issues with e.g. a screen vendor
rendering our pages inside a frame.
  • Loading branch information
digitalcora committed Aug 27, 2024
1 parent 6cab1ef commit 94b11e9
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 15 deletions.
8 changes: 7 additions & 1 deletion assets/src/components/admin/inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import { useHistory, useLocation } from "react-router-dom";
import AdminForm from "./admin_form";

import { doSubmit, type Config, type Screen } from "Util/admin";
import { type Message, sendMessage, useReceiveMessage } from "Util/inspector";
import {
type Message,
INSPECTOR_FRAME_NAME,
sendMessage,
useReceiveMessage,
} from "Util/inspector";

type ScreenWithId = { id: string; config: Screen };

Expand Down Expand Up @@ -90,6 +95,7 @@ const Inspector: ComponentType = () => {

<div className="viewer__screen">
<iframe
name={INSPECTOR_FRAME_NAME}
onLoad={adjustFrame}
ref={frameRef}
src={
Expand Down
9 changes: 3 additions & 6 deletions assets/src/hooks/v2/use_api_response.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { WidgetData } from "Components/v2/widget";
import useDriftlessInterval from "Hooks/use_driftless_interval";
import React, { useEffect, useMemo, useState } from "react";
import { getDatasetValue } from "Util/dataset";
import { sendMessage, useReceiveMessage } from "Util/inspector";
import { sendToInspector, useReceiveFromInspector } from "Util/inspector";
import { isDup, isOFM, isTriptych, getTriptychPane } from "Util/outfront";
import { getScreenSide, isRealScreen } from "Util/util";
import * as SentryLogger from "Util/sentry";
Expand Down Expand Up @@ -244,16 +244,13 @@ const useInspectorControls = (
fetchData: () => void,
lastSuccess: number | null,
): void => {
useReceiveMessage((message) => {
useReceiveFromInspector((message) => {
if (message.type == "refresh_data") fetchData();
});

useEffect(() => {
if (lastSuccess) {
sendMessage(window.parent, {
type: "data_refreshed",
timestamp: lastSuccess,
});
sendToInspector({ type: "data_refreshed", timestamp: lastSuccess });
}
}, [lastSuccess]);
};
Expand Down
39 changes: 31 additions & 8 deletions assets/src/util/inspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,46 @@ import { useEffect } from "react";
* iframe'd screens. Limit usage of these functions to this specific purpose;
* components _within_ a screen have many better options (props, contexts, etc.)
* for communicating with each other.
*
* In screen code, use ONLY the `toInspector`/`fromInspector` functions. These
* safely do nothing if the screen is not running within the inspector.
*/

export type Message =
| { type: "data_refreshed"; timestamp: number }
| { type: "refresh_data" }
| { type: "set_refresh_rate"; ms: number | null };

export const INSPECTOR_FRAME_NAME = "screen-inspector-frame";

const isFramed = (): boolean => window.name == INSPECTOR_FRAME_NAME;

const sendMessage = (window: Window, message: Message) =>
window.postMessage(message, { targetOrigin: location.origin });

const useReceiveMessage = (handler: (message: Message) => void) =>
useEffect(() => {
const listener = ({ data, origin }) =>
origin == location.origin && handler(data);
const sendToInspector = (message: Message) => {
if (isFramed()) sendMessage(window.parent, message);
};

type MessageHandler = (message: Message) => void;

const useReceiveMessage = (handler: MessageHandler) =>
useEffect(() => receiveHook(handler));

const useReceiveFromInspector = (handler: MessageHandler) =>
useEffect(() => (isFramed() ? receiveHook(handler) : undefined));

const receiveHook = (handler: MessageHandler) => {
const listener = ({ data, origin }) =>
origin == location.origin && handler(data);

window.addEventListener("message", listener);
return () => window.removeEventListener("message", listener);
});
window.addEventListener("message", listener);
return () => window.removeEventListener("message", listener);
};

export { sendMessage, useReceiveMessage };
export {
sendMessage,
sendToInspector,
useReceiveMessage,
useReceiveFromInspector,
};

0 comments on commit 94b11e9

Please sign in to comment.