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;
|
---|