SocketIoShim.js 5.7 KB

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