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