Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14864844
D8734.id26539.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Subscribers
None
D8734.id26539.diff
View Options
diff --git a/web/cashtab/extension/public/manifest.json b/web/cashtab/extension/public/manifest.json
--- a/web/cashtab/extension/public/manifest.json
+++ b/web/cashtab/extension/public/manifest.json
@@ -4,7 +4,18 @@
"name": "CashTab",
"description": "A browser-integrated BCHA wallet from Bitcoin ABC",
"version": "0.0.1",
-
+ "content_scripts": [
+ {
+ "matches": ["file://*/*", "http://*/*", "https://*/*"],
+ "js": ["contentscript.js"],
+ "run_at": "document_idle",
+ "all_frames": true
+ }
+ ],
+ "background": {
+ "scripts": ["background.js"],
+ "persistent": false
+ },
"browser_action": {
"default_popup": "index.html",
"default_title": "CashTab"
diff --git a/web/cashtab/extension/src/background.js b/web/cashtab/extension/src/background.js
new file mode 100644
--- /dev/null
+++ b/web/cashtab/extension/src/background.js
@@ -0,0 +1,113 @@
+const NOTIFICATION_HEIGHT = 600;
+const NOTIFICATION_WIDTH = 400;
+
+let popupIsOpen = false;
+let notificationIsOpen = false;
+const openMetamaskTabsIDs = {};
+const requestAccountTabIds = {};
+
+// This starts listening to the port created with `chrome.runtime.connect` in contentscript.js
+chrome.runtime.onConnect.addListener(function (port) {
+ console.assert(port.name == 'cashtabPort');
+ port.onMessage.addListener(function (msg) {
+ console.log('msg received in background.js');
+ console.log(msg.text);
+ if (msg.text == `CashTab` && msg.txInfo) {
+ console.log(`Caught, opening popup`);
+ triggerUi(msg.txInfo);
+ }
+ });
+});
+
+/**
+ * Opens the browser popup for user confirmation
+ */
+/*
+Breaking this function down
+1) Get all active tabs in browser
+2) Determine if the extension UI is currently open
+3) If extension is not open AND no other UI triggered popups are open, then open one
+
+Eventually will need similar model. Note that it actually goes much deeper than this in MetaMask.
+
+To start, just open a popup
+*/
+async function triggerUi(txInfo) {
+ /*
+ const tabs = await chrome.getActiveTabs();
+ const currentlyActiveCashtabTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id]));
+ if (!popupIsOpen && !currentlyActiveCashtabTab) {
+ await notificationManager.showPopup();
+ }
+ */
+ // Open a pop-up
+ let left = 0;
+ let top = 0;
+ try {
+ const lastFocused = await getLastFocusedWindow();
+ // Position window in top right corner of lastFocused window.
+ top = lastFocused.top;
+ left = lastFocused.left + (lastFocused.width - NOTIFICATION_WIDTH);
+ } catch (_) {
+ // The following properties are more than likely 0, due to being
+ // opened from the background chrome process for the extension that
+ // has no physical dimensions
+ const { screenX, screenY, outerWidth } = window;
+ top = Math.max(screenY, 0);
+ left = Math.max(screenX + (outerWidth - NOTIFICATION_WIDTH), 0);
+ }
+
+ console.log(`txInfo`);
+ console.log(txInfo);
+
+ const queryString = Object.keys(txInfo)
+ .map(key => key + '=' + txInfo[key])
+ .join('&');
+
+ // create new notification popup
+ const popupWindow = await openWindow({
+ url: `index.html#/send?${queryString}`,
+ type: 'popup',
+ width: NOTIFICATION_WIDTH,
+ height: NOTIFICATION_HEIGHT,
+ left,
+ top,
+ });
+}
+
+async function openWindow(options) {
+ return new Promise((resolve, reject) => {
+ chrome.windows.create(options, newWindow => {
+ const error = checkForError();
+ if (error) {
+ return reject(error);
+ }
+ return resolve(newWindow);
+ });
+ });
+}
+
+function checkForError() {
+ const { lastError } = chrome.runtime;
+ if (!lastError) {
+ return undefined;
+ }
+ // if it quacks like an Error, its an Error
+ if (lastError.stack && lastError.message) {
+ return lastError;
+ }
+ // repair incomplete error object (eg chromium v77)
+ return new Error(lastError.message);
+}
+
+async function getLastFocusedWindow() {
+ return new Promise((resolve, reject) => {
+ chrome.windows.getLastFocused(windowObject => {
+ const error = checkForError();
+ if (error) {
+ return reject(error);
+ }
+ return resolve(windowObject);
+ });
+ });
+}
diff --git a/web/cashtab/extension/src/contentscript.js b/web/cashtab/extension/src/contentscript.js
new file mode 100644
--- /dev/null
+++ b/web/cashtab/extension/src/contentscript.js
@@ -0,0 +1,33 @@
+// Insert flag into window object to denote CashTab is available and active as a browser extension
+// Could use a div or other approach for now, but emulate MetaMask this way so it is extensible to other items
+// Try window object approach
+var cashTabInject = document.createElement('script');
+cashTabInject.innerHTML = `window.bitcoinAbc = 'cashtab'`;
+document.head.appendChild(cashTabInject);
+
+// Process page messages
+// Chrome extensions communicate with web pages through the DOM
+// Page sends a message to itself, chrome extension intercepts it
+var port = chrome.runtime.connect({ name: 'cashtabPort' });
+//console.log(`port: ${JSON.stringify(port)}`);
+//console.log(port);
+
+window.addEventListener(
+ 'message',
+ function (event) {
+ if (typeof event.data.text !== 'undefined') {
+ console.log('Message received:');
+ console.log(event.data.text);
+ }
+
+ // We only accept messages from ourselves
+ if (event.source != window) return;
+
+ if (event.data.type && event.data.type == 'FROM_PAGE') {
+ console.log(event);
+ console.log('Content script received: ' + event.data.text);
+ port.postMessage(event.data);
+ }
+ },
+ false,
+);
diff --git a/web/cashtab/scripts/extension.sh b/web/cashtab/scripts/extension.sh
--- a/web/cashtab/scripts/extension.sh
+++ b/web/cashtab/scripts/extension.sh
@@ -43,6 +43,10 @@
# Copy extension build files to extension/ folder
cp -r build/* extension/dist
+# Copy other needed extension files
+cp extension/src/contentscript.js extension/dist
+cp extension/src/background.js extension/dist
+
# Delete extension build from build/ folder (reserved for web app builds)
rm -Rf build
diff --git a/web/cashtab/src/components/App.css b/web/cashtab/src/components/App.css
--- a/web/cashtab/src/components/App.css
+++ b/web/cashtab/src/components/App.css
@@ -131,7 +131,6 @@
/* .ant-radio-group-solid .ant-radio-button-wrapper {
margin-top: 0px;
}
-
.ant-radio-group-solid .ant-radio-button-wrapper-checked {
border: none !important;
box-shadow: none !important;
@@ -153,7 +152,6 @@
/* .ant-radio-group-solid .ant-radio-button-wrapper-checked {
background: #ff8d00 !important;
}
-
.ant-radio-group.ant-radio-group-solid.ant-radio-group-small {
font-size: 14px !important;
font-weight: 600 !important;
@@ -235,7 +233,6 @@
border: 1px solid #17171f;
border-radius: 0;
}
-
.ant-alert-message {
color: #fff;
}
@@ -312,7 +309,6 @@
/* .ant-radio-button-wrapper {
border: none;
}
-
.ant-radio-button-wrapper-checked {
border-radius: none !important;
} */
diff --git a/web/cashtab/src/components/App.js b/web/cashtab/src/components/App.js
--- a/web/cashtab/src/components/App.js
+++ b/web/cashtab/src/components/App.js
@@ -124,15 +124,12 @@
margin-bottom: 20px;
justify-content: space-between;
border-bottom: 1px solid #e2e2e2;
-
a {
color: #848484;
-
:hover {
color: #ff8d00;
}
}
-
@media (max-width: 768px) {
a {
font-size: 12px;
diff --git a/web/cashtab/src/components/Send/Send.js b/web/cashtab/src/components/Send/Send.js
--- a/web/cashtab/src/components/Send/Send.js
+++ b/web/cashtab/src/components/Send/Send.js
@@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { WalletContext } from '@utils/context';
-import { Form, notification, message, Spin } from 'antd';
-import { CashLoader, CashLoadingIcon } from '@components/Common/CustomIcons';
+import { Form, notification, message, Spin, Modal } from 'antd';
+import { CashLoader, CashLoadingIcon } from '@components/Common/CustomIcons';
import { Row, Col } from 'antd';
import Paragraph from 'antd/lib/typography/Paragraph';
import PrimaryButton, {
@@ -87,6 +87,25 @@
const [sendBchAmountError, setSendBchAmountError] = useState(false);
const [selectedCurrency, setSelectedCurrency] = useState(currency.ticker);
+ // Support cashtab button from web pages
+ const [txInfoFromUrl, setTxInfoFromUrl] = useState(false);
+
+ // Show a confirmation modal on transactions created by populating form from web page button
+ const [isModalVisible, setIsModalVisible] = useState(false);
+
+ const showModal = () => {
+ setIsModalVisible(true);
+ };
+
+ const handleOk = () => {
+ setIsModalVisible(false);
+ submit();
+ };
+
+ const handleCancel = () => {
+ setIsModalVisible(false);
+ };
+
const { getBCH, getRestUrl, sendBch, calcFee } = useBCH();
const BCH = getBCH();
@@ -96,6 +115,40 @@
setLoading(false);
}, [balances.totalBalance]);
+ useEffect(() => {
+ // Manually parse for txInfo object on page load when Send.js is loaded with a query string
+
+ // Do not set txInfo in state if query strings are not present
+ if (
+ !window.location ||
+ !window.location.hash ||
+ window.location.hash === '#/send'
+ ) {
+ console.log(`No tx info in URL`);
+ return;
+ }
+
+ const txInfoArr = window.location.hash.split('?')[1].split('&');
+
+ // Iterate over this to create object
+ const txInfo = {};
+ for (let i = 0; i < txInfoArr.length; i += 1) {
+ let txInfoKeyValue = txInfoArr[i].split('=');
+ let key = txInfoKeyValue[0];
+ let value = txInfoKeyValue[1];
+ txInfo[key] = value;
+ }
+ console.log(`txInfo from page params`, txInfo);
+ setTxInfoFromUrl(txInfo);
+ populateFormsFromUrl(txInfo);
+ }, []);
+
+ function populateFormsFromUrl(txInfo) {
+ if (txInfo && txInfo.address && txInfo.value) {
+ setFormData({ address: txInfo.address, value: txInfo.value });
+ }
+ }
+
async function submit() {
setFormData({
...formData,
@@ -264,6 +317,17 @@
return (
<>
+ <Modal
+ title="Confirm Send"
+ visible={isModalVisible}
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <p>
+ Are you sure you want to send {formData.value}{' '}
+ {currency.ticker} to {formData.address}?
+ </p>
+ </Modal>
{!balances.totalBalance ? (
<ZeroBalanceHeader>
You currently have 0 {currency.ticker}
@@ -348,9 +412,21 @@
sendBchAmountError ? (
<SecondaryButton>Send</SecondaryButton>
) : (
- <PrimaryButton onClick={() => submit()}>
- Send
- </PrimaryButton>
+ <>
+ {txInfoFromUrl ? (
+ <PrimaryButton
+ onClick={() => showModal()}
+ >
+ Send
+ </PrimaryButton>
+ ) : (
+ <PrimaryButton
+ onClick={() => submit()}
+ >
+ Send
+ </PrimaryButton>
+ )}
+ </>
)}
</div>
{apiError && (
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, May 20, 22:49 (4 h, 51 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5864886
Default Alt Text
D8734.id26539.diff (12 KB)
Attached To
D8734: Add webpage interaction to extension
Event Timeline
Log In to Comment