diff --git a/src/background/messaging/handleRequestFromTalon.ts b/src/background/messaging/handleRequestFromTalon.ts index a34f2e6..0139db8 100644 --- a/src/background/messaging/handleRequestFromTalon.ts +++ b/src/background/messaging/handleRequestFromTalon.ts @@ -2,10 +2,7 @@ import { retrieve } from "../../common/storage"; import { promiseWrap } from "../../lib/promiseWrap"; import { type RequestFromTalon } from "../../typings/RequestFromTalon"; import { dispatchCommand } from "../commands/dispatchCommand"; -import { - getRequestFromClipboard, - writeResponseToClipboard, -} from "../utils/clipboard"; +import { getRequest, postResponse } from "../utils/requestAndResponse"; import { constructTalonResponse } from "../utils/constructTalonResponse"; import { notify } from "../utils/notify"; import { shouldTryToFocusDocument } from "../utils/shouldTryToFocusDocument"; @@ -15,7 +12,7 @@ import { sendRequestToContent } from "./sendRequestToContent"; let talonIsWaitingForResponse = false; async function writeTypeCharactersResponse() { - await writeResponseToClipboard({ + await postResponse({ type: "response", action: { type: "noHintFound" }, actions: [ @@ -66,7 +63,7 @@ async function handleDirectClickElementRequest(request: RequestFromTalon) { export async function handleRequestFromTalon() { try { - const request = await getRequestFromClipboard(); + const request = await getRequest(); if (process.env["NODE_ENV"] !== "production") { console.log(JSON.stringify(request, null, 2)); } @@ -93,13 +90,13 @@ export async function handleRequestFromTalon() { (await shouldTryToFocusDocument()) ) { const response = constructTalonResponse([{ name: "focusPageAndResend" }]); - await writeResponseToClipboard(response); + await postResponse(response); return; } const response = await dispatchCommand(request.action); if (talonIsWaitingForResponse) { - await writeResponseToClipboard(response); + await postResponse(response); talonIsWaitingForResponse = false; } } catch (error: unknown) { diff --git a/src/background/utils/clipboard.ts b/src/background/utils/clipboard.ts index c01e10c..339cb3a 100644 --- a/src/background/utils/clipboard.ts +++ b/src/background/utils/clipboard.ts @@ -1,10 +1,5 @@ import browser from "webextension-polyfill"; import { urls } from "../../common/urls"; -import { - type RequestFromTalon, - type ResponseToTalon, -} from "../../typings/RequestFromTalon"; -import { notify } from "./notify"; import { isSafari } from "./isSafari"; /** @@ -27,7 +22,48 @@ const storageClipboard = { }, }; -async function getClipboardManifestV3(): Promise { +export async function readClipboard(): Promise { + if (process.env["NODE_ENV"] === "test") { + return storageClipboard.readText(); + } + + if (isSafari()) { + return readClipboardSafari(); + } + + if (navigator.clipboard) { + return navigator.clipboard.readText(); + } + + return readClipboardManifestV3(); +} + +export async function writeClipboard(text: string) { + if (process.env["NODE_ENV"] === "test") { + await storageClipboard.writeText(text); + } + + if (isSafari()) { + return writeClipboardSafari(text); + } + + if (navigator.clipboard) { + return navigator.clipboard.writeText(text); + } + + return writeClipboardManifestV3(text); +} + +async function readClipboardSafari() { + const response: { textFromClipboard: string } = + await browser.runtime.sendNativeMessage("", { + request: "getTextFromClipboard", + }); + + return response.textFromClipboard; +} + +async function readClipboardManifestV3(): Promise { try { const hasDocument = await chrome.offscreen.hasDocument(); if (!hasDocument) { @@ -49,7 +85,21 @@ async function getClipboardManifestV3(): Promise { return undefined; } -async function copyToClipboardManifestV3(text: string) { +async function writeClipboardSafari(text: string) { + const copyPasteArea = + document.querySelector("#copy-paste-area") ?? + document.createElement("textarea"); + copyPasteArea.id = "copy-paste-area"; + document.body.append(copyPasteArea); + if (copyPasteArea instanceof HTMLTextAreaElement) { + copyPasteArea.value = text; + copyPasteArea.select(); + document.execCommand("copy"); + copyPasteArea.value = ""; + } +} + +async function writeClipboardManifestV3(text: string) { try { const hasDocument = await chrome.offscreen.hasDocument(); if (!hasDocument) { @@ -69,88 +119,3 @@ async function copyToClipboardManifestV3(text: string) { console.error(error); } } - -async function getTextFromClipboard(): Promise { - if (process.env["NODE_ENV"] === "test") { - return storageClipboard.readText(); - } - - if (isSafari()) { - const response: { textFromClipboard: string } = - await browser.runtime.sendNativeMessage("", { - request: "getTextFromClipboard", - }); - - return response.textFromClipboard; - } - - if (navigator.clipboard) { - return navigator.clipboard.readText(); - } - - return getClipboardManifestV3(); -} - -export async function getRequestFromClipboard(): Promise< - RequestFromTalon | undefined -> { - const clipText = await getTextFromClipboard(); - let request: RequestFromTalon; - if (clipText) { - try { - request = JSON.parse(clipText) as RequestFromTalon; - // This is just to be extra safe - if (request.type !== "request") { - console.error( - 'Error: The message present in the clipboard is not of type "request"' - ); - } - - return request; - } catch (error: unknown) { - // We already check that we are sending valid json in rango-talon, but - // just to be extra sure - if (error instanceof SyntaxError) { - console.error(error); - } - } - } else { - await notify("Unable to read the request present on the clipboard", { - type: "error", - }); - } - - return undefined; -} - -export async function writeResponseToClipboard(response: ResponseToTalon) { - // We send the response so that talon can make sure the request was received - // and to tell talon to execute any actions - - const jsonResponse = JSON.stringify(response); - - if (process.env["NODE_ENV"] === "test") { - await storageClipboard.writeText(jsonResponse); - } - - if (navigator.clipboard) { - if (isSafari()) { - const copyPasteArea = - document.querySelector("#copy-paste-area") ?? - document.createElement("textarea"); - copyPasteArea.id = "copy-paste-area"; - document.body.append(copyPasteArea); - if (copyPasteArea instanceof HTMLTextAreaElement) { - copyPasteArea.value = jsonResponse; - copyPasteArea.select(); - document.execCommand("copy"); - copyPasteArea.value = ""; - return; - } - } else { - return navigator.clipboard.writeText(jsonResponse); - } - } - - return copyToClipboardManifestV3(jsonResponse); -} diff --git a/src/background/utils/requestAndResponse.ts b/src/background/utils/requestAndResponse.ts new file mode 100644 index 0000000..99f5815 --- /dev/null +++ b/src/background/utils/requestAndResponse.ts @@ -0,0 +1,48 @@ +import type { + RequestFromTalon, + ResponseToTalon, +} from "../../typings/RequestFromTalon"; +import { readClipboard, writeClipboard } from "./clipboard"; +import { notify } from "./notify"; + +/** + * Reads and parses the request from the clipboard. + */ +export async function getRequest(): Promise { + const clipText = await readClipboard(); + let request: RequestFromTalon; + + if (clipText) { + try { + request = JSON.parse(clipText) as RequestFromTalon; + // This is just to be extra safe + if (request.type !== "request") { + console.error( + 'Error: The message present in the clipboard is not of type "request"' + ); + } + + return request; + } catch (error: unknown) { + // We already check that we are sending valid json in rango-talon, but + // just to be extra sure + if (error instanceof SyntaxError) { + console.error(error); + } + } + } else { + await notify("Unable to read the request present on the clipboard", { + type: "error", + }); + } + + return undefined; +} + +/** + * Stringifies and writes the response to the clipboard. + */ +export async function postResponse(response: ResponseToTalon) { + const jsonResponse = JSON.stringify(response); + await writeClipboard(jsonResponse); +}