diff --git a/example-web/my-app/package-lock.json b/example-web/my-app/package-lock.json index 68cf8ba1..18f8c7d6 100644 --- a/example-web/my-app/package-lock.json +++ b/example-web/my-app/package-lock.json @@ -14,6 +14,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "eslint": "^8.56.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", @@ -2531,9 +2532,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -7795,14 +7796,14 @@ } }, "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", diff --git a/example-web/my-app/package.json b/example-web/my-app/package.json index 165c7953..78e4c384 100644 --- a/example-web/my-app/package.json +++ b/example-web/my-app/package.json @@ -9,6 +9,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "eslint": "^8.56.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", @@ -24,7 +25,10 @@ "extends": [ "react-app", "react-app/jest" - ] + ], + "globals": { + "chrome": true + } }, "browserslist": { "production": [ diff --git a/example-web/my-app/src/App.js b/example-web/my-app/src/App.js index 2cf0b771..531583f3 100644 --- a/example-web/my-app/src/App.js +++ b/example-web/my-app/src/App.js @@ -12,6 +12,16 @@ function App() { window.postMessage({ type: "init-req-credential" }, "*"); }; + const handleSyncRequest = () => { + // TODO extension Id harcoded just for testing, need to find a way to get it dynamically + chrome.runtime.sendMessage("fklmfbmpaimbgjplbambkdjphdadbmed", {data: "test"}, + function(response) { + if (!response.success) + console.log(response.data) + alert("Signed headers received\n"+ JSON.stringify(response.data.headers, null, 2)); + }); + }; + return ( @@ -25,6 +35,9 @@ function App() { + diff --git a/manifest.json b/manifest.json index b4557b28..b3cc0729 100755 --- a/manifest.json +++ b/manifest.json @@ -19,23 +19,14 @@ "permissions": [ "activeTab", "storage", - "tabs", "alarms" ], "content_scripts": [ { - "matches": [ - "http://*/*", - "https://*/*", - "" - ], + "matches": [""], "run_at": "document_end", - "js": [ - "src/pages/content/index.tsx" - ], - "css": [ - "contentStyle.css" - ] + "js": ["src/pages/content/index.tsx"], + "css": ["contentStyle.css"] } ], "web_accessible_resources": [ @@ -48,5 +39,8 @@ ], "content_security_policy": { "extension_pages": "script-src 'self' 'wasm-unsafe-eval'" + }, + "externally_connectable": { + "matches": [""] } } \ No newline at end of file diff --git a/src/components/main.tsx b/src/components/main.tsx index 5aa74e0b..0531e5ed 100644 --- a/src/components/main.tsx +++ b/src/components/main.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { Sidebar } from "@components/sidebar"; import { SelectIdentifier } from "@components/selectIdentifier"; import { SelectCredential } from "@components/selectCredential"; -import { APP_STATE } from "@pages/popup/constants"; +import { TAB_STATE } from "@pages/popup/constants"; import { IdentifierList } from "@components/identifierList"; import { CredentialList } from "@components/credentialList"; import { SigninList } from "@components/signinList"; @@ -13,29 +13,31 @@ interface IMain { export function Main(props: IMain): JSX.Element { const [activeSidebar, setActiveSidebar] = useState("Identifiers"); - const [tabState, setTabState] = useState(APP_STATE.DEFAULT); + const [tabState, setTabState] = useState(TAB_STATE.DEFAULT); const fetchTabState = async () => { - const { data } = await chrome.runtime.sendMessage({ - type: "tab", - subtype: "get-tab-state", - }); - if (!data) return; + chrome.tabs.query({ active: true, currentWindow: true }, async function (tabs) { + const { data } = await chrome.tabs.sendMessage( + tabs[0].id!, + { type: "tab", subtype: "get-tab-state" } + ); + if (!data) return; if (data?.appState) { setTabState(data?.appState); if ( - data?.appState === APP_STATE.SELECT_IDENTIFIER || - data?.appState === APP_STATE.SELECT_CREDENTIAL + data?.appState === TAB_STATE.SELECT_IDENTIFIER || + data?.appState === TAB_STATE.SELECT_CREDENTIAL ) { setActiveSidebar( - data?.appState === APP_STATE.SELECT_IDENTIFIER + data?.appState === TAB_STATE.SELECT_IDENTIFIER ? "Identifiers" : "Credentials" ); } } + }); }; useEffect(() => { @@ -43,9 +45,9 @@ export function Main(props: IMain): JSX.Element { }, []); const renderItems = () => { - if (tabState === APP_STATE.SELECT_IDENTIFIER) return ; + if (tabState === TAB_STATE.SELECT_IDENTIFIER) return ; - if (tabState === APP_STATE.SELECT_CREDENTIAL) return ; + if (tabState === TAB_STATE.SELECT_CREDENTIAL) return ; switch (activeSidebar) { case "Credentials": @@ -60,8 +62,8 @@ export function Main(props: IMain): JSX.Element { const isSidebarDisabled = () => { return ( - tabState === APP_STATE.SELECT_IDENTIFIER || - tabState === APP_STATE.SELECT_CREDENTIAL + tabState === TAB_STATE.SELECT_IDENTIFIER || + tabState === TAB_STATE.SELECT_CREDENTIAL ); }; diff --git a/src/components/selectCredential.tsx b/src/components/selectCredential.tsx index c07fa1f2..b6a3bc52 100644 --- a/src/components/selectCredential.tsx +++ b/src/components/selectCredential.tsx @@ -1,7 +1,6 @@ import { useState, useEffect } from "react"; import { CredentialCard } from "@components/credentialCard"; import { IMessage } from "@pages/background/types"; -import { APP_STATE } from "@pages/popup/constants"; export function SelectCredential(): JSX.Element { const [credentials, setCredentials] = useState([]); @@ -13,20 +12,18 @@ export function SelectCredential(): JSX.Element { setCredentials(data.credentials); }; - const createSigninWithCredential = async (credential) => { - const { data } = await chrome.runtime.sendMessage>({ + const createSigninWithCredential = async (credential: any) => { + await chrome.runtime.sendMessage>({ type: "create-resource", subtype: "signin", data: { credential, }, }); - await chrome.runtime.sendMessage({ - type: "tab", - subtype: "set-app-state", - data: { - appState: APP_STATE.DEFAULT, - }, + chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { + chrome.tabs.sendMessage( + tabs[0].id!, + { type: "tab", subtype: "reload-state" }); }); window.close(); }; diff --git a/src/components/selectIdentifier.tsx b/src/components/selectIdentifier.tsx index 638d5daa..746ebd2a 100644 --- a/src/components/selectIdentifier.tsx +++ b/src/components/selectIdentifier.tsx @@ -1,7 +1,6 @@ import { useState, useEffect } from "react"; import { IdentifierCard } from "@components/identifierCard"; import { IMessage } from "@pages/background/types"; -import { APP_STATE } from "@pages/popup/constants"; export function SelectIdentifier(): JSX.Element { const [aids, setAids] = useState([]); @@ -14,29 +13,19 @@ export function SelectIdentifier(): JSX.Element { setAids(data.aids); }; - const createSigninWithIdentifiers = async (aid) => { - const { data } = await chrome.runtime.sendMessage>({ + const createSigninWithIdentifiers = async (aid: any) => { + await chrome.runtime.sendMessage>({ type: "create-resource", subtype: "signin", data: { identifier: aid, }, }); - await chrome.runtime.sendMessage({ - type: "tab", - subtype: "set-app-state", - data: { - appState: APP_STATE.DEFAULT, - }, - }); - console.log("data.signins", data.signins); chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { chrome.tabs.sendMessage( - tabs[0].id, - { type: "tab", subtype: "reload-state" }, - function (response) {} - ); + tabs[0].id!, + { type: "tab", subtype: "reload-state" }); }); window.close(); }; diff --git a/src/pages/background/index.ts b/src/pages/background/index.ts index 8a5fec74..c075e58f 100644 --- a/src/pages/background/index.ts +++ b/src/pages/background/index.ts @@ -1,5 +1,4 @@ import { browserStorageService } from "@pages/background/services/browser-storage"; -import { webappService } from "@pages/background/services/webapp"; import { configService } from "@pages/background/services/config"; import { userService } from "@pages/background/services/user"; import { signifyService } from "@pages/background/services/signify"; @@ -9,6 +8,12 @@ import { getCurrentDomain } from "@pages/background/utils"; console.log("Background script loaded"); +chrome.runtime.onInstalled.addListener(function (object) { + if (object.reason === chrome.runtime.OnInstalledReason.INSTALL) { + console.log("Signify Browser Extension installed"); + } +}); + // Handle messages chrome.runtime.onMessage.addListener(function ( message: IMessage, @@ -18,7 +23,7 @@ chrome.runtime.onMessage.addListener(function ( (async () => { // Handle mesages from content script on active tab - if (sender.tab && sender.tab.active) { + if (sender.tab && sender.tab.active) { console.log( "Message received from content script at " + @@ -92,19 +97,6 @@ chrome.runtime.onMessage.addListener(function ( } } - if (message.type === "tab" && message.subtype === "get-tab-state") { - const currentDomain = await getCurrentDomain(); - const appData = await webappService.getAppData(currentDomain!.origin); - sendResponse({ data: appData }); - } - - if (message.type === "tab" && message.subtype === "set-app-state") { - const currentDomain = await getCurrentDomain(); - await webappService.setAppData(currentDomain!.origin, message.data); - const appData = await webappService.getAppData(currentDomain!.origin); - sendResponse({ data: appData }); - } - if (message.type === "create-resource" && message.subtype === "signin") { const signins = await browserStorageService.getValue("signins") as any[]; const currentDomain = await getCurrentDomain(); @@ -158,3 +150,26 @@ chrome.runtime.onMessage.addListener(function ( // return true to indicate chrome api to send a response asynchronously return true; }); + +chrome.runtime.onMessageExternal.addListener( + async function(request, sender, sendResponse) { + console.log("Message received from external source: " + sender.url); + // if (sender.url === blocklistedWebsite) + // return; // don't allow this web page access + // if (request.openUrlInEditor) + // openUrl(request.openUrlInEditor); + // sendResponse({data: "received"}) + const origin = sender.url!; + const signins = await browserStorageService.getValue("signins") as any; + // Validate that message comes from a page that has a signin + if (origin.startsWith(signins[0].domain)) { + const signedHeaders = await signifyService.signHeaders(signins[0].identifier.name, origin); + let jsonHeaders: { [key: string]: string; } = {}; + for (const pair of signedHeaders.entries()) { + jsonHeaders[pair[0]] = pair[1]; + } + sendResponse({ data: { headers: jsonHeaders } }); + + } + + }); diff --git a/src/pages/background/services/config.ts b/src/pages/background/services/config.ts index 4b22ad2f..dea41c09 100644 --- a/src/pages/background/services/config.ts +++ b/src/pages/background/services/config.ts @@ -6,7 +6,7 @@ const CONFIG_ENUMS = { const Config = () => { const getUrl = async (): Promise => { - return await browserStorageService.getValue(CONFIG_ENUMS.KERIA_URL); + return await browserStorageService.getValue(CONFIG_ENUMS.KERIA_URL) as string; } const removeUrl = async () => { diff --git a/src/pages/background/services/webapp.ts b/src/pages/background/services/webapp.ts deleted file mode 100644 index 142116fa..00000000 --- a/src/pages/background/services/webapp.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { browserStorageService } from "@pages/background/services/browser-storage"; - -const Webapp = () => { - const getAppData = async (domain: string) => { - return await browserStorageService.getValue(domain); - }; - - const setAppData = async (domain: string, newData = {}) => { - let appData = (await getAppData(domain)) ?? {}; - appData = { ...appData, ...newData }; - return await browserStorageService.setValue(domain, appData); - }; - - return { - getAppData, - setAppData, - }; -}; - -export const webappService = Webapp(); diff --git a/src/pages/background/utils.ts b/src/pages/background/utils.ts index 0aac359c..8e678042 100644 --- a/src/pages/background/utils.ts +++ b/src/pages/background/utils.ts @@ -23,6 +23,7 @@ export const getCurrentTab = (): Promise => { export const getCurrentDomain = async () => { const currentTab = await getCurrentTab(); + console.log("Current tab: ", currentTab) return currentTab ? new URL(currentTab.url!) : null; }; diff --git a/src/pages/content/index.tsx b/src/pages/content/index.tsx index be3f973a..99a54e1f 100644 --- a/src/pages/content/index.tsx +++ b/src/pages/content/index.tsx @@ -2,6 +2,9 @@ import { createRoot } from "react-dom/client"; import { IMessage } from "@pages/background/types"; import "./style.css"; import Dialog from "../dialog/Dialog"; +import { TAB_STATE } from "../popup/constants"; + +var tabState = TAB_STATE.NONE; // Handle messages from web page window.addEventListener( @@ -16,6 +19,7 @@ window.addEventListener( switch (event.data.type) { case "init-req-identifier": case "init-req-credential": + setTabState(TAB_STATE.DEFAULT) const { data } = await chrome.runtime.sendMessage>({ type: "authentication", subtype: "check-agent-connection", @@ -30,7 +34,7 @@ window.addEventListener( data.isConnected, data.tabUrl, tabSigninResp?.data?.signins, - "init-req-identifier" + event.data.type ); break; default: @@ -55,23 +59,35 @@ chrome.runtime.onMessage.addListener(async function ( message.subtype ); if (message.type === "tab" && message.subtype === "reload-state") { - removeDialog(); - const { data } = await chrome.runtime.sendMessage>({ - type: "authentication", - subtype: "check-agent-connection", - }); - const tabSigninResp = await chrome.runtime.sendMessage>({ - type: "fetch-resource", - subtype: "tab-signin", - }); - insertDialog( - data.isConnected, - data.tabUrl, - tabSigninResp?.data?.signins, - "init-req-identifier" - ); + if (getTabState() !== TAB_STATE.NONE) { + removeDialog(); + const { data } = await chrome.runtime.sendMessage>({ + type: "authentication", + subtype: "check-agent-connection", + }); + const tabSigninResp = await chrome.runtime.sendMessage>({ + type: "fetch-resource", + subtype: "tab-signin", + }); + insertDialog( + data.isConnected, + data.tabUrl, + tabSigninResp?.data?.signins, + "" + ); + } + } + + if (message.type === "tab" && message.subtype === "get-tab-state") { + sendResponse({data: {appState:getTabState()}}); + } + + if (message.type === "tab" && message.subtype === "set-tab-state") { + setTabState(message.data.appState); + } } + }); function insertDialog(isConnected: boolean, tabUrl: string, signins: any, eventType: string) { @@ -95,3 +111,13 @@ function removeDialog() { const element = document.getElementById("__root"); if (element) element.remove(); } + +export function setTabState(state: string) { + console.log("setTabState: " + state) + tabState = state; +} + +export function getTabState() { + console.log("getTabState: " + tabState) + return tabState; +} \ No newline at end of file diff --git a/src/pages/dialog/Dialog.tsx b/src/pages/dialog/Dialog.tsx index 492a29fc..27910cc7 100644 --- a/src/pages/dialog/Dialog.tsx +++ b/src/pages/dialog/Dialog.tsx @@ -1,7 +1,8 @@ -import React, { useState, useEffect } from "react"; -import { APP_STATE } from "@pages/popup/constants"; +import { useState, useEffect } from "react"; +import { TAB_STATE } from "@pages/popup/constants"; import { PopupPrompt } from "./popupPrompt"; import { SigninItem } from "./signin"; +import { setTabState } from "@pages/content/index"; export default function Dialog({ isConnected = false, @@ -12,35 +13,25 @@ export default function Dialog({ const logo = chrome.runtime.getURL("src/assets/img/128_keri_logo.png"); const [showPopupPrompt, setShowPopupPrompt] = useState(false); - const setAppState = async (state: string) => { - await chrome.runtime.sendMessage({ - type: "tab", - subtype: "set-app-state", - data: { - appState: state, - }, - }); - }; - const getEventTypeAppState = () => { return eventType === "init-req-identifier" - ? APP_STATE.SELECT_IDENTIFIER + ? TAB_STATE.SELECT_IDENTIFIER : eventType === "init-req-credential" - ? APP_STATE.SELECT_CREDENTIAL - : APP_STATE.DEFAULT; + ? TAB_STATE.SELECT_CREDENTIAL + : TAB_STATE.DEFAULT; }; const handleClick = () => { - setAppState(getEventTypeAppState()); + setTabState(getEventTypeAppState()); setShowPopupPrompt(true); }; useEffect(() => { if (!signins?.length) { - setAppState(getEventTypeAppState()); + setTabState(getEventTypeAppState()); setShowPopupPrompt(true); } else if (!isConnected) { - setAppState(APP_STATE.DEFAULT); + setTabState(TAB_STATE.DEFAULT); setShowPopupPrompt(true); } }, []); diff --git a/src/pages/dialog/signin.tsx b/src/pages/dialog/signin.tsx index 86c7ee21..428775b4 100644 --- a/src/pages/dialog/signin.tsx +++ b/src/pages/dialog/signin.tsx @@ -1,3 +1,6 @@ +import { TAB_STATE } from "@pages/popup/constants"; +import { setTabState } from "@pages/content/index"; + // TODO do not pass the full signins stored object (only AID name, schema name, web url) export const SigninItem = ({ signin }: { signin: any }): JSX.Element => { @@ -11,6 +14,7 @@ export const SigninItem = ({ signin }: { signin: any }): JSX.Element => { }); const element = document.getElementById("__root"); if (element) element.remove(); + setTabState(TAB_STATE.NONE); // Communicate headers to web page window.postMessage({ type: "signify-signature", data: headers.data }, "*"); }; diff --git a/src/pages/popup/Popup.tsx b/src/pages/popup/Popup.tsx index e5073073..e0bdeb90 100644 --- a/src/pages/popup/Popup.tsx +++ b/src/pages/popup/Popup.tsx @@ -5,7 +5,7 @@ import { Signin } from "@src/components/signin"; import { Loader } from "@components/loader"; import { Main } from "@components/main"; -// TODO Harcoded for initial development. Must be removed soon +// TODO Harcoded for initial development. Will be removed soon const url = "https://keria-dev.rootsid.cloud/admin"; const boot_url = "https://keria-dev.rootsid.cloud"; const password = "CqjYb60NT9gZl8itwuttD9"; @@ -43,10 +43,10 @@ export default function Popup(): JSX.Element { if (data.isConnected) { chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { if (tabs.length === 1) { + console.log("realoading tab") chrome.tabs.sendMessage( tabs[0].id!, - { type: "tab", subtype: "reload-state" }, - function (response) {} + { type: "tab", subtype: "reload-state" } ); } }); diff --git a/src/pages/popup/constants.ts b/src/pages/popup/constants.ts index 9e99323a..7d2fa88d 100644 --- a/src/pages/popup/constants.ts +++ b/src/pages/popup/constants.ts @@ -1,6 +1,6 @@ -export const APP_STATE = { +export const TAB_STATE = { DEFAULT: "default", SELECT_IDENTIFIER: "select-identifier", SELECT_CREDENTIAL: "select-credential", - KERIA_CONNECT: "keria-connect", + NONE: "none" } \ No newline at end of file