Merge pull request #308 from Terreii/event-target

EventTarget
This commit is contained in:
Christopher
2020-12-14 21:21:15 +01:00
committed by GitHub
8 changed files with 275 additions and 131 deletions
+54 -17
View File
@@ -1,35 +1,66 @@
import createCallback from './simAction'
import simAction from './simAction'
import { userWasKicked } from '../bundles/session'
// Starts listening to packets on the circuit and dispatch a parsed action.
export default function init () {
return (dispatch, getState, { circuit }) => {
let callback = createCallback(dispatch)
circuit.on('packetReceived', callback)
let closeHandler = getCloseHandler(dispatch)
circuit.on('close', closeHandler)
let listeners = connect(dispatch, circuit)
if (process.env.NODE_ENV !== 'production') {
if (module.hot) {
// Replace simAction-callback if it changes
module.hot.accept('./simAction', () => {
circuit.removeListener('packetReceived', callback)
callback = createCallback(dispatch)
circuit.on('packetReceived', callback)
removeEventListeners(circuit, listeners)
listeners = connect(dispatch, circuit)
})
module.hot.accept('../bundles/session', () => {
circuit.removeListener('close', closeHandler)
closeHandler = getCloseHandler(dispatch)
circuit.on('close', closeHandler)
removeEventListeners(circuit, listeners)
listeners = connect(dispatch, circuit)
})
}
}
}
}
function getCloseHandler (dispatch) {
/**
* Connects the event listeners to the Circuit.
* @param {function} dispatch Redux Store dispatch
* @param {EventTarget} circuit Circuit from network/circuit.
* @returns {{simAction: EventListener, closeHandler: EventListener}} Event listeners.
*/
function connect (dispatch, circuit) {
const listeners = {
simAction: event => {
dispatch(simAction(event))
},
closeHandler: null
}
listeners.closeHandler = closeHandler(dispatch, circuit, listeners)
circuit.addEventListener('packetReceived', listeners.simAction)
circuit.addEventListener('close', listeners.closeHandler)
return listeners
}
/**
* Remove all event listeners from the circuit.
* @param {EventTarget} circuit Circuit from network/circuit.
* @param {{simAction: EventListener, closeHandler: EventListener}} listeners Event listeners.
*/
function removeEventListeners (circuit, listeners) {
circuit.removeEventListener('packetReceived', listeners.simAction)
circuit.removeEventListener('close', listeners.closeHandler)
}
/**
* Listens to close event. If the there is a reason, dispatch it.
* @param {function} dispatch Redux Store dispatch
* @param {EventTarget} circuit Circuit from network/circuit.
* @param {simAction: EventListener, closeHandler: EventListener} listeners Event listeners.
*/
function closeHandler (dispatch, circuit, listeners) {
const disconnectMessage = 'You have been disconnected!\n\n' +
'Please check if you have an Internet connection.\n' +
'This problem could also be on our or the grids side.'
@@ -40,9 +71,15 @@ function getCloseHandler (dispatch) {
}
return event => {
const reason = event.reason in reasonTexts
? reasonTexts[event.reason]
: event.reason
dispatch(userWasKicked({ reason }))
const reason = event.detail.reason in reasonTexts
? reasonTexts[event.detail.reason]
: event.detail.reason
removeEventListeners(circuit, listeners)
if (event.detail.code !== 1000) {
// not normal circuit close
dispatch(userWasKicked({ reason }))
}
}
}
+94
View File
@@ -0,0 +1,94 @@
import connectCircuit from './connectCircuit'
it('should listen to packages on the Circuit in the thunk extra argument', () => {
const innerFn = connectCircuit()
const dispatch = jest.fn()
const getState = jest.fn(() => ({}))
const circuitAddEventListener = jest.fn()
innerFn(dispatch, getState, {
circuit: {
addEventListener: circuitAddEventListener
}
})
expect(dispatch).not.toBeCalled()
expect(getState).not.toBeCalled()
expect(circuitAddEventListener).toBeCalledTimes(2)
expect(circuitAddEventListener)
.toHaveBeenNthCalledWith(1, 'packetReceived', expect.any(Function))
expect(circuitAddEventListener).toHaveBeenNthCalledWith(2, 'close', expect.any(Function))
})
it('should close all event-listeners on the close event', () => {
const innerFn = connectCircuit()
const dispatch = jest.fn()
const getState = jest.fn(() => ({}))
const eventListeners = new Map()
const circuitRemoveEventListener = jest.fn()
const circuit = {
addEventListener (eventName, listener) {
eventListeners.set(eventName, listener)
},
removeEventListener: circuitRemoveEventListener
}
innerFn(dispatch, getState, { circuit })
const closeListener = eventListeners.get('close')
expect(typeof closeListener).toBe('function')
closeListener(new window.CustomEvent('close', {
detail: {
code: 1006,
reason: 'Max reconnection tries'
}
}))
expect(dispatch).toHaveBeenLastCalledWith({
type: 'session/userWasKicked',
payload: {
reason: 'You have been disconnected!\n\n' +
'Please check if you have an Internet connection.\n' +
'This problem could also be on our or the grids side.'
}
})
expect(getState).not.toHaveBeenCalled()
expect(circuitRemoveEventListener)
.toHaveBeenNthCalledWith(1, 'packetReceived', eventListeners.get('packetReceived'))
expect(circuitRemoveEventListener)
.toHaveBeenNthCalledWith(2, 'close', eventListeners.get('close'))
})
it('should not dispatch an kick event if the circuit did close with 1000', () => {
const innerFn = connectCircuit()
const dispatch = jest.fn()
const getState = jest.fn(() => ({}))
const eventListeners = new Map()
const circuitRemoveEventListener = jest.fn()
const circuit = {
addEventListener (eventName, listener) {
eventListeners.set(eventName, listener)
},
removeEventListener: circuitRemoveEventListener
}
innerFn(dispatch, getState, { circuit })
const closeListener = eventListeners.get('close')
expect(typeof closeListener).toBe('function')
closeListener(new window.CustomEvent('close', {
detail: {
code: 1000,
reason: 'session end'
}
}))
expect(dispatch).toBeCalledTimes(0)
expect(getState).not.toHaveBeenCalled()
expect(circuitRemoveEventListener)
.toHaveBeenNthCalledWith(1, 'packetReceived', eventListeners.get('packetReceived'))
expect(circuitRemoveEventListener)
.toHaveBeenNthCalledWith(2, 'close', eventListeners.get('close'))
})
+9 -6
View File
@@ -244,10 +244,14 @@ export function logout () {
dispatch(startLogout())
let isLoggedOut = false
const logoutHandler = msg => {
const logoutHandler = () => {
if (isLoggedOut) return
isLoggedOut = true
setTimeout(() => {
circuit.removeEventListener('packetReceived', console.log)
circuit.removeEventListener('LogoutReply', logoutHandler)
}, 0)
dispatch(afterAvatarSessionEnds())
dispatch(didLogout())
@@ -255,8 +259,8 @@ export function logout () {
resolve()
}
circuit.on('packetReceived', console.log)
circuit.once('LogoutReply', logoutHandler)
circuit.addEventListener('packetReceived', console.log)
circuit.addEventListener('LogoutReply', logoutHandler, { once: true })
setTimeout(logoutHandler, ms.seconds(30)) // timeout for LogoutReply
})
}
@@ -281,7 +285,7 @@ function connectToSim (sessionInfo, circuit) {
dispatch(connectCircuit()) // Connect message parsing with circuit.
activeCircuit.on('KickUser', msg => dispatch(getKicked(msg)))
activeCircuit.addEventListener('KickUser', msg => dispatch(getKicked(msg.detail)))
await activeCircuit.send('UseCircuitCode', {
CircuitCode: [
@@ -369,7 +373,6 @@ function getKicked (msg) {
function afterAvatarSessionEnds () {
return (dispatch, getState, extra) => {
extra.circuit.close()
extra.circuit.removeAllListeners()
extra.circuit = null
for (const cb of extra.onAvatarLogout || []) {
+10 -12
View File
@@ -5,8 +5,13 @@ import { changeRights } from '../bundles/friends'
import { selectAgentId, selectSessionId } from '../bundles/session'
import { doHandleFriendOnlineStateChange } from './friendsActions'
// Gets all messages from the SIM and filters them, and if needed: calls their own actions.
function simActionFilter (msg) {
/**
* Gets all messages from the SIM and filters them, and if needed: calls their own actions.
* @param {CustomEvent} event Event from circuit with the Package data in `detail`.
*/
export default function simActionFilter (event) {
const msg = event.detail
switch (msg.name) {
case 'ChatFromSimulator':
return receiveChatFromSimulator(msg)
@@ -37,7 +42,9 @@ function simActionFilter (msg) {
if (process.env.NODE_ENV !== 'production' && window.debugDispatchAllMsg) {
return msg
}
break
// Don't dispatch an action.
// This is an empty thunk action. It will be called and does then nothing.
return () => {}
}
}
@@ -81,12 +88,3 @@ function sendRegionHandshakeReply (RegionHandshake) {
})
}
}
export default function createCallback (dispatch) {
return msg => {
const action = simActionFilter(msg)
if (action != null) { // If the packet is parsed, an action will be dispatched.
dispatch(action)
}
}
}
+2 -2
View File
@@ -407,7 +407,7 @@ describe('grids', () => {
isLLSDLogin: false
})
await new Promise(resolve => setTimeout(resolve, 5))
await new Promise(resolve => setTimeout(resolve, 10))
expect(getDiff('A')).toEqual({
account: {
@@ -455,7 +455,7 @@ describe('grids', () => {
isLLSDLogin: false
})
await new Promise(resolve => setTimeout(resolve, 5))
await new Promise(resolve => setTimeout(resolve, 10))
expect(getDiff('A')).toEqual({
account: {
+32 -19
View File
@@ -6,13 +6,12 @@
* and the event 'packetReceived'
*/
import events from 'events'
import Queue from 'double-ended-queue'
import { parseBody, createBody } from './networkMessages'
import { getValueOf, mapBlockOf } from './msgGetters'
export default class Circuit extends events.EventEmitter {
export default class Circuit extends window.EventTarget {
/**
* sequenceNumber is the id of a packet.
* It will be increased for every packed.
@@ -117,25 +116,34 @@ export default class Circuit extends events.EventEmitter {
if (event.code === 1000) {
// Normal session end
this.dispatchEvent(new window.CustomEvent('close', {
detail: {
code: event.code,
reason: event.reason
}
}))
clearInterval(this.acksProcessInterval)
return
}
if (event.code === 1008) {
this.emit('close', {
code: event.code,
reason: event.reason
})
this.removeAllListeners()
this.dispatchEvent(new window.CustomEvent('close', {
detail: {
code: event.code,
reason: event.reason
}
}))
clearInterval(this.acksProcessInterval)
return
}
if (this.reconnectCount > 10) {
this.emit('close', {
code: 1006,
reason: 'Max reconnection tries'
})
this.removeAllListeners()
this.dispatchEvent(new window.CustomEvent('close', {
detail: {
code: 1006,
reason: 'Max reconnection tries'
}
}))
clearInterval(this.acksProcessInterval)
return
}
@@ -149,7 +157,6 @@ export default class Circuit extends events.EventEmitter {
close () {
this.websocket.close(1000, 'session end')
this.removeAllListeners()
clearInterval(this.acksProcessInterval)
}
@@ -217,8 +224,12 @@ export default class Circuit extends events.EventEmitter {
this._resolveAcks(mapBlockOf(parsedBody, 'Packets', getValue => getValue('ID')))
} else {
// For every message that is not a circuit control message
this.emit(parsedBody.name, parsedBody)
this.emit('packetReceived', parsedBody)
this.dispatchEvent(new window.CustomEvent(parsedBody.name, {
detail: parsedBody
}))
this.dispatchEvent(new window.CustomEvent('packetReceived', {
detail: parsedBody
}))
}
}
@@ -339,10 +350,12 @@ export default class Circuit extends events.EventEmitter {
this.lastReceivedCount += 1
if (this.lastReceivedCount > 1050) {
this.emit('close', {
code: 1006,
reason: 'UDP disconnect'
})
this.dispatchEvent(new window.CustomEvent('close', {
detail: {
code: 1006,
reason: 'UDP disconnect'
}
}))
this.close()
return
}
+73 -72
View File
@@ -107,15 +107,9 @@ test('it should create an instance', () => {
test('circuit closes', () => {
circuit = new Circuit('127.0.0.1', 8080, 123456, 'session id')
const removeAllListeners = circuit.removeAllListeners
circuit.removeAllListeners = jest.fn(() => {
removeAllListeners.call(circuit)
})
circuit.close()
expect(clearInterval).toBeCalled()
expect(circuit.removeAllListeners).toBeCalled()
expect(circuit.websocket.close).toBeCalled()
expect(circuit.websocket.close).lastCalledWith(1000, 'session end')
})
@@ -184,58 +178,26 @@ test('parse a received package', () => {
const messageBuffer = createTestMessage(false, false, false)
const handler = jest.fn()
circuit.on('packetReceived', handler)
circuit.addEventListener('packetReceived', handler)
circuit.websocket.onmessage({ data: messageBuffer })
expect(handler).toBeCalledWith({
frequency: 'Low',
from: {
ip: '127.0.0.1',
port: 33
},
isOld: undefined,
isReliable: false,
isResend: false,
name: 'TestMessage',
type: 'udp/TestMessage',
number: 1,
size: 52,
trusted: false,
NeighborBlock: [
{
Test0: 0,
Test1: 1,
Test2: 2
expect(handler).toBeCalledWith(new window.CustomEvent('packetReceived', {
detail: {
frequency: 'Low',
from: {
ip: '127.0.0.1',
port: 33
},
{
Test0: 3,
Test1: 4,
Test2: 5
},
{
Test0: 6,
Test1: 7,
Test2: 8
},
{
Test0: 9,
Test1: 10,
Test2: 11
}
],
TestBlock1: [
{
Test1: 0
}
],
blocks: [
[
{
Test1: 0
}
],
[
isOld: undefined,
isReliable: false,
isResend: false,
name: 'TestMessage',
type: 'udp/TestMessage',
number: 1,
size: 52,
trusted: false,
NeighborBlock: [
{
Test0: 0,
Test1: 1,
@@ -256,11 +218,44 @@ test('parse a received package', () => {
Test1: 10,
Test2: 11
}
],
TestBlock1: [
{
Test1: 0
}
],
blocks: [
[
{
Test1: 0
}
],
[
{
Test0: 0,
Test1: 1,
Test2: 2
},
{
Test0: 3,
Test1: 4,
Test2: 5
},
{
Test0: 6,
Test1: 7,
Test2: 8
},
{
Test0: 9,
Test1: 10,
Test2: 11
}
]
]
]
})
}
}))
expect(circuit.senderSequenceNumber).toBe(0)
circuit.removeAllListeners()
})
test('save sender sequence number of reliable packages as ack', () => {
@@ -766,7 +761,7 @@ describe('disconnection', () => {
setTimeout.mockReset()
const closeEvent = jest.fn()
circuit.on('close', closeEvent)
circuit.addEventListener('close', closeEvent)
circuit.websocket.onclose(new window.CloseEvent('Policy Violation', {
wasClean: true,
@@ -778,10 +773,12 @@ describe('disconnection', () => {
expect(window.WebSocket).toBeCalledTimes(1)
expect(closeEvent).toBeCalled()
expect(closeEvent).toHaveBeenLastCalledWith({
code: 1008,
reason: 'wrong session id'
})
expect(closeEvent).toHaveBeenLastCalledWith(new window.CustomEvent('close', {
detail: {
code: 1008,
reason: 'wrong session id'
}
}))
})
test('it should exponentially increase the reconnect timeout', () => {
@@ -817,7 +814,7 @@ describe('disconnection', () => {
openSocket()
const closeEvent = jest.fn()
circuit.on('close', closeEvent)
circuit.addEventListener('close', closeEvent)
expect(window.WebSocket).toBeCalledTimes(1)
jest.clearAllTimers()
@@ -836,10 +833,12 @@ describe('disconnection', () => {
expect(reconnectCounts).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11])
expect(window.WebSocket).toHaveBeenCalledTimes(12)
expect(closeEvent).toHaveBeenCalledWith({
code: 1006,
reason: 'Max reconnection tries'
})
expect(closeEvent).toHaveBeenCalledWith(new window.CustomEvent('close', {
detail: {
code: 1006,
reason: 'Max reconnection tries'
}
}))
})
// This is for developing and if there will be a direct UDP connection in the future
@@ -850,13 +849,15 @@ describe('disconnection', () => {
jest.runOnlyPendingTimers()
const closeHandler = jest.fn()
circuit.on('close', closeHandler)
circuit.addEventListener('close', closeHandler)
jest.runTimersToTime(ms.minutes(1) + ms.seconds(45) + 150)
expect(closeHandler).toHaveBeenCalledWith({
code: 1006,
reason: 'UDP disconnect'
})
expect(closeHandler).toHaveBeenCalledWith(new window.CustomEvent('close', {
detail: {
code: 1006,
reason: 'UDP disconnect'
}
}))
})
})
+1 -3
View File
@@ -1,5 +1,3 @@
import { EventEmitter } from 'events'
import { diff as getObjDiff } from 'deep-object-diff'
import PouchDB from 'pouchdb-browser'
import memoryAdapter from 'pouchdb-adapter-memory'
@@ -480,6 +478,6 @@ async function setStateToConnectedToGrid (
store.dispatch(connectCircuit())
}
export class CircuitMock extends EventEmitter {
export class CircuitMock extends window.EventTarget {
send = jest.fn(() => Promise.resolve())
}