Skip to content

Commit

Permalink
feat: background modules implementation wip
Browse files Browse the repository at this point in the history
  • Loading branch information
martonlederer committed Sep 15, 2022
1 parent a49d3fd commit 5eb1979
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 25 deletions.
2 changes: 1 addition & 1 deletion shim.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ declare module "webext-bridge" {
}
}

interface ApiCall<DataType = any> {
interface ApiCall<DataType = any> extends JsonValue {
type: string;
data?: DataType;
callID: number | string;
Expand Down
2 changes: 1 addition & 1 deletion src/api/background.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Runtime, Tabs } from "webextension-polyfill";
import type { Tabs } from "webextension-polyfill";
import type { Module } from "./module";

// import modules
Expand Down
92 changes: 92 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { OnMessageCallback } from "webext-bridge";
import { checkTypes, getAppURL } from "~utils/format";
import type { ApiCall, ApiResponse } from "shim";
import { pushEvent } from "~utils/events";
import { getTab } from "~applications";
import Application from "~applications/application";
import modules from "./background";

const handleApiCalls: OnMessageCallback<
// @ts-expect-error
ApiCall<{ params: any[] }>,
ApiResponse
> = async ({ data, sender }) => {
// contruct base message to extend and return
const baseMessage: ApiResponse = {
type: data.type + "_result",
callID: data.callID
};

try {
// check if the call is from the content-script
if (sender.context !== "content-script") {
throw new Error(
"API calls are only accepted from the injected-script -> content-script"
);
}

// grab the tab where the API call came from
const tab = await getTab(sender.tabId);

// if the tab is not found, reject the call
if (!tab || !tab.url) {
throw new Error("Call coming from invalid tab");
}

// check data types
checkTypes([data.callID, "string"], [data.type, "string"]);

// find module to execute
const functionName = data.type.replace("api_", "");
const mod = modules.find((mod) => mod.functionName === functionName);

// if we cannot find the module, we return with an error
if (!mod) {
throw new Error(`API function "${functionName}" not found`);
}

// grab app info
const app = new Application(getAppURL(tab.url));

// check permissions
const permissionCheck = await app.hasPermissions(mod.permissions);

if (!permissionCheck.result) {
throw new Error(
`Missing permission(s) for "${functionName}": ${permissionCheck.missing.join(
", "
)}`
);
}

// check if site is blocked
if (await app.isBlocked()) {
throw new Error(`${app.url} is blocked from interacting with ArConnect`);
}

// update events
await pushEvent({
type: data.type,
app: app.url,
date: Date.now()
});

// handle function
const functionResult = await mod.function(tab, ...(data.data.params || []));

// return result
return {
...baseMessage,
data: functionResult
};
} catch (e) {
// return error
return {
...baseMessage,
error: true,
data: e?.message || e
};
}
};

export default handleApiCalls;
15 changes: 12 additions & 3 deletions src/applications/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,25 @@ export default class Application {
*
* @param permissions Permissions to check for
*/
async hasPermissions(permissions: PermissionType[]) {
async hasPermissions(permissions: PermissionType[]): Promise<{
/** App has permissions or not */
result: boolean;
/** Missing permissions */
missing: PermissionType[];
}> {
const existingPermissions = await this.getPermissions();
const missing: PermissionType[] = [];

for (const permission of permissions) {
if (!existingPermissions.includes(permission)) {
return false;
missing.push(permission);
}
}

return true;
return {
result: missing.length === 0,
missing
};
}

/**
Expand Down
14 changes: 14 additions & 0 deletions src/applications/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export async function addApp({ url, ...rest }: InitAppParams) {

/**
* Remove an application (disconnect)
*
* @param url URL of the tab to remove
*/
export async function removeApp(url: string) {
const storedApps = await getStoredApps();
Expand All @@ -70,3 +72,15 @@ export const getActiveTab = async () =>
currentWindow: true
})
)[0];

/**
* Get a browser tab by id
*
* @param id ID of the tab to get
*/
export async function getTab(id: number) {
// get all tabs
const tabs = await browser.tabs.query({});

return tabs.find((tab) => tab.id === id);
}
31 changes: 21 additions & 10 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,30 @@ import { addressChangeListener } from "~wallets/event";
import { getStorageConfig } from "~utils/storage";
import { Storage } from "@plasmohq/storage";
import { onMessage } from "webext-bridge";
import handleApiCalls from "~api";

const storage = new Storage(getStorageConfig());
// TODO: handle chunks
// move chunks to a different message from "api_call"

// TODO: handle fee alarm (send fees asyncronously)

// TODO: open welcome page on extension install

// TODO: handle tab change (icon, context menus)

// TODO: save decryption key here if the extension is
// running in firefox. firefox still uses manifest v2,
// so it should allow us, to store the decryption key
// in the background scipt and have it destroyed once
// the browser is closed

// TODO: encode decryption key (base64)

// watch for API calls
onMessage("api_call", async ({ data }) => {
console.log(data);

return {
type: data.type + "_result",
data: "test",
callID: data.callID
};
});
onMessage("api_call", handleApiCalls);

// create storage client
const storage = new Storage(getStorageConfig());

// watch for active address changes
// and send them to the content script to
Expand Down
25 changes: 25 additions & 0 deletions src/utils/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getStorageConfig } from "./storage";
import { Storage } from "@plasmohq/storage";

