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

feat: add connect per site into the web extension #3626

Merged
merged 1 commit into from
Nov 29, 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
141 changes: 118 additions & 23 deletions applications/tari_web_extension/public/background.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,136 @@
console.log("background.js execute");

let selectedAsset = undefined;
let credentials = undefined;
let accounts = []; // Accounts registered
let triggered_port = []; // All the triggered ports
let connecting = {}; // Callbacks for the connect requests

const login = () => ({ successful: true, token: "token" });
// I'm not sure if this changes yet
const extension_url = "chrome-extension://kljpkkadbelknhmpabjkkhmljaebnopo";

const getAssets = () => ({
let allowedOrigins = {
[extension_url]: true,
};

const createAccount = (origin) => {
const account = "0xDEADBEEF";
accounts = [account];
return { successful: true, accounts };
};

const connect = (origin, sendResponse) => {
// This will popup a connect request, we store the callback
let callback_id = Math.random();
connecting[callback_id] = { origin, sendResponse };
window.open(
`${extension_url}/index.html#/connecting/${btoa(origin)}/${callback_id}`,
"_blank",
"width=400,height=600,titlebar=no,status=no,scrollbars=no,resizable=no,menubar=no,toolbar=no"
);
};

const connectSite = (origin, callback_id) => {
// Call the stored callback from connect function
connecting?.[callback_id]?.sendResponse?.({
successful: true,
payload: 0xdeadbeef,
});
// Add origin to allowed urls
const original_origin = connecting?.[callback_id]?.origin;
console.log("adding to allowed origins", original_origin);
if (original_origin) {
allowedOrigins[original_origin] = true;
}
triggered_port.forEach(({ port, origin }) => {
// We send it only to the port that belongs to this origin
if (origin === original_origin) {
port.postMessage({
event: "accountChange",
payload: { accounts },
});
}
});
};

const getConnectedSites = (origin) => {
if (origin in allowedOrigins) {
return {
successful: true,
// We don't want to expose the permission for the extension itself
sites: Object.keys(allowedOrigins)
.filter((site) => site !== extension_url)
.reduce(
(filtered, site) => ({ ...filtered, [site]: allowedOrigins[site] }),
{}
),
};
}
return { successful: false, sites: {} };
};

const getAccounts = (origin) => {
if (origin in allowedOrigins)
return { successful: true, payload: { accounts } };
else return { successful: false, payload: { accounts: [] } };
};

const getAssets = (origin) => ({
successful: true,
assets: ["asset1", "asset2"],
selected: selectedAsset,
});

const selectAsset = (request) => {
const selectAsset = (origin, request) => {
selectedAsset = request.name;
return { successful: true, selected: selectedAsset };
return { successful: true, payload: { selected: selectedAsset } };
};

const getSelectedAsset = () => ({ successful: true, selected: selectedAsset });

const loginRefresh = () => ({ successful: !!credentials, token: credentials });
const getSelectedAsset = (origin) => ({
successful: true,
payload: { selected: selectedAsset },
});

const getSeedWords = () => ({
const getSeedWords = (origin) => ({
successful: true,
seedWords:
"theme panther ladder custom field aspect misery shine bundle worry senior velvet brush tourist glide jump example vanish embody enemy struggle air extend empty",
payload: {
seedWords:
"theme panther ladder custom field aspect misery shine bundle worry senior velvet brush tourist glide jump example vanish embody enemy struggle air extend empty",
},
});

function messageCallback(request, sender, sendResponse) {
console.log(request);
const { origin } = sender;
console.log("messageCallback", request, origin);
switch (request?.action) {
case "tari-login":
credentials = "token";
sendResponse(login());
case "tari-create-account":
sendResponse(createAccount(origin));
break;
case "tari-connect": // Connecting app to the web extension
connect(origin, sendResponse);
break;
case "tari-connect-site":
connectSite(origin, request?.callback_id);
break;
case "tari-get-connected-sites":
sendResponse(getConnectedSites(origin));
break;
case "tari-get-accounts":
sendResponse(getAccounts(origin));
break;
case "tari-get-assets":
sendResponse(getAssets());
sendResponse(getAssets(origin));
break;
case "tari-select-asset":
sendResponse(selectAsset(request));
sendResponse(selectAsset(origin, request));
break;
case "tari-get-selected-asset":
sendResponse(getSelectedAsset());
sendResponse(getSelectedAsset(origin));
break;
case "tari-login-refresh":
sendResponse(loginRefresh());
sendResponse(loginRefresh(origin));
break;
case "tari-get-seedwords":
sendResponse(getSeedWords());
sendResponse(getSeedWords(origin));
break;
default:
console.log("unknown message", request?.action);
Expand All @@ -55,15 +139,26 @@ function messageCallback(request, sender, sendResponse) {
}

chrome.runtime.onConnect.addListener(function (port) {
if (port.name === "tari-port") {
port.onMessage.addListener(function (data) {
messageCallback(data, {}, (response) => {
if (port.name === "tari-port-injected") {
// We received message from the content.js script.
port.onMessage.addListener(function ({ data, origin }) {
messageCallback(data, { origin: origin }, (response) => {
port.postMessage({
...response,
id: data.id,
});
});
});
} else if (port.name === "tari-port-triggered") {
port.onDisconnect.addListener(function (delete_port) {
triggered_port = triggered_port.filter(
({ port, _origin }) => port !== delete_port
);
});
port.onMessage.addListener(function (origin) {
// Add ports once we know it origin
triggered_port.push({ port, origin });
});
}
});

Expand Down
91 changes: 70 additions & 21 deletions applications/tari_web_extension/public/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ console.log("content.js executed");
function script() {
// Injected script into the website, to add window.tari
let promises = {};

function createPromise(payload, id) {
return new Promise((resolve, reject) => {
promises[id] = { resolve, reject };
window.postMessage({
id: id,
...payload,
});
});
}

function createTimeoutPromise(payload, timeout_ms = 1000) {
let id = Math.random();
const timeout = new Promise((_resolve, reject) =>
Expand All @@ -11,38 +22,62 @@ function script() {
reject("timed out");
}, timeout_ms)
);
const response = new Promise((resolve, reject) => {
promises[id] = { resolve, reject };
window.postMessage({
id: id,
...payload,
});
});
return Promise.race([timeout, response]);
return Promise.race([timeout, createPromise(payload, id)]);
}

class Tari {
constructor() {
console.log("initiating tari");
this.listeners = {};
}
connect() {
return createPromise({ action: "tari-connect" });
}
checkAccounts() {
return createTimeoutPromise({ action: "tari-get-accounts" });
}
getSelectedAsset(timeout_ms = 1000) {
return createTimeoutPromise(
{ action: "tari-get-selected-asset" },
timeout_ms
);
}
on(event, listener) {
// Add listener for background events.
if (!(event in this.listeners)) {
this.listeners[event] = listener;
}
}
triggerEvent(event, payload) {
// Trigger event from background process.
console.log("triggering event", event, "with payload", payload);
this.listeners[event]?.(payload);
}
}

window.tari = new Tari();

const handleBackgroundResponse = (data) => {
if (promises?.[data?.id]) {
console.log("handling response", data);
promises[data.id].resolve(data.payload);
delete promises[data.id];
}
};
const handleBackgroundEvent = ({ event, payload }) => {
window.tari.triggerEvent(event, payload);
};

window.addEventListener("message", (event) => {
if (
event?.data?.action === "tari-background-response" &&
promises?.[event?.data?.id]
) {
promises[event.data.id].resolve(event?.data?.selected);
delete promises[event.data.id];
switch (event?.data?.action) {
case "tari-background-response":
handleBackgroundResponse(event?.data?.payload);
break;
case "tari-background-event":
handleBackgroundEvent(event?.data?.payload);
break;
}
});
window.tari = new Tari();
console.log("injecting tari");
// The end of injected script
}
Expand All @@ -55,28 +90,42 @@ function inject(fn) {

inject(script);

let port = chrome.runtime.connect({ name: "tari-port" });
// We open two ports. One is for function calls from the injected script
let portInjected = chrome.runtime.connect({ name: "tari-port-injected" });
// The other is from event triggered on the background script
let portTriggered = chrome.runtime.connect({ name: "tari-port-triggered" });
// We need to tell background.js where is this port coming from
portTriggered.postMessage(window.location.origin);

// This listeners is catching message from the injected script and from content.js as well.
// So we need to check what types of message is it.
window.addEventListener(
// Receive message from injected script and resend it to the background.js
"message",
(event) => {
if (event.source != window) {
return;
}
if (
event?.data?.action?.startsWith("tari-") &&
event?.data?.action !== "tari-background-response"
event?.data?.action !== "tari-background-event" && // tari-background-event and tari-background-response ..
event?.data?.action !== "tari-background-response" // .. are handled in the injected script
) {
port.postMessage(event.data);
// If we received a tari-message, and it's not response message, send it to the background.js
portInjected.postMessage({ data: event.data, origin: event.origin });
}
},
false
);

port.onMessage.addListener(function (payload) {
portInjected.onMessage.addListener(function (payload) {
// Receive message from background.js and resend to the injected script
console.log("Received from background", payload);
window.postMessage({
action: "tari-background-response",
...payload,
payload,
});
});

portTriggered.onMessage.addListener(function (payload) {
window.postMessage({ action: "tari-background-event", payload });
});
21 changes: 11 additions & 10 deletions applications/tari_web_extension/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,33 @@ import "./app.scss";
import React, { useEffect } from "react";
import { Navigate, Route, Routes } from "react-router";
import { useDispatch, useSelector } from "react-redux";
import {
getCredentials,
getCredentialsCalled,
refreshLogin,
} from "./redux/loginSlice";
import Onboarding from "./onboarding/Onboarding";
import { HashRouter } from "react-router-dom";
import Popup from "./popup/Popup";
import { getAccounts, getAccountsStatusSelector } from "./redux/accountSlice";
import Connecting from "./connecting/Connecting";

export default function App() {
const credentials = useSelector(getCredentials);
const credentialsCalled = useSelector(getCredentialsCalled);
const accountsStatus = useSelector(getAccountsStatusSelector);

const dispatch = useDispatch();
useEffect(() => {
dispatch(refreshLogin());
dispatch(getAccounts());
}, [dispatch]);
if (!credentials) {
if (!window.location.href.includes("#/onboarding") && credentialsCalled) {
console.log("accountsStatus", accountsStatus);
if (accountsStatus === "empty") {
if (!window.location.href.includes("#/onboarding")) {
window.open("#/onboarding");
window.close();
return <div></div>;
}
}
return (
<div className="main">
<HashRouter>
<Routes>
<Route path="/onboarding/*" element={<Onboarding />} />
<Route path="/connecting/:site/:id" element={<Connecting />} />
<Route path="/popup/*" element={<Popup />} />
<Route path="" element={<Navigate replace to="popup" />} />
</Routes>
Expand Down
Loading