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 1b2b760d..ea32ae6e 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', }); @@ -376,7 +382,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()) { @@ -385,6 +390,58 @@ 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 } = {}; + 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/rpc.ts b/src/entries/Background/rpc.ts index e123358a..04c050f5 100644 --- a/src/entries/Background/rpc.ts +++ b/src/entries/Background/rpc.ts @@ -26,6 +26,8 @@ import { getHeadersByHost, getAppState, setDefaultPluginsInstalled, + setLocalStorage, + setSessionStorage, } from './db'; import { addOnePlugin, removeOnePlugin } from '../../reducers/plugins'; import { @@ -120,7 +122,11 @@ export enum BackgroundActiontype { get_logging_level = 'get_logging_level', get_app_state = 'get_app_state', set_default_plugins_installed = 'set_default_plugins_installed', - // P2P + + set_local_storage = 'set_local_storage', + get_local_storage = 'get_local_storage', + set_session_storage = 'set_session_storage', + get_session_storage = 'get_session_storage', connect_rendezvous = 'connect_rendezvous', disconnect_rendezvous = 'disconnect_rendezvous', send_pair_request = 'send_pair_request', @@ -258,6 +264,10 @@ export const initRPC = () => { case BackgroundActiontype.set_default_plugins_installed: setDefaultPluginsInstalled(request.data).then(sendResponse); return true; + case BackgroundActiontype.set_local_storage: + return handleSetLocalStorage(request, sender, sendResponse); + case BackgroundActiontype.set_session_storage: + return handleSetSessionStorage(request, sender, sendResponse); case BackgroundActiontype.connect_rendezvous: connectSession().then(sendResponse); return; @@ -699,6 +709,41 @@ function handleGetHeadersByHostname( return true; } +async function handleSetLocalStorage( + request: BackgroundAction, + sender: browser.Runtime.MessageSender, + sendResponse: (data?: any) => void, +) { + if (sender.tab?.url) { + const url = new URL(sender.tab.url); + const hostname = url.hostname; + const { data } = request; + for (const [key, value] of Object.entries(data)) { + await setLocalStorage(hostname, key, value as string); + } + } +} + +async function handleSetSessionStorage( + request: BackgroundAction, + sender: browser.Runtime.MessageSender, + sendResponse: (data?: any) => void, +) { + if ( + request.type === BackgroundActiontype.set_session_storage && + sender.tab?.url + ) { + const url = new URL(sender.tab.url); + const hostname = url.hostname; + const { data } = request; + for (const [key, value] of Object.entries(data)) { + await setSessionStorage(hostname, key, value as string); + } + + sendResponse({ type: BackgroundActiontype.sessionStorage_set }); + } +} + 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..4fc588d4 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,24 @@ 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) { + chrome.runtime.sendMessage({ + type: BackgroundActiontype.set_local_storage, + data: { ...localStorage }, + }); + } + }); + + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === BackgroundActiontype.get_session_storage) { + chrome.runtime.sendMessage({ + type: BackgroundActiontype.set_session_storage, + data: { ...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 0a66be91..43877863 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -14,8 +14,12 @@ 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, +} from '../entries/Background/db'; const charwise = require('charwise'); export function urlify( @@ -242,7 +246,6 @@ export const makePlugin = async ( return context.store(`${id}`); }, }; - const funcs: { [key: string]: (callContext: CallContext, ...args: any[]) => any; } = {}; @@ -259,6 +262,46 @@ export const makePlugin = async ( } } + if (config?.localStorage) { + const localStorage: { [hostname: string]: { [key: string]: string } } = {}; + + 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 } } = + {}; + + 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) { @@ -302,12 +345,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) @@ -357,7 +402,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);