├── .gitattributes ├── index.js ├── .babelrc ├── .travis.yml ├── .gitignore ├── .npmignore ├── .editorconfig ├── src ├── text.jsx └── index.jsx ├── test ├── index.html └── tests.js ├── bower.json ├── LICENSE ├── webpack.config.js ├── .eslintrc ├── package.json ├── dist ├── jquery.transport.xdr.min.js └── jquery.transport.xdr.js ├── README.md └── README.ru.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | .* text eol=lf 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/index.jsx'); 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 0.11 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | bower_components/ 4 | build/ 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | .babelrc 3 | bower.json 4 | .editorconfig 5 | .eslintrc 6 | .gitattributes 7 | test/ 8 | .travis.yml 9 | webpack.config.js 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | trim_trailing_whitespace = true 8 | charset = utf-8 9 | 10 | [**.{js,jsx,html}] 11 | indent_size = 4 12 | 13 | [**.{css,scss,sass}] 14 | indent_size = 2 15 | 16 | [*.json] 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /src/text.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | get: function (code, param) { 5 | var _messages = { 6 | 0: 'Unknown Error', 7 | 1: 'No Transport', 8 | 2: `${param} Method Not Allowed`, 9 | 3: `${param} Scheme Not Supported`, 10 | 4: 'URI source and target scheme must be the same', 11 | 5: 'No Data', 12 | 6: `Bad Data: ${param}`, 13 | 7: 'Network Error', 14 | 8: 'Timeout' 15 | }; 16 | 17 | return _messages[code in _messages ? code : 0]; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-transport-xdr", 3 | "version": "1.0.10", 4 | "description": "A jQuery plugin that enables cross-domain AJAX requests for IE8 and IE9 with XDomainRequest.", 5 | "homepage": "https://github.com/gfdev/javascript-jquery-transport-xdr", 6 | "authors": [ 7 | "Gordon Freeman " 8 | ], 9 | "main": "dist/jquery.transport.xdr.min.js", 10 | "keywords": [ 11 | "XDomainRequest", 12 | "cross-domain", 13 | "XDR", 14 | "CORS", 15 | "AJAX", 16 | "IE9", 17 | "IE8", 18 | "jquery-plugin" 19 | ], 20 | "ignore": [ 21 | "**/.*", 22 | "node_modules", 23 | "bower_components", 24 | "test", 25 | "tests", 26 | "src", 27 | ".npmignore", 28 | ".babelrc", 29 | ".editorconfig", 30 | ".eslintrc", 31 | ".gitattributes", 32 | ".travis.yml", 33 | "index.js", 34 | "package.json", 35 | "webpack.config.js" 36 | ], 37 | "moduleType": [ 38 | "amd", 39 | "globals", 40 | "node" 41 | ], 42 | "license": "MIT", 43 | "devDependencies": {}, 44 | "dependencies": { 45 | "jquery": "^1.5.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (http://opensource.org/licenses/mit-license.php) 2 | 3 | Copyright (c) 2015 Gordon Freeman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | 3 | var pkg = require('./package.json') 4 | , webpack = require('webpack') 5 | , cloneDeep = require('lodash.clonedeep') 6 | , src = __dirname + '/src' 7 | ; 8 | 9 | var defaults = { 10 | context: src, 11 | entry: '../index', 12 | output: { 13 | path: __dirname + '/dist', 14 | filename: 'jquery.transport.xdr.js', 15 | libraryTarget: 'umd' 16 | }, 17 | resolve: { 18 | extensions: ['', '.js', '.jsx'] 19 | }, 20 | module: { 21 | preLoaders: [ 22 | { test: /\.jsx$/, include: src, loader: 'eslint' } 23 | ], 24 | loaders: [ 25 | { test: /\.jsx$/, include: src, loader: 'babel?cacheDirectory' } 26 | ] 27 | }, 28 | plugins: [ 29 | new webpack.ProvidePlugin({ 30 | $: 'jquery', 31 | jQuery: 'jquery', 32 | 'window.jQuery': 'jquery' 33 | }) 34 | ], 35 | externals: { 36 | 'jquery': 'jQuery' 37 | } 38 | }; 39 | 40 | var minimized = cloneDeep(defaults); 41 | 42 | minimized.plugins.push(new webpack.optimize.UglifyJsPlugin()); 43 | minimized.output.filename = 'jquery.transport.xdr.min.js'; 44 | 45 | module.exports = [ defaults, minimized ]; 46 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "node": true, 5 | "es6": true, 6 | "browser": true 7 | }, 8 | "ecmaFeatures": { 9 | "arrowFunctions": true, 10 | "blockBindings": true, 11 | "defaultParams": true, 12 | "modules": true, 13 | "restParams": true, 14 | "binaryLiterals": true, 15 | "forOf": true, 16 | "generators": true, 17 | "objectLiteralComputedProperties": true, 18 | "objectLiteralDuplicateProperties": true, 19 | "objectLiteralShorthandMethods": true, 20 | "objectLiteralShorthandProperties": true, 21 | "octalLiterals": true, 22 | "regexUFlag": true, 23 | "regexYFlag": true, 24 | "superInFunctions": true, 25 | "templateStrings": true, 26 | "unicodeCodePointEscapes": true, 27 | "globalReturn": true 28 | }, 29 | "rules": { 30 | "no-underscore-dangle": 1, 31 | "no-unused-vars": 1, 32 | "no-multi-spaces": 1, 33 | "key-spacing": 1, 34 | "no-return-assign": 1, 35 | "consistent-return": 1, 36 | "no-shadow": 1, 37 | "comma-dangle": 1, 38 | "no-use-before-define": 1, 39 | "no-empty": 1, 40 | "new-parens": 1, 41 | "no-cond-assign": 1, 42 | "quotes": [1, "single", "avoid-escape"], 43 | "camelcase": 1, 44 | "new-cap": [1, { "capIsNew": false }], 45 | "no-undef": 1, 46 | "semi": 1, 47 | "eol-last": 1, 48 | "max-len": [1, 120, 4], 49 | "no-var": 1, 50 | "space-before-function-paren": [1, "never"], 51 | "strict": [1, "global"] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-transport-xdr", 3 | "version": "1.0.10", 4 | "description": "A jQuery plugin that enables cross-domain AJAX requests for IE8 and IE9 with XDomainRequest.", 5 | "main": "index.js", 6 | "keywords": [ 7 | "XDomainRequest", 8 | "cross-domain", 9 | "XDR", 10 | "CORS", 11 | "AJAX", 12 | "IE9", 13 | "IE8", 14 | "jquery-plugin" 15 | ], 16 | "homepage": "https://github.com/gfdev/javascript-jquery-transport-xdr", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/gfdev/javascript-jquery-transport-xdr.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/gfdev/javascript-jquery-transport-xdr/issues" 23 | }, 24 | "directories": { 25 | "test": "test" 26 | }, 27 | "scripts": { 28 | "dist": "rm -rf dist/ && webpack", 29 | "test": "mocha --harmony --require babel-core/register" 30 | }, 31 | "author": { 32 | "name": "Gordon Freeman", 33 | "email": "eax@gmx.us", 34 | "url": "https://github.com/gfdev" 35 | }, 36 | "license": "MIT", 37 | "devDependencies": { 38 | "babel-core": "^6.7.6", 39 | "babel-eslint": "^6.0.2", 40 | "babel-loader": "^6.2.4", 41 | "babel-polyfill": "^6.7.4", 42 | "babel-preset-es2015": "^6.6.0", 43 | "babel-preset-stage-0": "^6.5.0", 44 | "chai": "^3.5.0", 45 | "eslint": "^2.7.0", 46 | "eslint-loader": "^1.3.0", 47 | "lodash.clonedeep": "^4.3.2", 48 | "mocha": "^2.4.5", 49 | "sinon": "^1.17.3", 50 | "webpack": "^1.12.15" 51 | }, 52 | "dependencies": { 53 | "jquery": "^1.5.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /dist/jquery.transport.xdr.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t(require("jQuery"));else if("function"==typeof define&&define.amd)define(["jQuery"],t);else{var o=t("object"==typeof exports?require("jQuery"):e.jQuery);for(var n in o)("object"==typeof exports?exports:e)[n]=o[n]}}(this,function(e){return function(e){function t(n){if(o[n])return o[n].exports;var r=o[n]={exports:{},id:n,loaded:!1};return e[n].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var o={};return t.m=e,t.c=o,t.p="",t(0)}([function(e,t,o){e.exports=o(1)},function(e,t,o){(function(e){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e},r=o(3),u=t(r);e.ajaxTransport("+*",function(t,o,r){if(t.crossDomain&&(document.addEventListener||document.querySelector)&&!window.atob&&window.XDomainRequest){var i=function(){var n=new XDomainRequest,i=t.type.toUpperCase(),s=t.contentType||o.contentType,a=t.url.substring(0,t.url.indexOf(":")).toUpperCase(),c=t.url,f=o.data||{},p=function(t,o){return{send:function(e,n){n(-1,u["default"].get(t,o))},abort:e.noop}};return n?o.forceMethod||-1!==e.inArray(i,["GET","POST"])?-1===e.inArray(a,["HTTP","HTTPS"])?{v:p(3,a)}:a!==location.protocol.substring(0,location.protocol.indexOf(":")).toUpperCase()?{v:p(4)}:(o.forceMethod&&("HEAD"===i&&(i="GET",c+=(-1===t.url.indexOf("?")?"?":"&")+"__method="+i),-1!==e.inArray(i,["PUT","DELETE","PATCH"])&&(i="POST",e.isPlainObject(f)?f.__method=i:"string"==typeof f&&(f+=(f.length?"&":"")+"__method="+i))),o.forceContentType&&("GET"===i&&(c+=(-1===t.url.indexOf("?")?"?":"&")+"__contentType="+encodeURIComponent(s)),"POST"===i&&(e.isPlainObject(f)?f.__contentType=s:"string"==typeof f&&(f+=(f.length?"&":"")+e.param({__contentType:s})))),t.timeout&&(n.timeout=t.timeout),n.onprogress=e.noop,{v:{send:function(s,a){n.onload=function(){var o={},r=null;switch(t.dataType){case"json":try{o.json=e.parseJSON(n.responseText)}catch(i){r=i.message}break;case"xml":try{o.xml=e.parseXML(n.responseText)}catch(i){r=i.message}break;case"text":o.text=n.responseText;break;case"html":o.html=n.responseText}if(r)return a(500,u["default"].get(6,r));var s=["Content-Type: "+n.contentType,"Content-Length: "+n.responseText.length];a(200,"OK",o,s.join("\r\n"))},n.onerror=function(){a(500,u["default"].get(7))},n.ontimeout=function(){a(500,u["default"].get(8))},o.__test===!0&&(r.__method=i,r.__uri=c),n.open(i,c),setTimeout(function(){n.send("POST"===i?"string"==typeof f?f:e.isPlainObject(f)?e.param(f):null:null)},0)},abort:function(){n.abort()}}}):{v:p(2,i)}:{v:p(1)}}();if("object"===("undefined"==typeof i?"undefined":n(i)))return i.v}})}).call(t,o(2))},function(t,o){t.exports=e},function(e,t){"use strict";e.exports={get:function(e,t){var o={0:"Unknown Error",1:"No Transport",2:t+" Method Not Allowed",3:t+" Scheme Not Supported",4:"URI source and target scheme must be the same",5:"No Data",6:"Bad Data: "+t,7:"Network Error",8:"Timeout"};return o[e in o?e:0]}}}])}); -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * jquery.transport.xdr 3 | * https://github.com/gfdev/javascript-jquery-transport-xdr 4 | * Copyright (c) 2015 Gordon Freeman 5 | */ 6 | 7 | import text from './text'; 8 | 9 | $.ajaxTransport('+*', (opts, optsUser, xhr) => { 10 | if (opts.crossDomain && (document.addEventListener || document.querySelector) && !window.atob && window.XDomainRequest) { 11 | let xdr = new XDomainRequest(), 12 | method = opts.type.toUpperCase(), 13 | contentType = opts.contentType || optsUser.contentType, 14 | scheme = opts.url.substring(0, opts.url.indexOf(':')).toUpperCase(), 15 | uri = opts.url, 16 | data = optsUser.data || {}, 17 | _error = (code, param) => { 18 | return { 19 | send: (hdr, cb) => { cb(-1, text.get(code, param)); }, 20 | abort: $.noop 21 | } 22 | }; 23 | 24 | if (!xdr) return _error(1); 25 | if (!optsUser.forceMethod && $.inArray(method, ['GET', 'POST']) === -1) return _error(2, method); 26 | if ($.inArray(scheme, ['HTTP', 'HTTPS']) === -1) return _error(3, scheme); 27 | if (scheme !== location.protocol.substring(0, location.protocol.indexOf(':')).toUpperCase()) return _error(4); 28 | 29 | if (optsUser.forceMethod) { 30 | if (method === 'HEAD') { 31 | method = 'GET'; 32 | uri += (opts.url.indexOf('?') === -1 ? '?' : '&') + '__method=' + method; 33 | } 34 | 35 | if ($.inArray(method, ['PUT', 'DELETE', 'PATCH']) !== -1) { 36 | method = 'POST'; 37 | 38 | if ($.isPlainObject(data)) 39 | data.__method = method; 40 | else if (typeof data === 'string') 41 | data += (data.length ? '&' : '') + '__method=' + method; 42 | } 43 | } 44 | 45 | if (optsUser.forceContentType) { 46 | if (method === 'GET') 47 | uri += (opts.url.indexOf('?') === -1 ? '?' : '&') + '__contentType=' + encodeURIComponent(contentType); 48 | 49 | if (method === 'POST') { 50 | if ($.isPlainObject(data)) 51 | data.__contentType = contentType; 52 | else if (typeof data === 'string') 53 | data += (data.length ? '&' : '') + $.param({ __contentType: contentType }); 54 | } 55 | } 56 | 57 | if (opts.timeout) xdr.timeout = opts.timeout; 58 | 59 | xdr.onprogress = $.noop; 60 | 61 | return { 62 | send: (hdr, cb) => { 63 | xdr.onload = () => { 64 | let data = {}, error = null; 65 | 66 | switch (opts.dataType) { 67 | case 'json': 68 | try { 69 | data.json = $.parseJSON(xdr.responseText); 70 | } catch (e) { 71 | error = e.message; 72 | } 73 | break; 74 | case 'xml': 75 | try { 76 | data.xml = $.parseXML(xdr.responseText); 77 | } catch (e) { 78 | error = e.message; 79 | } 80 | break; 81 | case 'text': 82 | data.text = xdr.responseText; 83 | break; 84 | case 'html': 85 | data.html = xdr.responseText; 86 | break; 87 | } 88 | 89 | if (error) return cb(500, text.get(6, error)); 90 | 91 | let headers = [ 92 | 'Content-Type: ' + xdr.contentType, 93 | 'Content-Length: ' + xdr.responseText.length 94 | ]; 95 | 96 | cb(200, 'OK', data, headers.join('\r\n')); 97 | }; 98 | 99 | xdr.onerror = () => { cb(500, text.get(7)); }; 100 | xdr.ontimeout = () => { cb(500, text.get(8)); }; 101 | 102 | if (optsUser.__test === true) { 103 | xhr.__method = method; 104 | xhr.__uri = uri; 105 | } 106 | 107 | xdr.open(method, uri); 108 | 109 | setTimeout(() => { 110 | xdr.send(method === 'POST' 111 | ? typeof data === 'string' 112 | ? data 113 | : $.isPlainObject(data) 114 | ? $.param(data) 115 | : null 116 | : null); 117 | }, 0); 118 | }, 119 | abort: () => { xdr.abort(); } 120 | }; 121 | } 122 | }); 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery plugin that allows cross-domain AJAX requests (CORS) for IE8 and IE9 2 | 3 | IE8 and IE9 doesn't support cross-domain AJAX requests with XMLHttpRequest, for these purposes IE8/9 used XDomainRequest. This plugin makes transparent replasement jQuery's transport, that allows cross-domain AJAX requests for IE8 and IE9 without changing your source code. 4 | 5 | ## Limitations 6 | 7 | XDomainRequest have some limitations: 8 | * server must return Access-Control-Allow-Origin header as for usual CORS request 9 | * HTTP and HTTPS only allowed 10 | * GET and POST only allowed 11 | * custom headers can't be added to request 12 | * there is no Content-Type header in request 13 | * Cookie can't be sended 14 | * source and destinations scheme of URI must be the same 15 | * there is no ability to get success response code 16 | * there is no ability to get failure response code 17 | 18 | ## Installation 19 | 1. Add [jquery-transport-xdr](http://cdn.rawgit.com/gfdev/javascript-jquery-transport-xdr/master/dist/jquery.transport.xdr.min.js) to HTML body after jQuery: 20 | 21 | ```html 22 | 23 | 24 | ``` 25 | Bower: 26 | ``` 27 | $ bower install jquery-transport-xdr 28 | ``` 29 | ```html 30 | 31 | 32 | ``` 33 | NPM: 34 | ``` 35 | $ npm install jquery-transport-xdr 36 | ``` 37 | ```html 38 | 39 | 40 | ``` 41 | 42 | ## Usage examples 43 | After adding [jquery-transport-xdr](http://cdn.rawgit.com/gfdev/javascript-jquery-transport-xdr/master/dist/jquery.transport.xdr.min.js) you can make ajax-requests as usual, without changing code: 44 | 45 | POST: 46 | ```javascript 47 | var xhr = $.ajax({ 48 | type: 'POST', 49 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 50 | dataType: 'json' 51 | }); 52 | ``` 53 | 54 | GET: 55 | ```javascript 56 | var xhr = $.ajax({ 57 | type: 'GET', 58 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 59 | dataType: 'json' 60 | }); 61 | ``` 62 | 63 | #### Option forceMethod 64 | XDomainRequest have limitations, it doesn't allow PUT, DELETE, PATCH and HEAD methods, you will receive error XXX Method Not Allowed if try to use some of them, but jquery-transport-xdr can make replacement: 65 | 66 | * HEAD => GET 67 | * PUT | DELETE | PATCH => POST 68 | 69 | You should use option forceMethod: 70 | 71 | HEAD: 72 | ```javascript 73 | var xhr = $.ajax({ 74 | type: 'HEAD', 75 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 76 | dataType: 'json', 77 | forceMethod: true 78 | }); 79 | ``` 80 | HEAD will be replaced with GET in this case and **__method=HEAD** will be added to request params for URI: 81 | 82 | `https://baconipsum.com/api/?type=meat-and-filler&format=json` 83 | => 84 | `https://baconipsum.com/api/?type=meat-and-filler&format=json&__method=HEAD` 85 | 86 | Param **__method** you can get on server side and deretmine original method. 87 | 88 | The same way for methods PUT, DELETE and PATCH except for param **__method** will be added to request body and original method will be replaced with POST: 89 | 90 | PUT: 91 | ```javascript 92 | var xhr = $.ajax({ 93 | type: 'PUT', 94 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 95 | data: { test: 'test' }, 96 | dataType: 'json', 97 | forceMethod: true 98 | }); 99 | ``` 100 | PUT => POST 101 | ``` 102 | POST /api/?type=meat-and-filler&format=json HTTP/1.1 103 | Host: baconipsum.com 104 | Connection: keep-alive 105 | Content-Length: 0 106 | Pragma: no-cache 107 | Cache-Control: no-cache 108 | 109 | test=test&__method=PUT 110 | ``` 111 | 112 | ### Option forceContentType 113 | 114 | XDomainRequest limitations doesn't allow to send Content-Type header, but you can send it if will add forceContentType option: 115 | 116 | ```javascript 117 | var xhr = $.ajax({ 118 | type: 'POST', 119 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 120 | data: { test: 'test' }, 121 | dataType: 'json', 122 | forceContentType: true 123 | }); 124 | ``` 125 | 126 | Content-Type value will be send in **__contentType** param. 127 | 128 | forceContentType and forceMethod options can be used together: 129 | 130 | ```javascript 131 | var xhr = $.ajax({ 132 | type: 'PATCH', 133 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 134 | contentType: 'multipart/form-data; charset=UTF-8', 135 | data: { test: 'test' }, 136 | dataType: 'json', 137 | forceMethod: true, 138 | forceContentType: true 139 | }); 140 | ``` 141 | 142 | ## License 143 | **MIT** license. 144 | -------------------------------------------------------------------------------- /README.ru.md: -------------------------------------------------------------------------------- 1 | # jQuery плагин для выполнения кросс-доменных **CORS** ajax-запросов в Internet Explorer 8 и 9 2 | 3 | Internet Explorer 8 и 9 версии не поддерживают кросс-доменные **CORS** ajax-запросы через стандартный объект **XMLHttpRequest**, 4 | для этих целей в IE 8/9 используется объект **XDomainRequest**. **jquery-transport-xdr** выполняет прозрачную замену транспорта в jQuery, что позволяет, не изменяя исходного кода, разрешить выполнение кросс-доменных ajax-запросов в IE8 и IE9. 5 | 6 | ## Ограничения 7 | **XDomainRequest** имеет ряд ограничений: 8 | * как и для обычного **CORS** запроса, сервер должен возвращать заголовок **Access-Control-Allow-Origin** 9 | * поддерживаются только протоколы **HTTP** и **HTTPS** 10 | * поддерживаются только методы **GET** и **POST** 11 | * в запрос нельзя добавить свои заголовки 12 | * в запросе отсутствует заголовок **Content-Type** 13 | * в запросе нельзя отправить **Cookie** 14 | * сетевой протокол URI с которого отправляется запрос (**https**://source), должен совпадать с протоколом URI сервера куда идет запрос (**https**://destination) 15 | * нет возможности получить код успешного ответа сервера 16 | * нет возможности получить код неудачного ответа сервера 17 | 18 | ## Установка 19 | 1. Добавить [jquery-transport-xdr](http://cdn.rawgit.com/gfdev/javascript-jquery-transport-xdr/master/dist/jquery.transport.xdr.min.js) в тело **HTML** страницы **после** загрузки **jQuery**: 20 | ```html 21 | 22 | 23 | ``` 24 | **Bower**: 25 | ``` 26 | $ bower install jquery-transport-xdr 27 | ``` 28 | ```html 29 | 30 | 31 | ``` 32 | **NPM**: 33 | ``` 34 | $ npm install jquery-transport-xdr 35 | ``` 36 | ```html 37 | 38 | 39 | ``` 40 | 41 | ## Использование 42 | После добавления [jquery-transport-xdr](http://cdn.rawgit.com/gfdev/javascript-jquery-transport-xdr/master/dist/jquery.transport.xdr.min.js), ajax-запросы используются как обычно, без каких либо изменений: 43 | 44 | `POST:` 45 | ```javascript 46 | var xhr = $.ajax({ 47 | type: 'POST', 48 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 49 | dataType: 'json' 50 | }); 51 | ``` 52 | `GET:` 53 | ```javascript 54 | var xhr = $.ajax({ 55 | type: 'GET', 56 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 57 | dataType: 'json' 58 | }); 59 | ``` 60 | 61 | #### Опция `forceMethod` 62 | Т.к. `XDomainRequest` имеет ограничения, нет возможности отправить `PUT`, `DELETE`, `PATCH` или `HEAD` запросы, в случае их использования будет выданна ошибка `XXX Method Not Allowed`, но `jquery-transport-xdr` позволяет использовать преобразование: 63 | 64 | * `HEAD` => `GET` 65 | * `PUT`|`DELETE`|`PATCH` => `POST` 66 | 67 | Для этого надо использовать параметр `forceMethod` в опциях запроса: 68 | 69 | `HEAD:` 70 | ```javascript 71 | var xhr = $.ajax({ 72 | type: 'HEAD', 73 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 74 | dataType: 'json', 75 | forceMethod: true 76 | }); 77 | ``` 78 | В этом случае метод `HEAD` будет заменен на `GET` и к параметрам запроса будет добавлен параметр `__method=HEAD`, т.е. к `URI` будет добавлен дополнительный параметр: 79 | 80 | `https://baconipsum.com/api/?type=meat-and-filler&format=json` => `https://baconipsum.com/api/?type=meat-and-filler&format=json&__method=HEAD` 81 | 82 | Парамерт `__method` можно получить на сервере и определить **оригинальный** метод. 83 | 84 | Тоже будет сделанно для методов `PUT`, `DELETE` и `PATCH`, за исключение того, что параметр `__method` будет добавлен в тело запроса и сам метод будет заменен на `POST`: 85 | 86 | `PUT:` 87 | ```javascript 88 | var xhr = $.ajax({ 89 | type: 'PUT', 90 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 91 | data: { test: 'test' }, 92 | dataType: 'json', 93 | forceMethod: true 94 | }); 95 | ``` 96 | `PUT` => `POST` 97 | ``` 98 | POST /api/?type=meat-and-filler&format=json HTTP/1.1 99 | Host: baconipsum.com 100 | Connection: keep-alive 101 | Content-Length: 0 102 | Pragma: no-cache 103 | Cache-Control: no-cache 104 | 105 | test=test&__method=PUT 106 | ``` 107 | 108 | ### Опция `forceContentType` 109 | Из-за ограничений `XDomainRequest` не отправляет заголовок `Content-Type`, передать его можно с помощью опции `forceContentType`: 110 | ```javascript 111 | var xhr = $.ajax({ 112 | type: 'POST', 113 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 114 | data: { test: 'test' }, 115 | dataType: 'json', 116 | forceContentType: true 117 | }); 118 | ``` 119 | Значение `Content-Type` будет переданно в параметре `__contentType`. 120 | 121 | Опции `forceContentType` и `forceMethod` можно использовать вместе: 122 | 123 | ```javascript 124 | var xhr = $.ajax({ 125 | type: 'PATCH', 126 | url: 'https://baconipsum.com/api/?type=meat-and-filler&format=json', 127 | contentType: 'multipart/form-data; charset=UTF-8', 128 | data: { test: 'test' }, 129 | dataType: 'json', 130 | forceMethod: true, 131 | forceContentType: true 132 | }); 133 | ``` 134 | 135 | ## Лицензия 136 | **jquery-transport-xdr** распространяется под лицензией **MIT**. 137 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = chai.expect; 4 | 5 | describe('ERRORS', function() { 6 | it('restrict method HEAD', function(done) { 7 | var uri = 'http://baconipsum.com/api/?type=meat-and-filler&format=json', 8 | rq = $.ajax({ 9 | type: 'HEAD', 10 | url: uri, 11 | dataType: 'json' 12 | }); 13 | 14 | rq.fail(function(xhr, status, error) { 15 | expect(this.crossDomain).true; 16 | expect(this.hasContent).false; 17 | expect(this.type).equal('HEAD'); 18 | expect(this.url).equal(uri); 19 | 20 | expect(xhr.status).equal(0); 21 | expect(status).equal('error'); 22 | expect(error).match(/Method Not Allowed$/i); 23 | 24 | done(); 25 | }); 26 | }); 27 | 28 | it('restrict method PUT', function(done) { 29 | var uri = 'http://baconipsum.com/api/?type=meat-and-filler&format=json', 30 | rq = $.ajax({ 31 | type: 'PUT', 32 | url: 'http://baconipsum.com/api/?type=meat-and-filler&format=json', 33 | dataType: 'json' 34 | }); 35 | 36 | rq.fail(function(xhr, status, error) { 37 | expect(this.crossDomain).true; 38 | expect(this.hasContent).true; 39 | expect(this.type).equal('PUT'); 40 | expect(this.url).equal(uri); 41 | 42 | expect(xhr.status).equal(0); 43 | expect(status).equal('error'); 44 | expect(error).match(/Method Not Allowed$/i); 45 | 46 | done(); 47 | }); 48 | }); 49 | 50 | it('restrict different sheme', function(done) { 51 | var uri = ( 52 | location.protocol.substring(0, location.protocol.indexOf(':')).toUpperCase() === 'HTTP' 53 | ? 'https://baconipsum.com/api/?type=meat-and-filler&format=json' 54 | : 'http://baconipsum.com/api/?type=meat-and-filler&format=json' 55 | ), 56 | rq = $.ajax({ 57 | type: 'GET', 58 | url: uri, 59 | dataType: 'json' 60 | }); 61 | 62 | rq.fail(function(xhr, status, error) { 63 | expect(this.crossDomain).true; 64 | expect(this.hasContent).false; 65 | expect(this.type).equal('GET'); 66 | expect(this.url).equal(uri); 67 | 68 | expect(xhr.status).equal(0); 69 | expect(status).equal('error'); 70 | expect(error).match(/URI source and target scheme must be the same$/i); 71 | 72 | done(); 73 | }); 74 | }); 75 | }); 76 | 77 | describe('GET', function() { 78 | it('simple', function(done) { 79 | var uri = 'http://baconipsum.com/api/?type=meat-and-filler&format=json', 80 | rq = $.ajax({ 81 | type: 'GET', 82 | url: uri, 83 | dataType: 'json' 84 | }); 85 | 86 | rq.done(function(data, status, xhr) { 87 | expect(this.crossDomain).true; 88 | expect(this.hasContent).false; 89 | expect(this.type).equal('GET'); 90 | expect(this.url).equal(uri); 91 | 92 | expect(xhr.status).equal(200); 93 | expect(xhr.responseJSON).instanceof(Array); 94 | 95 | done(); 96 | }); 97 | }); 98 | 99 | it('data', function(done) { 100 | var uri = 'http://baconipsum.com/api/?type=meat-and-filler&format=json', 101 | rq = $.ajax({ 102 | type: 'GET', 103 | url: uri, 104 | data: { test: 'test', test1: 'test1' }, 105 | dataType: 'json' 106 | }); 107 | 108 | rq.done(function(data, status, xhr) { 109 | expect(this.crossDomain).true; 110 | expect(this.hasContent).false; 111 | expect(this.type).equal('GET'); 112 | expect(this.url).equal(uri + '&test=test&test1=test1'); 113 | 114 | expect(xhr.status).equal(200); 115 | expect(xhr.responseJSON).instanceof(Array); 116 | 117 | done(); 118 | }); 119 | }); 120 | 121 | it('forceMethod', function(done) { 122 | var uri = 'http://baconipsum.com/api/?type=meat-and-filler&format=json', 123 | rq = $.ajax({ 124 | type: 'HEAD', 125 | url: uri, 126 | dataType: 'json', 127 | forceMethod: true, 128 | __test: true 129 | }); 130 | 131 | rq.done(function(data, status, xhr) { 132 | expect(this.crossDomain).true; 133 | expect(this.hasContent).false; 134 | expect(xhr.__method).equal('GET'); 135 | expect(xhr.__uri).equal(uri + '&__method=GET'); 136 | 137 | expect(xhr.status).equal(200); 138 | expect(xhr.statusText).equal('OK'); 139 | expect(xhr.responseJSON).instanceof(Array); 140 | 141 | done(); 142 | }); 143 | }); 144 | 145 | it('forceContentType', function(done) { 146 | var uri = 'http://baconipsum.com/api/?type=meat-and-filler&format=json', 147 | rq = $.ajax({ 148 | type: 'GET', 149 | url: uri, 150 | dataType: 'json', 151 | //forceMethod: true, 152 | forceContentType: true, 153 | __test: true 154 | }); 155 | 156 | rq.done(function(data, status, xhr) { 157 | expect(this.crossDomain).true; 158 | expect(this.hasContent).false; 159 | expect(xhr.__method).equal('GET'); 160 | expect(xhr.__uri).equal(uri + '&__contentType=' + encodeURIComponent(this.contentType)); 161 | 162 | expect(xhr.status).equal(200); 163 | expect(xhr.statusText).equal('OK'); 164 | expect(xhr.responseJSON).instanceof(Array); 165 | 166 | done(); 167 | }); 168 | }); 169 | 170 | it('forceMethod + forceContentType', function(done) { 171 | var uri = 'http://baconipsum.com/api/?type=meat-and-filler&format=json', 172 | rq = $.ajax({ 173 | type: 'HEAD', 174 | url: uri, 175 | dataType: 'json', 176 | forceMethod: true, 177 | forceContentType: true, 178 | __test: true 179 | }); 180 | 181 | rq.done(function(data, status, xhr) { 182 | expect(this.crossDomain).true; 183 | expect(this.hasContent).false; 184 | expect(xhr.__method).equal('GET'); 185 | expect(xhr.__uri).equal(uri + '&__method=GET&__contentType=' + encodeURIComponent(this.contentType)); 186 | 187 | expect(xhr.status).equal(200); 188 | expect(xhr.statusText).equal('OK'); 189 | expect(xhr.responseJSON).instanceof(Array); 190 | 191 | done(); 192 | }); 193 | }); 194 | }); 195 | -------------------------------------------------------------------------------- /dist/jquery.transport.xdr.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("jQuery")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["jQuery"], factory); 6 | else { 7 | var a = typeof exports === 'object' ? factory(require("jQuery")) : factory(root["jQuery"]); 8 | for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; 9 | } 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_2__) { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | 39 | 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | module.exports = __webpack_require__(1); 58 | 59 | 60 | /***/ }, 61 | /* 1 */ 62 | /***/ function(module, exports, __webpack_require__) { 63 | 64 | /* WEBPACK VAR INJECTION */(function($) {'use strict'; 65 | 66 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; /*! 67 | * jquery.transport.xdr 68 | * https://github.com/gfdev/javascript-jquery-transport-xdr 69 | * Copyright (c) 2015 Gordon Freeman 70 | */ 71 | 72 | var _text = __webpack_require__(3); 73 | 74 | var _text2 = _interopRequireDefault(_text); 75 | 76 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 77 | 78 | $.ajaxTransport('+*', function (opts, optsUser, xhr) { 79 | if (opts.crossDomain && (document.addEventListener || document.querySelector) && !window.atob && window.XDomainRequest) { 80 | var _ret = function () { 81 | var xdr = new XDomainRequest(), 82 | method = opts.type.toUpperCase(), 83 | contentType = opts.contentType || optsUser.contentType, 84 | scheme = opts.url.substring(0, opts.url.indexOf(':')).toUpperCase(), 85 | uri = opts.url, 86 | data = optsUser.data || {}, 87 | _error = function _error(code, param) { 88 | return { 89 | send: function send(hdr, cb) { 90 | cb(-1, _text2.default.get(code, param)); 91 | }, 92 | abort: $.noop 93 | }; 94 | }; 95 | 96 | if (!xdr) return { 97 | v: _error(1) 98 | }; 99 | if (!optsUser.forceMethod && $.inArray(method, ['GET', 'POST']) === -1) return { 100 | v: _error(2, method) 101 | }; 102 | if ($.inArray(scheme, ['HTTP', 'HTTPS']) === -1) return { 103 | v: _error(3, scheme) 104 | }; 105 | if (scheme !== location.protocol.substring(0, location.protocol.indexOf(':')).toUpperCase()) return { 106 | v: _error(4) 107 | }; 108 | 109 | if (optsUser.forceMethod) { 110 | if (method === 'HEAD') { 111 | method = 'GET'; 112 | uri += (opts.url.indexOf('?') === -1 ? '?' : '&') + '__method=' + method; 113 | } 114 | 115 | if ($.inArray(method, ['PUT', 'DELETE', 'PATCH']) !== -1) { 116 | method = 'POST'; 117 | 118 | if ($.isPlainObject(data)) data.__method = method;else if (typeof data === 'string') data += (data.length ? '&' : '') + '__method=' + method; 119 | } 120 | } 121 | 122 | if (optsUser.forceContentType) { 123 | if (method === 'GET') uri += (opts.url.indexOf('?') === -1 ? '?' : '&') + '__contentType=' + encodeURIComponent(contentType); 124 | 125 | if (method === 'POST') { 126 | if ($.isPlainObject(data)) data.__contentType = contentType;else if (typeof data === 'string') data += (data.length ? '&' : '') + $.param({ __contentType: contentType }); 127 | } 128 | } 129 | 130 | if (opts.timeout) xdr.timeout = opts.timeout; 131 | 132 | xdr.onprogress = $.noop; 133 | 134 | return { 135 | v: { 136 | send: function send(hdr, cb) { 137 | xdr.onload = function () { 138 | var data = {}, 139 | error = null; 140 | 141 | switch (opts.dataType) { 142 | case 'json': 143 | try { 144 | data.json = $.parseJSON(xdr.responseText); 145 | } catch (e) { 146 | error = e.message; 147 | } 148 | break; 149 | case 'xml': 150 | try { 151 | data.xml = $.parseXML(xdr.responseText); 152 | } catch (e) { 153 | error = e.message; 154 | } 155 | break; 156 | case 'text': 157 | data.text = xdr.responseText; 158 | break; 159 | case 'html': 160 | data.html = xdr.responseText; 161 | break; 162 | } 163 | 164 | if (error) return cb(500, _text2.default.get(6, error)); 165 | 166 | var headers = ['Content-Type: ' + xdr.contentType, 'Content-Length: ' + xdr.responseText.length]; 167 | 168 | cb(200, 'OK', data, headers.join('\r\n')); 169 | }; 170 | 171 | xdr.onerror = function () { 172 | cb(500, _text2.default.get(7)); 173 | }; 174 | xdr.ontimeout = function () { 175 | cb(500, _text2.default.get(8)); 176 | }; 177 | 178 | if (optsUser.__test === true) { 179 | xhr.__method = method; 180 | xhr.__uri = uri; 181 | } 182 | 183 | xdr.open(method, uri); 184 | 185 | setTimeout(function () { 186 | xdr.send(method === 'POST' ? typeof data === 'string' ? data : $.isPlainObject(data) ? $.param(data) : null : null); 187 | }, 0); 188 | }, 189 | abort: function abort() { 190 | xdr.abort(); 191 | } 192 | } 193 | }; 194 | }(); 195 | 196 | if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; 197 | } 198 | }); 199 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2))) 200 | 201 | /***/ }, 202 | /* 2 */ 203 | /***/ function(module, exports) { 204 | 205 | module.exports = __WEBPACK_EXTERNAL_MODULE_2__; 206 | 207 | /***/ }, 208 | /* 3 */ 209 | /***/ function(module, exports) { 210 | 211 | 'use strict'; 212 | 213 | module.exports = { 214 | get: function get(code, param) { 215 | var _messages = { 216 | 0: 'Unknown Error', 217 | 1: 'No Transport', 218 | 2: param + ' Method Not Allowed', 219 | 3: param + ' Scheme Not Supported', 220 | 4: 'URI source and target scheme must be the same', 221 | 5: 'No Data', 222 | 6: 'Bad Data: ' + param, 223 | 7: 'Network Error', 224 | 8: 'Timeout' 225 | }; 226 | 227 | return _messages[code in _messages ? code : 0]; 228 | } 229 | }; 230 | 231 | /***/ } 232 | /******/ ]) 233 | }); 234 | ; --------------------------------------------------------------------------------