SocketIoShim.js 5.7 KB

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