polling-jsonp.js 4.8 KB

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