polling-jsonp.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /**
  2. * Module requirements.
  3. */
  4. var Polling = require('./polling');
  5. var inherit = require('component-inherit');
  6. /**
  7. * Module exports.
  8. */
  9. module.exports = JSONPPolling;
  10. /**
  11. * Cached regular expressions.
  12. */
  13. var rNewline = /\n/g;
  14. var rEscapedNewline = /\\n/g;
  15. /**
  16. * Global JSONP callbacks.
  17. */
  18. var callbacks;
  19. /**
  20. * Callbacks count.
  21. */
  22. var index = 0;
  23. /**
  24. * Noop.
  25. */
  26. function empty () { }
  27. /**
  28. * JSONP Polling constructor.
  29. *
  30. * @param {Object} opts.
  31. * @api public
  32. */
  33. function JSONPPolling (opts) {
  34. Polling.call(this, opts);
  35. this.query = this.query || {};
  36. // define global callbacks array if not present
  37. // we do this here (lazily) to avoid unneeded global pollution
  38. if (!callbacks) {
  39. // we need to consider multiple engines in the same page
  40. if (!global.___eio) global.___eio = [];
  41. callbacks = global.___eio;
  42. }
  43. // callback identifier
  44. this.index = callbacks.length;
  45. // add callback to jsonp global
  46. var self = this;
  47. callbacks.push(function (msg) {
  48. self.onData(msg);
  49. });
  50. // append to query string
  51. this.query.j = this.index;
  52. // prevent spurious errors from being emitted when the window is unloaded
  53. if (global.document && global.addEventListener) {
  54. global.addEventListener('beforeunload', function () {
  55. if (self.script) self.script.onerror = empty;
  56. }, false);
  57. }
  58. }
  59. /**
  60. * Inherits from Polling.
  61. */
  62. inherit(JSONPPolling, Polling);
  63. /*
  64. * JSONP only supports binary as base64 encoded strings
  65. */
  66. JSONPPolling.prototype.supportsBinary = false;
  67. /**
  68. * Closes the socket.
  69. *
  70. * @api private
  71. */
  72. JSONPPolling.prototype.doClose = function () {
  73. if (this.script) {
  74. this.script.parentNode.removeChild(this.script);
  75. this.script = null;
  76. }
  77. if (this.form) {
  78. this.form.parentNode.removeChild(this.form);
  79. this.form = null;
  80. this.iframe = null;
  81. }
  82. Polling.prototype.doClose.call(this);
  83. };
  84. /**
  85. * Starts a poll cycle.
  86. *
  87. * @api private
  88. */
  89. JSONPPolling.prototype.doPoll = function () {
  90. var self = this;
  91. var script = document.createElement('script');
  92. if (this.script) {
  93. this.script.parentNode.removeChild(this.script);
  94. this.script = null;
  95. }
  96. script.async = true;
  97. script.src = this.uri();
  98. script.onerror = function(e){
  99. self.onError('jsonp poll error',e);
  100. };
  101. var insertAt = document.getElementsByTagName('script')[0];
  102. if (insertAt) {
  103. insertAt.parentNode.insertBefore(script, insertAt);
  104. }
  105. else {
  106. (document.head || document.body).appendChild(script);
  107. }
  108. this.script = script;
  109. var isUAgecko = 'undefined' != typeof navigator && /gecko/i.test(navigator.userAgent);
  110. if (isUAgecko) {
  111. setTimeout(function () {
  112. var iframe = document.createElement('iframe');
  113. document.body.appendChild(iframe);
  114. document.body.removeChild(iframe);
  115. }, 100);
  116. }
  117. };
  118. /**
  119. * Writes with a hidden iframe.
  120. *
  121. * @param {String} data to send
  122. * @param {Function} called upon flush.
  123. * @api private
  124. */
  125. JSONPPolling.prototype.doWrite = function (data, fn) {
  126. var self = this;
  127. if (!this.form) {
  128. var form = document.createElement('form');
  129. var area = document.createElement('textarea');
  130. var id = this.iframeId = 'eio_iframe_' + this.index;
  131. var iframe;
  132. form.className = 'socketio';
  133. form.style.position = 'absolute';
  134. form.style.top = '-1000px';
  135. form.style.left = '-1000px';
  136. form.target = id;
  137. form.method = 'POST';
  138. form.setAttribute('accept-charset', 'utf-8');
  139. area.name = 'd';
  140. form.appendChild(area);
  141. document.body.appendChild(form);
  142. this.form = form;
  143. this.area = area;
  144. }
  145. this.form.action = this.uri();
  146. function complete () {
  147. initIframe();
  148. fn();
  149. }
  150. function initIframe () {
  151. if (self.iframe) {
  152. try {
  153. self.form.removeChild(self.iframe);
  154. } catch (e) {
  155. self.onError('jsonp polling iframe removal error', e);
  156. }
  157. }
  158. try {
  159. // ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
  160. var html = '<iframe src="javascript:0" name="'+ self.iframeId +'">';
  161. iframe = document.createElement(html);
  162. } catch (e) {
  163. iframe = document.createElement('iframe');
  164. iframe.name = self.iframeId;
  165. iframe.src = 'javascript:0';
  166. }
  167. iframe.id = self.iframeId;
  168. self.form.appendChild(iframe);
  169. self.iframe = iframe;
  170. }
  171. initIframe();
  172. // escape \n to prevent it from being converted into \r\n by some UAs
  173. // double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side
  174. data = data.replace(rEscapedNewline, '\\\n');
  175. this.area.value = data.replace(rNewline, '\\n');
  176. try {
  177. this.form.submit();
  178. } catch(e) {}
  179. if (this.iframe.attachEvent) {
  180. this.iframe.onreadystatechange = function(){
  181. if (self.iframe.readyState == 'complete') {
  182. complete();
  183. }
  184. };
  185. } else {
  186. this.iframe.onload = complete;
  187. }
  188. };