|
|
|
@ -5,78 +5,112 @@ |
|
|
|
|
|
|
|
import { Middleware } from "redux"; |
|
|
|
|
|
|
|
import { |
|
|
|
SocketMessageDataHandler, |
|
|
|
SocketMessageHandler, |
|
|
|
parseMessage, |
|
|
|
} from "./message"; |
|
|
|
import { |
|
|
|
socketOpen, |
|
|
|
socketOpened, |
|
|
|
socketClose, |
|
|
|
socketClosed, |
|
|
|
socketSendMessage, |
|
|
|
socketReceiveMessage, |
|
|
|
} from "./slice"; |
|
|
|
import { RootState } from "../app/store"; |
|
|
|
import { SOCKET_RECEIVE_MESSAGE } from "../../constants/ActionTypes"; |
|
|
|
import { AppDispatch, RootState } from "../app/store"; |
|
|
|
|
|
|
|
// The WebSocket singleton.
|
|
|
|
// TODO: use undefined instead, it is more idiomatic.
|
|
|
|
let socket: WebSocket | null = null; |
|
|
|
|
|
|
|
const onOpen = dispatch => event => { |
|
|
|
const onOpen = (dispatch: AppDispatch) => event => { |
|
|
|
console.log('Websocket open', event.target.url); |
|
|
|
dispatch(socketOpened(event.target.url)); |
|
|
|
}; |
|
|
|
|
|
|
|
const onClose = dispatch => () => { |
|
|
|
const onClose = (dispatch: AppDispatch) => () => { |
|
|
|
dispatch(socketClosed()); |
|
|
|
}; |
|
|
|
|
|
|
|
const onMessage = dispatch => event => { |
|
|
|
console.log(`Websocket received message: ${event.data}`); |
|
|
|
function prepareHandlers(handlers: SocketMessageHandler[]) |
|
|
|
: Map<string, SocketMessageDataHandler[]> { |
|
|
|
const map = new Map(); |
|
|
|
|
|
|
|
for (const handler of handlers) { |
|
|
|
console.log(`Registering handler for message variant ${handler.variant}`); |
|
|
|
|
|
|
|
let list = map.get(handler.variant); |
|
|
|
if (list === undefined) { |
|
|
|
list = []; |
|
|
|
map.set(handler.variant, list); |
|
|
|
} |
|
|
|
|
|
|
|
list.push(handler.handleData); |
|
|
|
} |
|
|
|
|
|
|
|
return map; |
|
|
|
} |
|
|
|
|
|
|
|
// TODO: dispatch different actions based on payload type.
|
|
|
|
const onMessage = |
|
|
|
(dispatch: AppDispatch, handlers: Map<string, SocketMessageDataHandler[]>) => |
|
|
|
(event: MessageEvent) => { |
|
|
|
console.log("Websocket received message", event.data); |
|
|
|
|
|
|
|
const action = { type: SOCKET_RECEIVE_MESSAGE }; |
|
|
|
let message; |
|
|
|
try { |
|
|
|
const { variant, fields: [data] } = JSON.parse(event.data); |
|
|
|
if (typeof variant === "undefined") { |
|
|
|
throw new Error('Missing "variant" field in control response'); |
|
|
|
} |
|
|
|
action.payload = { variant, data }; |
|
|
|
message = parseMessage(event.data); |
|
|
|
} catch (err) { |
|
|
|
action.error = true; |
|
|
|
action.payload = err; |
|
|
|
console.log("Websocket received invalid message:", err); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const list = handlers.get(message.variant); |
|
|
|
if (list === undefined) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
dispatch(action); |
|
|
|
for (handler of list) { |
|
|
|
const action = handler(message); |
|
|
|
if (action !== undefined) { |
|
|
|
dispatch(action); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
|
|
|
|
const middleware: Middleware<{}, RootState> = storeApi => next => action => { |
|
|
|
if (socketOpen.match(action)) { |
|
|
|
if (socket !== null) { |
|
|
|
socket.close(); |
|
|
|
} |
|
|
|
function makeMiddleware(handlers: SocketMessageHandler[]) |
|
|
|
: Middleware<{}, RootState> { |
|
|
|
const handlerMap = prepareHandlers(handlers); |
|
|
|
|
|
|
|
return storeApi => next => action => { |
|
|
|
if (socketOpen.match(action)) { |
|
|
|
if (socket !== null) { |
|
|
|
socket.close(); |
|
|
|
} |
|
|
|
|
|
|
|
// Connect to the remote host.
|
|
|
|
socket = new WebSocket(action.payload); |
|
|
|
// Connect to the remote host.
|
|
|
|
socket = new WebSocket(action.payload); |
|
|
|
|
|
|
|
// Bind websocket handlers.
|
|
|
|
socket.onmessage = onMessage(storeApi.dispatch); |
|
|
|
socket.onclose = onClose(storeApi.dispatch); |
|
|
|
socket.onopen = onOpen(storeApi.dispatch); |
|
|
|
// Bind websocket handlers.
|
|
|
|
socket.onmessage = onMessage(storeApi.dispatch, handlerMap); |
|
|
|
socket.onclose = onClose(storeApi.dispatch); |
|
|
|
socket.onopen = onOpen(storeApi.dispatch); |
|
|
|
|
|
|
|
} else if (socketClose.match(action)) { |
|
|
|
if (socket !== null) { |
|
|
|
socket.close(); |
|
|
|
} |
|
|
|
} else if (socketClose.match(action)) { |
|
|
|
if (socket !== null) { |
|
|
|
socket.close(); |
|
|
|
} |
|
|
|
|
|
|
|
socket = null; |
|
|
|
console.log('Websocket closed.'); |
|
|
|
socket = null; |
|
|
|
console.log('Websocket closed.'); |
|
|
|
|
|
|
|
} else if (socketSendMessage.match(action)) { |
|
|
|
console.log('Websocket sending message', action.payload); |
|
|
|
socket.send(JSON.stringify(action.payload)); |
|
|
|
} |
|
|
|
} else if (socketSendMessage.match(action)) { |
|
|
|
console.log('Websocket sending message', action.payload); |
|
|
|
socket.send(JSON.stringify(action.payload)); |
|
|
|
} |
|
|
|
|
|
|
|
return next(action); |
|
|
|
}; |
|
|
|
return next(action); |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
export default middleware; |
|
|
|
export default makeMiddleware; |