-
-
Notifications
You must be signed in to change notification settings - Fork 649
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Try Firefox hack for MetaMask mobile on iOS * Try manually injecting mobile provider * Dont import * Add ReactNativePostMessageStream * Add full MM mobile injection * Simplify * Remove web3 shim * Update deps * Update FF hack * Add comment * Show MM mobile links for web3 mobile install
- Loading branch information
1 parent
1e2bd04
commit 7e17317
Showing
8 changed files
with
402 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
const { Duplex } = require('readable-stream'); | ||
const { inherits } = require('util'); | ||
|
||
const noop = () => undefined; | ||
|
||
module.exports = MobilePortStream; | ||
|
||
inherits(MobilePortStream, Duplex); | ||
|
||
/** | ||
* Creates a stream that's both readable and writable. | ||
* The stream supports arbitrary objects. | ||
* | ||
* @class | ||
* @param {Object} port Remote Port object | ||
*/ | ||
function MobilePortStream(port) { | ||
Duplex.call(this, { | ||
objectMode: true | ||
}); | ||
this._name = port.name; | ||
this._targetWindow = window; | ||
this._port = port; | ||
this._origin = location.origin; | ||
window.addEventListener('message', this._onMessage.bind(this), false); | ||
} | ||
|
||
/** | ||
* Callback triggered when a message is received from | ||
* the remote Port associated with this Stream. | ||
* | ||
* @private | ||
* @param {Object} msg - Payload from the onMessage listener of Port | ||
*/ | ||
MobilePortStream.prototype._onMessage = function (event) { | ||
const msg = event.data; | ||
|
||
// validate message | ||
if (this._origin !== '*' && event.origin !== this._origin) { | ||
return; | ||
} | ||
if (!msg || typeof msg !== 'object') { | ||
return; | ||
} | ||
if (!msg.data || typeof msg.data !== 'object') { | ||
return; | ||
} | ||
if (msg.target && msg.target !== this._name) { | ||
return; | ||
} | ||
// Filter outgoing messages | ||
if (msg.data.data && msg.data.data.toNative) { | ||
return; | ||
} | ||
|
||
if (Buffer.isBuffer(msg)) { | ||
delete msg._isBuffer; | ||
const data = Buffer.from(msg); | ||
this.push(data); | ||
} else { | ||
this.push(msg); | ||
} | ||
}; | ||
|
||
/** | ||
* Callback triggered when the remote Port | ||
* associated with this Stream disconnects. | ||
* | ||
* @private | ||
*/ | ||
MobilePortStream.prototype._onDisconnect = function () { | ||
this.destroy(); | ||
}; | ||
|
||
/** | ||
* Explicitly sets read operations to a no-op | ||
*/ | ||
MobilePortStream.prototype._read = noop; | ||
|
||
/** | ||
* Called internally when data should be written to | ||
* this writable stream. | ||
* | ||
* @private | ||
* @param {*} msg Arbitrary object to write | ||
* @param {string} encoding Encoding to use when writing payload | ||
* @param {Function} cb Called when writing is complete or an error occurs | ||
*/ | ||
MobilePortStream.prototype._write = function (msg, _encoding, cb) { | ||
try { | ||
if (Buffer.isBuffer(msg)) { | ||
const data = msg.toJSON(); | ||
data._isBuffer = true; | ||
window.ReactNativeWebView.postMessage( | ||
JSON.stringify({ ...data, origin: window.location.href }) | ||
); | ||
} else { | ||
if (msg.data) { | ||
msg.data.toNative = true; | ||
} | ||
window.ReactNativeWebView.postMessage( | ||
JSON.stringify({ ...msg, origin: window.location.href }) | ||
); | ||
} | ||
} catch (err) { | ||
return cb(new Error('MobilePortStream - disconnected')); | ||
} | ||
return cb(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
const { Duplex } = require('readable-stream'); | ||
const { inherits } = require('util'); | ||
|
||
const noop = () => undefined; | ||
|
||
module.exports = PostMessageStream; | ||
|
||
inherits(PostMessageStream, Duplex); | ||
|
||
function PostMessageStream(opts) { | ||
Duplex.call(this, { | ||
objectMode: true | ||
}); | ||
|
||
this._name = opts.name; | ||
this._target = opts.target; | ||
this._targetWindow = opts.targetWindow || window; | ||
this._origin = opts.targetWindow ? '*' : location.origin; | ||
|
||
// initialization flags | ||
this._init = false; | ||
this._haveSyn = false; | ||
|
||
window.addEventListener('message', this._onMessage.bind(this), false); | ||
// send syncorization message | ||
this._write('SYN', null, noop); | ||
this.cork(); | ||
} | ||
|
||
// private | ||
PostMessageStream.prototype._onMessage = function (event) { | ||
const msg = event.data; | ||
|
||
// validate message | ||
if (this._origin !== '*' && event.origin !== this._origin) { | ||
return; | ||
} | ||
if (event.source !== this._targetWindow && window === top) { | ||
return; | ||
} | ||
if (!msg || typeof msg !== 'object') { | ||
return; | ||
} | ||
if (msg.target !== this._name) { | ||
return; | ||
} | ||
if (!msg.data) { | ||
return; | ||
} | ||
|
||
if (this._init) { | ||
// forward message | ||
try { | ||
this.push(msg.data); | ||
} catch (err) { | ||
this.emit('error', err); | ||
} | ||
} else if (msg.data === 'SYN') { | ||
this._haveSyn = true; | ||
this._write('ACK', null, noop); | ||
} else if (msg.data === 'ACK') { | ||
this._init = true; | ||
if (!this._haveSyn) { | ||
this._write('ACK', null, noop); | ||
} | ||
this.uncork(); | ||
} | ||
}; | ||
|
||
// stream plumbing | ||
PostMessageStream.prototype._read = noop; | ||
|
||
PostMessageStream.prototype._write = function (data, _encoding, cb) { | ||
const message = { | ||
target: this._target, | ||
data | ||
}; | ||
this._targetWindow.postMessage(message, this._origin); | ||
cb(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Based of: https://github.com/MetaMask/mobile-provider/blob/main/src/inpage/index.js | ||
|
||
import { initializeProvider } from '@metamask/inpage-provider'; | ||
import ObjectMultiplex from '@metamask/object-multiplex'; | ||
import pump from 'pump'; | ||
|
||
import MobilePortStream from './MetaMask/MobilePortStream'; | ||
import ReactNativePostMessageStream from './MetaMask/ReactNativePostMessageStream'; | ||
|
||
const INPAGE = 'metamask-inpage'; | ||
const CONTENT_SCRIPT = 'metamask-contentscript'; | ||
const PROVIDER = 'metamask-provider'; | ||
|
||
export const injectMobile = () => { | ||
// Setup stream for content script communication | ||
const metamaskStream = new ReactNativePostMessageStream({ | ||
name: INPAGE, | ||
target: CONTENT_SCRIPT | ||
}); | ||
|
||
// Initialize provider object (window.ethereum) | ||
initializeProvider({ | ||
connectionStream: metamaskStream, | ||
shouldSendMetadata: false | ||
}); | ||
|
||
setupProviderStreams(); | ||
}; | ||
|
||
// Functions | ||
|
||
/** | ||
* Setup function called from content script after the DOM is ready. | ||
*/ | ||
function setupProviderStreams() { | ||
// the transport-specific streams for communication between inpage and background | ||
const pageStream = new ReactNativePostMessageStream({ | ||
name: CONTENT_SCRIPT, | ||
target: INPAGE | ||
}); | ||
|
||
const appStream = new MobilePortStream({ | ||
name: CONTENT_SCRIPT | ||
}); | ||
|
||
// create and connect channel muxes | ||
// so we can handle the channels individually | ||
const pageMux = new ObjectMultiplex(); | ||
pageMux.setMaxListeners(25); | ||
const appMux = new ObjectMultiplex(); | ||
appMux.setMaxListeners(25); | ||
|
||
pump(pageMux, pageStream, pageMux, (err) => | ||
logStreamDisconnectWarning('MetaMask Inpage Multiplex', err) | ||
); | ||
pump(appMux, appStream, appMux, (err) => { | ||
logStreamDisconnectWarning('MetaMask Background Multiplex', err); | ||
notifyProviderOfStreamFailure(); | ||
}); | ||
|
||
// forward communication across inpage-background for these channels only | ||
forwardTrafficBetweenMuxes(PROVIDER, pageMux, appMux); | ||
} | ||
|
||
/** | ||
* Set up two-way communication between muxes for a single, named channel. | ||
* | ||
* @param {string} channelName - The name of the channel. | ||
* @param {ObjectMultiplex} muxA - The first mux. | ||
* @param {ObjectMultiplex} muxB - The second mux. | ||
*/ | ||
function forwardTrafficBetweenMuxes(channelName, muxA, muxB) { | ||
const channelA = muxA.createStream(channelName); | ||
const channelB = muxB.createStream(channelName); | ||
pump(channelA, channelB, channelA, (err) => | ||
logStreamDisconnectWarning(`MetaMask muxed traffic for channel "${channelName}" failed.`, err) | ||
); | ||
} | ||
|
||
/** | ||
* Error handler for page to extension stream disconnections | ||
* | ||
* @param {string} remoteLabel - Remote stream name | ||
* @param {Error} err - Stream connection error | ||
*/ | ||
function logStreamDisconnectWarning(remoteLabel, err) { | ||
let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}`; | ||
if (err) { | ||
warningMsg += `\n${err.stack}`; | ||
} | ||
console.warn(warningMsg); | ||
console.error(err); | ||
} | ||
|
||
/** | ||
* This function must ONLY be called in pump destruction/close callbacks. | ||
* Notifies the inpage context that streams have failed, via window.postMessage. | ||
* Relies on @metamask/object-multiplex and post-message-stream implementation details. | ||
*/ | ||
function notifyProviderOfStreamFailure() { | ||
window.postMessage( | ||
{ | ||
target: INPAGE, // the post-message-stream "target" | ||
data: { | ||
// this object gets passed to object-multiplex | ||
name: PROVIDER, // the object-multiplex channel name | ||
data: { | ||
jsonrpc: '2.0', | ||
method: 'METAMASK_STREAM_FAILURE' | ||
} | ||
} | ||
}, | ||
window.location.origin | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,28 @@ | ||
import { initProvider } from '@metamask/inpage-provider'; | ||
import LocalMessageDuplexStream from 'post-message-stream'; | ||
import { initializeProvider } from '@metamask/inpage-provider'; | ||
import { WindowPostMessageStream } from '@metamask/post-message-stream'; | ||
|
||
// Firefox Metamask Hack | ||
import { injectMobile } from './inpage-metamask-mobile'; | ||
|
||
// Metamask injection hack | ||
// Due to https://github.com/MetaMask/metamask-extension/issues/3133 | ||
|
||
(() => { | ||
if (!window.ethereum && !window.web3 && navigator.userAgent.includes('Firefox')) { | ||
if (window.ethereum || window.web3) { | ||
return; | ||
} | ||
if (navigator.userAgent.includes('Firefox')) { | ||
// setup background connection | ||
const metamaskStream = new LocalMessageDuplexStream({ | ||
name: 'inpage', | ||
target: 'contentscript' | ||
const metamaskStream = new WindowPostMessageStream({ | ||
name: 'metamask-inpage', | ||
target: 'metamask-contentscript' | ||
}); | ||
|
||
// this will initialize the provider and set it as window.ethereum | ||
initProvider({ | ||
connectionStream: metamaskStream | ||
initializeProvider({ | ||
connectionStream: metamaskStream, | ||
shouldShimWeb3: true | ||
}); | ||
} else if (navigator.userAgent.includes('iPhone')) { | ||
injectMobile(); | ||
} | ||
})(); |
Oops, something went wrong.