Skip to content

Commit

Permalink
Merge pull request #7 from getstation/fix/mixmax
Browse files Browse the repository at this point in the history
Add WebRequest API
  • Loading branch information
alexstrat authored Nov 28, 2017
2 parents f3cf436 + 7680b32 commit 6f48209
Show file tree
Hide file tree
Showing 12 changed files with 297 additions and 27 deletions.
4 changes: 3 additions & 1 deletion example/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
</style>

<div class="container">
<webview allowPopups webpreferences="webSecurity=no" preload='../preload.js' src="https://mail.google.com/"></webview>
<!--<webview allowPopups webpreferences="webSecurity=no" preload='../preload.js' src="https://mail.google.com/"></webview>-->
<webview allowPopups preload='../preload.js' src="http://motherfuckingwebsite.com/"></webview>
<!--<webview allowPopups preload='../preload.js' src="https://inbox.google.com/"></webview>-->
</div>
<script>
setTimeout(() => Array.from(document.getElementsByTagName('webview')).forEach(w => w.openDevTools()), 1000)
Expand Down
8 changes: 7 additions & 1 deletion example/extensions/dummy-extension/manifest.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
{
"content_scripts": [ {
"css": [ "/hello.css" ],
"js": [ "/a.js", "/b.js", "/crash.js"],
"js": [ "/a.js", "/b.js", "/web-request.js", "/crash.js"],
"__matches": [ "https://github.com/*", "https://developer.mozilla.org/*", "https://mail.google.com/*"],
"matches": [ "<all_urls>"],
"run_at": "document_end"
}],
"permissions": [
"webRequest",
"webRequestBlocking",
"*://*/*",
"background"
],
"description": "Dummy Extension",
"manifest_version": 2,
"name": "Dummy Extension",
Expand Down
116 changes: 116 additions & 0 deletions example/extensions/dummy-extension/web-request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Try it with motherfuckingwebsite.com

chrome.webRequest.onBeforeRequest.addListener(function(details) {
// Fires when a request is about to occur.
// This event is sent before any TCP connection is made and can be used to cancel or redirect requests.
console.log('--- onBeforeRequest details')
console.log(details)
return {
cancel: details.url.indexOf("://evil.com/") !== -1
};
}, {
urls: ['<all_urls>'],
types: ['main_frame']
});


chrome.webRequest.onBeforeSendHeaders.addListener(function(details) {
// Fires when a request is about to occur and the initial headers have been prepared.
// The event is intended to allow extensions to add, modify, and delete request headers (*).
// The onBeforeSendHeaders event is passed to all subscribers, so different subscribers may attempt to modify the request;
// see the Implementation details section for how this is handled. This event can be used to cancel the request.
details.requestHeaders['User-Agent'] = 'My Awesome Agent added onBeforeSendHeaders'
console.log('--- onBeforeSendHeaders details')
console.log(details)
return {
cancel: false,
requestHeaders: details.requestHeaders,
};
}, {
urls: ['<all_urls>'],
types: ['main_frame']
});

chrome.webRequest.onSendHeaders.addListener(function(details) {
// Fires after all extensions have had a chance to modify the request headers, and presents the final (*) version.
// The event is triggered before the headers are sent to the network. This event is informational and handled asynchronously.
// It does not allow modifying or cancelling the request.
console.log('--- onSendHeaders details')
console.log(details)
}, {
urls: ['<all_urls>'],
types: ['main_frame']
});

chrome.webRequest.onHeadersReceived.addListener(function(details) {
// Fired when HTTP response headers of a request have been received.
details.responseHeaders.push({
name: 'X-onHeadersReceived',
value: 'ok'
});

return {
cancel: false,
responseHeaders: details.responseHeaders,
};
}, {
urls: ['<all_urls>'],
types: ['main_frame']
});
//
// chrome.webRequest.onAuthRequired.addListener(function(details) {
// // not available in electron. We have to implement it.
// }, {
// urls: ['<all_urls>'],
// types: ['main_frame']
// });

chrome.webRequest.onBeforeRedirect.addListener(function(details) {
// Fires when a redirect is about to be executed. A redirection can be triggered by an HTTP response code or by an extension.
// This event is informational and handled asynchronously.
// It does not allow you to modify or cancel the request.
console.log('--- onBeforeRedirect details')
console.log(details)
}, {
urls: ['<all_urls>'],
types: ['main_frame']
});


chrome.webRequest.onResponseStarted.addListener(function(details) {
// Fires when the first byte of the response body is received.
// For HTTP requests, this means that the status line and response headers are available.
// This event is informational and handled asynchronously.
// It does not allow modifying or cancelling the request.
console.log('--- onResponseStarted details')
console.log(details)
}, {
urls: ['<all_urls>'],
types: ['main_frame']
});

chrome.webRequest.onCompleted.addListener(function(details) {
// Fires when a request has been processed successfully.
console.log('--- onCompleted details')
console.log(details)
}, {
urls: ['<all_urls>'],
types: ['main_frame']
});

chrome.webRequest.onErrorOccurred.addListener(function(details) {
// Fires when a request could not be processed successfully.
console.log('--- onErrorOccurred details')
console.log(details)
}, {
urls: ['<all_urls>'],
types: ['main_frame']
});

const sampleRequest = new XMLHttpRequest();
sampleRequest.addEventListener('load', function () {
console.log('--- sampleRequest AllResponseHeaders');
console.log(this.getAllResponseHeaders());
});
sampleRequest.open('GET', `http://motherfuckingwebsite.com/?foo=${Date.now()}`);
sampleRequest.send();
6 changes: 3 additions & 3 deletions example/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ function createWindow () {
app.on('ready', createWindow)

app.on('ready', () => {
//extensions.addExtension(path.join(__dirname, './extensions/mixmax'))
extensions.addExtension(path.join(__dirname, './extensions/gmelius'))
//extensions.addExtension(path.join(__dirname, './extensions/dummy-extension'))
// extensions.addExtension(path.join(__dirname, './extensions/mixmax'))
// extensions.addExtension(path.join(__dirname, './extensions/gmelius'))
extensions.addExtension(path.join(__dirname, './extensions/dummy-extension'))
})

// Quit when all windows are closed.
Expand Down
4 changes: 3 additions & 1 deletion lib/browser/api/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
const ChromeStorageAPIHandler = require('./storage');
const ChromeWebRequestAPIHandler = require('./web-request');

class ChromeAPIHandler {
constructor(extensionId) {
this.storage = new ChromeStorageAPIHandler(extensionId);
this.webRequest = new ChromeWebRequestAPIHandler(extensionId);
}

release() {
this.storage.release();
[this.storage, this.webRequest].forEach((api) => api.release())
}
}

Expand Down
54 changes: 54 additions & 0 deletions lib/browser/api/web-request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const { session } = require('electron');
const RCEventController = require('../rc-event-controller');

const WEBREQUEST_EVENTS = ['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders',
'onHeadersReceived', 'onAuthRequired', 'onResponseStarted', 'onBeforeRedirect', 'onCompleted',
'onErrorOccurred'];

const fromElectronHeadersToChromeHeaders = (cbReturn) => {
if (cbReturn && cbReturn.responseHeaders) {
const responseHeaders = {};
cbReturn.responseHeaders.forEach((header) => responseHeaders[header.name] = [header.value]);
cbReturn.responseHeaders = responseHeaders;
}

return cbReturn
};

const fromChromeHeadersToElectronHeaders = (details) => {
if (details.responseHeaders)
details.responseHeaders = Object.keys(details.responseHeaders).map((k) =>
({ name: k, value: details.responseHeaders[k][0]}));

return details;
};

class ChromeWebRequestAPIHandler {
constructor(extensionId) {
this.electronWebRequestApi = session.defaultSession.webRequest;
this.rcEventControllers = [];

WEBREQUEST_EVENTS.forEach(methodName => {
const controller = new RCEventController(`${extensionId}-webRequest-${methodName}`);
this.rcEventControllers.push(controller);
controller.on('add-listener', (listenerArgs, remoteCallListener) => {
const [filter, extraInfos] = listenerArgs;
this.electronWebRequestApi[methodName](filter, (details, callback) => {
if (callback)
remoteCallListener(fromChromeHeadersToElectronHeaders(details))
.then(returnedDetails => {
if (!returnedDetails) return callback(details);
return callback(fromElectronHeadersToChromeHeaders(returnedDetails))
})
.catch(e => {})
})
})
});
};

release () {
this.rcEventControllers.forEach(c => c.release())
}
}

module.exports = ChromeWebRequestAPIHandler;
1 change: 1 addition & 0 deletions lib/browser/chrome-extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const startBackgroundPages = function (manifest) {
`--preload=${path.join(__dirname, '../../preload.js')}`
]
})
// contents.openDevTools()
backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name }
contents.loadURL(url.format({
protocol: constants.EXTENSION_PROTOCOL,
Expand Down
31 changes: 31 additions & 0 deletions lib/browser/rc-event-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const { rpc, RpcIpcManager } = require('electron-simple-rpc');
const { ipcMain } = require('electron');
const EventEmitter = require('events');

ipcMain.setMaxListeners(100);

class RCEventController extends EventEmitter {
constructor(eventId) {
super();

this.controlerRPCScope = `${eventId}-controller`;
this.eventRPCScope = `${eventId}-event`;

this.rpcManager = new RpcIpcManager({
addListener: this._addListener.bind(this)
}, this.controlerRPCScope);

}

_addListener(listenerId, listenerArgs) {
const remoteCallListener = (args) =>
rpc(this.eventRPCScope, 'triggerListener')(listenerId, args).timeout(2500);
this.emit('add-listener', listenerArgs, remoteCallListener);
}

release() {
this.rpcManager.release();
}
}

module.exports = RCEventController;
14 changes: 9 additions & 5 deletions lib/renderer/extensions/event.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
class Event {
constructor () {
this.listeners = []
this.listeners = [];
}

addListener (callback) {
this.listeners.push(callback)
this.listeners.push(callback);
}

hasListener (callback) {
return this.listeners.indexOf(callback) !== -1;
}

removeListener (callback) {
const index = this.listeners.indexOf(callback)
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1)
}
}

emit (...args) {
for (const listener of this.listeners) {
listener(...args)
listener(...args);
}
}
}

module.exports = Event
module.exports = Event;
48 changes: 48 additions & 0 deletions lib/renderer/extensions/rc-event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const { rpc, RpcIpcManager } = require('electron-simple-rpc');
const uuid = require('uuid/v1');

class RCEvent {
constructor(eventId) {
this.listeners = new Map();

this.controllerRPCScope = `${eventId}-controller`;
this.eventRPCScope = `${eventId}-event`;

this.rpcManager = new RpcIpcManager({
triggerListener: this._triggerListener.bind(this)
}, this.eventRPCScope)
}

addListener(callback, ...args) {
const listenerId = uuid();
this.listeners.set(listenerId, callback);
rpc(this.controllerRPCScope, 'addListener')(listenerId, args);
}

hasListener(callback) {
return !!this._listenerIdFromCallback(callback)
}

removeListener(callback) {
const [listenerId, cb] = this._listenerIdFromCallback(callback);
this.listeners.delete(listenerId)
}

_triggerListener(listenerId, args) {
const listener = this.listeners.get(listenerId);
if (!listener) return;

try {
return listener.call(this, args);
} catch (e) {
console.error(e);
}
}

_listenerIdFromCallback(callback) {
return this.listeners.entries()
.find(e => e[1] === callback)
}
}

module.exports = RCEvent;
33 changes: 19 additions & 14 deletions lib/renderer/extensions/web-request.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
const Event = require('./event');
const { rpc, RpcIpcManager } = require('electron-simple-rpc');
const capitalize = require('capitalize');
const Event = require('../../renderer/extensions/event');
const RCEvent = require('./rc-event');

exports.setup = extensionId => {
return {
onBeforeRequest: new Event(),
onBeforeSendHeaders: new Event(),
onSendHeaders: new Event(),
onHeadersReceived: new Event(),
onAuthRequired: new Event(),
onResponseStarted: new Event(),
onBeforeRedirect: new Event(),
onCompleted: new Event(),
onErrorOccurred: new Event(),
handlerBehaviorChanged () {},
MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES: 10
const WEBREQUEST_EVENTS = ['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders',
'onHeadersReceived', 'onAuthRequired', 'onResponseStarted', 'onBeforeRedirect', 'onCompleted',
'onErrorOccurred'];

class ChromeWebRequestAPIClient {
constructor(extensionId) {
this.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES = 10;
WEBREQUEST_EVENTS.forEach(event => this[event] = new RCEvent(`${extensionId}-webRequest-${event}`));
}

handlerBehaviorChanged () {
console.warn('Call not implemented method **handlerBehaviorChanged**');
return Promise.resolve()
}
}

exports.setup = extensionId => new ChromeWebRequestAPIClient(extensionId);
Loading

0 comments on commit 6f48209

Please sign in to comment.