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);