Last active
December 21, 2015 00:37
-
-
Save gtk2k/2f7b40afcd7a1c7df909 to your computer and use it in GitHub Desktop.
Get _getLivePreview() stream via chrome.sockets.tcp.xhr
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
(function () { | |
'use strict'; | |
var ChromeSocketsXMLHttpRequest = chrome.sockets.tcp.xhr = function () { | |
Object.defineProperties(this, { | |
options: { | |
enumerable: false, | |
writable: true, | |
value: { | |
uri: null, | |
data: null, | |
events: {}, | |
method: null, | |
createInfo: null, | |
inprogress: false, | |
redirects: { | |
max: 10, | |
current: 0, | |
last: null | |
}, | |
timer: { | |
id: null, | |
expired: false | |
}, | |
headers: { | |
'Connection': 'close',//'keep-alive', | |
'User-Agent': 'chrome.sockets.tcp.xhr' | |
}, | |
response: { | |
headers: [], | |
headersText: null | |
} | |
} | |
}, | |
props: { | |
enumerable: false, | |
configurable: false, | |
value: { | |
readyState: 0, | |
} | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-unsent | |
*/ | |
UNSENT: { | |
enumerable: false, | |
writable: true, | |
value: 0 | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-opened | |
*/ | |
OPENED: { | |
enumerable: false, | |
writable: true, | |
value: 1 | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-headers_received | |
* TODO: time in milliseconds. | |
*/ | |
HEADERS_RECEIVED: { | |
enumerable: false, | |
writable: true, | |
value: 2 | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-loading | |
* TODO: time in milliseconds. | |
*/ | |
LOADING: { | |
enumerable: false, | |
writable: true, | |
value: 3 | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-done | |
*/ | |
DONE: { | |
enumerable: false, | |
writable: true, | |
value: 4 | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute | |
* TODO: time in milliseconds. | |
*/ | |
timeout: { | |
enumerable: true, | |
writable: true, | |
value: 0 | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute | |
*/ | |
withCredentials: { | |
enumerable: true, | |
writable: true, | |
value: false | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-upload-attribute | |
*/ | |
upload: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-status-attribute | |
*/ | |
status: { | |
enumerable: true, | |
writable: true, | |
value: 0 | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-statustext-attribute | |
*/ | |
statusText: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute | |
*/ | |
responseType: { | |
enumerable: true, | |
writable: true, | |
value: 'text' | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-response-attribute | |
*/ | |
response: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute | |
*/ | |
responseText: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-responsexml-attribute | |
*/ | |
responseXML: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#event-handlers | |
*/ | |
onreadystatechange: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
ontimeout: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
onabort: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
onerror: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
onload: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
onloadstart: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
onloadend: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
onprogress: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
buffer:{ | |
enumerable: true, | |
writable: true, | |
value: [] | |
}, | |
loaded: { | |
enumerable: true, | |
writable: true, | |
value: 0 | |
}, | |
total: { | |
enumerable: true, | |
writable: true, | |
value: 0 | |
}, | |
isStreaming: { | |
enumerable: false, | |
writable: true, | |
value: false | |
}, | |
multipart:{ | |
enumerable: true, | |
writable: true, | |
value: false | |
}, | |
encoder:{ | |
enumerable: true, | |
writable: false, | |
value: new TextEncoder('utf8') | |
}, | |
decoder:{ | |
enumerable: true, | |
writable: false, | |
value: new TextDecoder('utf8') | |
}, | |
imagePreview:{ | |
enumerable: false, | |
writable: true, | |
value: null | |
}, | |
/** | |
* custom event to match `chrome.webRequest.onBeforeRedirect` | |
* http://developer.chrome.com/extensions/webRequest#event-onBeforeRedirect | |
*/ | |
beforeredirect: { | |
enumerable: true, | |
writable: true, | |
value: null | |
}, | |
readyState: { | |
enumerable: true, | |
get: function () { | |
return this.props.readyState; | |
}, | |
set: function (value) { | |
this.props.readyState = value; | |
this.dispatchEvent('readystatechange'); | |
} | |
} | |
}); | |
}; | |
/** | |
* Regular Expression for URL validation | |
* Modified: added capturing groups | |
* | |
* Author: Diego Perini | |
* Updated: 2010/12/05 | |
* License: MIT | |
* | |
* Copyright (c) 2010-2013 Diego Perini (http://www.iport.it) | |
* | |
* https://gist.github.com/dperini/729294 | |
*/ | |
ChromeSocketsXMLHttpRequest.prototype.regex = new RegExp( | |
'^' + | |
// protocol identifier | |
'(?:(https?|ftp)://)' + | |
// user:pass authentication | |
'(?:\\S+(?::\\S*)?@)?' + | |
'(' + | |
// IP address exclusion | |
// private & local networks | |
'(?!(?:10|127)(?:\\.\\d{1,3}){3})' + | |
'(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' + | |
'(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' + | |
// IP address dotted notation octets | |
// excludes loopback network 0.0.0.0 | |
// excludes reserved space >= 224.0.0.0 | |
// excludes network & broacast addresses | |
// (first & last IP address of each class) | |
'(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' + | |
'(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' + | |
'(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' + | |
'|' + | |
// host name | |
'(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)' + | |
// domain name | |
'(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*' + | |
// TLD identifier | |
'(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))' + | |
')' + | |
// port number | |
'(?::(\\d{2,5}))?' + | |
// resource path | |
'(/[^\\s]*)?' + | |
'$', 'i' | |
); | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-open()-method | |
*/ | |
ChromeSocketsXMLHttpRequest.prototype.open = function (method, url) { | |
this.options.method = method; | |
this.options.uri = new URL(url);//this.regex.exec(url); | |
// check if the method is valid | |
if (!this.options.method) { | |
throw new TypeError('method is not a valid HTTP method'); | |
} | |
// check if the URI parsed properly | |
if (this.options.uri === null) { | |
throw new TypeError('url cannot be parsed'); | |
} | |
// catch no path specified error | |
if (this.options.uri[4] === undefined) { | |
this.options.uri[4] = '/'; | |
} | |
// set readyState to OPENED | |
this.readyState = this.OPENED; | |
this.setRequestHeader('Host', this.options.uri.host); | |
}; | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method | |
*/ | |
ChromeSocketsXMLHttpRequest.prototype.setRequestHeader = function (header, value) { | |
if (this.readyState !== this.OPENED || this.options.inprogress === true) { | |
throw new TypeError('InvalidStateError'); | |
} | |
if (!header) { | |
throw new TypeError('header is not a valid HTTP header field name'); | |
} | |
if (!value && value.length !== 0) { | |
throw new TypeError('value is not a valid HTTP header field value.'); | |
} | |
// TODO: If header is in the headers list, append ",", followed by U+0020, followed by value. | |
this.options.headers[header] = value; | |
}; | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-send()-method | |
*/ | |
ChromeSocketsXMLHttpRequest.prototype.send = function (data) { | |
// If the state is not OPENED, throw an "InvalidStateError" exception. | |
// If the send() flag is set, throw an "InvalidStateError" exception. | |
if (this.readyState !== this.OPENED || this.options.inprogress === true) { | |
throw new TypeError('InvalidStateError'); | |
} | |
// If the request method is GET or HEAD, set data to null | |
if (['GET', 'HEADER'].indexOf(this.options.method.toUpperCase()) !== -1) { | |
data = null; | |
} | |
// If data is null, do not include a request entity body and go to the next step. | |
if (data !== null) { | |
var encoding = null; | |
var mimetype = null; | |
if (data instanceof ArrayBuffer) { | |
// Let the request entity body be the raw data represented by data. | |
} else if (data instanceof Blob) { | |
// if the object's type attribute is not the empty string let mime type be its value. | |
// Let the request entity body be the raw data represented by data. | |
} else if (data instanceof HTMLElement) { | |
// Let encoding be "UTF-8". | |
encoding = 'UTF-8'; | |
// If data is an HTML document, let mime type be "text/html" | |
// or let mime type be "application/xml" otherwise. | |
mimetype = 'text/html'; | |
// Then append ";charset=UTF-8" to mime type. | |
mimetype += ';charset=UTF-8'; | |
//Let the request entity body be data, serialized, converted to Unicode, and utf-8 encoded. Re-throw any exception serializing throws. | |
} else if (data instanceof FormData) { | |
// Let the request entity body be the result of running the multipart/form-data encoding algorithm with data as form data set and with utf-8 as the explicit character encoding. | |
//Let mime type be the concatenation of "multipart/form-data;", a U+0020 SPACE character, "boundary=", and the multipart/form-data boundary string generated by the multipart/form-data encoding algorithm. | |
} else if (typeof (data) === 'string') { | |
// Let encoding be "UTF-8". | |
encoding = 'UTF-8'; | |
// Let mime type be "text/plain;charset=UTF-8". | |
mimetype = 'text/plain;charset=UTF-8'; | |
// Let the request entity body be data, utf-8 encoded. | |
} | |
this.options.data = data; | |
} | |
// If a Content-Type header is in author request headers and its value is a valid MIME type that has a charset parameter whose value is not a case-insensitive match for encoding, and encoding is not null, set all the charset parameters of that Content-Type header to encoding. | |
// If no Content-Type header is in author request headers and mime type is not null, append a Content-Type header with value mime type to author request headers. | |
// Unset the error flag, upload complete flag and upload events flag. | |
// If there is no request entity body or if it is empty, set the upload complete flag. | |
// Set the send() flag. | |
this.options.inprogress = true; | |
// Fire a progress event named loadstart. | |
this.dispatchEvent('loadstart'); | |
// continue with sockets setup | |
var socketProperties = { | |
persistent: false, | |
name: 'chrome.sockets.tcp.xhr' | |
}; | |
chrome.sockets.tcp.create(socketProperties, this.onCreate.bind(this)); | |
if (this.timeout > 0) { | |
this.options.timer.id = setTimeout(this.expireTimer.bind(this), this.timeout); | |
} | |
}; | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-abort()-method | |
*/ | |
ChromeSocketsXMLHttpRequest.prototype.abort = function () { | |
this.disconnect(); | |
}; | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method | |
*/ | |
ChromeSocketsXMLHttpRequest.prototype.getResponseHeader = function (header) { | |
return this.options.response.headers[header]; | |
}; | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method | |
*/ | |
ChromeSocketsXMLHttpRequest.prototype.getAllResponseHeaders = function () { | |
return this.options.response.headersText; | |
}; | |
/** | |
* http://www.w3.org/TR/XMLHttpRequest/#the-overridemimetype()-method | |
*/ | |
ChromeSocketsXMLHttpRequest.prototype.overrideMimeType = function (mimetype) { | |
}; | |
/** | |
* event managers | |
*/ | |
ChromeSocketsXMLHttpRequest.prototype.addEventListener = function (name, callback) { | |
if (this.options.events[name]) { | |
this.options.events[name].push(callback); | |
} else { | |
this.options.events[name] = new Array(callback); | |
} | |
return this; | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.removeEventListener = function (name, callback) { | |
if (this.options.events[name]) { | |
var i = this.options.events[name].indexOf(callback); | |
if (i > -1) { | |
this.options.events[name].splice(i, 1); | |
} else { | |
return false; | |
} | |
return true; | |
} else { | |
return false; | |
} | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.dispatchEvent = function (name) { | |
var args = arguments; | |
if (this.hasOwnProperty('on' + name)) { | |
if (this['on' + name]) { | |
this['on' + name].apply(this, Array.prototype.slice.call(args, 1)); | |
} | |
} | |
if (!this.options.events[name]) { | |
return false; | |
} | |
this.options.events[name].forEach(function (event) { | |
event.apply(this, Array.prototype.slice.call(args, 1)); | |
}.bind(this)); | |
}; | |
/** | |
* chrome.sockets.tcp events | |
*/ | |
ChromeSocketsXMLHttpRequest.prototype.onCreate = function (createInfo) { | |
if (!this.options.inprogress) { | |
return; | |
} | |
var port = this.options.uri.port ? parseInt(this.options.uri.port, null) : 80; | |
this.options.createInfo = createInfo; | |
chrome.sockets.tcp.connect(createInfo.socketId, this.options.uri.host, port, this.onConnect.bind(this)); | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.onConnect = function (result) { | |
if (!this.options.inprogress) { | |
return; | |
} | |
if (this.options.timer.expired) { | |
return; | |
} else if (result < 0) { | |
this.error({ | |
error: 'connect error', | |
resultCode: result | |
}); | |
} else { | |
// assign recieve listner | |
chrome.sockets.tcp.onReceive.addListener(this.onReceive.bind(this)); | |
// send message as ArrayBuffer | |
this.generateMessage().toArrayBuffer(function sendMessage(buffer) { | |
chrome.sockets.tcp.send(this.options.createInfo.socketId, buffer, this.onSend.bind(this)); | |
}.bind(this)); | |
} | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.onSend = function (sendInfo) { | |
if (sendInfo.resultCode < 0) { | |
this.error({ | |
error: 'send error', | |
resultCode: sendInfo.resultCode | |
}); | |
this.disconnect(); | |
} | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.onReceiveError = function (info) { | |
this.error({ | |
error: 'receive error', | |
resultCode: info.resultCode | |
}); | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.onReceive = function (info) { | |
// chrome.sockets.tcp.onReceiveError.addListener(this.onReceiveError.bind(this)); | |
if (!this.options.inprogress) { | |
return; | |
} | |
if (info.socketId !== this.options.createInfo.socketId) { | |
return; | |
} | |
// immediately disconnect on first respond | |
//this.disconnect(); | |
var ui8a = new Uint8Array(info.data); | |
var bLen = ui8a.byteLength; | |
var offset = 0; | |
var i = 0; | |
//var str = this.decoder.decode(ui8a); | |
//console.log('raw', str, this.total, this.loaded); | |
if (ui8a[0] === 0x48 && ui8a[1] === 0x54 && ui8a[2] === 0x54 && ui8a[3] === 0x50) { // 'HTTP' | |
for (i = 10; i < bLen; i++) { | |
if (ui8a[i] === 0xD && ui8a[i + 1] === 0xA && ui8a[i + 2] === 0xD && ui8a[i + 3] === 0xA) { // '\r\n\r\n' | |
var str = this.decoder.decode(ui8a.subarray(0, i)); | |
this.options.response.headersText = str; | |
var headerLines = str.split('\r\n'); | |
var statusLine = headerLines.shift(); | |
var statusLineMatch = statusLine.match(/(HTTP\/\d\.\d)\s+((\d+)\s+.*)/); | |
if (statusLineMatch) { | |
this.status = parseInt(statusLineMatch[3], 0); | |
this.statusText = statusLineMatch[2]; | |
} | |
var headers = {}; | |
headerLines.forEach(function (line) { | |
if (line.indexOf(':') !== -1) { | |
var kv = line.split(':').map(function (val) { return val.trim(); }); | |
headers[kv[0]] = kv[1]; | |
} | |
}); | |
this.loaded = 0; | |
this.total = headers['Content-Length'] ? +headers['Content-Length'] : 0; | |
this.buffer = []; | |
this.options.response.headers = headers; | |
offset = i + 4; | |
if (offset >= bLen) return; | |
break; | |
} | |
} | |
} | |
if (this.multipart) { | |
this.isStreaming = true; | |
if (this.total) { | |
var len = Math.min(this.total - this.loaded, ui8a.byteLength); | |
this.buffer.push(ui8a.subarray(0, len)); | |
this.loaded += len; | |
if (this.loaded >= this.total) { | |
if (this.imagePreview) { // this.imagePrevew is <img>Element | |
var oldSrc = this.imagePreview.src; | |
this.imagePreview.src = URL.createObjectURL(new Blob(this.buffer)); | |
oldSrc && URL.revokeObjectURL(oldSrc); | |
} | |
//that.response = new Blob(bufArray); | |
this.total = this.loaded = 0; | |
this.buffer = []; | |
//this.disconnect(); | |
} | |
} | |
if (this.total === 0) { | |
var parseEnd = i + 100; | |
for (i = i; i < parseEnd; i++) { | |
// search Content"-Length:" | |
if ( | |
ui8a[i] === 45 && // - | |
ui8a[i + 1] === 76 && // L | |
ui8a[i + 2] === 101 && // e | |
ui8a[i + 3] === 110 && // n | |
ui8a[i + 4] === 103 && // g | |
ui8a[i + 5] === 116 && // t | |
ui8a[i + 6] === 104 && // h | |
ui8a[i + 7] === 58 // : | |
) { | |
i += 8; | |
for (var j = i; j < parseEnd; j++) { | |
if (ui8a[j] == 0xD && ui8a[j + 1] === 0xA && ui8a[j + 2] == 0xD && ui8a[j + 3] === 0xA) {// '\r\n\r\n' | |
this.loaded = 0; | |
this.total = +this.decoder.decode(ui8a.subarray(i, j)); | |
this.buffer = []; | |
j += 4; | |
if (j < ui8a.length) { | |
var buf = ui8a.subarray(j); | |
this.buffer.push(buf); | |
this.loaded += buf.length; | |
} | |
} | |
} | |
} | |
} | |
} | |
} else { | |
if (!this.total) return; | |
var buf = ui8a.subarray(offset); | |
this.loaded += buf.length; | |
this.buffer.push(buf); | |
//this.onprogress && this.onprogress({ type: 'image', loaded: this.loaded, total: this.total }); | |
if (this.loaded >= this.total) { | |
var blob = new Blob(this.buffer); | |
this.buffer = new Uint8Array(); | |
this.total = this.loaded = 0; | |
var that = this; | |
if (this.responseType === 'json') { | |
var fr = new FileReader(); | |
fr.onload = function (e) { | |
try { | |
that.response = JSON.parse(e.target.result); | |
} catch (e) { | |
//this.onerror && this.onerror(e); | |
} | |
that.disconnect(); | |
that.processResponse(); | |
}.bind(this); | |
fr.onerror = function (e) { | |
//this.onerror && this.onerror(e); | |
that.disconnect(); | |
that.processResponse(); | |
} | |
fr.readAsText(blob); | |
} else { | |
this.response = blob; | |
this.processResponse(); | |
} | |
} | |
} | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.parseContentLength = function (ui8a, offset) { | |
var searchEnd = offset + 100; | |
for (var i = offset; i < searchEnd; i++) { | |
// search Content"-Length:" | |
if ( | |
ui8a[i] === 45 && // - | |
ui8a[i + 1] === 76 && // L | |
ui8a[i + 2] === 101 && // e | |
ui8a[i + 3] === 110 && // n | |
ui8a[i + 4] === 103 && // g | |
ui8a[i + 5] === 116 && // t | |
ui8a[i + 6] === 104 && // h | |
ui8a[i + 7] === 58 // : | |
) { | |
i += 8; | |
for (var j = i; j < searchEnd; j++) { | |
if (ui8a[j] == 0xD && ui8a[j + 1] === 0xA && ui8a[j + 2] == 0xD && ui8a[j + 3] === 0xA) { | |
this.loaded = 0; | |
this.total = +this.decoder.decode(ui8a.subarray(i, j)); | |
this.buffer = []; | |
j += 4; | |
if (j < ui8a.length) { | |
body = ui8a.subarray(j); | |
} | |
} | |
} | |
} | |
} | |
}; | |
/** | |
* internal methods | |
*/ | |
ChromeSocketsXMLHttpRequest.prototype.parseResponse = function (response) { | |
// detect CRLFx2 position | |
console.log(response); | |
var responseMatch = response.match(/\r\n\r\n/); | |
// something went wrong | |
if (responseMatch === null) { | |
this.error({ | |
error: 'could not parse response' | |
}); | |
return; | |
} | |
// slice the headers up to CRLFx2 | |
this.options.response.headersText = response.slice(0, responseMatch.index); | |
// slice the body right after CRLFx2 and set the response object | |
this.responseText = response.slice(responseMatch.index + 4); | |
// parse headers | |
var headerLines = this.options.response.headersText.split('\r\n'); | |
var statusLine = headerLines.shift(); | |
var statusLineMatch = statusLine.match(/(HTTP\/\d\.\d)\s+((\d+)\s+.*)/); | |
if (statusLineMatch) { | |
this.status = parseInt(statusLineMatch[3], 0); | |
this.statusText = statusLineMatch[2]; | |
} | |
headerLines.forEach(function (headerLine) { | |
// detect CRLFx2 position | |
var headerLineMatch = headerLine.match(/:/); | |
// sanity check | |
if (headerLineMatch) { | |
// slice the header line at the colon and trim output | |
var header = headerLine.slice(0, headerLineMatch.index).trim(); | |
var value = headerLine.slice(headerLineMatch.index + 1).trim(); | |
this.options.response.headers[header] = value; | |
} | |
}.bind(this)); | |
this.processResponse(); | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.processResponse = function () { | |
// If the response has an HTTP status code of 301, 302, 303, 307, or 308 | |
// TODO: implement infinite loop precautions | |
if ([301, 302, 303, 307, 308].indexOf(this.status) !== -1) { | |
// stop | |
this.disconnect(); | |
// set the new destination | |
var redirectUrl = this.getResponseHeader('Location'); | |
// notify | |
this.dispatchEvent('beforeredirect', redirectUrl, this.options.response.headers, this.statusText); | |
// enforece the redirect limit | |
if (this.options.redirects.current === this.options.redirects.max) { | |
this.error({ | |
error: 'max redirects', | |
resultCode: 310 | |
}); | |
return; | |
} | |
// detect a loop | |
if (this.options.redirects.last === redirectUrl) { | |
this.error({ | |
error: 'redirect loop' | |
}); | |
return; | |
} else { | |
this.options.redirects.last = redirectUrl; | |
} | |
// count | |
this.options.redirects.current++; | |
// start a new call | |
this.open(this.options.method, redirectUrl); | |
this.send(this.options.data); | |
return; | |
} | |
// set readyState to HEADERS_RECEIVED | |
this.readyState = this.HEADERS_RECEIVED; | |
// set readyState to LOADING | |
this.readyState = this.LOADING; | |
// TODO: set the response entity body according to responseType, as an ArrayBuffer, Blob, Document, JavaScript object (for "json"), or string. | |
//this.response = this.responseText; | |
// set readyState to DONE | |
this.readyState = this.DONE; | |
// Fire a progress event named "progress". | |
this.dispatchEvent('progress'); | |
// Fire a progress event named load. | |
this.dispatchEvent('load'); | |
// Fire a progress event named loadend | |
this.dispatchEvent('loadend'); | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.generateMessage = function () { | |
var headers = []; | |
// add missing parts to header | |
headers.push(this.options.method + ' ' + this.options.uri.pathname + ' HTTP/1.1'); | |
for (var name in this.options.headers) { | |
headers.push(name + ': ' + this.options.headers[name]); | |
} | |
if (this.options.data) { | |
headers.push('Content-Length: ' + this.options.data.byteLength); | |
return headers.join('\r\n') + '\r\n\r\n' + this.options.data; | |
} else { | |
return headers.join('\r\n') + '\r\n\r\n'; | |
} | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.error = function (error) { | |
// list of network errors as defined in chromium source: | |
// https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h&sq=package:chromium | |
// | |
// Ranges: | |
// 0- 99 System related errors | |
// 100-199 Connection related errors | |
// 200-299 Certificate errors | |
// 300-399 HTTP errors | |
// 800-899 DNS resolver errors | |
var errorCodes = { | |
1: 'An asynchronous IO operation is not yet complete.', | |
2: 'A generic failure occurred.', | |
3: 'An operation was aborted (due to user action)', | |
4: 'An argument to the function is incorrect.', | |
5: 'The handle or file descriptor is invalid', | |
6: 'The file or directory cannot be found', | |
7: 'An operation timed out', | |
8: 'The file is too large', | |
9: 'An unexpected error. This may be caused by a programming mistake or an invalid assumption', | |
10: 'Permission to access a resource, other than the network, was denied', | |
11: 'The operation failed because of unimplemented functionality', | |
12: 'There were not enough resources to complete the operation', | |
13: 'Memory allocation failed', | |
14: 'The file upload failed because the file\'s modification time was different from the expectation', | |
15: 'The socket is not connected', | |
16: 'The file already exists', | |
17: 'The path or file name is too long', | |
18: 'Not enough room left on the disk', | |
19: 'The file has a virus', | |
20: 'The client chose to block the request', | |
21: 'The network changed', | |
22: 'The request was blocked by the URL blacklist configured by the domain administrator', | |
23: 'The socket is already connected', | |
100: 'A connection was closed (corresponding to a TCP FIN)', | |
101: 'A connection was reset (corresponding to a TCP RST)', | |
102: 'A connection attempt was refused', | |
103: 'A connection timed out as a result of not receiving an ACK for data sent. This can include a FIN packet that did not get ACK\'d', | |
104: 'A connection attempt failed', | |
105: 'The host name could not be resolved', | |
106: 'The Internet connection has been lost', | |
107: 'An SSL protocol error occurred', | |
108: 'The IP address or port number is invalid (e.g., cannot connect to the IP address 0 or the port 0)', | |
109: 'The IP address is unreachable. This usually means that there is no route to the specified host or network', | |
110: 'The server requested a client certificate for SSL client authentication', | |
111: 'A tunnel connection through the proxy could not be established', | |
112: 'No SSL protocol versions are enabled', | |
113: 'The client and server don\'t support a common SSL protocol version or cipher suite', | |
114: 'The server requested a renegotiation (rehandshake)', | |
115: 'The proxy requested authentication (for tunnel establishment) with an unsupported method', | |
116: 'During SSL renegotiation (rehandshake), the server sent a certificate with an error', | |
117: 'The SSL handshake failed because of a bad or missing client certificate', | |
118: 'A connection attempt timed out', | |
119: 'There are too many pending DNS resolves, so a request in the queue was aborted', | |
120: 'Failed establishing a connection to the SOCKS proxy server for a target host', | |
121: 'The SOCKS proxy server failed establishing connection to the target host because that host is unreachable', | |
122: 'The request to negotiate an alternate protocol failed', | |
123: 'The peer sent an SSL no_renegotiation alert message', | |
124: 'Winsock sometimes reports more data written than passed. This is probably due to a broken LSP', | |
125: 'An SSL peer sent us a fatal decompression_failure alert.', | |
126: 'An SSL peer sent us a fatal bad_record_mac alert', | |
127: 'The proxy requested authentication (for tunnel establishment)', | |
128: 'A known TLS strict server didn\'t offer the renegotiation extension', | |
129: 'The SSL server attempted to use a weak ephemeral Diffie-Hellman key', | |
130: 'Could not create a connection to the proxy server.', | |
131: 'A mandatory proxy configuration could not be used.', | |
133: 'We\'ve hit the max socket limit for the socket pool while preconnecting.', | |
134: 'The permission to use the SSL client certificate\'s private key was denied', | |
135: 'The SSL client certificate has no private key', | |
136: 'The certificate presented by the HTTPS Proxy was invalid', | |
137: 'An error occurred when trying to do a name resolution (DNS)', | |
138: 'Permission to access the network was denied.', | |
139: 'The request throttler module cancelled this request to avoid DDOS', | |
140: 'A request to create an SSL tunnel connection through the HTTPS proxy received a non-200 (OK) and non-407 (Proxy Auth) response.', | |
141: 'We were unable to sign the CertificateVerify data of an SSL client auth handshake with the client certificate\'s private key', | |
142: 'The message was too large for the transport', | |
143: 'A SPDY session already exists, and should be used instead of this connection', | |
145: 'Websocket protocol error.', | |
146: 'Connection was aborted for switching to another ptotocol.', | |
147: 'Returned when attempting to bind an address that is already in use', | |
148: 'An operation failed because the SSL handshake has not completed', | |
149: 'SSL peer\'s public key is invalid', | |
150: 'The certificate didn\'t match the built-in public key pins for the host name', | |
151: 'Server request for client certificate did not contain any types we support', | |
152: 'Server requested one type of cert, then requested a different type while the first was still being generated', | |
153: 'An SSL peer sent us a fatal decrypt_error alert. ', | |
154: 'There are too many pending WebSocketJob instances, so the new job was not pushed to the queue', | |
155: 'There are too many active SocketStream instances, so the new connect request was rejected', | |
156: 'The SSL server certificate changed in a renegotiation', | |
157: 'The SSL server indicated that an unnecessary TLS version fallback was performed', | |
158: 'Certificate Transparency: All Signed Certificate Timestamps failed to verify', | |
159: 'The SSL server sent us a fatal unrecognized_name alert', | |
300: 'The URL is invalid', | |
301: 'The scheme of the URL is disallowed', | |
302: 'The scheme of the URL is unknown', | |
310: 'Attempting to load an URL resulted in too many redirects', | |
311: 'Attempting to load an URL resulted in an unsafe redirect (e.g., a redirect to file: is considered unsafe)', | |
312: 'Attempting to load an URL with an unsafe port number.', | |
320: 'The server\'s response was invalid', | |
321: 'Error in chunked transfer encoding', | |
322: 'The server did not support the request method', | |
323: 'The response was 407 (Proxy Authentication Required), yet we did not send the request to a proxy', | |
324: 'The server closed the connection without sending any data', | |
325: 'The headers section of the response is too large', | |
326: 'The PAC requested by HTTP did not have a valid status code (non-200)', | |
327: 'The evaluation of the PAC script failed', | |
328: 'The response was 416 (Requested range not satisfiable) and the server cannot satisfy the range requested', | |
329: 'The identity used for authentication is invalid', | |
330: 'Content decoding of the response body failed', | |
331: 'An operation could not be completed because all network IO is suspended', | |
332: 'FLIP data received without receiving a SYN_REPLY on the stream', | |
333: 'Converting the response to target encoding failed', | |
334: 'The server sent an FTP directory listing in a format we do not understand', | |
335: 'Attempted use of an unknown SPDY stream id', | |
336: 'There are no supported proxies in the provided list', | |
337: 'There is a SPDY protocol error', | |
338: 'Credentials could not be established during HTTP Authentication', | |
339: 'An HTTP Authentication scheme was tried which is not supported on this machine', | |
340: 'Detecting the encoding of the response failed', | |
341: '(GSSAPI) No Kerberos credentials were available during HTTP Authentication', | |
342: 'An unexpected, but documented, SSPI or GSSAPI status code was returned', | |
343: 'The environment was not set up correctly for authentication', | |
344: 'An undocumented SSPI or GSSAPI status code was returned', | |
345: 'The HTTP response was too big to drain', | |
346: 'The HTTP response contained multiple distinct Content-Length headers', | |
347: 'SPDY Headers have been received, but not all of them - status or version headers are missing, so we\'re expecting additional frames to complete them', | |
348: 'No PAC URL configuration could be retrieved from DHCP.', | |
349: 'The HTTP response contained multiple Content-Disposition headers', | |
350: 'The HTTP response contained multiple Location headers', | |
351: 'SPDY server refused the stream. Client should retry. This should never be a user-visible error', | |
352: 'SPDY server didn\'t respond to the PING message', | |
353: 'The request couldn\'t be completed on an HTTP pipeline. Client should retry', | |
354: 'The HTTP response body transferred fewer bytes than were advertised by the Content-Length header when the connection is closed', | |
355: 'The HTTP response body is transferred with Chunked-Encoding, but the terminating zero-length chunk was never sent when the connection is closed', | |
356: 'There is a QUIC protocol error', | |
357: 'The HTTP headers were truncated by an EOF', | |
358: 'The QUIC crytpo handshake failed.', | |
359: 'An https resource was requested over an insecure QUIC connection', | |
501: 'The server\'s response was insecure (e.g. there was a cert error)', | |
502: 'The server responded to a <keygen> with a generated client cert that we don\'t have the matching private key for', | |
503: 'An error adding to the OS certificate database (e.g. OS X Keychain)', | |
800: 'DNS resolver received a malformed response', | |
801: 'DNS server requires TCP', | |
802: 'DNS server failed.', | |
803: 'DNS transaction timed out', | |
804: 'The entry was not found in cache, for cache-only lookups', | |
805: 'Suffix search list rules prevent resolution of the given host name', | |
806: 'Failed to sort addresses according to RFC3484' | |
}; | |
if (this.options.inprogress) { | |
this.disconnect(); | |
} | |
if (error.resultCode) { | |
error.resultCode = Math.abs(error.resultCode); | |
error.message = errorCodes[Math.abs(error.resultCode)]; | |
} | |
this.dispatchEvent('error', error); | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.disconnect = function (callback) { | |
this.options.inprogress = false; | |
if (this.options.createInfo !== null) { | |
chrome.sockets.tcp.disconnect(this.options.createInfo.socketId); | |
chrome.sockets.tcp.close(this.options.createInfo.socketId, function () { | |
callback && callback(); | |
}); | |
this.options.createInfo = null; | |
return; | |
} | |
callback && callback(); | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.expireTimer = function () { | |
if (this.readyState === this.OPENED) { | |
this.disconnect(); | |
this.options.timer.expired = true; | |
this.error({ | |
error: 'timed out' | |
}); | |
this.dispatchEvent('timeout'); | |
} | |
}; | |
ChromeSocketsXMLHttpRequest.prototype.setMaxRedirects = function (max) { | |
this.options.redirects.max = max; | |
}; | |
/** | |
* internal methods | |
* TODO: consider removing from global objects | |
*/ | |
ArrayBuffer.prototype.toString = function (callback) { | |
var blob = new Blob([this]); | |
var reader = new FileReader(); | |
reader.onload = function (e) { | |
callback(e.target.result); | |
}; | |
reader.readAsText(blob); | |
}; | |
String.prototype.toArrayBuffer = function (callback) { | |
var blob = new Blob([this]); | |
var reader = new FileReader(); | |
reader.onload = function (e) { | |
callback(e.target.result); | |
}; | |
reader.readAsArrayBuffer(blob); | |
}; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment