From 31758ac81003e98a3bd8bfe133a5d372143cce8f Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 30 Oct 2024 19:42:33 -0400 Subject: [PATCH 01/10] Add fix for input not getting focused --- src/core/content/dodao-content.js | 39 +++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/core/content/dodao-content.js b/src/core/content/dodao-content.js index 221c864..42ecd9c 100644 --- a/src/core/content/dodao-content.js +++ b/src/core/content/dodao-content.js @@ -3,13 +3,29 @@ import { DODAO_API_BASE_URL, slugify, } from "../common/dodao-utils.js"; export function init() {} + +const SPACE_ID_INPUT_ID = "space-id-input"; +const API_KEY_INPUT_ID = "api-key-input"; +const COLLECTION_NAME_INPUT_ID = "collection-name-input"; +const COLLECTION_DESCRIPTION_INPUT_ID = "collection-description-input"; +const DEMO_NAME_INPUT_ID = "demo-name-input"; +const DEMO_DESCRIPTION_INPUT_ID = "demo-description-input"; +const FILE_NAME_INPUT_ID = "file-name-input"; + +document.addEventListener('focusin', event => { + event.stopPropagation(); + event.preventDefault(); +}, true); + + browser.runtime.onMessage.addListener(async (message) => { + if (message.method === "dodaoContent.captureApiKey") { showSaveApiKeyAndSpaceIdScreen(message); return {}; } if (message.method === "dodaoContent.selectClickableDemo") { - showsaveClickableDemoScreen(message); + showSaveClickableDemoScreen(message); return {}; } if (message.method === "dodaoContent.renderBottomBar") { @@ -56,7 +72,7 @@ function showSaveApiKeyAndSpaceIdScreen(message) { } } -async function showsaveClickableDemoScreen(message) { +async function showSaveClickableDemoScreen(message) { if (message.data.error) { showErrorNotification(message.data.error); setTimeout(async () => { @@ -82,6 +98,7 @@ function showLoginScreen(message) { { className: "space-id-input", placeholder: "Enter your Space ID", + id: SPACE_ID_INPUT_ID, styles: { width: "90%", padding: "10px", @@ -90,6 +107,7 @@ function showLoginScreen(message) { }, { className: "api-key-input", + id: API_KEY_INPUT_ID, placeholder: "Enter your API key from the space settings page:", styles: { width: "90%", @@ -273,6 +291,9 @@ function getDemosFromCollection(collection) { .map((item) => item.demo); } + + + function showCreateCollectionScreen( spaceId, apiKey, @@ -283,6 +304,7 @@ function showCreateCollectionScreen( { className: "collection-name-input", placeholder: "Enter collection name", + id: COLLECTION_NAME_INPUT_ID, styles: { width: "90%", padding: "10px", @@ -292,6 +314,7 @@ function showCreateCollectionScreen( { className: "collection-description-input", placeholder: "Enter collection description", + id: COLLECTION_DESCRIPTION_INPUT_ID, styles: { width: "90%", padding: "10px", @@ -597,6 +620,7 @@ function showCreateDemoScreen( { className: "demo-name-input", placeholder: "Enter demo name", + id: DEMO_NAME_INPUT_ID, styles: { width: "90%", padding: "10px", @@ -606,6 +630,7 @@ function showCreateDemoScreen( { className: "demo-description-input", placeholder: "Enter demo description", + id: DEMO_DESCRIPTION_INPUT_ID, styles: { width: "90%", padding: "10px", @@ -780,6 +805,7 @@ async function captureScreenHtml(spaceId, apiKey, demo, collection) { { className: "file-name-input", placeholder: "Enter file name", + id: FILE_NAME_INPUT_ID, styles: { width: "90%", marginBottom: "10px", @@ -870,6 +896,7 @@ function createModalForm({ inputConfig.className || "", inputConfig.styles || {} ); + inputElement.id = inputConfig.id; formContainer.appendChild(inputElement); // Store the input element reference inside the inputConfig object inputConfig.element = inputElement; @@ -940,6 +967,12 @@ function createNewModalElement( } const fullScreenModalWrapper = document.createElement("div"); fullScreenModalWrapper.id = "dodao-full-screen-modal-wrapper"; + + fullScreenModalWrapper.style.cssText = `position: fixed; top: 0; left: 0; right: 0; bottom: 0; font-size: 24px; z-index: 2147483640;`; + + // Append styles to head to affect the entire document + document.head.appendChild(createModalStyle()); + document.body.appendChild(fullScreenModalWrapper); const shadowRoot = fullScreenModalWrapper.attachShadow({ mode: "open" }); shadowRoot.appendChild(createModalStyle()); @@ -988,6 +1021,8 @@ function createModalStyle() { position: fixed; top: 0; left: 0; + bottom: 0; + right: 0; font-size: 24px; flex-direction: column; width: 100%; From 8ea290b2770e5a8bdf752f2bce8aea740e234bfc Mon Sep 17 00:00:00 2001 From: Muhammad Sami Tariq Date: Thu, 31 Oct 2024 16:40:55 +0500 Subject: [PATCH 02/10] added focusin event on modal creation and removed when modal removed --- src/core/content/dodao-content.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/content/dodao-content.js b/src/core/content/dodao-content.js index 42ecd9c..2137551 100644 --- a/src/core/content/dodao-content.js +++ b/src/core/content/dodao-content.js @@ -12,10 +12,10 @@ const DEMO_NAME_INPUT_ID = "demo-name-input"; const DEMO_DESCRIPTION_INPUT_ID = "demo-description-input"; const FILE_NAME_INPUT_ID = "file-name-input"; -document.addEventListener('focusin', event => { +function focusInHandler(event) { event.stopPropagation(); event.preventDefault(); -}, true); +} browser.runtime.onMessage.addListener(async (message) => { @@ -875,6 +875,7 @@ function createModalForm({ cancelButtonClass = "cancel-button", cancelButtonHandler, }) { + document.addEventListener('focusin', focusInHandler, true); // Create the modal element and container for the form const modalElement = createNewModalElement(title); const formContainer = modalElement; @@ -1220,6 +1221,7 @@ function removeModalElement() { if (modalWrapper) { modalWrapper.remove(); } + document.removeEventListener('focusin', focusInHandler, true); } function displayErrorModal(message, retryHandler) { From 18bdc872f284550dc6795cc89a19c12de195eeb9 Mon Sep 17 00:00:00 2001 From: Muhammad Sami Tariq Date: Thu, 31 Oct 2024 17:22:15 +0500 Subject: [PATCH 03/10] Made buttons smaller with a cleaner UI --- src/core/content/dodao-content.js | 53 ++++++++++++++++--------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/core/content/dodao-content.js b/src/core/content/dodao-content.js index 2137551..cfd85bb 100644 --- a/src/core/content/dodao-content.js +++ b/src/core/content/dodao-content.js @@ -1,7 +1,5 @@ import { v4 as uuidv4 } from "uuid"; -import { - DODAO_API_BASE_URL, slugify, -} from "../common/dodao-utils.js"; +import { DODAO_API_BASE_URL, slugify } from "../common/dodao-utils.js"; export function init() {} const SPACE_ID_INPUT_ID = "space-id-input"; @@ -17,9 +15,7 @@ function focusInHandler(event) { event.preventDefault(); } - browser.runtime.onMessage.addListener(async (message) => { - if (message.method === "dodaoContent.captureApiKey") { showSaveApiKeyAndSpaceIdScreen(message); return {}; @@ -235,7 +231,11 @@ function showCollectionList( collections.length > 0 ? "Select a Collection from the List Below:" : "No collections found. Create a new collection.", - { marginBottom: "10px", color: "#FFF" } + { + marginBottom: "10px", + color: "#FFF", + fontSize: "20px" + } ); const collectionList = document.createElement("div"); @@ -260,6 +260,7 @@ function showCollectionList( marginTop: "20px", marginBottom: "10px", color: "#FFF", + fontSize: "20px", } ); const createCollectionButton = createButton( @@ -291,9 +292,6 @@ function getDemosFromCollection(collection) { .map((item) => item.demo); } - - - function showCreateCollectionScreen( spaceId, apiKey, @@ -451,7 +449,7 @@ function showDemoList( demos.length > 0 ? "Select a Demo from the List Below:" : "No demos found. Create a new demo.", - { marginBottom: "10px", color: "#FFF" } + { marginBottom: "10px", color: "#FFF", fontSize: "20px" } ); const demoList = document.createElement("div"); @@ -471,6 +469,7 @@ function showDemoList( marginTop: "20px", marginBottom: "10px", color: "#FFF", + fontSize: "20px", }); const createDemoButton = createButton( @@ -564,9 +563,9 @@ function setupBottomBarWithDemo( removeModalElement(); showLoginScreen(); }); - logoutButton.style.marginLeft = "10px"; - logoutButton.style.marginRight = "10px"; - logoutButton.style.width = "10%"; + logoutButton.style.marginLeft = "5px"; + logoutButton.style.marginRight = "5px"; + logoutButton.style.width = "8%"; const demoTitle = document.createElement("span"); demoTitle.id = selectedClickableDemo.id; @@ -875,7 +874,7 @@ function createModalForm({ cancelButtonClass = "cancel-button", cancelButtonHandler, }) { - document.addEventListener('focusin', focusInHandler, true); + document.addEventListener("focusin", focusInHandler, true); // Create the modal element and container for the form const modalElement = createNewModalElement(title); const formContainer = modalElement; @@ -1080,8 +1079,8 @@ function createModalStyle() { transition: background-color 0.3s ease; } .modal-content { - width: 90%; - max-width:800px; + width: 80%; + max-width:700px; overflow:hidden; display: flex; flex-direction: column; @@ -1101,9 +1100,9 @@ function createModalStyle() { margin-bottom: 10px; } input, button { - padding: 14px 22px; - margin-top: 12px; - font-size: 24px; + padding: 7px 11px; + margin-top: 6px; + font-size: 16px; width: 100%; box-sizing: border-box; } @@ -1123,7 +1122,11 @@ function createModalStyle() { border: none; border-radius: 4px; cursor: pointer; + padding: 10px 20px; + font-size: 16px; + max-width: 100%; transition: background-color 0.3s, box-shadow 0.3s; + white-space: nowrap; } button:hover, button:focus { background-color: #0056b3; @@ -1155,14 +1158,15 @@ function createBottomBarStyle() { padding: 0 20px; } #bottom-bar .demo-name { + padding-left: 20px; flex-grow: 1; - font-size:24px; + font-size: 18px; font-weight: bold; - line-height: 70px; + line-height: 50px; } #bottom-bar button { - padding: 10px 20px; - font-size: 24px; + padding: 6px 12px; + font-size: 16px; border: none; background-color: #007bff; color: #fff; @@ -1221,7 +1225,7 @@ function removeModalElement() { if (modalWrapper) { modalWrapper.remove(); } - document.removeEventListener('focusin', focusInHandler, true); + document.removeEventListener("focusin", focusInHandler, true); } function displayErrorModal(message, retryHandler) { @@ -1247,7 +1251,6 @@ function createMessageElement(text, styles = {}) { return messageElement; } - function createNewEntityId(entityName, spaceId) { const firstSegment = spaceId.split("-")[0]; const normalizedSpaceId = firstSegment.substring( From 400315fc79d19cf22cba4aa0aecb16a539b5f6cb Mon Sep 17 00:00:00 2001 From: robin Date: Thu, 31 Oct 2024 09:03:32 -0400 Subject: [PATCH 04/10] Add README.MD --- README.MD | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/README.MD b/README.MD index 08f3688..1fd1b11 100644 --- a/README.MD +++ b/README.MD @@ -15,3 +15,94 @@ these instructions: https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked - Microsoft Edge: https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/getting-started/extension-sideloading + + +# How does the extension work. + +Extensions have two types of scripts: background scripts and content scripts. +- Background scripts run in the background of the browser and can be used to + listen to events and perform actions that are common to all tabs. +- Content scripts run in the context of a tab and can be used to interact with + the DOM of the page. + +Here in our extension, we store the spaceId, apiKey, selectedCollection, and selectedDemo. Storing and retrieving these +values is done using the chrome.storage API. The background script does the storing and retrieving of these values. + + +Messaging between the background script and the content script is done using the chrome.runtime API. + +# dodao-upload.js and dodao-content.js +All the content on the DOM is created in dodao-content.js. This is the content script. When any action is done on the +UI, we listen to the action, and pass a message to the background script. + +For example + +# Loading of the extension +When the extension icon is clicked, the background checks if the spaceId and apiKey exists, if it exists, it send a message +to the content script(dodao-content.js), to draw the bottom bar. + +If the spaceId and apiKey does not exist, it sends a message to the content script to draw the modal to capture the spaceId +and apiKey. + +The UI captures it and then sends a message to the background script to store the spaceId and apiKey. + + +# When we enter file name of capture +when user enters the "Html capture" file name, we call the background script and pass the filename. + +Background script has the logic to see what needs to be done, and what UI needs to be shown next. In this case, the +background will initiate download of the file by calling `business.saveTabs` method and passing dodao specific config. + + +# Showing of extension +The extension is shown when the user clicks on the extension icon. The background script listens to the click event. Its +present in `ui-button.js` + +```javascript +browser.action.onClicked.addListener(async tab => { + await business.extensionIconClicked(tab); +}); +``` + +Earlier(base code of extension), used to call saveTab, which used to download the file. Now we show the bottom bar +or the spaceId and apiKey modal. `business.extensionIconClicked(tab)` this function was written by us. + +# Important files +- `dodao-upload.js` - This is the background script. This is the script that listens to the messages from the content script + and does the necessary actions. This has the logic to decide what should be done next, i.e. flow of the extension. +- `dodao-content.js` - This is the content script. This is the script that interacts with the DOM of the page. This is the + script that creates the UI on the page. i.e. the bottom bar or the modals, or the success and error messages. +- `background.js` - This has the logic for post handler of download of the file. This in our flow is called from `content.js` (existing logic of the extension) + ```javascript + const pageData = await processPage(options); + if (pageData) { + await download.downloadPage(pageData, options); + } + ``` + We just modify a couple of lines in download.js. This file then does the post handling of the download and send a message "downloads.download" + + Here is some of the custom logic + ```javascript + if (options.saveWithTidbitsHub) { + // we capture screenshot using options.content as it contains whole page content for the screesnhot + message.dodaoScreenshotBlobUrl = await getDodaoScreenshotBlobUrl(options.content); + } + ``` +- `download.js` - This is the file that handles the download of the file. This is the file that listens to the message "downloads.download" + We added some custom code to this file which looks like + ```javascript + if (message.saveWithTidbitsHub) { + // adding the scripts to the content + message.pageData.content=injectScriptLinkTags(message.pageData.content) + } + ``` + This is the code that injects the scripts to the content. Like the dodao tooltip, selector, css, and tippy scripts + + ```javascript + else if (message.saveWithTidbitsHub) { + const blob = await (await fetch(blobURI)).blob(); + const screenshotBlob = new Blob([await (await fetch(message.dodaoScreenshotBlobUrl)).blob()], { type: "image/png" }); + await uploadFileToDodao(message.captureHtmlScreenFileName, blob, screenshotBlob); + } + ``` + This code send the message to dodao api to upload the html capture. From 11d8717908e033762b3cbaeff0ba426d6dd2cc5f Mon Sep 17 00:00:00 2001 From: Muhammad Sami Tariq Date: Thu, 31 Oct 2024 18:31:24 +0500 Subject: [PATCH 05/10] api endpoint changes --- src/core/content/dodao-content.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/content/dodao-content.js b/src/core/content/dodao-content.js index cfd85bb..a631dc5 100644 --- a/src/core/content/dodao-content.js +++ b/src/core/content/dodao-content.js @@ -698,7 +698,7 @@ async function createDemo( try { const response = await fetch( - `${DODAO_API_BASE_URL}/api/${spaceId}/clickable-demos/${demoId}`, + `${DODAO_API_BASE_URL}/api/${spaceId}/clickable-demos?clickableDemoId=${demoId}`, { method: "POST", headers: { @@ -761,7 +761,7 @@ function addLogoutButton() { async function captureScreenHtml(spaceId, apiKey, demo, collection) { const demoId = demo.demoId; - const apiUrl = `${DODAO_API_BASE_URL}/api/${spaceId}/html-captures/${demoId}`; + const apiUrl = `${DODAO_API_BASE_URL}/api/${spaceId}/html-captures?clickableDemoId=${demoId}`; let existingFiles = []; try { const response = await fetch(apiUrl, { From 4753c74081e22b5a2be46385fbc977d8a8c225dc Mon Sep 17 00:00:00 2001 From: Muhammad Sami Tariq <68412933+MSamiTariq@users.noreply.github.com> Date: Thu, 31 Oct 2024 23:24:52 +0500 Subject: [PATCH 06/10] Use built in feature to capture screenshot (#17) * Use built in feature to capture screenshot * addressed feedback --- src/core/common/dodao-screenshot.js | 43 ++++++++++------------------- src/core/common/download.js | 10 ++----- src/core/content/content.js | 2 +- 3 files changed, 18 insertions(+), 37 deletions(-) diff --git a/src/core/common/dodao-screenshot.js b/src/core/common/dodao-screenshot.js index 428fd4c..1c38a7a 100644 --- a/src/core/common/dodao-screenshot.js +++ b/src/core/common/dodao-screenshot.js @@ -1,32 +1,19 @@ -import html2canvas from "html2canvas"; - -export async function getDodaoScreenshotBlobUrl(content) { - const iframe = createIframeWithContent(content); - document.body.appendChild(iframe); - - const dodaoScreenshotBlobUrl = await new Promise((resolve, reject) => { - iframe.onload = async () => { - const iframeDocument = iframe.contentDocument; - const canvas = await html2canvas(iframeDocument.body, { - width: 1920, - height: 1080, - windowWidth: 1920, - windowHeight: 1080, - useCORS: true, - allowTaint: true, - }); - - const canvasBlob = await canvasToBlob(canvas); - - const dodaoScreenshotBlobUrl = URL.createObjectURL(canvasBlob); - // remove the iframe after screesnhot is taken - document.body.removeChild(iframe); - - resolve(dodaoScreenshotBlobUrl); - }; +import * as ui from "./../../ui/content/content-ui.js"; + +export async function getDodaoScreenshotBlobUrl(options) { + ui.setVisible(false); + const screenshotBlobURI = await browser.runtime.sendMessage({ + method: "tabs.getScreenshot", + width: document.documentElement.scrollWidth, + height: document.documentElement.scrollHeight, + innerHeight: globalThis.innerHeight, }); - - return dodaoScreenshotBlobUrl; + ui.setVisible(true); + const embeddedImage = new Uint8Array( + await (await fetch(screenshotBlobURI)).arrayBuffer() + ); + const blob = new Blob([embeddedImage], { type: "image/png" }); + return URL.createObjectURL(blob); } function createIframeWithContent(htmlContent) { diff --git a/src/core/common/download.js b/src/core/common/download.js index c1709c1..e7534ff 100644 --- a/src/core/common/download.js +++ b/src/core/common/download.js @@ -26,8 +26,7 @@ import * as yabson from "./../../lib/yabson/yabson.js"; import * as ui from "./../../ui/content/content-ui.js"; import { getSharePageBar, setLabels } from "./../../ui/common/common-content-ui.js"; -import html2canvas from "html2canvas"; -import {getDodaoScreenshotBlobUrl} from "./dodao-screenshot.js"; +import { getDodaoScreenshotBlobUrl } from "./dodao-screenshot.js"; const MAX_CONTENT_SIZE = 16 * (1024 * 1024); @@ -129,7 +128,6 @@ async function downloadPage(pageData, options) { } if (pageData.filename) { if (options.saveWithTidbitsHub) { - // we capture screenshot using options.content as it contains whole page content for the screesnhot message.dodaoScreenshotBlobUrl = await getDodaoScreenshotBlobUrl(options.content); } const blob = new Blob([await yabson.serialize(pageData)], { type: pageData.mimeType }); @@ -173,10 +171,6 @@ async function downloadPage(pageData, options) { message.filename = pageData.filename = filename; pageData.filename = options.captureHtmlScreenFileName; - if(options.saveWithTidbitsHub) { - message.dodaoScreenshotBlobUrl = await getDodaoScreenshotBlobUrl(pageData.content); - } - const blobURL = URL.createObjectURL(new Blob([pageData.content], { type: pageData.mimeType })); message.blobURL = blobURL; console.log('message in download.js', message); @@ -272,4 +266,4 @@ function saveToClipboard(pageData) { event.clipboardData.setData("text/plain", pageData.content); event.preventDefault(); } -} +} \ No newline at end of file diff --git a/src/core/content/content.js b/src/core/content/content.js index 2eb81da..22bf7d3 100644 --- a/src/core/content/content.js +++ b/src/core/content/content.js @@ -362,4 +362,4 @@ async function processPage(options) { } } return page; -} +} \ No newline at end of file From 55ffc40976c1acffee1da3fe3495883cc30eef5a Mon Sep 17 00:00:00 2001 From: Muhammad Sami Tariq Date: Thu, 31 Oct 2024 23:34:08 +0500 Subject: [PATCH 07/10] removed unused code --- src/core/content/dodao-content.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/core/content/dodao-content.js b/src/core/content/dodao-content.js index a631dc5..cb8a12d 100644 --- a/src/core/content/dodao-content.js +++ b/src/core/content/dodao-content.js @@ -968,11 +968,6 @@ function createNewModalElement( const fullScreenModalWrapper = document.createElement("div"); fullScreenModalWrapper.id = "dodao-full-screen-modal-wrapper"; - fullScreenModalWrapper.style.cssText = `position: fixed; top: 0; left: 0; right: 0; bottom: 0; font-size: 24px; z-index: 2147483640;`; - - // Append styles to head to affect the entire document - document.head.appendChild(createModalStyle()); - document.body.appendChild(fullScreenModalWrapper); const shadowRoot = fullScreenModalWrapper.attachShadow({ mode: "open" }); shadowRoot.appendChild(createModalStyle()); From fbd53da02f47d139484ffc164652c3c3afd36738 Mon Sep 17 00:00:00 2001 From: Muhammad Sami Tariq Date: Wed, 6 Nov 2024 16:45:31 +0500 Subject: [PATCH 08/10] Add `allow-same-origin` to iframes --- src/core/common/dodao-utils.js | 31 +++++++++++++++++++++++++++++-- src/core/content/dodao-content.js | 4 ++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/core/common/dodao-utils.js b/src/core/common/dodao-utils.js index 48725b4..5a25b97 100644 --- a/src/core/common/dodao-utils.js +++ b/src/core/common/dodao-utils.js @@ -5,11 +5,13 @@ export const CLIKABLE_FILES_HOST_URL = "https://dodao-prod-public-assets.s3.amaz export function injectScriptLinkTags(htmlContent) { console.log("Injecting script and link tags into HTML content"); - const insertionIndex = findInsertionIndex(htmlContent); + // Add `allow-same-origin` to iframes + const updatedHtmlContent = addAllowSameOriginToIframes(htmlContent); + const insertionIndex = findInsertionIndex(updatedHtmlContent); if (insertionIndex !== undefined) { const tags = getScriptLinkTags(); - const modifiedHtml = insertTagsIntoHtml(htmlContent, insertionIndex, tags); + const modifiedHtml = insertTagsIntoHtml(updatedHtmlContent, insertionIndex, tags); return modifiedHtml; } else { console.warn("Unable to find opening style tag in HTML content"); @@ -17,6 +19,31 @@ export function injectScriptLinkTags(htmlContent) { } } +function addAllowSameOriginToIframes(htmlContent) { + // Regular expression to find all