diff --git a/apps/mocksi-lite-next/src/pages/background/index.ts b/apps/mocksi-lite-next/src/pages/background/index.ts index da3affe..0f955d0 100644 --- a/apps/mocksi-lite-next/src/pages/background/index.ts +++ b/apps/mocksi-lite-next/src/pages/background/index.ts @@ -1,4 +1,9 @@ -import { AppEvents, AuthEvents, LayoutEvents } from "@pages/events"; +import { + AppEvents, + AuthEvents, + DemoEditEvents, + LayoutEvents, +} from "@pages/events"; import { jwtDecode } from "jwt-decode"; console.log("background script loaded"); @@ -8,6 +13,26 @@ const MOCKSI_AUTH = "mocksi-auth"; let fallbackTab: null | chrome.tabs.Tab = null; let prevLayoutEvent = ""; +let mainIframeSrcPort: null | chrome.runtime.Port = null; +let topIframeSrcPort: null | chrome.runtime.Port = null; + +addEventListener("install", () => { + // TODO test if this works on other browsers + chrome.tabs.create({ + url: import.meta.env.VITE_NEST_APP, + }); +}); + +chrome.runtime.onConnectExternal.addListener((port) => { + console.log("connecting...", port); + if (port.name === "extension/main") { + mainIframeSrcPort = port; + } + if (port.name === "extension/top") { + topIframeSrcPort = port; + } +}); + const getAuth = async (): Promise { - // TODO test if this works on other browsers - chrome.tabs.create({ - url: import.meta.env.VITE_NEST_APP, - }); -}); - // when user clicks toolbar mount extension chrome.action.onClicked.addListener((tab) => { if (!tab?.id) { console.log("No tab exits click, could not mount extension"); - return; + return true; } // store the tab they clicked on to open the extension // so we can use it as a fallback @@ -129,19 +147,9 @@ chrome.action.onClicked.addListener((tab) => { }); prevLayoutEvent = LayoutEvents.HIDE; } + return true; }); -chrome.runtime.onMessage.addListener( - (request, _sender, sendResponse): boolean => { - sendResponse({ - data: request.data, - message: request.message, - status: "ok", - }); - return true; - }, -); - chrome.runtime.onMessageExternal.addListener( (request, _sender, sendResponse) => { console.log("on message external: ", request); @@ -149,10 +157,26 @@ chrome.runtime.onMessageExternal.addListener( // execute in async block so that we return true // synchronously, telling chrome to wait for the response (async () => { + if ( + request.source === "extension/top" && + request.message === AppEvents.EDIT_DEMO_STOP + ) { + if (mainIframeSrcPort) { + // notify extension/main that demo edit mode exited in extension/top + mainIframeSrcPort.postMessage({ + ...request, + message: AppEvents.EDIT_DEMO_STOP, + }); + } else { + console.log("mainIframeSrcPort is not connected"); + } + } + if (request.message === AuthEvents.AUTH_ERROR) { await clearAuth(); sendResponse({ message: AuthEvents.RETRY, + source: "background", status: "ok", }); } else if (request.message === AuthEvents.UNAUTHORIZED) { @@ -162,23 +186,22 @@ chrome.runtime.onMessageExternal.addListener( const tab = await getCurrentTab(); sendResponse({ message: { accessToken, email, url: tab?.url }, + source: "background", status: "ok", }); } else { await showAuthTab(true); sendResponse({ message: AuthEvents.AUTHENTICATING, + source: "background", status: "ok", }); } } else { const tab = await getCurrentTab(); if (!tab?.id) { - sendResponse({ - message: LayoutEvents.NO_TAB, - status: "ok", - }); - return; + console.error("No tab found: "); + return true; } if ( @@ -201,19 +224,41 @@ chrome.runtime.onMessageExternal.addListener( showDefaultIcon(tab.id); } - chrome.tabs.sendMessage( - tab.id, - { - data: request.data, - message: request.message, - }, - (response) => { - sendResponse(response); - }, - ); + // send message to iframes and reactor in mocksi-extension + chrome.tabs.sendMessage(tab.id, request, async (response) => { + console.log("response from content script in background:", response); + if (response.message === DemoEditEvents.UNDO) { + // pass updated modifications from reactor to extension/main to store + if (mainIframeSrcPort) { + await mainIframeSrcPort.postMessage({ + ...response, + status: "ok", // response handler expects status + }); + } else { + console.log("mainIframeSrcPort is not connected"); + } + } + if ( + response.message === AppEvents.EDIT_DEMO_START || + response.message === DemoEditEvents.CHAT_RESPONSE || + response.message === DemoEditEvents.NEW_EDIT + ) { + // notify extension/top # of edits changed + if (topIframeSrcPort) { + await topIframeSrcPort.postMessage({ + ...response, + status: "ok", + }); + } else { + console.log("topIframeSrcPort is not connected"); + } + } + + sendResponse(response); + return true; + }); } })(); - return true; }, ); diff --git a/apps/mocksi-lite-next/src/pages/content/main-iframe.tsx b/apps/mocksi-lite-next/src/pages/content/main-iframe.tsx new file mode 100644 index 0000000..f3c0b7f --- /dev/null +++ b/apps/mocksi-lite-next/src/pages/content/main-iframe.tsx @@ -0,0 +1,123 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import { LayoutEvents } from "../events"; + +export enum IframePosition { + BOTTOM_CENTER = "BOTTOM_CENTER", + TOP_RIGHT = "TOP_RIGHT", + BOTTOM_RIGHT = "BOTTOM_RIGHT", +} + +export interface IframeResizeArgs { + height: number; + id?: string; + position: + | IframePosition.BOTTOM_CENTER + | IframePosition.BOTTOM_RIGHT + | IframePosition.TOP_RIGHT; + width: number; +} + +function getIframeStyles({ height, position, width }: IframeResizeArgs) { + if (!height || !width || !position) { + console.error( + "Cannot update iframe size / position, make sure 'request.data.iframe' has 'height', 'width', and 'position' set correctly", + ); + return; + } + + const bounds = document.body.getBoundingClientRect(); + + let styles = {}; + switch (position) { + case IframePosition.BOTTOM_CENTER: + styles = { + bottom: "0px", + right: `${bounds.width / 2 - width / 2}px`, + top: "auto", + }; + break; + case IframePosition.BOTTOM_RIGHT: + styles = { + bottom: "10px", + right: "10px", + top: "auto", + }; + break; + case IframePosition.TOP_RIGHT: + styles = { + bottom: "auto", + display: "block", + right: "10px", + top: "10px", + }; + break; + } + + return Object.assign( + { + bottom: "auto", + display: "block", + height: `${height}px`, + left: "auto", + right: "auto", + top: "auto", + width: `${width}px`, + }, + styles, + ); +} + +function MainIframe() { + const iframeRef = React.useRef(null); + + React.useEffect(() => { + chrome.runtime.onMessage.addListener((request) => { + if (iframeRef.current) { + switch (request.message) { + case LayoutEvents.HIDE: + iframeRef.current.style.display = "none"; + break; + case LayoutEvents.RESIZE: + const styles = getIframeStyles(request.data.iframe); + Object.assign(iframeRef.current.style, styles); + break; + case LayoutEvents.SHOW: + iframeRef.current.style.display = "block"; + break; + } + } + return true; + }); + }, []); + + return ( + <> + {ReactDOM.createPortal( +