-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This reverts commit 17826da.
- Loading branch information
Showing
3 changed files
with
266 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import ee from 'namespace-emitter' | ||
|
||
export default class UppySocket { | ||
#queued = [] | ||
|
||
#emitter = ee() | ||
|
||
#isOpen = false | ||
|
||
#socket | ||
|
||
constructor (opts) { | ||
this.opts = opts | ||
|
||
if (!opts || opts.autoOpen !== false) { | ||
this.open() | ||
} | ||
} | ||
|
||
get isOpen () { return this.#isOpen } | ||
|
||
[Symbol.for('uppy test: getSocket')] () { return this.#socket } | ||
|
||
[Symbol.for('uppy test: getQueued')] () { return this.#queued } | ||
|
||
open () { | ||
if (this.#socket != null) return | ||
|
||
this.#socket = new WebSocket(this.opts.target) | ||
|
||
this.#socket.onopen = () => { | ||
this.#isOpen = true | ||
|
||
while (this.#queued.length > 0 && this.#isOpen) { | ||
const first = this.#queued.shift() | ||
this.send(first.action, first.payload) | ||
} | ||
} | ||
|
||
this.#socket.onclose = () => { | ||
this.#isOpen = false | ||
this.#socket = null | ||
} | ||
|
||
this.#socket.onmessage = this.#handleMessage | ||
} | ||
|
||
close () { | ||
this.#socket?.close() | ||
} | ||
|
||
send (action, payload) { | ||
// attach uuid | ||
|
||
if (!this.#isOpen) { | ||
this.#queued.push({ action, payload }) | ||
return | ||
} | ||
|
||
this.#socket.send(JSON.stringify({ | ||
action, | ||
payload, | ||
})) | ||
} | ||
|
||
on (action, handler) { | ||
this.#emitter.on(action, handler) | ||
} | ||
|
||
emit (action, payload) { | ||
this.#emitter.emit(action, payload) | ||
} | ||
|
||
once (action, handler) { | ||
this.#emitter.once(action, handler) | ||
} | ||
|
||
#handleMessage = (e) => { | ||
try { | ||
const message = JSON.parse(e.data) | ||
this.emit(message.action, message.payload) | ||
} catch (err) { | ||
// TODO: use a more robust error handler. | ||
console.log(err) // eslint-disable-line no-console | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import { afterEach, beforeEach, vi, describe, it, expect } from 'vitest' | ||
import UppySocket from './Socket.js' | ||
|
||
describe('Socket', () => { | ||
let webSocketConstructorSpy | ||
let webSocketCloseSpy | ||
let webSocketSendSpy | ||
|
||
beforeEach(() => { | ||
webSocketConstructorSpy = vi.fn() | ||
webSocketCloseSpy = vi.fn() | ||
webSocketSendSpy = vi.fn() | ||
|
||
globalThis.WebSocket = class WebSocket { | ||
constructor (target) { | ||
webSocketConstructorSpy(target) | ||
} | ||
|
||
// eslint-disable-next-line class-methods-use-this | ||
close (args) { | ||
webSocketCloseSpy(args) | ||
} | ||
|
||
// eslint-disable-next-line class-methods-use-this | ||
send (json) { | ||
webSocketSendSpy(json) | ||
} | ||
|
||
triggerOpen () { | ||
this.onopen() | ||
} | ||
|
||
triggerClose () { | ||
this.onclose() | ||
} | ||
} | ||
}) | ||
afterEach(() => { | ||
globalThis.WebSocket = undefined | ||
}) | ||
|
||
it('should expose a class', () => { | ||
expect(UppySocket.name).toEqual('UppySocket') | ||
expect( | ||
new UppySocket({ | ||
target: 'foo', | ||
}) instanceof UppySocket, | ||
) | ||
}) | ||
|
||
it('should setup a new WebSocket', () => { | ||
new UppySocket({ target: 'foo' }) // eslint-disable-line no-new | ||
expect(webSocketConstructorSpy.mock.calls[0][0]).toEqual('foo') | ||
}) | ||
|
||
it('should send a message via the websocket if the connection is open', () => { | ||
const uppySocket = new UppySocket({ target: 'foo' }) | ||
const webSocketInstance = uppySocket[Symbol.for('uppy test: getSocket')]() | ||
webSocketInstance.triggerOpen() | ||
|
||
uppySocket.send('bar', 'boo') | ||
expect(webSocketSendSpy.mock.calls.length).toEqual(1) | ||
expect(webSocketSendSpy.mock.calls[0]).toEqual([ | ||
JSON.stringify({ action: 'bar', payload: 'boo' }), | ||
]) | ||
}) | ||
|
||
it('should queue the message for the websocket if the connection is not open', () => { | ||
const uppySocket = new UppySocket({ target: 'foo' }) | ||
|
||
uppySocket.send('bar', 'boo') | ||
expect(uppySocket[Symbol.for('uppy test: getQueued')]()).toEqual([{ action: 'bar', payload: 'boo' }]) | ||
expect(webSocketSendSpy.mock.calls.length).toEqual(0) | ||
}) | ||
|
||
it('should queue any messages for the websocket if the connection is not open, then send them when the connection is open', () => { | ||
const uppySocket = new UppySocket({ target: 'foo' }) | ||
const webSocketInstance = uppySocket[Symbol.for('uppy test: getSocket')]() | ||
|
||
uppySocket.send('bar', 'boo') | ||
uppySocket.send('moo', 'baa') | ||
expect(uppySocket[Symbol.for('uppy test: getQueued')]()).toEqual([ | ||
{ action: 'bar', payload: 'boo' }, | ||
{ action: 'moo', payload: 'baa' }, | ||
]) | ||
expect(webSocketSendSpy.mock.calls.length).toEqual(0) | ||
|
||
webSocketInstance.triggerOpen() | ||
|
||
expect(uppySocket[Symbol.for('uppy test: getQueued')]()).toEqual([]) | ||
expect(webSocketSendSpy.mock.calls.length).toEqual(2) | ||
expect(webSocketSendSpy.mock.calls[0]).toEqual([ | ||
JSON.stringify({ action: 'bar', payload: 'boo' }), | ||
]) | ||
expect(webSocketSendSpy.mock.calls[1]).toEqual([ | ||
JSON.stringify({ action: 'moo', payload: 'baa' }), | ||
]) | ||
}) | ||
|
||
it('should start queuing any messages when the websocket connection is closed', () => { | ||
const uppySocket = new UppySocket({ target: 'foo' }) | ||
const webSocketInstance = uppySocket[Symbol.for('uppy test: getSocket')]() | ||
webSocketInstance.triggerOpen() | ||
uppySocket.send('bar', 'boo') | ||
expect(uppySocket[Symbol.for('uppy test: getQueued')]()).toEqual([]) | ||
|
||
webSocketInstance.triggerClose() | ||
uppySocket.send('bar', 'boo') | ||
expect(uppySocket[Symbol.for('uppy test: getQueued')]()).toEqual([{ action: 'bar', payload: 'boo' }]) | ||
}) | ||
|
||
it('should close the websocket when it is force closed', () => { | ||
const uppySocket = new UppySocket({ target: 'foo' }) | ||
const webSocketInstance = uppySocket[Symbol.for('uppy test: getSocket')]() | ||
webSocketInstance.triggerOpen() | ||
|
||
uppySocket.close() | ||
expect(webSocketCloseSpy.mock.calls.length).toEqual(1) | ||
}) | ||
|
||
it('should be able to subscribe to messages received on the websocket', () => { | ||
const uppySocket = new UppySocket({ target: 'foo' }) | ||
const webSocketInstance = uppySocket[Symbol.for('uppy test: getSocket')]() | ||
|
||
const emitterListenerMock = vi.fn() | ||
uppySocket.on('hi', emitterListenerMock) | ||
|
||
webSocketInstance.triggerOpen() | ||
webSocketInstance.onmessage({ | ||
data: JSON.stringify({ action: 'hi', payload: 'ho' }), | ||
}) | ||
expect(emitterListenerMock.mock.calls).toEqual([ | ||
['ho', undefined, undefined, undefined, undefined, undefined], | ||
]) | ||
}) | ||
|
||
it('should be able to emit messages and subscribe to them', () => { | ||
const uppySocket = new UppySocket({ target: 'foo' }) | ||
|
||
const emitterListenerMock = vi.fn() | ||
uppySocket.on('hi', emitterListenerMock) | ||
|
||
uppySocket.emit('hi', 'ho') | ||
uppySocket.emit('hi', 'ho') | ||
uppySocket.emit('hi', 'off to work we go') | ||
|
||
expect(emitterListenerMock.mock.calls).toEqual([ | ||
['ho', undefined, undefined, undefined, undefined, undefined], | ||
['ho', undefined, undefined, undefined, undefined, undefined], | ||
[ | ||
'off to work we go', | ||
undefined, | ||
undefined, | ||
undefined, | ||
undefined, | ||
undefined, | ||
], | ||
]) | ||
}) | ||
|
||
it('should be able to subscribe to the first event for a particular action', () => { | ||
const uppySocket = new UppySocket({ target: 'foo' }) | ||
|
||
const emitterListenerMock = vi.fn() | ||
uppySocket.once('hi', emitterListenerMock) | ||
|
||
uppySocket.emit('hi', 'ho') | ||
uppySocket.emit('hi', 'ho') | ||
uppySocket.emit('hi', 'off to work we go') | ||
|
||
expect(emitterListenerMock.mock.calls.length).toEqual(1) | ||
expect(emitterListenerMock.mock.calls).toEqual([ | ||
['ho', undefined, undefined, undefined, undefined, undefined], | ||
]) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters