Skip to content

Commit

Permalink
feat: Add plugin context to get browser storage (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
Codetrauma authored Dec 3, 2024
1 parent 4c908d0 commit b8d068b
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 9 deletions.
16 changes: 16 additions & 0 deletions src/components/PluginInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,22 @@ export function PluginPermissions({
</span>
</PermissionDescription>
)}
{pluginContent.localStorage && (
<PermissionDescription fa="fa-solid fa-database">
<span className="cursor-default">
<span className="mr-1">Access local storage storage from</span>
<MultipleParts parts={pluginContent.localStorage} />
</span>
</PermissionDescription>
)}
{pluginContent.sessionStorage && (
<PermissionDescription fa="fa-solid fa-database">
<span className="cursor-default">
<span className="mr-1">Access session storage from</span>
<MultipleParts parts={pluginContent.sessionStorage} />
</span>
</PermissionDescription>
)}
{pluginContent.requests && (
<PermissionDescription fa="fa-solid fa-globe">
<span className="cursor-default">
Expand Down
59 changes: 58 additions & 1 deletion src/entries/Background/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ const cookiesDb = db.sublevel<string, boolean>('cookies', {
const headersDb = db.sublevel<string, boolean>('headers', {
valueEncoding: 'json',
});
const localStorageDb = db.sublevel<string, any>('sessionStorage', {
valueEncoding: 'json',
});
const sessionStorageDb = db.sublevel<string, any>('localStorage', {
valueEncoding: 'json',
});
const appDb = db.sublevel<string, any>('app', {
valueEncoding: 'json',
});
Expand Down Expand Up @@ -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()) {
Expand All @@ -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<boolean> {
return appDb.get(AppDatabaseKey.DefaultPluginsInstalled).catch(() => false);
}
Expand Down
1 change: 0 additions & 1 deletion src/entries/Background/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) => {
Expand Down
47 changes: 46 additions & 1 deletion src/entries/Background/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
getHeadersByHost,
getAppState,
setDefaultPluginsInstalled,
setLocalStorage,
setSessionStorage,
} from './db';
import { addOnePlugin, removeOnePlugin } from '../../reducers/plugins';
import {
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 19 additions & 1 deletion src/entries/Content/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
Expand Down
64 changes: 59 additions & 5 deletions src/utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -242,7 +246,6 @@ export const makePlugin = async (
return context.store(`${id}`);
},
};

const funcs: {
[key: string]: (callContext: CallContext, ...args: any[]) => any;
} = {};
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit b8d068b

Please sign in to comment.