├── .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 | ;
--------------------------------------------------------------------------------