├── .gitignore ├── readme.markdown ├── package.json ├── LICENSE ├── LICENSE_HTTP_BROWSERIFY ├── lib ├── response.js └── request.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | # @tradle/react-native-http 2 | 3 | The 4 | [http](http://nodejs.org/docs/v0.4.10/api/all.html#hTTP) module from node.js, 5 | but for React Native. 6 | 7 | Fork of [http-browserify](https://github.com/substack/http-browserify) 8 | 9 | # install 10 | 11 | ``` 12 | npm install @tradle/react-native-http 13 | ``` 14 | 15 | # usage 16 | 17 | To load this module when you `require('http')`, ensure you have a mapping in either the "browser" or "react-native" field in your package.json: 18 | 19 | ```json 20 | "browser": { 21 | "http": "@tradle/react-native-http" 22 | } 23 | ``` 24 | 25 | or 26 | 27 | ```json 28 | "react-native": { 29 | "http": "@tradle/react-native-http" 30 | } 31 | ``` 32 | 33 | # license 34 | 35 | MIT 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tradle/react-native-http", 3 | "version": "2.0.1", 4 | "description": "http module compatibility for react-native", 5 | "main": "index.js", 6 | "directories": { 7 | "lib": ".", 8 | "example": "example", 9 | "test": "test" 10 | }, 11 | "scripts": { 12 | "test": "tape test/*.js" 13 | }, 14 | "dependencies": { 15 | "Base64": "~0.2.0", 16 | "inherits": "~2.0.1" 17 | }, 18 | "devDependencies": { 19 | "ecstatic": "~0.1.6", 20 | "tape": "~2.3.2" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/tradle/react-native-http.git" 25 | }, 26 | "keywords": [ 27 | "http", 28 | "react-native", 29 | "compatible", 30 | "shim" 31 | ], 32 | "license": "MIT/X11" 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015 Tradle, Inc. 4 | 5 | Permission is hereby granted, free of charge, 6 | to any person obtaining a copy of this software and 7 | associated documentation files (the "Software"), to 8 | deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, 10 | merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom 12 | the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 22 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /LICENSE_HTTP_BROWSERIFY: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 James Halliday (mail@substack.net) 4 | 5 | Permission is hereby granted, free of charge, 6 | to any person obtaining a copy of this software and 7 | associated documentation files (the "Software"), to 8 | deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, 10 | merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom 12 | the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 22 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/response.js: -------------------------------------------------------------------------------- 1 | var Stream = require('stream'); 2 | var util = require('util'); 3 | 4 | var Response = module.exports = function (res) { 5 | this.offset = 0; 6 | this.readable = true; 7 | }; 8 | 9 | util.inherits(Response, Stream); 10 | 11 | var capable = { 12 | streaming : true, 13 | status2 : true 14 | }; 15 | 16 | function parseHeaders (res) { 17 | var lines = res.getAllResponseHeaders().split(/\r?\n/); 18 | var headers = {}; 19 | for (var i = 0; i < lines.length; i++) { 20 | var line = lines[i]; 21 | if (line === '') continue; 22 | 23 | var m = line.match(/^([^:]+):\s*(.*)/); 24 | if (m) { 25 | var key = m[1].toLowerCase(), value = m[2]; 26 | 27 | if (headers[key] !== undefined) { 28 | 29 | if (isArray(headers[key])) { 30 | headers[key].push(value); 31 | } 32 | else { 33 | headers[key] = [ headers[key], value ]; 34 | } 35 | } 36 | else { 37 | headers[key] = value; 38 | } 39 | } 40 | else { 41 | headers[line] = true; 42 | } 43 | } 44 | return headers; 45 | } 46 | 47 | Response.prototype.getResponse = function (xhr) { 48 | var respType = String(xhr.responseType).toLowerCase(); 49 | if (respType === 'blob') return xhr.responseBlob || xhr.response; 50 | if (respType === 'arraybuffer') return xhr.response; 51 | return xhr.responseText; 52 | } 53 | 54 | Response.prototype.getHeader = function (key) { 55 | return this.headers[key.toLowerCase()]; 56 | }; 57 | 58 | Response.prototype.handle = function (res) { 59 | if (res.readyState === 2 && capable.status2) { 60 | try { 61 | this.statusCode = res.status; 62 | this.headers = parseHeaders(res); 63 | } 64 | catch (err) { 65 | capable.status2 = false; 66 | } 67 | 68 | if (capable.status2) { 69 | this.emit('ready'); 70 | } 71 | } 72 | else if (capable.streaming && res.readyState === 3) { 73 | try { 74 | if (!this.statusCode) { 75 | this.statusCode = res.status; 76 | this.headers = parseHeaders(res); 77 | this.emit('ready'); 78 | } 79 | } 80 | catch (err) {} 81 | 82 | try { 83 | this._emitData(res); 84 | } 85 | catch (err) { 86 | capable.streaming = false; 87 | } 88 | } 89 | else if (res.readyState === 4) { 90 | if (!this.statusCode) { 91 | this.statusCode = res.status; 92 | this.emit('ready'); 93 | } 94 | this._emitData(res); 95 | 96 | if (res.error) { 97 | this.emit('error', this.getResponse(res)); 98 | } 99 | else this.emit('end'); 100 | 101 | this.emit('close'); 102 | } 103 | }; 104 | 105 | Response.prototype._emitData = function (res) { 106 | var respBody = this.getResponse(res) 107 | if (respBody.length > this.offset) { 108 | this.emit('data', new Buffer(respBody.slice(this.offset))); 109 | this.offset = respBody.length; 110 | } 111 | }; 112 | 113 | var isArray = Array.isArray || function (xs) { 114 | return Object.prototype.toString.call(xs) === '[object Array]'; 115 | }; 116 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adapted from substack/http-browserify 3 | */ 4 | 5 | var http = module.exports 6 | var EventEmitter = require('events').EventEmitter 7 | var Request = require('./lib/request') 8 | var url = require('url') 9 | var defaults = { 10 | port: 80, 11 | protocol: 'http:' 12 | } 13 | 14 | http.request = function (params, cb) { 15 | if (typeof params === 'string') { 16 | params = url.parse(params) 17 | } 18 | if (!params) params = {} 19 | if (!params.host && !params.port) { 20 | params.port = defaults.port 21 | } 22 | if (!params.host && params.hostname) { 23 | params.host = params.hostname 24 | } 25 | 26 | if (!params.protocol) { 27 | if (params.scheme) { 28 | params.protocol = params.scheme + ':' 29 | } else { 30 | params.protocol = defaults.protocol 31 | } 32 | } 33 | 34 | if (!params.host) { 35 | throw new Error('missing host') 36 | } 37 | 38 | if (/:/.test(params.host)) { 39 | if (!params.port) { 40 | params.port = params.host.split(':')[1] 41 | } 42 | params.host = params.host.split(':')[0] 43 | } 44 | if (!params.port) params.port = params.protocol == 'https:' ? 443 : 80 45 | 46 | var req = new Request(new XMLHttpRequest(), params) 47 | if (cb) req.on('response', cb) 48 | return req 49 | } 50 | 51 | http.get = function (params, cb) { 52 | params.method = 'GET' 53 | var req = http.request(params, cb) 54 | req.end() 55 | return req 56 | } 57 | 58 | http.Agent = function () {} 59 | http.Agent.defaultMaxSockets = 4 60 | 61 | http.STATUS_CODES = { 62 | 100 : 'Continue', 63 | 101 : 'Switching Protocols', 64 | 102 : 'Processing', // RFC 2518, obsoleted by RFC 4918 65 | 200 : 'OK', 66 | 201 : 'Created', 67 | 202 : 'Accepted', 68 | 203 : 'Non-Authoritative Information', 69 | 204 : 'No Content', 70 | 205 : 'Reset Content', 71 | 206 : 'Partial Content', 72 | 207 : 'Multi-Status', // RFC 4918 73 | 300 : 'Multiple Choices', 74 | 301 : 'Moved Permanently', 75 | 302 : 'Moved Temporarily', 76 | 303 : 'See Other', 77 | 304 : 'Not Modified', 78 | 305 : 'Use Proxy', 79 | 307 : 'Temporary Redirect', 80 | 400 : 'Bad Request', 81 | 401 : 'Unauthorized', 82 | 402 : 'Payment Required', 83 | 403 : 'Forbidden', 84 | 404 : 'Not Found', 85 | 405 : 'Method Not Allowed', 86 | 406 : 'Not Acceptable', 87 | 407 : 'Proxy Authentication Required', 88 | 408 : 'Request Time-out', 89 | 409 : 'Conflict', 90 | 410 : 'Gone', 91 | 411 : 'Length Required', 92 | 412 : 'Precondition Failed', 93 | 413 : 'Request Entity Too Large', 94 | 414 : 'Request-URI Too Large', 95 | 415 : 'Unsupported Media Type', 96 | 416 : 'Requested Range Not Satisfiable', 97 | 417 : 'Expectation Failed', 98 | 418 : 'I\'m a teapot', // RFC 2324 99 | 422 : 'Unprocessable Entity', // RFC 4918 100 | 423 : 'Locked', // RFC 4918 101 | 424 : 'Failed Dependency', // RFC 4918 102 | 425 : 'Unordered Collection', // RFC 4918 103 | 426 : 'Upgrade Required', // RFC 2817 104 | 428 : 'Precondition Required', // RFC 6585 105 | 429 : 'Too Many Requests', // RFC 6585 106 | 431 : 'Request Header Fields Too Large',// RFC 6585 107 | 500 : 'Internal Server Error', 108 | 501 : 'Not Implemented', 109 | 502 : 'Bad Gateway', 110 | 503 : 'Service Unavailable', 111 | 504 : 'Gateway Time-out', 112 | 505 : 'HTTP Version Not Supported', 113 | 506 : 'Variant Also Negotiates', // RFC 2295 114 | 507 : 'Insufficient Storage', // RFC 4918 115 | 509 : 'Bandwidth Limit Exceeded', 116 | 510 : 'Not Extended', // RFC 2774 117 | 511 : 'Network Authentication Required' // RFC 6585 118 | } 119 | -------------------------------------------------------------------------------- /lib/request.js: -------------------------------------------------------------------------------- 1 | var Stream = require('stream'); 2 | var Response = require('./response'); 3 | var Base64 = require('Base64'); 4 | var inherits = require('inherits'); 5 | 6 | var Request = module.exports = function (xhr, params) { 7 | var self = this; 8 | self.writable = true; 9 | self.xhr = xhr; 10 | self.body = []; 11 | 12 | self.uri = (params.protocol || 'http:') + '//' 13 | + params.host 14 | + (params.port ? ':' + params.port : '') 15 | + (params.path || '/') 16 | ; 17 | 18 | if (typeof params.withCredentials === 'undefined') { 19 | params.withCredentials = true; 20 | } 21 | 22 | try { xhr.withCredentials = params.withCredentials } 23 | catch (e) {} 24 | 25 | if (params.responseType) try { xhr.responseType = params.responseType } 26 | catch (e) {} 27 | 28 | xhr.open( 29 | params.method || 'GET', 30 | self.uri, 31 | true 32 | ); 33 | 34 | xhr.onerror = function(event) { 35 | self.emit('error', new Error('Network error')); 36 | }; 37 | 38 | self._headers = {}; 39 | 40 | if (params.headers) { 41 | var keys = objectKeys(params.headers); 42 | for (var i = 0; i < keys.length; i++) { 43 | var key = keys[i]; 44 | if (!self.isSafeRequestHeader(key)) continue; 45 | var value = params.headers[key]; 46 | self.setHeader(key, value); 47 | } 48 | } 49 | 50 | if (params.auth) { 51 | //basic auth 52 | this.setHeader('Authorization', 'Basic ' + Base64.btoa(params.auth)); 53 | } 54 | 55 | var res = new Response; 56 | res.on('close', function () { 57 | self.emit('close'); 58 | }); 59 | 60 | res.on('ready', function () { 61 | self.emit('response', res); 62 | }); 63 | 64 | res.on('error', function (err) { 65 | self.emit('error', err); 66 | }); 67 | 68 | xhr.onreadystatechange = function () { 69 | // Fix for IE9 bug 70 | // SCRIPT575: Could not complete the operation due to error c00c023f 71 | // It happens when a request is aborted, calling the success callback anyway with readyState === 4 72 | if (xhr.__aborted) return; 73 | res.handle(xhr); 74 | }; 75 | }; 76 | 77 | inherits(Request, Stream); 78 | 79 | Request.prototype.setHeader = function (key, value) { 80 | this._headers[key.toLowerCase()] = value 81 | }; 82 | 83 | Request.prototype.getHeader = function (key) { 84 | return this._headers[key.toLowerCase()] 85 | }; 86 | 87 | Request.prototype.removeHeader = function (key) { 88 | delete this._headers[key.toLowerCase()] 89 | }; 90 | 91 | Request.prototype.write = function (s) { 92 | this.body.push(s); 93 | }; 94 | 95 | Request.prototype.destroy = function (s) { 96 | this.xhr.__aborted = true; 97 | this.xhr.abort(); 98 | this.emit('close'); 99 | }; 100 | 101 | Request.prototype.end = function (s) { 102 | if (s !== undefined) this.body.push(s); 103 | 104 | var keys = objectKeys(this._headers); 105 | for (var i = 0; i < keys.length; i++) { 106 | var key = keys[i]; 107 | var value = this._headers[key]; 108 | if (isArray(value)) { 109 | for (var j = 0; j < value.length; j++) { 110 | this.xhr.setRequestHeader(key, value[j]); 111 | } 112 | } 113 | else this.xhr.setRequestHeader(key, value) 114 | } 115 | 116 | if (this.body.length === 0) { 117 | this.xhr.send(''); 118 | } 119 | else if (typeof this.body[0] === 'string') { 120 | this.xhr.send(this.body.join('')); 121 | } 122 | else if (isArray(this.body[0])) { 123 | var body = []; 124 | for (var i = 0; i < this.body.length; i++) { 125 | body.push.apply(body, this.body[i]); 126 | } 127 | this.xhr.send(body); 128 | } 129 | else if (/Array/.test(Object.prototype.toString.call(this.body[0]))) { 130 | var len = 0; 131 | for (var i = 0; i < this.body.length; i++) { 132 | len += this.body[i].length; 133 | } 134 | var body = new(this.body[0].constructor)(len); 135 | var k = 0; 136 | 137 | for (var i = 0; i < this.body.length; i++) { 138 | var b = this.body[i]; 139 | for (var j = 0; j < b.length; j++) { 140 | body[k++] = b[j]; 141 | } 142 | } 143 | this.xhr.send(body); 144 | } 145 | else if (isXHR2Compatible(this.body[0])) { 146 | this.xhr.send(this.body[0]); 147 | } 148 | else { 149 | var body = ''; 150 | for (var i = 0; i < this.body.length; i++) { 151 | body += this.body[i].toString(); 152 | } 153 | this.xhr.send(body); 154 | } 155 | }; 156 | 157 | // Taken from http://dxr.mozilla.org/mozilla/mozilla-central/content/base/src/nsXMLHttpRequest.cpp.html 158 | Request.unsafeHeaders = [ 159 | "accept-charset", 160 | "accept-encoding", 161 | "access-control-request-headers", 162 | "access-control-request-method", 163 | "connection", 164 | "content-length", 165 | "cookie", 166 | "cookie2", 167 | "content-transfer-encoding", 168 | "date", 169 | "expect", 170 | "host", 171 | "keep-alive", 172 | "origin", 173 | "referer", 174 | "te", 175 | "trailer", 176 | "transfer-encoding", 177 | "upgrade", 178 | "user-agent", 179 | "via" 180 | ]; 181 | 182 | Request.prototype.isSafeRequestHeader = function (headerName) { 183 | if (!headerName) return false; 184 | return indexOf(Request.unsafeHeaders, headerName.toLowerCase()) === -1; 185 | }; 186 | 187 | var objectKeys = Object.keys || function (obj) { 188 | var keys = []; 189 | for (var key in obj) keys.push(key); 190 | return keys; 191 | }; 192 | 193 | var isArray = Array.isArray || function (xs) { 194 | return Object.prototype.toString.call(xs) === '[object Array]'; 195 | }; 196 | 197 | var indexOf = function (xs, x) { 198 | if (xs.indexOf) return xs.indexOf(x); 199 | for (var i = 0; i < xs.length; i++) { 200 | if (xs[i] === x) return i; 201 | } 202 | return -1; 203 | }; 204 | 205 | var isXHR2Compatible = function (obj) { 206 | if (typeof Blob !== 'undefined' && obj instanceof Blob) return true; 207 | if (typeof ArrayBuffer !== 'undefined' && obj instanceof ArrayBuffer) return true; 208 | if (typeof FormData !== 'undefined' && obj instanceof FormData) return true; 209 | }; 210 | --------------------------------------------------------------------------------