-
Notifications
You must be signed in to change notification settings - Fork 26
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
Changes from 26 commits
a15d459
d016ab6
617dceb
49b707d
a44802f
0cd332f
72464cd
6ff8393
9e4bce4
01a503f
569452e
5ddf5ab
1dcd9e1
cbc95cc
f4202af
bcc9ba7
6f535a6
62b47f4
ca809df
093cd9c
88f0831
395db78
130da8f
adf9472
24d22e5
332d1f2
c90bc7e
d8a63d5
7680b32
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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(); |
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: end of line |
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({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; |
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; |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} | ||
} | ||
|
||
_listenerIdFromCallback(callback) { | ||
return this.listeners.entries() | ||
.find(e => e[1] === callback) | ||
} | ||
} | ||
|
||
module.exports = RCEvent; |
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 () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's now ddd a |
||
console.warn('Call not implemented method **handlerBehaviorChanged**'); | ||
return Promise.resolve() | ||
} | ||
} | ||
|
||
exports.setup = extensionId => new ChromeWebRequestAPIClient(extensionId); |
There was a problem hiding this comment.
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