Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extension provider #59

Merged
merged 17 commits into from
Aug 20, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 212 additions & 0 deletions src/dapp/extensionProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import { SignableMessage } from "../signableMessage";
import { Transaction } from "../transaction";
import { IDappProvider } from "./interface";

declare global {
interface Window {
elrondWallet: { extensionId: string };
}
}

export class ExtensionProvider implements IDappProvider {
private popupName = "connectPopup";
private popupOptions =
"directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,width=375,height=569";

private extensionId: string = "";
private extensionURL: string = "";
private extensionPopupWindow: Window | null;
public account: any;
private initialized: boolean = false;

constructor() {
this.extensionPopupWindow = null;
this.init().then();
}

async init(): Promise<boolean> {
if (window && window.elrondWallet) {
this.extensionId = window.elrondWallet.extensionId;
this.extensionURL = `chrome-extension://${this.extensionId}/index.html`;
this.initialized = true;
}
return this.initialized;
}

async login(
options: {
callbackUrl?: string;
token?: string;
} = {}
): Promise<string> {
if (!this.initialized) {
throw new Error("Wallet provider is not initialised, call init() first");
}
this.openExtensionPopup();
const { token } = options;
const data = token ? token : "";
await this.startExtMsgChannel("connect", data);
return this.account.address;
}

async logout(): Promise<boolean> {
if (!this.initialized) {
throw new Error("Wallet provider is not initialised, call init() first");
}
return await this.startBgrMsgChannel("logout", this.account.address);
}

async getAddress(): Promise<string> {
if (!this.initialized) {
throw new Error("Wallet provider is not initialised, call init() first");
}
return this.account.address;
}

isInitialized(): boolean {
return this.initialized;
}

async isConnected(): Promise<boolean> {
return this.account ? true : false;
}

async sendTransaction(transaction: Transaction): Promise<Transaction> {
return (await this.processTransactions([transaction], false))[0];
}
async sendTransactions(
transactions: Array<Transaction>
): Promise<Array<Transaction>> {
return await this.processTransactions(transactions, false);
}

async signTransaction(transaction: Transaction): Promise<Transaction> {
return (await this.processTransactions([transaction], true))[0];
}

async signTransactions(
transactions: Array<Transaction>
): Promise<Array<Transaction>> {
return await this.processTransactions(transactions, true);
}

async signMessage(message: SignableMessage): Promise<SignableMessage> {
this.openExtensionPopup();
const data = {
account: this.account.index,
message: message.message,
};
return await this.startExtMsgChannel("signMessage", data);
}

private openExtensionPopup() {
if (!this.initialized) {
throw new Error("Wallet provider is not initialised, call init() first");
}
this.extensionPopupWindow = window.open(
this.extensionURL,
this.popupName,
this.popupOptions
);
}

private startBgrMsgChannel(
operation: string,
connectData: any
): Promise<any> {
return new Promise((resolve, reject) => {
window.postMessage(
{
target: "erdw-inpage",
type: operation,
data: connectData,
},
window.origin
);

const eventHandler = (event: any) => {
if (
event.isTrusted &&
event.data.type &&
event.data.target === "erdw-contentScript"
) {
switch (event.data.type) {
case "logoutResponse":
window.removeEventListener("message", eventHandler);
resolve(true);
break;
}
}
};
setTimeout(() => {
reject(
"Extension logout response timeout. No response from extension."
);
}, 3000);
window.addEventListener("message", eventHandler, false);
});
}

private startExtMsgChannel(
operation: string,
connectData: any
): Promise<any> {
return new Promise((resolve, reject) => {
var isResolved = false;
const eventHandler = (event: any) => {
if (
event.isTrusted &&
event.data.type &&
event.data.target === "erdw-extension"
) {
switch (event.data.type) {
case "popupReady":
event.ports[0].postMessage({
target: "erdw-inpage",
type: operation,
data: connectData,
});
break;
case "connectResult":
this.extensionPopupWindow?.close();
this.account = event.data.data;
window.removeEventListener("message", eventHandler);
isResolved = true;
resolve(event.data.data);
break;
default:
this.extensionPopupWindow?.close();
window.removeEventListener("message", eventHandler);
isResolved = true;
resolve(event.data.data);
break;
}
}
};
var windowCloseInterval = setInterval(() => {
if (this.extensionPopupWindow?.closed) {
window.removeEventListener("message", eventHandler);
clearInterval(windowCloseInterval);
if (!isResolved)
reject("Extension window was closed without response.");
}
}, 500);

window.addEventListener("message", eventHandler, false);
});
}

async processTransactions(
transactions: Array<Transaction>,
signOnly: boolean
): Promise<Array<Transaction>> {
this.openExtensionPopup();
const data = {
from: this.account.index,
transactions: transactions,
signOnly,
};

return await this.startExtMsgChannel("transaction", data);
}
}
1 change: 1 addition & 0 deletions src/dapp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from "./interface";
export * from "./hwProvider";
export * from "./walletProvider";
export * from "./walletConnectProvider";
export * from "./extensionProvider";