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.

233 lines
4.7 KiB

  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. });
  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. insertAt.parentNode.insertBefore(script, insertAt);
  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. };