Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WebRequest API #7

Merged
merged 29 commits into from
Nov 28, 2017
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
"*://*/*",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One day we'll need to handle this correctly :) one day

"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
51 changes: 51 additions & 0 deletions lib/browser/api/web-request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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 =>
callback(fromElectronHeadersToChromeHeaders(returnedDetails)));
})
})
});
};

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

module.exports = ChromeWebRequestAPIHandler;
Copy link
Contributor

@alexstrat alexstrat Nov 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: end of line

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
28 changes: 28 additions & 0 deletions lib/browser/rc-event-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { rpc, RpcIpcManager } = require('electron-simple-rpc');
const EventEmitter = require('events');

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

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

this.rcpManager = new RpcIpcManager({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: type rpc

addListener: this._addListener.bind(this)
}, this.controlerRPCScope);

}

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

release() {
this.rcpManager.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.rcpManager = 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) {
return e
Copy link
Contributor

@alexstrat alexstrat Nov 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather console.error and return nothing, otherwise it'll try to pass it into callback on main's side

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless electron-simple-rpc is smart enough to recognize it's an error.
However we don't care

}
}

_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 () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's now ddd a console.warning when dummy-mock methods

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

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