diff options
-rw-r--r-- | app/scripts/metamask-controller.js | 1 | ||||
-rw-r--r-- | library/controller.js | 172 | ||||
-rw-r--r-- | library/controllers/index-db-controller.js | 87 | ||||
-rw-r--r-- | library/index.js | 2 | ||||
-rw-r--r-- | library/popup.js | 20 | ||||
-rw-r--r-- | library/server.js | 6 | ||||
-rw-r--r-- | library/sw-controller.js | 78 | ||||
-rw-r--r-- | library/sw-core.js | 204 | ||||
-rw-r--r-- | package.json | 3 | ||||
-rw-r--r-- | ui/index.js | 1 |
10 files changed, 407 insertions, 167 deletions
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 92533e022..15bf9f436 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -208,7 +208,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/controller.js b/library/controller.js index 5823287cc..f0aa7172c 100644 --- a/library/controller.js +++ b/library/controller.js @@ -1,159 +1,13 @@ -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' - } - } - -} +// const SWcontroller = require('./sw-controller') +// const SwStream = require('sw-stream/lib/sw-stream.js') +// const startPopup = require('../app/scripts/popup-core') + +// console.log('outside:open') +// const background = new SWcontroller({ +// fileName: 'sw-build.js', +// }) +// background.on('ready', (readSw) => { +// startPopup(SwStream(background.controller)) +// }) +// background.startWorker() +console.log('hello from controller') diff --git a/library/controllers/index-db-controller.js b/library/controllers/index-db-controller.js new file mode 100644 index 000000000..6bcae9845 --- /dev/null +++ b/library/controllers/index-db-controller.js @@ -0,0 +1,87 @@ +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 + if (!this.db.objectStoreNames.length) { + Object.keys(this.initialState).forEach((key) => { + this._add(key, this.initialState[key]) + }) + } + this.emit('success') + resolve(this.db) + } + dbOpenRequest.onupgradeneeded = (event) => { + // if (this.migrators) + this.db = event.target.result + this.migrate() + } + }) + } + + 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) { + 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) + }) + } + + migrate () { + this.db.createObjectStore('dataStore') + } + + _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/index.js b/library/index.js index b5f4f6637..af82c6546 100644 --- a/library/index.js +++ b/library/index.js @@ -26,7 +26,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...') }) diff --git a/library/popup.js b/library/popup.js index 667b13371..3825661cf 100644 --- a/library/popup.js +++ b/library/popup.js @@ -1,7 +1,10 @@ const injectCss = require('inject-css') const MetaMaskUiCss = require('../ui/css') -const startPopup = require('../app/scripts/popup-core') const setupIframe = require('./lib/setup-iframe.js') +const MetamaskInpageProvider = require('../app/scripts/lib/inpage-provider.js') +const SWcontroller = require('./sw-controller') +const SwStream = require('sw-stream/lib/sw-stream.js') +const startPopup = require('../app/scripts/popup-core') var css = MetaMaskUiCss() @@ -11,9 +14,18 @@ var name = 'popup' window.METAMASK_UI_TYPE = name 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, }) - -startPopup(iframeStream) +console.log('outside:open') +const background = new SWcontroller({ + fileName: '/popup/sw-build.js', +}) +background.on('ready', (readSw) => { + // var inpageProvider = new MetamaskInpageProvider(SwStream(background.controller)) + // startPopup(inpageProvider) + startPopup(SwStream(background.controller)) +}) +background.startWorker() +console.log('hello from /library/popup.js') diff --git a/library/server.js b/library/server.js index 797ad8a77..a284d3e05 100644 --- a/library/server.js +++ b/library/server.js @@ -7,6 +7,7 @@ const zeroBundle = createBundle('./index.js') const controllerBundle = createBundle('./controller.js') const popupBundle = createBundle('./popup.js') const appBundle = createBundle('./example/index.js') +const swBuild = createBundle('./sw-core.js') // // Iframe Server @@ -24,6 +25,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/sw-controller.js b/library/sw-controller.js new file mode 100644 index 000000000..23d67026e --- /dev/null +++ b/library/sw-controller.js @@ -0,0 +1,78 @@ +const EventEmitter = require('events') + +module.exports = class ClientSideServiceWorker extends EventEmitter{ + constructor (opts) { + super() + this.fileName = opts.fileName + this.startDelay = opts.startDelay + + this.serviceWorkerApi = navigator.serviceWorker + this.serviceWorkerApi.onmessage = (event) => this.emit('message', event) + this.serviceWorkerApi.onerror = (event) => this.emit('error') + + // temporary function + this.askForId = (message) => { + this.sendMessage('check-in') + .then((data) => console.log(`${message}----${data}`)) + } + + // if (!!this.serviceWorkerApi) this.askForId('before') + + if (opts.initStart) this.startWorker() + + this.on('ready', (sw) => { + this.askForId('ready') + }) + } + + get controller () { + return this.sw || this.serviceWorkerApi.controller + } + + + startWorker () { + return this.registerWorker() + .then((sw) => { + this.sw = sw + this.askForId('after register:') + this.sw.onerror = (err) => this.emit('error', err) + this.sw = sw + this.emit('ready', this.sw) + }) + .catch((err) => this.emit('error', err)) + } + + registerWorker () { + return this.serviceWorkerApi.register(this.fileName) + .then((registerdWorker) => { + return new Promise((resolve, reject) => { + this.askForId('after') + let timeOutId = setTimeout(() => { + if (this.serviceWorkerApi.controller) return resolve(this.serviceWorkerApi.controller) + if (registerdWorker.active) return resolve(registerdWorker.active) + return reject(new Error('ClientSideServiceWorker: No controller found and onupdatefound timed out')) + }, this.startDelay || 1000 ) + + registerdWorker.onupdatefound = (event) => { + this.emit('updatefound') + registerdWorker.update() + } + }) + }) + } + + sendMessage (message) { + const self = this + return new Promise((resolve, reject) => { + var messageChannel = new MessageChannel() + messageChannel.port1.onmessage = (event) => { + if (event.data.err) { + reject(event.data.error) + } else { + resolve(event.data.data) + } + } + this.controller.postMessage(message, [messageChannel.port2]) + }) + } +} diff --git a/library/sw-core.js b/library/sw-core.js new file mode 100644 index 000000000..8c7de2c32 --- /dev/null +++ b/library/sw-core.js @@ -0,0 +1,204 @@ +global.window = global +const SWGlobal = self +const urlUtil = require('url') +const endOfStream = require('end-of-stream') +const asyncQ = require('async-q') +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 notification = require('../app/scripts/lib/notifications.js') + +const DbController = require('./controllers/index-db-controller') + +const MetamaskController = require('../app/scripts/metamask-controller') +// const extension = require('../app/scripts/lib/extension') +// const LocalStorageStore = require('obs-store/lib/localStorage') +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()) +}) + +self.onsync = function (syncEvent) { +// What is done when a sync even is fired + console.log('inside:sync') + var focused + self.clients.matchAll() + .then(clients => { + clients.forEach(function(client) { + + }) + }) +} + + + +console.log('inside:open') + + +// // state persistence +let diskStore +const dbController = new DbController({ + key: STORAGE_KEY, + global: self, + version: 2, + initialState: { + dataStore: { + meta: 2, + data: firstTimeState, + }, + }, +}) +asyncQ.waterfall([ + () => loadStateFromPersistence(), + (initState) => setupController(initState), +]) +.then(() => console.log('MetaMask initialization complete.')) +.catch((err) => { + console.log('WHILE SETTING UP:') + console.error(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((openRequest) => { + return dbController.get('dataStore') + }) + .then((data) => { + if (!data) { + return dbController._add('dataStore', initialState) + .then(() => dbController.get('dataStore')) + .then((versionedData) => Promise.resolve(versionedData.data)) + } + + return Promise.resolve(data.data) + }) + .catch((err) => console.error(err)) + /* + need to get migrations working + */ + + // return asyncQ.waterfall([ + // // read from disk + // () => Promise.resolve(diskStore || initialState), + // // migrate data + // (versionedData) => migrator.migrateData(versionedData), + // // write to disk + // (versionedData) => { + // diskStore.put(versionedData) + // return Promise.resolve(versionedData) + // }, + // // resolve to just data + // (versionedData) => Promise.resolve(versionedData.data), + // ]) +} + +function setupController (initState, client) { + + // + // MetaMask Controller + // + + const controller = new MetamaskController({ + // User confirmation callbacks: + showUnconfirmedMessage: triggerUi, + unlockAccountMessage: triggerUi, + showUnapprovedTx: triggerUi, + // initial state + initState, + }) + global.metamaskController = controller + + // setup state persistence + // pipe( + // controller.store, + // storeTransform(versionifyData), + // diskStore + // ) + controller.store.subscribe((state) => { + dbController.put(versionifyData(state)) + .catch((err) => {console.error(err)}) + }) + function versionifyData(state) { + // let versionedData = diskStore.getState() + // versionedData.data = state + let versionedData = {data: state} + return versionedData + } + + // + // connect to other contexts + // + /* + need to write a service worker stream for this + */ + connectionListener.on('remote', (portStream, messageEvent) => { + connectRemote(portStream, messageEvent.origin) + }) + + function connectRemote (connectionStream, originDomain) { + var isMetaMaskInternalProcess = (originDomain === 'http://localhost:9001') + if (isMetaMaskInternalProcess) { + // communication with popup + controller.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 + controller.setupProviderConnection(mx.createStream('provider'), originDomain) + } + // + // User Interface setup + // + return Promise.resolve() + +} + +// // // +// // // Etc... +// // // + +// // // popup trigger +function triggerUi () { + if (!popupIsOpen) notification.show() +} diff --git a/package.json b/package.json index 488e7e90d..c08d92339 100644 --- a/package.json +++ b/package.json @@ -103,13 +103,14 @@ "request-promise": "^4.1.1", "sandwich-expando": "^1.0.5", "semaphore": "^1.0.5", + "sw-stream": "^1.0.2", "textarea-caret": "^3.0.1", "three.js": "^0.73.2", "through2": "^2.0.1", "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^10.0.1", + "web3-provider-engine": "^11.0.0", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, diff --git a/ui/index.js b/ui/index.js index 1a65f813c..16875fce4 100644 --- a/ui/index.js +++ b/ui/index.js @@ -14,7 +14,6 @@ log.setLevel(debugMode ? 'debug' : 'warn') function launchApp (opts) { var accountManager = opts.accountManager actions._setBackgroundConnection(accountManager) - // check if we are unlocked first accountManager.getState(function (err, metamaskState) { if (err) throw err |