Skip to content

Commit

Permalink
Merge pull request #80 from planetarium/storage-test
Browse files Browse the repository at this point in the history
refactor(background): separate storage implementation
  • Loading branch information
moreal authored May 30, 2024
2 parents e72b2f3 + 9363099 commit 5724743
Show file tree
Hide file tree
Showing 23 changed files with 636 additions and 412 deletions.
7 changes: 3 additions & 4 deletions background/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"serve": "vite preview --port 8002",
"build": "vite build --base /background --outDir ../build/background",
"fmt": "pnpm dlx @biomejs/biome format --write ./src",
"test": "jest --coverage --detectOpenHandles"
"test": "vitest"
},
"dependencies": {
"@noble/hashes": "^1.4.0",
Expand All @@ -28,13 +28,12 @@
"@types/chrome": "^0.0.266",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@vue/test-utils": "^1.3.0",
"axios-mock-adapter": "^1.20.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.7.1",
"jest": "^27.4.5",
"jest-serializer-vue": "^2.0.2",
"fast-check": "^3.19.0",
"typescript": "^5.4.5",
"vite": "^5.2.8",
"vitest": "^1.6.0",
"vue-jest": "^3.0.7"
},
"eslintConfig": {
Expand Down
8 changes: 4 additions & 4 deletions background/src/api/graphql.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from "axios";
import type Storage from "@/storage/storage";
import type LocalStorage from "@/storage/local";
import {
CURRENT_NETWORK,
NETWORKS,
Expand Down Expand Up @@ -29,7 +29,7 @@ async function getLastBlockIndex(endpoint: string) {
return data["data"]["chainQuery"]["blockQuery"]["blocks"][0]["index"];
}

async function getEndpoints(storage: Storage): Promise<string[]> {
async function getEndpoints(storage: LocalStorage): Promise<string[]> {
const currentNetworkId = await storage.get<NetworkId>(CURRENT_NETWORK);
const networks = await storage.get<Network[]>(NETWORKS);
const network = networks.find((n) => n.id === currentNetworkId);
Expand Down Expand Up @@ -59,7 +59,7 @@ async function getEndpoints(storage: Storage): Promise<string[]> {
}

export default class Graphql {
private readonly storage: Storage;
private readonly storage: LocalStorage;
private readonly endpoints: string[];
private readonly canCall: string[];

Expand All @@ -80,7 +80,7 @@ export default class Graphql {
return this.canCall.indexOf(method) >= 0;
}

static async createInstance(storage: Storage) {
static async createInstance(storage: LocalStorage) {
const endpoints = await getEndpoints(storage);
return new Graphql(storage, endpoints);
}
Expand Down
4 changes: 2 additions & 2 deletions background/src/controllers/confirmation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Storage from "@/storage/storage";
import { APPROVAL_REQUESTS } from "@/constants/constants";
import { nanoid } from "nanoid";
import { PopupController } from "./popup";
import { IStorage } from "@/storage";

interface Request {
id: string;
Expand All @@ -18,7 +18,7 @@ const pendingApprovals: Map<string, Handlers> = new Map();

export class ConfirmationController {
constructor(
private readonly storage: Storage,
private readonly storage: IStorage,
private readonly popupController: PopupController,
) {}

Expand Down
4 changes: 2 additions & 2 deletions background/src/controllers/network.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Storage from "@/storage/storage";
import { IStorage } from "@/storage/index.js";
import {
CURRENT_NETWORK,
NETWORKS,
Expand All @@ -9,7 +9,7 @@ import { Emitter } from "@/event";

export class NetworkController {
constructor(
private readonly storage: Storage,
private readonly storage: IStorage,
private readonly emitter: Emitter,
) {}

Expand Down
83 changes: 44 additions & 39 deletions background/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Graphql from "@/api/graphql";
import Storage from "@/storage/storage";
import { Storage } from "@/storage/index.js";
import Wallet from "@/wallet/wallet";
import { Buffer } from "buffer";
import {
Expand Down Expand Up @@ -119,7 +119,8 @@ window.Buffer = Buffer;
}

if (req.action == "wallet") {
Wallet.createInstance(passphrase, emitter).then((wallet) => {
const storage = new Storage(passphrase);
Wallet.createInstance(storage, passphrase, emitter).then((wallet) => {
if (wallet[req.method] && wallet.canCallExternal(req.method)) {
wallet[req.method]
.call(wallet, ...req.params)
Expand Down Expand Up @@ -176,46 +177,50 @@ window.Buffer = Buffer;
port.onMessage.addListener(function (req) {
console.log(port.name, req);
if (req.action == "wallet") {
Wallet.createInstance(() => passphrase, emitter, req.origin).then(
(wallet) => {
wallet.isConnected().then((connected) => {
if (
!connected &&
req.method !== "connect" &&
req.method !== "isConnected"
) {
port.postMessage({
error: `${req.origin} is not connected. Call 'window.chronoWallet.connect' first.`,
messageId: req.messageId,
});
}
const storage = new Storage(() => passphrase);
Wallet.createInstance(
storage,
() => passphrase,
emitter,
req.origin,
).then((wallet) => {
wallet.isConnected().then((connected) => {
if (
!connected &&
req.method !== "connect" &&
req.method !== "isConnected"
) {
port.postMessage({
error: `${req.origin} is not connected. Call 'window.chronoWallet.connect' first.`,
messageId: req.messageId,
});
}

if (wallet[req.method] && wallet.canCallExternal(req.method)) {
wallet[req.method]
.call(wallet, ...req.params)
.then((x) => {
console.log(x);
port.postMessage({
result: x,
messageId: req.messageId,
});
})
.catch((e) => {
console.error(e);
port.postMessage({
error: `${req.method} is rejected`,
messageId: req.messageId,
});
if (wallet[req.method] && wallet.canCallExternal(req.method)) {
wallet[req.method]
.call(wallet, ...req.params)
.then((x) => {
console.log(x);
port.postMessage({
result: x,
messageId: req.messageId,
});
})
.catch((e) => {
console.error(e);
port.postMessage({
error: `${req.method} is rejected`,
messageId: req.messageId,
});
} else {
port.postMessage({
error: "Unknown Method",
messageId: req.messageId,
});
}
});
},
);
} else {
port.postMessage({
error: "Unknown Method",
messageId: req.messageId,
});
}
});
});
}

if (req.action == "network") {
Expand Down
4 changes: 4 additions & 0 deletions background/src/storage/backend/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IStorageBackend {
get<T>(key: string): Promise<T | null>;
set<T>(key: string, value: T): Promise<void>;
}
2 changes: 2 additions & 0 deletions background/src/storage/backend/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { IStorageBackend } from "./common.js";
export { LocalStorageBackend } from "./local-storage.js";
15 changes: 15 additions & 0 deletions background/src/storage/backend/local-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IStorageBackend } from "./common.js";

export class LocalStorageBackend implements IStorageBackend {
set<T>(name: string, value: T): Promise<void> {
return chrome.storage.local.set({ [name]: value });
}

get<T>(name: string): Promise<T | null> {
return new Promise((resolve) => {
chrome.storage.local.get([name], (res) => {
resolve((res && res[name]) || null);
});
});
}
}
10 changes: 10 additions & 0 deletions background/src/storage/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface IStorage {
canCallExternal(method: string): boolean;
secureSet<T>(name: string, value: T): Promise<void>;
secureGet<T>(name: string): Promise<T | null>;
set<T>(name: string, value: T): Promise<void>;
get<T>(name: string): Promise<T>;
remove(name: string): Promise<void>;
has(name: string): Promise<boolean>;
clearAll(): Promise<void>;
}
3 changes: 3 additions & 0 deletions background/src/storage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { Storage } from "./storage.js";

export { IStorage } from "./common.js";
21 changes: 10 additions & 11 deletions background/src/storage/storage.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import aes256 from "@/utils/aes256";
import { Lazyable, resolve } from "@/utils/lazy";
import { IStorage } from "./common.js";
import { IStorageBackend, LocalStorageBackend } from "./backend/index.js";

class Storage {
export class Storage implements IStorage {
private readonly passphrase: Lazyable<string>;
private readonly canCall: string[];
private readonly backend: IStorageBackend;

constructor(passphrase: Lazyable<string>) {
constructor(passphrase: Lazyable<string>, backend?: IStorageBackend) {
this.passphrase = passphrase;
this.canCall = [
"set",
Expand All @@ -15,20 +18,17 @@ class Storage {
"secureSet",
"clearAll",
] as const;
this.backend = backend || new LocalStorageBackend();
}
canCallExternal(method: string): boolean {
return this.canCall.indexOf(method) >= 0;
}

async rawSet<T>(name: string, value: T): Promise<void> {
await chrome.storage.local.set({ [name]: value });
private rawSet<T>(name: string, value: T): Promise<void> {
return this.backend.set(name, value);
}
rawGet<T>(name: string): Promise<T | null> {
return new Promise((resolve) => {
chrome.storage.local.get([name], (res) => {
resolve((res && res[name]) || null);
});
});
private rawGet<T>(name: string): Promise<T | null> {
return this.backend.get(name);
}

/*
Expand Down Expand Up @@ -85,4 +85,3 @@ class Storage {
await chrome.storage.local.clear();
}
}
export default Storage;
10 changes: 5 additions & 5 deletions background/src/utils/aes256.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { keccak_256 } from "@noble/hashes/sha3";
const IV_LENGTH = 16;
export default {
encrypt: async (text: string, passphrase: string): Promise<string> => {
const key = await window.crypto.subtle.importKey(
const key = await crypto.subtle.importKey(
"raw",
Buffer.from(keccak_256(passphrase)),
{ name: "AES-CBC" },
true,
["encrypt", "decrypt"],
);
const iv = window.crypto.getRandomValues(new Uint8Array(IV_LENGTH));
const encrypted = await window.crypto.subtle.encrypt(
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
const encrypted = await crypto.subtle.encrypt(
{
name: "AES-CBC",
iv,
Expand All @@ -31,14 +31,14 @@ export default {
const textParts = text.split(":");
const iv = Buffer.from(textParts.shift(), "hex");
const encryptedText = Buffer.from(textParts.join(":"), "hex");
const key = await window.crypto.subtle.importKey(
const key = await crypto.subtle.importKey(
"raw",
Buffer.from(keccak_256(passphrase)),
{ name: "AES-CBC" },
true,
["encrypt", "decrypt"],
);
const decrypted = await window.crypto.subtle.decrypt(
const decrypted = await crypto.subtle.decrypt(
{
name: "AES-CBC",
iv,
Expand Down
8 changes: 4 additions & 4 deletions background/src/wallet/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Graphql from "@/api/graphql";
import Storage from "@/storage/storage";
import { IStorage } from "@/storage/index.js";
import {
ENCRYPTED_WALLET,
TXS,
Expand Down Expand Up @@ -50,7 +50,7 @@ interface SavedTransactionHistory {
}

export default class Wallet {
private readonly storage: Storage;
private readonly storage: IStorage;
private readonly api: Graphql;
private readonly popup: PopupController;
private readonly networkController: NetworkController;
Expand All @@ -69,7 +69,7 @@ export default class Wallet {
constructor(
passphrase: Lazyable<string>,
origin: string | undefined,
storage: Storage,
storage: IStorage,
api: Graphql,
popupController: PopupController,
networkController: NetworkController,
Expand Down Expand Up @@ -103,12 +103,12 @@ export default class Wallet {
}

static async createInstance(
storage: IStorage,
passphrase: Lazyable<string>,
emitter: Emitter,
origin?: string | undefined,
) {
const popup = new PopupController();
const storage = new Storage(passphrase);
const api = await Graphql.createInstance(storage);
const networkController = new NetworkController(storage, emitter);
const approvalRequestController = new ConfirmationController(
Expand Down
38 changes: 0 additions & 38 deletions background/test/api/graphql.test.js

This file was deleted.

Loading

0 comments on commit 5724743

Please sign in to comment.