[6a3a178] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | var utils = require('../../utils/iframe')
|
---|
| 4 | , random = require('../../utils/random')
|
---|
| 5 | , browser = require('../../utils/browser')
|
---|
| 6 | , urlUtils = require('../../utils/url')
|
---|
| 7 | , inherits = require('inherits')
|
---|
| 8 | , EventEmitter = require('events').EventEmitter
|
---|
| 9 | ;
|
---|
| 10 |
|
---|
| 11 | var debug = function() {};
|
---|
| 12 | if (process.env.NODE_ENV !== 'production') {
|
---|
| 13 | debug = require('debug')('sockjs-client:receiver:jsonp');
|
---|
| 14 | }
|
---|
| 15 |
|
---|
| 16 | function JsonpReceiver(url) {
|
---|
| 17 | debug(url);
|
---|
| 18 | var self = this;
|
---|
| 19 | EventEmitter.call(this);
|
---|
| 20 |
|
---|
| 21 | utils.polluteGlobalNamespace();
|
---|
| 22 |
|
---|
| 23 | this.id = 'a' + random.string(6);
|
---|
| 24 | var urlWithId = urlUtils.addQuery(url, 'c=' + encodeURIComponent(utils.WPrefix + '.' + this.id));
|
---|
| 25 |
|
---|
| 26 | global[utils.WPrefix][this.id] = this._callback.bind(this);
|
---|
| 27 | this._createScript(urlWithId);
|
---|
| 28 |
|
---|
| 29 | // Fallback mostly for Konqueror - stupid timer, 35 seconds shall be plenty.
|
---|
| 30 | this.timeoutId = setTimeout(function() {
|
---|
| 31 | debug('timeout');
|
---|
| 32 | self._abort(new Error('JSONP script loaded abnormally (timeout)'));
|
---|
| 33 | }, JsonpReceiver.timeout);
|
---|
| 34 | }
|
---|
| 35 |
|
---|
| 36 | inherits(JsonpReceiver, EventEmitter);
|
---|
| 37 |
|
---|
| 38 | JsonpReceiver.prototype.abort = function() {
|
---|
| 39 | debug('abort');
|
---|
| 40 | if (global[utils.WPrefix][this.id]) {
|
---|
| 41 | var err = new Error('JSONP user aborted read');
|
---|
| 42 | err.code = 1000;
|
---|
| 43 | this._abort(err);
|
---|
| 44 | }
|
---|
| 45 | };
|
---|
| 46 |
|
---|
| 47 | JsonpReceiver.timeout = 35000;
|
---|
| 48 | JsonpReceiver.scriptErrorTimeout = 1000;
|
---|
| 49 |
|
---|
| 50 | JsonpReceiver.prototype._callback = function(data) {
|
---|
| 51 | debug('_callback', data);
|
---|
| 52 | this._cleanup();
|
---|
| 53 |
|
---|
| 54 | if (this.aborting) {
|
---|
| 55 | return;
|
---|
| 56 | }
|
---|
| 57 |
|
---|
| 58 | if (data) {
|
---|
| 59 | debug('message', data);
|
---|
| 60 | this.emit('message', data);
|
---|
| 61 | }
|
---|
| 62 | this.emit('close', null, 'network');
|
---|
| 63 | this.removeAllListeners();
|
---|
| 64 | };
|
---|
| 65 |
|
---|
| 66 | JsonpReceiver.prototype._abort = function(err) {
|
---|
| 67 | debug('_abort', err);
|
---|
| 68 | this._cleanup();
|
---|
| 69 | this.aborting = true;
|
---|
| 70 | this.emit('close', err.code, err.message);
|
---|
| 71 | this.removeAllListeners();
|
---|
| 72 | };
|
---|
| 73 |
|
---|
| 74 | JsonpReceiver.prototype._cleanup = function() {
|
---|
| 75 | debug('_cleanup');
|
---|
| 76 | clearTimeout(this.timeoutId);
|
---|
| 77 | if (this.script2) {
|
---|
| 78 | this.script2.parentNode.removeChild(this.script2);
|
---|
| 79 | this.script2 = null;
|
---|
| 80 | }
|
---|
| 81 | if (this.script) {
|
---|
| 82 | var script = this.script;
|
---|
| 83 | // Unfortunately, you can't really abort script loading of
|
---|
| 84 | // the script.
|
---|
| 85 | script.parentNode.removeChild(script);
|
---|
| 86 | script.onreadystatechange = script.onerror =
|
---|
| 87 | script.onload = script.onclick = null;
|
---|
| 88 | this.script = null;
|
---|
| 89 | }
|
---|
| 90 | delete global[utils.WPrefix][this.id];
|
---|
| 91 | };
|
---|
| 92 |
|
---|
| 93 | JsonpReceiver.prototype._scriptError = function() {
|
---|
| 94 | debug('_scriptError');
|
---|
| 95 | var self = this;
|
---|
| 96 | if (this.errorTimer) {
|
---|
| 97 | return;
|
---|
| 98 | }
|
---|
| 99 |
|
---|
| 100 | this.errorTimer = setTimeout(function() {
|
---|
| 101 | if (!self.loadedOkay) {
|
---|
| 102 | self._abort(new Error('JSONP script loaded abnormally (onerror)'));
|
---|
| 103 | }
|
---|
| 104 | }, JsonpReceiver.scriptErrorTimeout);
|
---|
| 105 | };
|
---|
| 106 |
|
---|
| 107 | JsonpReceiver.prototype._createScript = function(url) {
|
---|
| 108 | debug('_createScript', url);
|
---|
| 109 | var self = this;
|
---|
| 110 | var script = this.script = global.document.createElement('script');
|
---|
| 111 | var script2; // Opera synchronous load trick.
|
---|
| 112 |
|
---|
| 113 | script.id = 'a' + random.string(8);
|
---|
| 114 | script.src = url;
|
---|
| 115 | script.type = 'text/javascript';
|
---|
| 116 | script.charset = 'UTF-8';
|
---|
| 117 | script.onerror = this._scriptError.bind(this);
|
---|
| 118 | script.onload = function() {
|
---|
| 119 | debug('onload');
|
---|
| 120 | self._abort(new Error('JSONP script loaded abnormally (onload)'));
|
---|
| 121 | };
|
---|
| 122 |
|
---|
| 123 | // IE9 fires 'error' event after onreadystatechange or before, in random order.
|
---|
| 124 | // Use loadedOkay to determine if actually errored
|
---|
| 125 | script.onreadystatechange = function() {
|
---|
| 126 | debug('onreadystatechange', script.readyState);
|
---|
| 127 | if (/loaded|closed/.test(script.readyState)) {
|
---|
| 128 | if (script && script.htmlFor && script.onclick) {
|
---|
| 129 | self.loadedOkay = true;
|
---|
| 130 | try {
|
---|
| 131 | // In IE, actually execute the script.
|
---|
| 132 | script.onclick();
|
---|
| 133 | } catch (x) {
|
---|
| 134 | // intentionally empty
|
---|
| 135 | }
|
---|
| 136 | }
|
---|
| 137 | if (script) {
|
---|
| 138 | self._abort(new Error('JSONP script loaded abnormally (onreadystatechange)'));
|
---|
| 139 | }
|
---|
| 140 | }
|
---|
| 141 | };
|
---|
| 142 | // IE: event/htmlFor/onclick trick.
|
---|
| 143 | // One can't rely on proper order for onreadystatechange. In order to
|
---|
| 144 | // make sure, set a 'htmlFor' and 'event' properties, so that
|
---|
| 145 | // script code will be installed as 'onclick' handler for the
|
---|
| 146 | // script object. Later, onreadystatechange, manually execute this
|
---|
| 147 | // code. FF and Chrome doesn't work with 'event' and 'htmlFor'
|
---|
| 148 | // set. For reference see:
|
---|
| 149 | // http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
|
---|
| 150 | // Also, read on that about script ordering:
|
---|
| 151 | // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
|
---|
| 152 | if (typeof script.async === 'undefined' && global.document.attachEvent) {
|
---|
| 153 | // According to mozilla docs, in recent browsers script.async defaults
|
---|
| 154 | // to 'true', so we may use it to detect a good browser:
|
---|
| 155 | // https://developer.mozilla.org/en/HTML/Element/script
|
---|
| 156 | if (!browser.isOpera()) {
|
---|
| 157 | // Naively assume we're in IE
|
---|
| 158 | try {
|
---|
| 159 | script.htmlFor = script.id;
|
---|
| 160 | script.event = 'onclick';
|
---|
| 161 | } catch (x) {
|
---|
| 162 | // intentionally empty
|
---|
| 163 | }
|
---|
| 164 | script.async = true;
|
---|
| 165 | } else {
|
---|
| 166 | // Opera, second sync script hack
|
---|
| 167 | script2 = this.script2 = global.document.createElement('script');
|
---|
| 168 | script2.text = "try{var a = document.getElementById('" + script.id + "'); if(a)a.onerror();}catch(x){};";
|
---|
| 169 | script.async = script2.async = false;
|
---|
| 170 | }
|
---|
| 171 | }
|
---|
| 172 | if (typeof script.async !== 'undefined') {
|
---|
| 173 | script.async = true;
|
---|
| 174 | }
|
---|
| 175 |
|
---|
| 176 | var head = global.document.getElementsByTagName('head')[0];
|
---|
| 177 | head.insertBefore(script, head.firstChild);
|
---|
| 178 | if (script2) {
|
---|
| 179 | head.insertBefore(script2, head.firstChild);
|
---|
| 180 | }
|
---|
| 181 | };
|
---|
| 182 |
|
---|
| 183 | module.exports = JsonpReceiver;
|
---|