SocketIoShim.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /* global io */
  2. define([], function() {
  3. class SocketShimBase {
  4. static connect(url, options) {
  5. return new SocketShimBase()
  6. }
  7. constructor(socket) {
  8. this._socket = socket
  9. }
  10. }
  11. const transparentMethods = [
  12. 'connect',
  13. 'disconnect',
  14. 'emit',
  15. 'on',
  16. 'removeListener'
  17. ]
  18. for (let method of transparentMethods) {
  19. SocketShimBase.prototype[method] = function() {
  20. this._socket[method].apply(this._socket, arguments)
  21. }
  22. }
  23. class SocketShimNoop extends SocketShimBase {
  24. static connect() {
  25. return new SocketShimNoop()
  26. }
  27. constructor(socket) {
  28. super(socket)
  29. this.socket = {
  30. get connected() {
  31. return false
  32. },
  33. get sessionid() {
  34. return undefined
  35. },
  36. get transport() {
  37. return {}
  38. },
  39. connect() {},
  40. disconnect(reason) {}
  41. }
  42. }
  43. connect() {}
  44. disconnect(reason) {}
  45. emit() {}
  46. on() {}
  47. removeListener() {}
  48. }
  49. class SocketShimV0 extends SocketShimBase {
  50. static connect(url, options) {
  51. return new SocketShimV0(
  52. io.connect(
  53. url,
  54. options
  55. )
  56. )
  57. }
  58. constructor(socket) {
  59. super(socket)
  60. this.socket = this._socket.socket
  61. }
  62. }
  63. class SocketShimV2 extends SocketShimBase {
  64. static connect(url, options) {
  65. options.forceNew = options['force new connection']
  66. // .resource has no leading slash, path wants to see one.
  67. options.path = '/' + options.resource
  68. options.reconnection = options.reconnect
  69. options.timeout = options['connect timeout']
  70. return new SocketShimV2(url, options)
  71. }
  72. static get EVENT_MAP() {
  73. // Use the v2 event names transparently to the frontend.
  74. const connectionFailureEvents = [
  75. 'connect_error',
  76. 'connect_timeout',
  77. 'error'
  78. ]
  79. return new Map([
  80. ['connect_failed', connectionFailureEvents],
  81. ['error', connectionFailureEvents]
  82. ])
  83. }
  84. _on(event, handler) {
  85. // Keep track of our event listeners.
  86. // We move them to a new socket in ._replaceSocketWithNewInstance()
  87. if (!this._events.has(event)) {
  88. this._events.set(event, [handler])
  89. } else {
  90. this._events.get(event).push(handler)
  91. }
  92. this._socket.on(event, handler)
  93. }
  94. on(event, handler) {
  95. if (SocketShimV2.EVENT_MAP.has(event)) {
  96. for (const v2Event of SocketShimV2.EVENT_MAP.get(event)) {
  97. this._on(v2Event, handler)
  98. }
  99. } else {
  100. this._on(event, handler)
  101. }
  102. }
  103. _removeListener(event, handler) {
  104. // Keep track of our event listeners.
  105. // We move them to a new socket in ._replaceSocketWithNewInstance()
  106. if (this._events.has(event)) {
  107. const listeners = this._events.get(event)
  108. const pos = listeners.indexOf(handler)
  109. if (pos !== -1) {
  110. listeners.splice(pos, 1)
  111. }
  112. }
  113. this._socket.removeListener(event, handler)
  114. }
  115. removeListener(event, handler) {
  116. if (SocketShimV2.EVENT_MAP.has(event)) {
  117. for (const v2Event of SocketShimV2.EVENT_MAP.get(event)) {
  118. this._removeListener(v2Event, handler)
  119. }
  120. } else {
  121. this._removeListener(event, handler)
  122. }
  123. }
  124. static createNewSocket(url, options) {
  125. // open a brand new connection for the default namespace '/'
  126. // The old socket can still leak 'disconnect' events from the teardown
  127. // of the old transport. The leaking 'disconnect' events interfere with
  128. // the _new_ connection and cancel the new connect attempt.
  129. // Also skip the caching in these locations:
  130. // - `io.connect()` caches `io.Manager`s in `io.managers`
  131. // - `io.Manager().socket()` caches `io.Socket`s in its `this.nsps`
  132. return io.Manager(url, options).socket('/', options)
  133. }
  134. _replaceSocketWithNewInstance() {
  135. const oldSocket = this._socket
  136. const newSocket = SocketShimV2.createNewSocket(this._url, this._options)
  137. // move our existing event handlers to the new socket
  138. this._events.forEach((listeners, event) => {
  139. for (const listener of listeners) {
  140. oldSocket.removeListener(event, listener)
  141. newSocket.on(event, listener)
  142. }
  143. })
  144. if (oldSocket.connected) {
  145. // We overwrite the reference to oldSocket soon.
  146. // Make sure we are disconnected.
  147. oldSocket.disconnect()
  148. }
  149. this._socket = newSocket
  150. }
  151. connect() {
  152. // have the same logic behind socket.connect and socket.socket.connect
  153. this._replaceSocketWithNewInstance()
  154. }
  155. constructor(url, options) {
  156. super(SocketShimV2.createNewSocket(url, options))
  157. this._url = url
  158. this._options = options
  159. this._events = new Map()
  160. const self = this
  161. function _getEngine() {
  162. return (self._socket.io && self._socket.io.engine) || {}
  163. }
  164. this.socket = {
  165. get connected() {
  166. return self._socket.connected
  167. },
  168. get sessionid() {
  169. if (self._socket.id) {
  170. return self._socket.id
  171. }
  172. // socket.id is discarded upon disconnect
  173. // the id is still available in the internal state
  174. return _getEngine().id
  175. },
  176. get transport() {
  177. return _getEngine().transport
  178. },
  179. connect() {
  180. self._replaceSocketWithNewInstance()
  181. },
  182. disconnect(reason) {
  183. return self._socket.disconnect(reason)
  184. }
  185. }
  186. }
  187. }
  188. let current
  189. if (typeof io === 'undefined' || !io) {
  190. sl_console.log('[socket.io] Shim: socket.io is not loaded, returning noop')
  191. current = SocketShimNoop
  192. } else if (typeof io.version === 'string' && io.version.slice(0, 1) === '0') {
  193. sl_console.log('[socket.io] Shim: detected v0')
  194. current = SocketShimV0
  195. } else {
  196. // socket.io v2 does not have a global io.version attribute.
  197. sl_console.log('[socket.io] Shim: detected v2')
  198. current = SocketShimV2
  199. }
  200. return {
  201. SocketShimNoop,
  202. SocketShimV0,
  203. SocketShimV2,
  204. current,
  205. connect: current.connect,
  206. stub: () => new SocketShimNoop()
  207. }
  208. })