interface SecurityEvent {
type: string;
app: string;
date: number;
}

const storage = new Storage(getStorageConfig());

/**
* Push an event to the stored events array
*
* @param event Event to push
*/
export async function pushEvent(event: SecurityEvent) {
let events = (await storage.get<SecurityEvent[]>("events")) || [];

// only allow the last 99 events
events = events.filter((_, i) => i < 98);
events.push(event);

await storage.set("events", events);
}
27 changes: 27 additions & 0 deletions src/utils/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,30 @@ export function getAppURL(link: string) {

return url.hostname;
}

// possible types
type Types =
| "string"
| "number"
| "bigint"
| "boolean"
| "symbol"
| "undefined"
| "object"
| "function";

/**
* Check types of values
*/
export function checkTypes(...values: [any, Types | Types[]][]) {
for (const [value, expectedType] of values) {
const expectedTypes =
typeof expectedType === "string" ? [expectedType] : expectedType;

if (!expectedTypes.includes(typeof value)) {
throw new Error(
`Type of value ${value} is not one of: ${expectedTypes.join(", ")}`
);
}
}
}
12 changes: 6 additions & 6 deletions src/utils/icon.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import offline64 from "url:~assets/icons/offline/logo64.png";
import offline128 from "url:~assets/icons/offline/logo128.png";
import offline256 from "url:~assets/icons/offline/logo256.png";
import offline64 from "url:../assets/icons/offline/logo64.png";
import offline128 from "url:../assets/icons/offline/logo128.png";
import offline256 from "url:../assets/icons/offline/logo256.png";

import online64 from "url:~assets/icons/online/logo64.png";
import online128 from "url:~assets/icons/online/logo128.png";
import online256 from "url:~assets/icons/online/logo256.png";
import online64 from "url:../assets/icons/online/logo64.png";
import online128 from "url:../assets/icons/online/logo128.png";
import online256 from "url:../assets/icons/online/logo256.png";

import browser from "webextension-polyfill";

Expand Down
7 changes: 6 additions & 1 deletion src/utils/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
* Storage values protected from leaking
* into window.localStorage
*/
const protected_stores = ["wallets", "decryption_key", "active_address"];
const protected_stores = [
"wallets",
"decryption_key",
"active_address",
"events"
];

/**
* Get a secure config for the storage module.
Expand Down
9 changes: 6 additions & 3 deletions src/wallets/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ export async function addressChangeListener({
const app = new Application(getAppURL(tab.url));

// check required permissions
if (
!(await app.hasPermissions(["ACCESS_ALL_ADDRESSES", "ACCESS_ADDRESS"]))
) {
const permissionCheck = await app.hasPermissions([
"ACCESS_ALL_ADDRESSES",
"ACCESS_ADDRESS"
]);

if (!permissionCheck.result) {
continue;
}

Expand Down

0 comments on commit 5eb1979

Please sign in to comment.