diff options
author | kumavis <kumavis@users.noreply.github.com> | 2017-04-05 02:27:45 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-04-05 02:27:45 +0800 |
commit | 5d967eeebb9f3cf4c2d3fcfe0b74cf6e8440c3cb (patch) | |
tree | 38c3fac654c41df24f01b6de3aa453d68b259c07 | |
parent | 39181ed33f1b9829f82c44d2f21e2f3ab1d1c979 (diff) | |
parent | 4779999bfc7e03eedf3fd2702f7f448d751218f8 (diff) | |
download | tangerine-wallet-browser-5d967eeebb9f3cf4c2d3fcfe0b74cf6e8440c3cb.tar.gz tangerine-wallet-browser-5d967eeebb9f3cf4c2d3fcfe0b74cf6e8440c3cb.tar.zst tangerine-wallet-browser-5d967eeebb9f3cf4c2d3fcfe0b74cf6e8440c3cb.zip |
Merge pull request #1307 from MetaMask/mascara
Proof of Concept: Mascara
-rw-r--r-- | app/scripts/metamask-controller.js | 1 | ||||
-rw-r--r-- | library/README.md | 24 | ||||
-rw-r--r-- | library/controller.js | 159 | ||||
-rw-r--r-- | library/popup.js | 19 | ||||
-rw-r--r-- | mascara/README.md | 20 | ||||
-rw-r--r-- | mascara/example/index.html (renamed from library/example/index.html) | 0 | ||||
-rw-r--r-- | mascara/example/index.js (renamed from library/example/index.js) | 11 | ||||
-rw-r--r-- | mascara/server.js (renamed from library/server.js) | 13 | ||||
-rw-r--r-- | mascara/server/index.html (renamed from library/server/index.html) | 0 | ||||
-rw-r--r-- | mascara/src/background.js | 139 | ||||
-rw-r--r-- | mascara/src/dapp-connection.js | 21 | ||||
-rw-r--r-- | mascara/src/lib/index-db-controller.js | 88 | ||||
-rw-r--r-- | mascara/src/lib/setup-iframe.js (renamed from library/lib/setup-iframe.js) | 0 | ||||
-rw-r--r-- | mascara/src/lib/setup-provider.js (renamed from library/lib/setup-provider.js) | 7 | ||||
-rw-r--r-- | mascara/src/mascara.js (renamed from library/index.js) | 7 | ||||
-rw-r--r-- | mascara/src/popup.js | 36 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | ui/index.js | 1 |
18 files changed, 328 insertions, 220 deletions
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 5f71fc369..edb9bbbd9 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -212,7 +212,6 @@ module.exports = class MetamaskController extends EventEmitter { // getState () { - const wallet = this.configManager.getWallet() const vault = this.keyringController.store.getState().vault const isInitialized = (!!wallet || !!vault) diff --git a/library/README.md b/library/README.md deleted file mode 100644 index 6a6574dbd..000000000 --- a/library/README.md +++ /dev/null @@ -1,24 +0,0 @@ -start the dual servers (dapp + mascara) -``` -node server.js -``` - -open the example dapp at `http://localhost:9002/` - -*You will need to build MetaMask in order for this to work* -``` -gulp dev -``` -to build MetaMask and have it live reload if you make changes - - -## First time use: - -- navigate to: http://127.0.0.1:9001/popup/popup.html -- Create an Account -- go back to http://localhost:9002/ -- open devTools -- click Sync Tx - -### Todos -- Look into using [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) diff --git a/library/controller.js b/library/controller.js deleted file mode 100644 index 5823287cc..000000000 --- a/library/controller.js +++ /dev/null @@ -1,159 +0,0 @@ -const urlUtil = require('url') -const extend = require('xtend') -const Dnode = require('dnode') -const eos = require('end-of-stream') -const ParentStream = require('iframe-stream').ParentStream -const PortStream = require('../app/scripts/lib/port-stream.js') -const notification = require('../app/scripts/lib/notifications.js') -const messageManager = require('../app/scripts/lib/message-manager') -const setupMultiplex = require('../app/scripts/lib/stream-utils.js').setupMultiplex -const MetamaskController = require('../app/scripts/metamask-controller') -const extension = require('../app/scripts/lib/extension') - -const STORAGE_KEY = 'metamask-config' - - -initializeZeroClient() - -function initializeZeroClient() { - - const controller = new MetamaskController({ - // User confirmation callbacks: - showUnconfirmedMessage, - unlockAccountMessage, - showUnapprovedTx, - // Persistence Methods: - setData, - loadData, - }) - const idStore = controller.idStore - - function unlockAccountMessage () { - console.log('notif stub - unlockAccountMessage') - } - - function showUnconfirmedMessage (msgParams, msgId) { - console.log('notif stub - showUnconfirmedMessage') - } - - function showUnapprovedTx (txParams, txData, onTxDoneCb) { - console.log('notif stub - showUnapprovedTx') - } - - // - // connect to other contexts - // - - var connectionStream = new ParentStream() - - connectRemote(connectionStream, getParentHref()) - - function connectRemote (connectionStream, originDomain) { - var isMetaMaskInternalProcess = (originDomain === '127.0.0.1:9001') - if (isMetaMaskInternalProcess) { - // communication with popup - setupTrustedCommunication(connectionStream, 'MetaMask') - } else { - // communication with page - setupUntrustedCommunication(connectionStream, originDomain) - } - } - - function setupUntrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - var mx = setupMultiplex(connectionStream) - // connect features - controller.setupProviderConnection(mx.createStream('provider'), originDomain) - controller.setupPublicConfig(mx.createStream('publicConfig')) - } - - function setupTrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - var mx = setupMultiplex(connectionStream) - // connect features - setupControllerConnection(mx.createStream('controller')) - controller.setupProviderConnection(mx.createStream('provider'), originDomain) - } - - // - // remote features - // - - function setupControllerConnection (stream) { - controller.stream = stream - var api = controller.getApi() - var dnode = Dnode(api) - stream.pipe(dnode).pipe(stream) - dnode.on('remote', (remote) => { - // push updates to popup - controller.ethStore.on('update', controller.sendUpdate.bind(controller)) - controller.listeners.push(remote) - idStore.on('update', controller.sendUpdate.bind(controller)) - - // teardown on disconnect - eos(stream, () => { - controller.ethStore.removeListener('update', controller.sendUpdate.bind(controller)) - }) - }) - } - - function loadData () { - var oldData = getOldStyleData() - var newData - try { - newData = JSON.parse(window.localStorage[STORAGE_KEY]) - } catch (e) {} - - var data = extend({ - meta: { - version: 0, - }, - data: { - config: { - provider: { - type: 'testnet', - }, - }, - }, - }, oldData || null, newData || null) - return data - } - - function getOldStyleData () { - var config, wallet, seedWords - - var result = { - meta: { version: 0 }, - data: {}, - } - - try { - config = JSON.parse(window.localStorage['config']) - result.data.config = config - } catch (e) {} - try { - wallet = JSON.parse(window.localStorage['lightwallet']) - result.data.wallet = wallet - } catch (e) {} - try { - seedWords = window.localStorage['seedWords'] - result.data.seedWords = seedWords - } catch (e) {} - - return result - } - - function setData (data) { - window.localStorage[STORAGE_KEY] = JSON.stringify(data) - } - - function getParentHref(){ - try { - var parentLocation = window.parent.location - return parentLocation.hostname + ':' + parentLocation.port - } catch (err) { - return 'unknown' - } - } - -} diff --git a/library/popup.js b/library/popup.js deleted file mode 100644 index 667b13371..000000000 --- a/library/popup.js +++ /dev/null @@ -1,19 +0,0 @@ -const injectCss = require('inject-css') -const MetaMaskUiCss = require('../ui/css') -const startPopup = require('../app/scripts/popup-core') -const setupIframe = require('./lib/setup-iframe.js') - - -var css = MetaMaskUiCss() -injectCss(css) - -var name = 'popup' -window.METAMASK_UI_TYPE = name - -var iframeStream = setupIframe({ - zeroClientProvider: 'http://127.0.0.1:9001', - sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], - container: document.body, -}) - -startPopup(iframeStream) diff --git a/mascara/README.md b/mascara/README.md new file mode 100644 index 000000000..cdeb4795c --- /dev/null +++ b/mascara/README.md @@ -0,0 +1,20 @@ +start the dual servers (dapp + mascara) +``` +node server.js +``` + +## First time use: + +- navigate to: http://localhost:9001/popup/popup.html +- Create an Account +- go back to http://localhost:9002/ +- open devTools +- click Sync Tx + +### Todos + + - [ ] Figure out user flows and UI redesign + - [ ] Figure out FireFox + Standing problems: + - [ ] IndexDb + diff --git a/library/example/index.html b/mascara/example/index.html index 47d6da34f..47d6da34f 100644 --- a/library/example/index.html +++ b/mascara/example/index.html diff --git a/library/example/index.js b/mascara/example/index.js index 4a107df6a..aae7ccd19 100644 --- a/library/example/index.js +++ b/mascara/example/index.js @@ -1,5 +1,5 @@ - window.addEventListener('load', web3Detect) +window.addEventListener('message', console.warn) function web3Detect() { if (global.web3) { @@ -13,10 +13,10 @@ function web3Detect() { function startApp(){ console.log('app started') - var primaryAccount = null + var primaryAccount console.log('getting main account...') - web3.eth.getAccounts(function(err, addresses){ - if (err) throw err + web3.eth.getAccounts((err, addresses) => { + if (err) console.error(err) console.log('set address', addresses[0]) primaryAccount = addresses[0] }) @@ -24,6 +24,7 @@ function startApp(){ document.querySelector('.action-button-1').addEventListener('click', function(){ console.log('saw click') console.log('sending tx') + primaryAccount web3.eth.sendTransaction({ from: primaryAccount, to: primaryAccount, @@ -53,4 +54,4 @@ function startApp(){ function logToDom(message){ document.body.appendChild(document.createTextNode(message)) console.log(message) -}
\ No newline at end of file +} diff --git a/library/server.js b/mascara/server.js index 797ad8a77..67c89f11b 100644 --- a/library/server.js +++ b/mascara/server.js @@ -3,9 +3,11 @@ const browserify = require('browserify') const watchify = require('watchify') const babelify = require('babelify') -const zeroBundle = createBundle('./index.js') -const controllerBundle = createBundle('./controller.js') -const popupBundle = createBundle('./popup.js') +const zeroBundle = createBundle('./src/mascara.js') +const controllerBundle = createBundle('./src/dapp-connection.js') +const popupBundle = createBundle('./src/popup.js') +const swBuild = createBundle('./src/background.js') + const appBundle = createBundle('./example/index.js') // @@ -24,6 +26,11 @@ iframeServer.use('/popup', express.static('../dist/chrome')) iframeServer.get('/controller.js', function(req, res){ res.send(controllerBundle.latest) }) +iframeServer.get('/popup/sw-build.js', function(req, res){ + console.log('/sw-build.js') + res.setHeader('Content-Type', 'application/javascript') + res.send(swBuild.latest) +}) // serve background controller iframeServer.use(express.static('./server')) diff --git a/library/server/index.html b/mascara/server/index.html index 2308dd98b..2308dd98b 100644 --- a/library/server/index.html +++ b/mascara/server/index.html diff --git a/mascara/src/background.js b/mascara/src/background.js new file mode 100644 index 000000000..6f9fb3d13 --- /dev/null +++ b/mascara/src/background.js @@ -0,0 +1,139 @@ +global.window = global +const pipe = require('pump') + +const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js') +const connectionListener = new SwGlobalListener(self) +const setupMultiplex = require('../../app/scripts/lib/stream-utils.js').setupMultiplex +const PortStream = require('../../app/scripts/lib/port-stream.js') + +const DbController = require('./lib/index-db-controller') + +const MetamaskController = require('../../app/scripts/metamask-controller') +const extension = {} //require('../../app/scripts/lib/extension') + +const storeTransform = require('obs-store/lib/transform') +const Migrator = require('../../app/scripts/lib/migrator/') +const migrations = require('../../app/scripts/migrations/') +const firstTimeState = require('../../app/scripts/first-time-state') + +const STORAGE_KEY = 'metamask-config' +const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' +let popupIsOpen = false + +const log = require('loglevel') +global.log = log +log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') + +self.addEventListener('install', function(event) { + event.waitUntil(self.skipWaiting()) +}) +self.addEventListener('activate', function(event) { + event.waitUntil(self.clients.claim()) +}) + +console.log('inside:open') + + +// // state persistence +let diskStore +const dbController = new DbController({ + key: STORAGE_KEY, + version: 2, +}) +loadStateFromPersistence() +.then((initState) => setupController(initState)) +.then(() => console.log('MetaMask initialization complete.')) +.catch((err) => console.error('WHILE SETTING UP:', err)) + +// initialization flow + +// +// State and Persistence +// +function loadStateFromPersistence() { + // migrations + let migrator = new Migrator({ migrations }) + const initialState = migrator.generateInitialState(firstTimeState) + dbController.initialState = initialState + return dbController.open() + .then((versionedData) => migrator.migrateData(versionedData)) + .then((versionedData) => { + dbController.put(versionedData) + return Promise.resolve(versionedData) + }) + .then((versionedData) => Promise.resolve(versionedData.data)) +} + +function setupController (initState, client) { + + // + // MetaMask Controller + // + + const controller = new MetamaskController({ + // User confirmation callbacks: + showUnconfirmedMessage: noop, + unlockAccountMessage: noop, + showUnapprovedTx: noop, + // initial state + initState, + }) + global.metamaskController = controller + + controller.store.subscribe((state) => { + versionifyData(state) + .then((versionedData) => dbController.put(versionedData)) + .catch((err) => {console.error(err)}) + }) + function versionifyData(state) { + return dbController.get() + .then((rawData) => { + return Promise.resolve({ + data: state, + meta: rawData.meta, + })} + ) + } + + // + // connect to other contexts + // + + connectionListener.on('remote', (portStream, messageEvent) => { + console.log('REMOTE CONECTION FOUND***********') + connectRemote(portStream, messageEvent.data.context) + }) + + function connectRemote (connectionStream, context) { + var isMetaMaskInternalProcess = (context === 'popup') + if (isMetaMaskInternalProcess) { + // communication with popup + controller.setupTrustedCommunication(connectionStream, 'MetaMask') + popupIsOpen = true + } else { + // communication with page + setupUntrustedCommunication(connectionStream, context) + } + } + + function setupUntrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + var mx = setupMultiplex(connectionStream) + // connect features + controller.setupProviderConnection(mx.createStream('provider'), originDomain) + controller.setupPublicConfig(mx.createStream('publicConfig')) + } + + function setupTrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + var mx = setupMultiplex(connectionStream) + // connect features + controller.setupProviderConnection(mx.createStream('provider'), originDomain) + } + // + // User Interface setup + // + return Promise.resolve() + +} +function noop () {} diff --git a/mascara/src/dapp-connection.js b/mascara/src/dapp-connection.js new file mode 100644 index 000000000..30680c9d7 --- /dev/null +++ b/mascara/src/dapp-connection.js @@ -0,0 +1,21 @@ +const ParentStream = require('iframe-stream').ParentStream +const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') +const SwStream = require('sw-stream/lib/sw-stream.js') +const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') + +const background = new SWcontroller({ + fileName: '/popup/sw-build.js', +}) + +const pageStream = new ParentStream() +background.on('ready', (_) => { + let swStream = SwStream({ + serviceWorker: background.controller, + context: 'dapp', + }) + pageStream.pipe(swStream).pipe(pageStream) + +}) + +background.on('error', console.error) +background.startWorker() diff --git a/mascara/src/lib/index-db-controller.js b/mascara/src/lib/index-db-controller.js new file mode 100644 index 000000000..8db1d5d21 --- /dev/null +++ b/mascara/src/lib/index-db-controller.js @@ -0,0 +1,88 @@ +const EventEmitter = require('events') +module.exports = class IndexDbController extends EventEmitter { + + constructor (opts) { + super() + this.migrations = opts.migrations + this.key = opts.key + this.dbObject = global.indexedDB + this.IDBTransaction = global.IDBTransaction || global.webkitIDBTransaction || global.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers + this.IDBKeyRange = global.IDBKeyRange || global.webkitIDBKeyRange || global.msIDBKeyRange; + this.version = opts.version + this.logging = opts.logging + this.initialState = opts.initialState + if (this.logging) this.on('log', logger) + } + + // Opens the database connection and returns a promise + open (version = this.version) { + return new Promise((resolve, reject) => { + const dbOpenRequest = this.dbObject.open(this.key, version) + dbOpenRequest.onerror = (event) => { + return reject(event) + } + dbOpenRequest.onsuccess = (event) => { + this.db = dbOpenRequest.result + this.emit('success') + resolve(this.db) + } + dbOpenRequest.onupgradeneeded = (event) => { + this.db = event.target.result + this.db.createObjectStore('dataStore') + } + }) + .then((openRequest) => { + return this.get('dataStore') + }) + .then((data) => { + if (!data) { + return this._add('dataStore', this.initialState) + .then(() => this.get('dataStore')) + .then((versionedData) => Promise.resolve(versionedData)) + } + return Promise.resolve(data) + }) + } + + requestObjectStore (key, type = 'readonly') { + return new Promise((resolve, reject) => { + const dbReadWrite = this.db.transaction(key, type) + const dataStore = dbReadWrite.objectStore(key) + resolve(dataStore) + }) + } + + get (key = 'dataStore') { + return this.requestObjectStore(key) + .then((dataObject)=> { + return new Promise((resolve, reject) => { + const getRequest = dataObject.get(key) + getRequest.onsuccess = (event) => resolve(event.currentTarget.result) + getRequest.onerror = (event) => reject(event) + }) + }) + } + + put (state) { + return this.requestObjectStore('dataStore', 'readwrite') + .then((dataObject)=> { + const putRequest = dataObject.put(state, 'dataStore') + putRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) + putRequest.onerror = (event) => Promise.reject(event) + }) + } + + _add (key, objStore, cb = logger) { + return this.requestObjectStore(key, 'readwrite') + .then((dataObject)=> { + const addRequest = dataObject.add(objStore, key) + addRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) + addRequest.onerror = (event) => Promise.reject(event) + }) + } + +} + +function logger (err, ress) { + err ? console.error(`Logger says: ${err}`) : console.dir(`Logger says: ${ress}`) +} diff --git a/library/lib/setup-iframe.js b/mascara/src/lib/setup-iframe.js index db67163df..db67163df 100644 --- a/library/lib/setup-iframe.js +++ b/mascara/src/lib/setup-iframe.js diff --git a/library/lib/setup-provider.js b/mascara/src/lib/setup-provider.js index 9efd209cb..4f2432ae4 100644 --- a/library/lib/setup-provider.js +++ b/mascara/src/lib/setup-provider.js @@ -1,19 +1,17 @@ const setupIframe = require('./setup-iframe.js') -const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js') +const MetamaskInpageProvider = require('../../../app/scripts/lib/inpage-provider.js') module.exports = getProvider function getProvider(){ - if (global.web3) { console.log('MetaMask ZeroClient - using environmental web3 provider') return global.web3.currentProvider } - console.log('MetaMask ZeroClient - injecting zero-client iframe!') var iframeStream = setupIframe({ - zeroClientProvider: 'http://127.0.0.1:9001', + zeroClientProvider: 'http://localhost:9001', sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], container: document.body, }) @@ -22,4 +20,3 @@ function getProvider(){ return inpageProvider } - diff --git a/library/index.js b/mascara/src/mascara.js index b5f4f6637..759353c1b 100644 --- a/library/index.js +++ b/mascara/src/mascara.js @@ -10,8 +10,7 @@ var web3 = new Web3(provider) web3.setProvider = function(){ console.log('MetaMask - overrode web3.setProvider') } -console.log('metamask lib hijacked provider') - +// // // export web3 // @@ -26,7 +25,7 @@ var shouldPop = false window.addEventListener('click', function(){ if (!shouldPop) return shouldPop = false - window.open('http://127.0.0.1:9001/popup/popup.html', '', 'width=360 height=500') + window.open('http://localhost:9001/popup/popup.html', '', 'width=360 height=500') console.log('opening window...') }) @@ -41,3 +40,5 @@ function hijackProvider(provider){ _super(payload, cb) } } + + diff --git a/mascara/src/popup.js b/mascara/src/popup.js new file mode 100644 index 000000000..ef7759a81 --- /dev/null +++ b/mascara/src/popup.js @@ -0,0 +1,36 @@ +const injectCss = require('inject-css') +const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') +const SwStream = require('sw-stream/lib/sw-stream.js') +const MetaMaskUiCss = require('../../ui/css') +const setupIframe = require('./lib/setup-iframe.js') +const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js') +const startPopup = require('../../app/scripts/popup-core') + +var css = MetaMaskUiCss() +injectCss(css) +const container = document.getElementById('app-content') + +var name = 'popup' +window.METAMASK_UI_TYPE = name + +const background = new SWcontroller({ + fileName: '/popup/sw-build.js', +}) + +// Setup listener for when the service worker is read +background.on('ready', (readSw) => { + let connectionStream = SwStream({ + serviceWorker: background.controller, + context: name, + }) + startPopup({container, connectionStream}, (err, store) => { + if (err) return displayCriticalError(err) + store.subscribe(() => { + const state = store.getState() + if (state.appState.shouldClose) window.close() + }) + }) +}) + +background.startWorker() +console.log('hello from /library/popup.js') diff --git a/package.json b/package.json index 9b83ca317..b96197915 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "bluebird": "^3.5.0", "browser-passworder": "^2.0.3", "browserify-derequire": "^0.9.4", + "client-sw-ready-event": "^1.0.2", "clone": "^1.0.2", "copy-to-clipboard": "^2.0.0", "debounce": "^1.0.0", @@ -104,6 +105,7 @@ "request-promise": "^4.1.1", "sandwich-expando": "^1.0.5", "semaphore": "^1.0.5", + "sw-stream": "^2.0.0", "textarea-caret": "^3.0.1", "three.js": "^0.73.2", "through2": "^2.0.1", diff --git a/ui/index.js b/ui/index.js index e3648c374..a729138d3 100644 --- a/ui/index.js +++ b/ui/index.js @@ -14,7 +14,6 @@ log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn') function launchMetamaskUi (opts, cb) { var accountManager = opts.accountManager actions._setBackgroundConnection(accountManager) - // check if we are unlocked first accountManager.getState(function (err, metamaskState) { if (err) return cb(err) |