You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

231 lines
4.8 KiB

8 years ago
  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. * Noop.
  21. */
  22. function empty () { }
  23. /**
  24. * JSONP Polling constructor.
  25. *
  26. * @param {Object} opts.
  27. * @api public
  28. */
  29. function JSONPPolling (opts) {
  30. Polling.call(this, opts);
  31. this.query = this.query || {};
  32. // define global callbacks array if not present
  33. // we do this here (lazily) to avoid unneeded global pollution
  34. if (!callbacks) {
  35. // we need to consider multiple engines in the same page
  36. if (!global.___eio) global.___eio = [];
  37. callbacks = global.___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 (global.document && global.addEventListener) {
  50. global.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. };