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

Connector: disconnects #1994

Merged
merged 3 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 18 additions & 7 deletions packages/yoroi-ergo-connector/example/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import * as wasm from "ergo-lib-wasm-browser";

if (typeof ergo_request_read_access === "undefined") {
alert("ergo not found");
} else {
console.log("ergo found")
function initDapp() {
ergo_request_read_access().then(function(access_granted) {
if (!access_granted) {
//alert("ergo access denied");
Expand All @@ -13,9 +10,6 @@ if (typeof ergo_request_read_access === "undefined") {
const status = document.getElementById("status");
status.innerText = "Wallet successfully connected";
console.log("ergo access given");
window.addEventListener("ergo_wallet_disconnected", function(event) {
alert("wallet disconnected");
});
// ergo.get_unused_addresses().then(function(addresses) {
// //console.log(`get_unused_addresses() = {`);
// // for (const address of addresses) {
Expand Down Expand Up @@ -140,3 +134,20 @@ if (typeof ergo_request_read_access === "undefined") {
}
});
}

if (typeof ergo_request_read_access === "undefined") {
alert("ergo not found");
} else {
console.log("ergo found");
window.addEventListener("ergo_wallet_disconnected", function(event) {
const status = document.getElementById("status");
status.innerText = "";
const div = document.getElementById("balance");
div.innerText = "Wallet disconnected.";
const button = document.createElement("button");
button.textContent = "Reconnect";
button.onclick = initDapp;
div.appendChild(button);
});
initDapp();
}
156 changes: 96 additions & 60 deletions packages/yoroi-ergo-connector/src/inject.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
// sets up RPC communication with the connector + access check/request functions
const initialInject = `
var timeout = 0;

var connectRequests = [];
var ergoConnectRequests = [];

window.addEventListener("message", function(event) {
if (event.data.type == "connector_connected") {
if (event.data.err !== undefined) {
connectRequests.forEach(promise => promise.reject(event.data.err));
ergoConnectRequests.forEach(promise => promise.reject(event.data.err));
} else {
connectRequests.forEach(promise => promise.resolve(event.data.success));
ergoConnectRequests.forEach(promise => promise.resolve(event.data.success));
}
}
});

function ergo_request_read_access() {
if (typeof ergo !== "undefined") {
return Promise.resolve(true);
} else {
return new Promise(function(resolve, reject) {
window.postMessage({
type: "connector_connect_request",
}, location.origin);
connectRequests.push({ resolve: resolve, reject: reject });
});
}
return new Promise(function(resolve, reject) {
window.postMessage({
type: "connector_connect_request",
}, location.origin);
ergoConnectRequests.push({ resolve: resolve, reject: reject });
});
}

function ergo_check_read_access() {
Expand All @@ -34,39 +28,18 @@ function ergo_check_read_access() {
return Promise.resolve(false);
}
}

// TODO: fix or change back how RPCs work
// // disconnect detector
// setInterval(function() {
// if (timeout == 20) {
// window.dispatchEvent(new Event("ergo_wallet_disconnected"));
// }
// if (timeout == 25) {
// rpcResolver.forEach(function(rpc) {
// rpc.reject("timed out");
// });
// }
// timeout += 1;
// }, 1000);

// // ping sender
// setInterval(function() {
// _ergo_rpc_call("ping", []).then(function() {
// timeout = 0;
// });
// }, 10000);
`

// client-facing ergo object API
const apiInject = `
// RPC set-up
var rpcUid = 0;
var rpcResolver = new Map();
var ergoRpcUid = 0;
var ergoRpcResolver = new Map();

window.addEventListener("message", function(event) {
if (event.data.type == "connector_rpc_response") {
console.log("page received from connector: " + JSON.stringify(event.data) + " with source = " + event.source + " and origin = " + event.origin);
const rpcPromise = rpcResolver.get(event.data.uid);
const rpcPromise = ergoRpcResolver.get(event.data.uid);
if (rpcPromise !== undefined) {
const ret = event.data.return;
if (ret.err !== undefined) {
Expand Down Expand Up @@ -126,19 +99,21 @@ class ErgoAPI {
return new Promise(function(resolve, reject) {
window.postMessage({
type: "connector_rpc_request",
uid: rpcUid,
uid: ergoRpcUid,
function: func,
params: params
}, location.origin);
console.log("rpcUid = " + rpcUid);
rpcResolver.set(rpcUid, { resolve: resolve, reject: reject });
rpcUid += 1;
console.log("ergoRpcUid = " + ergoRpcUid);
ergoRpcResolver.set(ergoRpcUid, { resolve: resolve, reject: reject });
ergoRpcUid += 1;
});
}
}

const ergo = Object.freeze(new ErgoAPI());
`
const API_INTERNAL_ERROR = -2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three things:

  • Maybe these error codes should be documented somewhere?
  • Maybe these should should be in an enum instead of two separate variables
  • Why does it start at -2 instead of -1? Is -1 used for something else? Maybe document this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ergoplatform/eips#23 they're documented here in the EIP-0012 spec. They do start at -1, but we just don't reject things for InvalidRequest: -1 in the connector since input data verification happens elsewhere.

const API_REFUSED = -3;

function injectIntoPage(code) {
try {
Expand All @@ -164,24 +139,37 @@ function shouldInject() {
return docElemCheck && docTypeCheck;
}

if (shouldInject()) {
console.log(`content script injected into ${location.hostname}`);
injectIntoPage(initialInject);
let yoroiPort = null;
let fullApiInjected = false;

function disconnectWallet() {
yoroiPort = null;
window.dispatchEvent(new Event("ergo_wallet_disconnected"));
}

function createYoroiPort() {
// events from Yoroi
let yoroiPort = chrome.runtime.connect(extensionId);
yoroiPort = chrome.runtime.connect(extensionId);
yoroiPort.onMessage.addListener(message => {
//alert("content script message: " + JSON.stringify(message));
if (message.type == "connector_rpc_response") {
window.postMessage(message, location.origin);
} else if (message.type == "yoroi_connect_response") {
if (message.success) {
// inject full API here
if (injectIntoPage(apiInject)) {
chrome.runtime.sendMessage({type: "init_page_action"});
} else {
alert("failed to inject Ergo API");
// TODO: return an error instead here if injection fails?
if (!fullApiInjected) {
// inject full API here
if (injectIntoPage(apiInject)) {
fullApiInjected = true;
} else {
console.error()
window.postMessage({
type: "connector_connected",
err: {
code: API_INTERNAL_ERROR,
info: "failed to inject Ergo API"
}
}, location.origin);
}
}
}
window.postMessage({
Expand All @@ -191,18 +179,66 @@ if (shouldInject()) {
}
});

yoroiPort.onDisconnect.addListener(event => {
disconnectWallet();
});
}

if (shouldInject()) {
console.log(`content script injected into ${location.hostname}`);
injectIntoPage(initialInject);

// events from page (injected code)
window.addEventListener("message", function(event) {
if (event.data.type === "connector_rpc_request") {
console.log("connector received from page: " + JSON.stringify(event.data) + " with source = " + event.source + " and origin = " + event.origin);
yoroiPort.postMessage(event.data);
if (yoroiPort) {
try {
yoroiPort.postMessage(event.data);
return;
} catch (e) {
console.error(`Could not send RPC to Yoroi: ${e}`);
window.postMessage({
type: "connector_rpc_response",
uid: event.data.uid,
return: {
err: {
code: API_INTERNAL_ERROR,
info: `Could not send RPC to Yoroi: ${e}`
}
}
}, location.origin);
}
} else {
window.postMessage({
type: "connector_rpc_response",
uid: event.data.uid,
return: {
err: {
code: API_REFUSED,
info: 'Wallet disconnected'
}
}
}, location.origin);
}
} else if (event.data.type == "connector_connect_request") {
// URL must be provided here as the url field of Tab is only available
// with the "tabs" permission which Yoroi doesn't have
yoroiPort.postMessage({
type: "yoroi_connect_request",
url: location.hostname
});
if (fullApiInjected && yoroiPort) {
// we can skip communication - API injected + hasn't been disconnected
window.postMessage({
type: "connector_connected",
success: true
}, location.origin);
} else {
if (yoroiPort == null) {
createYoroiPort();
}
// URL must be provided here as the url field of Tab is only available
// with the "tabs" permission which Yoroi doesn't have
yoroiPort.postMessage({
type: "yoroi_connect_request",
url: location.hostname,
});
}
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,10 @@ export default class ConnectorStore extends Store<StoresMap, ActionsMap> {
whitelist: filter,
});
await this.getConnectorWhitelist.execute();
window.chrome.runtime.sendMessage({
type: 'remove_wallet_from_whitelist',
url,
});
};

_refreshActiveSites: void => Promise<void> = async () => {
Expand Down
22 changes: 20 additions & 2 deletions packages/yoroi-extension/chrome/extension/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,13 @@ type ConnectedSite = {|
|};


// tab id key
const connectedSites: Map<number, ConnectedSite> = new Map();
type TabId = number;

const connectedSites: Map<TabId, ConnectedSite> = new Map();

// tabid => chrome.runtime.Port
const ports: Map<TabId, any> = new Map();


let pendingTxs: PendingTransaction[] = [];

Expand Down Expand Up @@ -197,6 +202,7 @@ async function getSelectedWallet(tabId: number): Promise<PublicDeriver<>> {
return Promise.reject(new Error(`could not find tabId ${tabId} in connected sites`));
}

// messages from other parts of Yoroi (i.e. the UI for the connector)
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
async function signTxInputs(
tx,
Expand Down Expand Up @@ -296,6 +302,17 @@ chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
}
}
sendResponse(null);
} else if (request.type === 'remove_wallet_from_whitelist') {
for (const [tabId, site] of connectedSites) {
if (site.url === request.url) {
const port = ports.get(tabId);
if (port) {
port.disconnect();
ports.delete(tabId);
}
break;
}
}
} else if (request.type === 'get_connected_sites') {
const activeSites = []
for (const value of connectedSites.values()){
Expand Down Expand Up @@ -383,6 +400,7 @@ chrome.runtime.onMessageExternal.addListener((message, sender) => {
chrome.runtime.onConnectExternal.addListener(port => {
if (port.sender.id === environment.ergoConnectorExtensionId) {
const tabId = port.sender.tab.id;
ports.set(tabId, port);
port.onMessage.addListener(async message => {
function rpcResponse(response) {
port.postMessage({
Expand Down