Skip to content

Commit

Permalink
Add wasmSafeCall utility method to beekeeper TS to make errors easier…
Browse files Browse the repository at this point in the history
… to read
  • Loading branch information
mtyszczak committed Oct 31, 2024
1 parent 16ed49f commit f606c7b
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 33 deletions.
18 changes: 10 additions & 8 deletions programs/beekeeper/beekeeper_wasm/src/detailed/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { BeekeeperError } from "../errors.js";
import { BeekeeperFileSystem } from "./fs.js";
import { IBeekeeperInstance, IBeekeeperOptions, IBeekeeperSession } from "../interfaces.js";
import { BeekeeperSession } from "./session.js";
import { safeAsyncWasmCall } from "../util/wasm_error.js";
import { safeWasmCall } from '../util/wasm_error';

// We would like to expose our api using BeekeeperInstance interface, but we would not like to expose users a way of creating instance of BeekeeperApi
export class BeekeeperApi implements IBeekeeperInstance {
Expand Down Expand Up @@ -38,21 +40,21 @@ export class BeekeeperApi implements IBeekeeperInstance {
}

public async init({ storageRoot, enableLogs, unlockTimeout }: IBeekeeperOptions) {
await this.fs.init(storageRoot);
await safeAsyncWasmCall(() => this.fs.init(storageRoot));

const WALLET_OPTIONS = ['--wallet-dir', `${storageRoot}/.beekeeper`, '--enable-logs', Boolean(enableLogs).toString(), '--unlock-timeout', String(unlockTimeout)];

const beekeeperOptions = new this.provider.StringList();
WALLET_OPTIONS.forEach((opt) => void beekeeperOptions.push_back(opt));

this.api = new this.provider.beekeeper_api(beekeeperOptions);
beekeeperOptions.delete();
safeWasmCall(() => beekeeperOptions.delete());

this.extract(this.api.init() as string);
this.extract(safeWasmCall(() => this.api.init() as string));
}

public createSession(salt: string): IBeekeeperSession {
const { token } = this.extract(this.api.create_session(salt) as string) as { token: string };
const { token } = this.extract(safeWasmCall(() => this.api.create_session(salt) as string)) as { token: string };
const session = new BeekeeperSession(this, token);

this.sessions.set(token, session);
Expand All @@ -64,15 +66,15 @@ export class BeekeeperApi implements IBeekeeperInstance {
if(!this.sessions.delete(token))
throw new BeekeeperError(`This Beekeeper API instance is not the owner of session identified by token: "${token}"`);

this.extract(this.api.close_session(token) as string);
this.extract(safeWasmCall(() => this.api.close_session(token) as string));
}

public async delete(): Promise<void> {
for(const session of this.sessions.values())
await session.close();
session.close();

this.api.delete();
safeWasmCall(() => this.api.delete());

await this.fs.sync();
await safeAsyncWasmCall(() => this.fs.sync());
}
}
3 changes: 2 additions & 1 deletion programs/beekeeper/beekeeper_wasm/src/detailed/beekeeper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type Beekeeper from "../beekeeper.js";

import { BeekeeperApi } from "./api.js";
import { IBeekeeperInstance, IBeekeeperOptions } from "../interfaces.js";
import { safeAsyncWasmCall } from "../util/wasm_error.js";

const DEFAULT_BEEKEEPER_OPTIONS: Omit<IBeekeeperOptions, 'storageRoot'> = {
enableLogs: true,
Expand All @@ -13,7 +14,7 @@ const createBeekeeper = async(
storageRoot: string,
options: Partial<IBeekeeperOptions> = {}
): Promise<IBeekeeperInstance> => {
const beekeeperProvider = await beekeeperContstructor();
const beekeeperProvider = await safeAsyncWasmCall(() => beekeeperContstructor());
const api = new BeekeeperApi(beekeeperProvider);

await api.init({ ...DEFAULT_BEEKEEPER_OPTIONS, storageRoot, ...options });
Expand Down
15 changes: 8 additions & 7 deletions programs/beekeeper/beekeeper_wasm/src/detailed/fs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { BeekeeperModule } from "../beekeeper.js";
import { safeWasmCall } from "../util/wasm_error.js";

export class BeekeeperFileSystem {
public constructor(
Expand All @@ -7,27 +8,27 @@ export class BeekeeperFileSystem {

public sync(): Promise<void> {
return new Promise((resolve, reject) => {
this.fs.syncfs((err?: unknown) => {
safeWasmCall(() => this.fs.syncfs((err?: unknown) => {
if (err) reject(err);

resolve(undefined);
});
}));
});
}

public init(walletDir: string): Promise<void> {
if(!this.fs.analyzePath(walletDir).exists)
this.fs.mkdir(walletDir);
if(!safeWasmCall(() => this.fs.analyzePath(walletDir).exists))
safeWasmCall(() => this.fs.mkdir(walletDir));

if(process.env.ROLLUP_TARGET_ENV === "web")
this.fs.mount(this.fs.filesystems.IDBFS, {}, walletDir);
safeWasmCall(() => this.fs.mount(this.fs.filesystems.IDBFS, {}, walletDir));

return new Promise((resolve, reject) => {
this.fs.syncfs(true, (err?: unknown) => {
safeWasmCall(() => this.fs.syncfs(true, (err?: unknown) => {
if (err) reject(err);

resolve(undefined);
});
}));
});
}
}
15 changes: 8 additions & 7 deletions programs/beekeeper/beekeeper_wasm/src/detailed/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BeekeeperError } from "../errors.js";
import { BeekeeperApi } from "./api.js";
import { IBeekeeperInfo, IBeekeeperInstance, IBeekeeperSession, IBeekeeperWallet, IWalletCreated } from "../interfaces.js";
import { BeekeeperLockedWallet, BeekeeperUnlockedWallet } from "./wallet.js";
import { safeWasmCall } from '../util/wasm_error';

interface IBeekeeperWalletPassword {
password: string;
Expand All @@ -27,13 +28,13 @@ export class BeekeeperSession implements IBeekeeperSession {
public readonly wallets: Map<string, BeekeeperLockedWallet> = new Map();

public getInfo(): IBeekeeperInfo {
const result = this.api.extract(this.api.api.get_info(this.token) as string) as IBeekeeperInfo;
const result = this.api.extract(safeWasmCall(() => this.api.api.get_info(this.token) as string)) as IBeekeeperInfo;

return result;
}

public listWallets(): Array<IBeekeeperWallet> {
const result = this.api.extract(this.api.api.list_wallets(this.token) as string) as IBeekeeperWallets;
const result = this.api.extract(safeWasmCall(() => this.api.api.list_wallets(this.token) as string)) as IBeekeeperWallets;

const wallets: IBeekeeperWallet[] = [];

Expand All @@ -50,16 +51,16 @@ export class BeekeeperSession implements IBeekeeperSession {
}

public hasWallet(name: string): boolean {
const result = this.api.extract(this.api.api.has_wallet(this.token, name) as string) as IBeekeeperHasWallet;
const result = this.api.extract(safeWasmCall(() => this.api.api.has_wallet(this.token, name) as string)) as IBeekeeperHasWallet;

return result.exists;
}

public async createWallet(name: string, password: string | undefined, isTemporary: boolean = false): Promise<IWalletCreated> {
if(typeof password === 'string')
this.api.extract(this.api.api.create(this.token, name, password, isTemporary) as string);
this.api.extract(safeWasmCall(() => this.api.api.create(this.token, name, password as string, isTemporary) as string));
else {
const result = this.api.extract(this.api.api.create(this.token, name) as string) as IBeekeeperWalletPassword;
const result = this.api.extract(safeWasmCall(() => this.api.api.create(this.token, name) as string)) as IBeekeeperWalletPassword;
({ password } = result);
}

Expand All @@ -80,7 +81,7 @@ export class BeekeeperSession implements IBeekeeperSession {
if(this.wallets.has(name))
return this.wallets.get(name) as IBeekeeperWallet;

this.api.extract(this.api.api.open(this.token, name) as string);
this.api.extract(safeWasmCall(() => this.api.api.open(this.token, name) as string));
const wallet = new BeekeeperLockedWallet(this.api, this, name, false);

this.wallets.set(name, wallet);
Expand All @@ -92,7 +93,7 @@ export class BeekeeperSession implements IBeekeeperSession {
if(!this.wallets.delete(name))
throw new BeekeeperError(`This Beekeeper API session is not the owner of wallet identified by name: "${name}"`);

this.api.extract(this.api.api.close(this.token, name) as string);
this.api.extract(safeWasmCall(() => this.api.api.close(this.token, name) as string));
}

public lockAll(): Array<IBeekeeperWallet> {
Expand Down
21 changes: 11 additions & 10 deletions programs/beekeeper/beekeeper_wasm/src/detailed/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BeekeeperApi } from "./api.js";
import { BeekeeperSession } from "./session.js";
import { IBeekeeperUnlockedWallet, IBeekeeperSession, TPublicKey, IBeekeeperWallet, TSignature } from "../interfaces.js";
import { StringList } from "../beekeeper.js";
import { safeWasmCall } from '../util/wasm_error';

interface IImportKeyResponse {
public_key: string;
Expand Down Expand Up @@ -41,42 +42,42 @@ export class BeekeeperUnlockedWallet implements IBeekeeperUnlockedWallet {
}

public lock(): BeekeeperLockedWallet {
this.api.extract(this.api.api.lock(this.session.token, this.locked.name) as string);
this.api.extract(safeWasmCall(() => this.api.api.lock(this.session.token, this.locked.name) as string));
this.locked.unlocked = undefined;

return this.locked;
}

public async importKey(wifKey: string): Promise<TPublicKey> {
const { public_key } = this.api.extract(this.api.api.import_key(this.session.token, this.locked.name, wifKey) as string) as IImportKeyResponse;
const { public_key } = this.api.extract(safeWasmCall(() => this.api.api.import_key(this.session.token, this.locked.name, wifKey) as string)) as IImportKeyResponse;

await this.api.fs.sync();

return public_key;
}

public async importKeys(wifKeys: StringList): Promise<TPublicKey> {
const { public_key } = this.api.extract(this.api.api.import_keys(this.session.token, this.locked.name, wifKeys) as string) as IImportKeyResponse;
const { public_key } = this.api.extract(safeWasmCall(() => this.api.api.import_keys(this.session.token, this.locked.name, wifKeys) as string)) as IImportKeyResponse;

await this.api.fs.sync();

return public_key;
}

public async removeKey(publicKey: TPublicKey): Promise<void> {
this.api.extract(this.api.api.remove_key(this.session.token, this.locked.name, publicKey) as string);
this.api.extract(safeWasmCall(() => this.api.api.remove_key(this.session.token, this.locked.name, publicKey) as string));

await this.api.fs.sync();
}

public signDigest(publicKey: string, sigDigest: string): TSignature {
const result = this.api.extract(this.api.api.sign_digest(this.session.token, sigDigest, publicKey) as string) as IBeekeeperSignature;
const result = this.api.extract(safeWasmCall(() => this.api.api.sign_digest(this.session.token, sigDigest, publicKey) as string)) as IBeekeeperSignature;

return result.signature;
}

public getPublicKeys(): TPublicKey[] {
const result = this.api.extract(this.api.api.get_public_keys(this.session.token) as string) as IBeekeeperKeys;
const result = this.api.extract(safeWasmCall(() => this.api.api.get_public_keys(this.session.token) as string)) as IBeekeeperKeys;

return result.keys.map(value => value.public_key);
}
Expand All @@ -85,17 +86,17 @@ export class BeekeeperUnlockedWallet implements IBeekeeperUnlockedWallet {
let call_result;

if(typeof nonce === 'number')
call_result = this.api.api.encrypt_data(this.session.token, key, anotherKey || key, this.locked.name, content, nonce);
call_result = safeWasmCall(() => this.api.api.encrypt_data(this.session.token, key, anotherKey || key, this.locked.name, content, nonce));
else
call_result = this.api.api.encrypt_data(this.session.token, key, anotherKey || key, this.locked.name, content);
call_result = safeWasmCall(() => this.api.api.encrypt_data(this.session.token, key, anotherKey || key, this.locked.name, content));

const result = this.api.extract(call_result) as IEncryptData;

return result.encrypted_content;
}

public decryptData(content: string, key: TPublicKey, anotherKey?: TPublicKey): string {
const result = this.api.extract(this.api.api.decrypt_data(this.session.token, key, anotherKey || key, this.locked.name, content)) as IDecryptData;
const result = this.api.extract(safeWasmCall(() => this.api.api.decrypt_data(this.session.token, key, anotherKey || key, this.locked.name, content))) as IDecryptData;

return result.decrypted_content;
}
Expand All @@ -116,7 +117,7 @@ export class BeekeeperLockedWallet implements IBeekeeperWallet {
) {}

public unlock(password: string): IBeekeeperUnlockedWallet {
this.api.extract(this.api.api.unlock(this.session.token, this.name, password) as string);
this.api.extract(safeWasmCall(() => this.api.api.unlock(this.session.token, this.name, password) as string));
this.unlocked = new BeekeeperUnlockedWallet(this.api, this.session, this);

return this.unlocked;
Expand Down
17 changes: 17 additions & 0 deletions programs/beekeeper/beekeeper_wasm/src/util/wasm_error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BeekeeperError } from "../errors.js";

export const safeWasmCall = <T extends () => any>(fn: T): ReturnType<T> => {
try {
return fn()
} catch (error) {
throw new BeekeeperError(`Error during Wasm call: ${error instanceof Error ? error.message : error}`);
}
};

export const safeAsyncWasmCall = async <T extends () => any>(fn: T): Promise<ReturnType<T>> => {
try {
return await fn();
} catch (error) {
throw new BeekeeperError(`Error during Wasm call: ${error instanceof Error ? error.message : error}`);
}
};

0 comments on commit f606c7b

Please sign in to comment.