diff --git a/src/components/PluginInfo/index.tsx b/src/components/PluginInfo/index.tsx index b08dc14e..63815020 100644 --- a/src/components/PluginInfo/index.tsx +++ b/src/components/PluginInfo/index.tsx @@ -193,6 +193,22 @@ export function PluginPermissions({ )} + {pluginContent.localStorage && ( + + + Access local storage storage from + + + + )} + {pluginContent.sessionStorage && ( + + + Access session storage from + + + + )} {pluginContent.requests && ( diff --git a/src/entries/Background/db.ts b/src/entries/Background/db.ts index 4677e03b..e9188158 100644 --- a/src/entries/Background/db.ts +++ b/src/entries/Background/db.ts @@ -28,6 +28,12 @@ const cookiesDb = db.sublevel('cookies', { const headersDb = db.sublevel('headers', { valueEncoding: 'json', }); +const localStorageDb = db.sublevel('sessionStorage', { + valueEncoding: 'json', +}); +const sessionStorageDb = db.sublevel('localStorage', { + valueEncoding: 'json', +}); const appDb = db.sublevel('app', { valueEncoding: 'json', }); @@ -370,7 +376,6 @@ export async function getHeaders(host: string, name: string) { return null; } } - export async function getHeadersByHost(host: string) { const ret: { [key: string]: string } = {}; for await (const [key, value] of headersDb.sublevel(host).iterator()) { @@ -379,6 +384,59 @@ export async function getHeadersByHost(host: string) { return ret; } +export async function setLocalStorage( + host: string, + name: string, + value: string, +) { + return mutex.runExclusive(async () => { + await localStorageDb.sublevel(host).put(name, value); + return true; + }); +} + +export async function setSessionStorage( + host: string, + name: string, + value: string, +) { + return mutex.runExclusive(async () => { + await sessionStorageDb.sublevel(host).put(name, value); + return true; + }); +} + +export async function clearLocalStorage(host: string) { + return mutex.runExclusive(async () => { + await localStorageDb.sublevel(host).clear(); + return true; + }); +} + +export async function clearSessionStorage(host: string) { + return mutex.runExclusive(async () => { + await sessionStorageDb.sublevel(host).clear(); + return true; + }); +} + +export async function getLocalStorageByHost(host: string) { + const ret: { [key: string]: string } = {}; + console.log('in func', host); + for await (const [key, value] of localStorageDb.sublevel(host).iterator()) { + ret[key] = value; + } + return ret; +} + +export async function getSessionStorageByHost(host: string) { + const ret: { [key: string]: string } = {}; + for await (const [key, value] of sessionStorageDb.sublevel(host).iterator()) { + ret[key] = value; + } + return ret; +} + async function getDefaultPluginsInstalled(): Promise { return appDb.get(AppDatabaseKey.DefaultPluginsInstalled).catch(() => false); } diff --git a/src/entries/Background/handlers.ts b/src/entries/Background/handlers.ts index 22058d1d..b585ecef 100644 --- a/src/entries/Background/handlers.ts +++ b/src/entries/Background/handlers.ts @@ -5,7 +5,6 @@ import browser from 'webextension-polyfill'; import { addRequest } from '../../reducers/requests'; import { urlify } from '../../utils/misc'; import { setCookies, setHeaders } from './db'; - export const onSendHeaders = ( details: browser.WebRequest.OnSendHeadersDetailsType, ) => { diff --git a/src/entries/Background/index.ts b/src/entries/Background/index.ts index 554bf82e..7934687b 100644 --- a/src/entries/Background/index.ts +++ b/src/entries/Background/index.ts @@ -3,8 +3,40 @@ import { deleteCacheByTabId } from './cache'; import browser from 'webextension-polyfill'; import { getAppState, setDefaultPluginsInstalled } from './db'; import { installPlugin } from './plugins/utils'; +import { BackgroundActiontype } from './rpc'; +import { setLocalStorage, setSessionStorage } from './db'; (async () => { + chrome.runtime.onMessage.addListener( + async (request, sender, sendResponse) => { + if ( + request.type === BackgroundActiontype.set_local_storage && + sender.tab?.url + ) { + const url = new URL(sender.tab.url); + const hostname = url.hostname; + const { localStorageData } = request; + for (const [key, value] of Object.entries(localStorageData)) { + await setLocalStorage(hostname, key, value as string); + } + } + }, + ); + + chrome.runtime.onMessage.addListener(async (request, sender) => { + if ( + request.type === BackgroundActiontype.set_session_storage && + sender.tab?.url + ) { + const url = new URL(sender.tab.url); + const hostname = url.hostname; + const { sessionStorageData } = request; + for (const [key, value] of Object.entries(sessionStorageData)) { + await setSessionStorage(hostname, key, value as string); + } + } + }); + browser.webRequest.onSendHeaders.addListener( onSendHeaders, { diff --git a/src/entries/Background/rpc.ts b/src/entries/Background/rpc.ts index 3f9ccc94..4a6d7b25 100644 --- a/src/entries/Background/rpc.ts +++ b/src/entries/Background/rpc.ts @@ -26,6 +26,8 @@ import { getHeadersByHost, getAppState, setDefaultPluginsInstalled, + getSessionStorageByHost, + getLocalStorageByHost, } from './db'; import { addOnePlugin, removeOnePlugin } from '../../reducers/plugins'; import { @@ -89,6 +91,10 @@ export enum BackgroundActiontype { get_logging_level = 'get_logging_level', get_app_state = 'get_app_state', set_default_plugins_installed = 'set_default_plugins_installed', + set_local_storage = 'set_local_storage', + get_local_storage = 'get_local_storage', + set_session_storage = 'set_session_storage', + get_session_storage = 'get_session_storage', } export type BackgroundAction = { @@ -201,6 +207,14 @@ export const initRPC = () => { case BackgroundActiontype.set_default_plugins_installed: setDefaultPluginsInstalled(request.data).then(sendResponse); return true; + case BackgroundActiontype.set_local_storage: + return; + case BackgroundActiontype.get_local_storage: + return; + case BackgroundActiontype.set_session_storage: + return; + case BackgroundActiontype.get_session_storage: + return; default: break; } @@ -464,6 +478,29 @@ function handleGetHeadersByHostname( return true; } +function handleGetSessionStorageByHostname( + request: BackgroundAction, + sendResponse: (data?: any) => void, +): boolean { + (async () => { + const sessionStorage = await getSessionStorageByHost(request.data); + sendResponse(sessionStorage); + })(); + return true; +} + +function handleGetLocalStorageByHostName( + request: BackgroundAction, + sendResponse: (data?: any) => void, +): boolean { + (async () => { + console.log('in rpc', request); + const localStorage = await getLocalStorageByHost(request.data); + sendResponse(localStorage); + })(); + return true; +} + async function handleAddPlugin( request: BackgroundAction, sendResponse: (data?: any) => void, diff --git a/src/entries/Content/index.ts b/src/entries/Content/index.ts index c274b0cf..5ebb414d 100644 --- a/src/entries/Content/index.ts +++ b/src/entries/Content/index.ts @@ -1,4 +1,4 @@ -import browser from 'webextension-polyfill'; +import browser, { browserAction } from 'webextension-polyfill'; import { ContentScriptRequest, ContentScriptTypes, RPCServer } from './rpc'; import { BackgroundActiontype, RequestHistory } from '../Background/rpc'; import { urlify } from '../../utils/misc'; @@ -7,6 +7,25 @@ import { urlify } from '../../utils/misc'; loadScript('content.bundle.js'); const server = new RPCServer(); + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === BackgroundActiontype.get_local_storage) { + console.log('in content'); + chrome.runtime.sendMessage({ + type: BackgroundActiontype.set_local_storage, + localStorageData: { ...localStorage }, + }); + } + }); + + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === BackgroundActiontype.get_session_storage) { + chrome.runtime.sendMessage({ + type: BackgroundActiontype.set_session_storage, + sessionStorageData: { ...sessionStorage }, + }); + } + }); + server.on(ContentScriptTypes.connect, async () => { const connected = await browser.runtime.sendMessage({ type: BackgroundActiontype.connect_request, diff --git a/src/utils/misc.ts b/src/utils/misc.ts index d8adb17e..16cbca27 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -13,8 +13,13 @@ import browser from 'webextension-polyfill'; import NodeCache from 'node-cache'; import { getNotaryApi, getProxyApi } from './storage'; import { minimatch } from 'minimatch'; -import { getCookiesByHost, getHeadersByHost } from '../entries/Background/db'; - +import { + getCookiesByHost, + getHeadersByHost, + getLocalStorageByHost, + getSessionStorageByHost, + setLocalStorage, +} from '../entries/Background/db'; const charwise = require('charwise'); export function urlify( @@ -233,7 +238,6 @@ export const makePlugin = async ( return context.store(`${id}`); }, }; - const funcs: { [key: string]: (callContext: CallContext, ...args: any[]) => any; } = {}; @@ -250,6 +254,48 @@ export const makePlugin = async ( } } + if (config?.localStorage) { + const localStorage: { [hostname: string]: { [key: string]: string } } = {}; + + (async () => { + const [tab] = await chrome.tabs.query({ + active: true, + lastFocusedWindow: true, + }); + await chrome.tabs.sendMessage(tab.id as number, { + type: BackgroundActiontype.get_local_storage, + }); + })(); + //@ts-ignore + for (const host of config.localStorage) { + const cache = await getLocalStorageByHost(host); + localStorage[host] = cache; + } + //@ts-ignore + injectedConfig.localStorage = JSON.stringify(localStorage); + } + + if (config?.sessionStorage) { + const sessionStorage: { [hostname: string]: { [key: string]: string } } = + {}; + (async () => { + const [tab] = await chrome.tabs.query({ + active: true, + lastFocusedWindow: true, + }); + await chrome.tabs.sendMessage(tab.id as number, { + type: BackgroundActiontype.get_session_storage, + }); + })(); + //@ts-ignore + for (const host of config.sessionStorage) { + const cache = await getSessionStorageByHost(host); + sessionStorage[host] = cache; + } + //@ts-ignore + injectedConfig.sessionStorage = JSON.stringify(sessionStorage); + } + if (config?.cookies) { const cookies: { [hostname: string]: { [key: string]: string } } = {}; for (const host of config.cookies) { @@ -293,12 +339,14 @@ export type StepConfig = { export type PluginConfig = { title: string; // The name of the plugin - description: string; // A description of the plugin's purpose + description: string; // A description of the plugin purpose icon?: string; // A base64-encoded image string representing the plugin's icon (optional) steps?: StepConfig[]; // An array describing the UI steps and behavior (see Step UI below) (optional) hostFunctions?: string[]; // Host functions that the plugin will have access to cookies?: string[]; // Cookies the plugin will have access to, cached by the extension from specified hosts (optional) headers?: string[]; // Headers the plugin will have access to, cached by the extension from specified hosts (optional) + localStorage?: string[]; // LocalStorage the plugin will have access to, cached by the extension from specified hosts (optional) + sessionStorage?: string[]; // SessionStorage the plugin will have access to, cached by the extension from specified hosts (optional) requests: { method: string; url: string }[]; // List of requests that the plugin is allowed to make notaryUrls?: string[]; // List of notary services that the plugin is allowed to use (optional) proxyUrls?: string[]; // List of websocket proxies that the plugin is allowed to use (optional) @@ -348,7 +396,16 @@ export const getPluginConfig = async ( assert(typeof name === 'string' && name.length); } } - + if (config.localStorage) { + for (const name of config.localStorage) { + assert(typeof name === 'string' && name.length); + } + } + if (config.sessionStorage) { + for (const name of config.sessionStorage) { + assert(typeof name === 'string' && name.length); + } + } if (config.headers) { for (const name of config.headers) { assert(typeof name === 'string' && name.length);