diff --git a/src/config/defaults.js b/src/config/defaults.js new file mode 100644 index 00000000..e4c89c9a --- /dev/null +++ b/src/config/defaults.js @@ -0,0 +1,8 @@ +const defaults = { + port: 8088, + base_url: '/', + ping_interval: 10000, + static_path: null, +}; + +export default defaults; diff --git a/src/config.js b/src/config/index.js similarity index 72% rename from src/config.js rename to src/config/index.js index 5ab6cf65..2ad1f29c 100644 --- a/src/config.js +++ b/src/config/index.js @@ -1,4 +1,5 @@ import nconf from 'nconf'; +import defaults from './defaults'; nconf .argv({ @@ -11,10 +12,6 @@ nconf parseValues: true, }); -nconf.defaults({ - port: 8088, - base_url: '/', - ping_interval: 10000, -}); +nconf.defaults(defaults); export default nconf; diff --git a/src/index.js b/src/index.js index 5d92e0f8..849f0a7a 100755 --- a/src/index.js +++ b/src/index.js @@ -1,15 +1,8 @@ #!/usr/bin/env node -import express from 'express'; -import cors from 'cors'; -import http from 'http'; -import urljoin from 'url-join'; - import config from './config'; import socketServer from './socketserver'; -import { getHealth } from './socketserver/state'; - // Using a single function to handle multiple signals const handle = (signal) => { console.log(`Received ${signal}. Exiting`); @@ -19,36 +12,9 @@ const handle = (signal) => { process.on('SIGINT', handle); process.on('SIGTERM', handle); -http.globalAgent.keepAlive = true; - -const app = express(); -const server = http.Server(app); -const router = express.Router(); - -app.use(cors()); - -app.use(config.get('base_url'), router); - -socketServer.attach(server, { - // TODO: Check if options are merged - path: urljoin(config.get('base_url'), '/socket.io'), -}); - -// Setup our router -if (config.get('static_path')) { - console.log('Serving static files at', config.get('static_path')); - router.use(express.static(config.get('static_path'))); -} else { - router.get('/', (req, res) => { - res.send('You\'ve connected to the SLServer, you\'re probably looking for the webapp.'); - }); -} - -router.get('/health', (req, res) => { - res.json(getHealth()); -}); - -server.listen(config.get('port'), () => { - console.log('SyncLounge Server successfully started on port', config.get('port')); - console.log('Running with base URL:', config.get('base_url')); +socketServer({ + baseUrl: config.get('base_url'), + staticPath: config.get('static_path'), + port: config.get('port'), + pingInterval: config.get('ping_interval'), }); diff --git a/src/socketserver/actions.js b/src/socketserver/actions.js new file mode 100644 index 00000000..b6303aa9 --- /dev/null +++ b/src/socketserver/actions.js @@ -0,0 +1,175 @@ +import { + isUserInARoom, getRoomUserData, getUserRoomId, makeUserHost, + getRoomSocketIds, removeUser, isRoomEmpty, removeRoom, getAnySocketIdInRoom, + generateAndSetSocketLatencySecret, formatUserData, getRoomHostId, +} from './state'; + +export const log = ({ socketId, message }) => { + const identifier = isUserInARoom(socketId) + ? `[${socketId}] ${getRoomUserData(socketId).username}` + : `[${socketId}]`; + + console.log(new Date().toISOString(), identifier, ':', message); +}; + +export const emitToSocket = ({ + server, socketId, eventName, data, +}) => { + // console.log(data); + server.to(socketId).emit(eventName, data); +}; + +export const emitToUserRoomExcept = ({ + server, eventName, data, exceptSocketId, +}) => { + getRoomSocketIds(getUserRoomId(exceptSocketId)) + .filter((socketId) => socketId !== exceptSocketId) + .forEach((socketId) => { + emitToSocket({ + server, socketId, eventName, data, + }); + }); +}; + +const emitToRoom = ({ + server, roomId, eventName, data, +}) => { + getRoomSocketIds(roomId).forEach((socketId) => { + emitToSocket({ + server, socketId, eventName, data, + }); + }); +}; + +export const emitToSocketRoom = ({ + server, socketId, eventName, data, +}) => { + emitToRoom({ + server, roomId: getUserRoomId(socketId), eventName, data, + }); +}; + +export const announceNewHost = ({ server, roomId, hostId }) => { + emitToRoom({ + server, + roomId, + eventName: 'newHost', + data: hostId, + }); +}; + +export const removeUserAndUpdateRoom = ({ server, socketId }) => { + const roomId = getUserRoomId(socketId); + + removeUser(socketId); + + if (isRoomEmpty(roomId)) { + removeRoom(roomId); + return; + } + + if (getRoomHostId(roomId) === socketId) { + // Make someone else host + const desiredHostId = getAnySocketIdInRoom(roomId); + makeUserHost(desiredHostId); + + log({ + socketId, + message: `Transferring host to: [${desiredHostId}] ${getRoomUserData(desiredHostId).username}`, + }); + emitToRoom({ + server, + roomId, + eventName: 'userLeft', + data: { + id: socketId, + newHostId: desiredHostId, + }, + }); + + return; + } + + emitToRoom({ + server, + roomId, + eventName: 'userLeft', + data: { + id: socketId, + }, + }); +}; + +export const sendPing = ({ server, socketId }) => { + const secret = generateAndSetSocketLatencySecret(socketId); + + emitToSocket({ + server, + socketId, + eventName: 'slPing', + data: secret, + }); +}; + +// Used to emit both player state updates and media updates. +// Adjusts the time by the latency to the recipient +export const emitAdjustedUserDataToRoom = ({ + server, eventName, exceptSocketId, userData, +}) => { + getRoomSocketIds(getUserRoomId(exceptSocketId)) + .filter((socketId) => socketId !== exceptSocketId) + .forEach((socketId) => { + emitToSocket({ + server, + socketId, + eventName, + data: { + ...formatUserData({ + ...userData, + recipientId: socketId, + }), + id: exceptSocketId, + }, + }); + }); +}; + +export const emitPlayerStateUpdateToRoom = ({ server, socketId }) => { + const { + updatedAt, state, time, duration, playbackRate, + } = getRoomUserData(socketId); + + emitAdjustedUserDataToRoom({ + server, + eventName: 'playerStateUpdate', + exceptSocketId: socketId, + userData: { + updatedAt, + state, + time, + duration, + playbackRate, + }, + }); +}; + +export const emitMediaUpdateToRoom = ({ server, socketId, makeHost }) => { + const { + updatedAt, state, time, duration, playbackRate, media, + } = getRoomUserData(socketId); + + emitAdjustedUserDataToRoom({ + server, + eventName: 'mediaUpdate', + exceptSocketId: socketId, + userData: { + updatedAt, + state, + time, + duration, + playbackRate, + media, + makeHost, + }, + }); +}; diff --git a/src/socketserver/handlers.js b/src/socketserver/handlers.js new file mode 100644 index 00000000..09bae560 --- /dev/null +++ b/src/socketserver/handlers.js @@ -0,0 +1,363 @@ +import { + doesRoomExist, isUserInARoom, getRoomUserData, isUserHost, removeSocketLatencyData, + getJoinData, isRoomPasswordCorrect, createRoom, addUserToRoom, clearSocketLatencyInterval, + getUserRoomId, isUserInRoom, updateUserMedia, makeUserHost, updateUserPlayerState, + getSocketPingSecret, updateSocketLatency, setSocketLatencyIntervalId, doesSocketHaveRtt, + setIsPartyPausingEnabledInSocketRoom, updateUserSyncFlexibility, setIsAutoHostEnabledInSocketRoom, + isPartyPausingEnabledInSocketRoom, isAutoHostEnabledInSocketRoom, initSocketLatencyData, +} from './state'; + +import { + removeUserAndUpdateRoom, emitToSocket, log, emitAdjustedUserDataToRoom, announceNewHost, + emitPlayerStateUpdateToRoom, emitMediaUpdateToRoom, sendPing, emitToSocketRoom, + emitToUserRoomExcept, +} from './actions'; + +const join = ({ + server, socket, data: { + roomId, password, desiredUsername, desiredPartyPausingEnabled, desiredAutoHostEnabled, thumb, + playerProduct, state, time, duration, playbackRate, media, syncFlexibility, + }, +}) => { + // TODO: validate timeline thign + + if (!doesSocketHaveRtt(socket.id)) { + // Ignore join if we don't have rtt yet. + // Client should never do this so this just exists for bad actors + log({ socketId: socket.id, message: 'Socket tried to join without finishing initial ping/pong' }); + socket.disconnect(true); + return; + } + + if (isUserInARoom(socket.id)) { + // TODO: remove listeners? + removeUserAndUpdateRoom({ server, socketId: socket.id }); + } + + const roomExists = doesRoomExist(roomId); + + if (roomExists) { + if (!isRoomPasswordCorrect({ roomId, password })) { + emitToSocket({ + server, + socketId: socket.id, + eventName: 'joinResult', + data: { + success: false, + error: 'Password wrong', + }, + }); + return; + } + } else { + log({ socketId: socket.id, message: `Creating room: ${roomId}` }); + + createRoom({ + id: roomId, + password, + isPartyPausingEnabled: desiredPartyPausingEnabled, + isAutoHostEnabled: desiredAutoHostEnabled, + hostId: socket.id, + }); + } + + addUserToRoom({ + socketId: socket.id, + roomId, + desiredUsername, + thumb, + playerProduct, + }); + + log({ socketId: socket.id, message: `join "${roomId}"` }); + + updateUserPlayerState({ + socketId: socket.id, state, time, duration, playbackRate, + }); + + updateUserSyncFlexibility({ + socketId: socket.id, + syncFlexibility, + }); + + updateUserMedia({ + socketId: socket.id, + media, + }); + + // Broadcast user joined to everyone but this + emitAdjustedUserDataToRoom({ + server, + exceptSocketId: socket.id, + eventName: 'userJoined', + userData: getRoomUserData(socket.id), + }); + + emitToSocket({ + server, + socketId: socket.id, + eventName: 'joinResult', + data: { + success: true, + ...getJoinData({ roomId, socketId: socket.id }), + }, + }); +}; + +const disconnect = ({ server, socket }) => { + log({ socketId: socket.id, message: 'disconnect' }); + + if (isUserInARoom(socket.id)) { + removeUserAndUpdateRoom({ server, socketId: socket.id }); + } + + clearSocketLatencyInterval(socket.id); + removeSocketLatencyData(socket.id); +}; + +const transferHost = ({ server, socket, data: desiredHostId }) => { + if (!isUserInARoom(socket.id) || !isUserHost(socket.id)) { + socket.disconnect(true); + return; + } + + const roomId = getUserRoomId(socket.id); + if (!isUserInRoom({ roomId, socketId: desiredHostId })) { + socket.disconnect(true); + return; + } + + log({ + socketId: socket.id, + message: `Transferring host to: [${desiredHostId}] ${getRoomUserData(desiredHostId).username}`, + }); + makeUserHost(desiredHostId); + announceNewHost({ + server, + roomId, + hostId: desiredHostId, + }); +}; + +const playerStateUpdate = ({ + server, socket, data: { + state, time, duration, playbackRate, + }, +}) => { + if (!isUserInARoom(socket.id)) { + socket.disconnect(true); + return; + } + + updateUserPlayerState({ + socketId: socket.id, state, time, duration, playbackRate, + }); + + emitPlayerStateUpdateToRoom({ server, socketId: socket.id }); +}; + +const mediaUpdate = ({ + server, socket, data: { + state, time, duration, playbackRate, media, userInitiated, + }, +}) => { + if (!isUserInARoom(socket.id)) { + socket.disconnect(true); + return; + } + + updateUserPlayerState({ + socketId: socket.id, state, time, duration, playbackRate, + }); + + updateUserMedia({ + socketId: socket.id, + media, + }); + + const makeHost = userInitiated && !isUserHost(socket.id) + && isAutoHostEnabledInSocketRoom(socket.id); + + if (makeHost) { + // Emit to user that they are host now + makeUserHost(socket.id); + emitToSocket({ + server, + socketId: socket.id, + eventName: 'newHost', + data: socket.id, + }); + + log({ + socketId: socket.id, + message: 'Making host because user initiated media change', + }); + } + + emitMediaUpdateToRoom({ server, socketId: socket.id, makeHost }); +}; + +const slPong = ({ + server, pingInterval, socket, data: secret, +}) => { + const expectedSecret = getSocketPingSecret(socket.id); + if (expectedSecret === null || secret !== expectedSecret) { + log({ + socketId: socket.id, + message: `Incorrect secret. Expected "${expectedSecret}", got "${secret}"`, + }); + + socket.disconnect(true); + return; + } + + updateSocketLatency(socket.id); + + setSocketLatencyIntervalId({ + socketId: socket.id, + intervalId: setTimeout(() => { + sendPing({ server, socketId: socket.id }); + }, pingInterval), + }); +}; + +const sendMessage = ({ server, socket, data: text }) => { + if (!isUserInARoom(socket.id)) { + socket.disconnect(true); + return; + } + + emitToUserRoomExcept({ + server, + eventName: 'newMessage', + data: { + text, + senderId: socket.id, + }, + exceptSocketId: socket.id, + }); +}; + +const setPartyPausingEnabled = ({ server, socket, data: isPartyPausingEnabled }) => { + if (!isUserInARoom(socket.id) || !isUserHost(socket.id)) { + socket.disconnect(true); + return; + } + + log({ + socketId: socket.id, + message: `set party pausing to: ${isPartyPausingEnabled}`, + }); + + setIsPartyPausingEnabledInSocketRoom({ socketId: socket.id, isPartyPausingEnabled }); + + // Emitting to everyone including sender as an ack that it went through + emitToSocketRoom({ + server, + socketId: socket.id, + eventName: 'setPartyPausingEnabled', + data: isPartyPausingEnabled, + }); +}; + +const setAutoHostEnabled = ({ server, socket, data: isAutoHostEnabled }) => { + if (!isUserInARoom(socket.id) || !isUserHost(socket.id)) { + socket.disconnect(true); + return; + } + + log({ + socketId: socket.id, + message: `set auto host to: ${isAutoHostEnabled}`, + }); + + setIsAutoHostEnabledInSocketRoom({ socketId: socket.id, isAutoHostEnabled }); + + // Emitting to everyone including sender as an ack that it went through + emitToSocketRoom({ + server, + socketId: socket.id, + eventName: 'setAutoHostEnabled', + data: isAutoHostEnabled, + }); +}; + +const partyPause = ({ server, socket, data: isPause }) => { + if (!isUserInARoom(socket.id) || !isPartyPausingEnabledInSocketRoom(socket.id)) { + socket.disconnect(true); + return; + } + + emitToSocketRoom({ + server, + socketId: socket.id, + eventName: 'partyPause', + data: { + senderId: socket.id, + isPause, + }, + }); +}; + +const syncFlexibilityUpdate = ({ server, socket, data: syncFlexibility }) => { + if (!isUserInARoom(socket.id)) { + socket.disconnect(true); + return; + } + + updateUserSyncFlexibility({ + socketId: socket.id, + syncFlexibility, + }); + + emitToUserRoomExcept({ + server, + eventName: 'syncFlexibilityUpdate', + data: { + syncFlexibility, + id: socket.id, + }, + exceptSocketId: socket.id, + }); +}; + +const eventHandlers = { + join, + slPong, + playerStateUpdate, + mediaUpdate, + syncFlexibilityUpdate, + transferHost, + sendMessage, + setPartyPausingEnabled, + setAutoHostEnabled, + partyPause, + disconnect, +}; + +const attachEventHandlers = ({ server, pingInterval }) => { + server.on('connection', (socket) => { + const forwardedHeader = socket.handshake.headers['x-forwarded-for']; + const addressInfo = forwardedHeader + ? `${forwardedHeader} (${socket.conn.remoteAddres})` + : socket.conn.remoteAddres; + + log({ socketId: socket.id, message: `connection: ${addressInfo}` }); + initSocketLatencyData(socket.id); + sendPing({ server, socketId: socket.id }); + + Object.entries(eventHandlers).forEach(([name, handler]) => { + socket.on(name, (data) => { + // TODO: eventually pass in state to everything rather than having it all global + // TODO: move ping interval into state too + handler({ + server, pingInterval, socket, data, + }); + }); + }); + + // Register handlers + }); +}; + +export default attachEventHandlers; diff --git a/src/socketserver/index.js b/src/socketserver/index.js index 5e4b2bc3..c663e1f9 100644 --- a/src/socketserver/index.js +++ b/src/socketserver/index.js @@ -1,497 +1,56 @@ -import io from 'socket.io'; +#!/usr/bin/env node -import config from '../config'; -import { - doesRoomExist, isUserInARoom, getRoomUserData, isUserHost, removeSocketLatencyData, - getJoinData, isRoomPasswordCorrect, createRoom, addUserToRoom, clearSocketLatencyInterval, - getUserRoomId, isUserInRoom, updateUserMedia, makeUserHost, updateUserPlayerState, - getSocketPingSecret, updateSocketLatency, setSocketLatencyIntervalId, doesSocketHaveRtt, - getRoomSocketIds, removeUser, isRoomEmpty, removeRoom, getAnySocketIdInRoom, - generateAndSetSocketLatencySecret, initSocketLatencyData, formatUserData, getRoomHostId, - setIsPartyPausingEnabledInSocketRoom, updateUserSyncFlexibility, setIsAutoHostEnabledInSocketRoom, - isPartyPausingEnabledInSocketRoom, isAutoHostEnabledInSocketRoom, -} from './state'; +import express from 'express'; +import cors from 'cors'; +import http from 'http'; +import urljoin from 'url-join'; -const server = io({ - serveClient: false, - cookie: false, - // Use websockets first - transports: ['websockets', 'polling'], -}); +import io from 'socket.io'; +import attachEventHandlers from './handlers'; -const log = ({ socketId, message }) => { - const identifier = isUserInARoom(socketId) - ? `[${socketId}] ${getRoomUserData(socketId).username}` - : `[${socketId}]`; +import { getHealth } from './state'; - console.log(new Date().toISOString(), identifier, ':', message); -}; - -const emitToSocket = ({ socketId, eventName, data }) => { - // console.log(data); - server.to(socketId).emit(eventName, data); -}; - -const emitToUserRoomExcept = ({ - eventName, data, exceptSocketId, +const socketServer = ({ + baseUrl, staticPath, port, pingInterval, }) => { - getRoomSocketIds(getUserRoomId(exceptSocketId)) - .filter((socketId) => socketId !== exceptSocketId) - .forEach((socketId) => { - emitToSocket({ socketId, eventName, data }); - }); -}; - -const emitToRoom = ({ roomId, eventName, data }) => { - getRoomSocketIds(roomId).forEach((socketId) => { - emitToSocket({ socketId, eventName, data }); - }); -}; - -const emitToSocketRoom = ({ socketId, eventName, data }) => { - emitToRoom({ roomId: getUserRoomId(socketId), eventName, data }); -}; - -const announceNewHost = ({ roomId, hostId }) => { - emitToRoom({ - roomId, - eventName: 'newHost', - data: hostId, - }); -}; - -const removeUserAndUpdateRoom = (socketId) => { - const roomId = getUserRoomId(socketId); + http.globalAgent.keepAlive = true; - removeUser(socketId); + const app = express(); + const server = http.Server(app); + const router = express.Router(); - if (isRoomEmpty(roomId)) { - removeRoom(roomId); - return; - } + app.use(cors()); - if (getRoomHostId(roomId) === socketId) { - // Make someone else host - const desiredHostId = getAnySocketIdInRoom(roomId); - makeUserHost(desiredHostId); + app.use(baseUrl, router); - log({ - socketId, - message: `Transferring host to: [${desiredHostId}] ${getRoomUserData(desiredHostId).username}`, - }); - emitToRoom({ - roomId, - eventName: 'userLeft', - data: { - id: socketId, - newHostId: desiredHostId, - }, - }); - - return; - } - - emitToRoom({ - roomId, - eventName: 'userLeft', - data: { - id: socketId, - }, + const socketio = io(server, { + path: urljoin(baseUrl, '/socket.io'), + serveClient: false, + cookie: false, + // Use websockets first + transports: ['websockets', 'polling'], }); -}; - -const sendPing = (socketId) => { - const secret = generateAndSetSocketLatencySecret(socketId); - - emitToSocket({ - socketId, - eventName: 'slPing', - data: secret, - }); -}; - -// Used to emit both player state updates and media updates. -// Adjusts the time by the latency to the recipient -const emitAdjustedUserDataToRoom = ({ eventName, exceptSocketId, userData }) => { - getRoomSocketIds(getUserRoomId(exceptSocketId)) - .filter((socketId) => socketId !== exceptSocketId) - .forEach((socketId) => { - emitToSocket({ - socketId, - eventName, - data: { - ...formatUserData({ - ...userData, - recipientId: socketId, - }), - id: exceptSocketId, - }, - }); - }); -}; - -const join = ({ - socket, data: { - roomId, password, desiredUsername, desiredPartyPausingEnabled, desiredAutoHostEnabled, thumb, - playerProduct, state, time, duration, playbackRate, media, syncFlexibility, - }, -}) => { - // TODO: validate timeline thign - - if (!doesSocketHaveRtt(socket.id)) { - // Ignore join if we don't have rtt yet. - // Client should never do this so this just exists for bad actors - log({ socketId: socket.id, message: 'Socket tried to join without finishing initial ping/pong' }); - socket.disconnect(true); - return; - } - - if (isUserInARoom(socket.id)) { - // TODO: remove listeners? - removeUserAndUpdateRoom(socket.id); - } - const roomExists = doesRoomExist(roomId); + attachEventHandlers({ server: socketio, pingInterval }); - if (roomExists) { - if (!isRoomPasswordCorrect({ roomId, password })) { - emitToSocket({ - socketId: socket.id, - eventName: 'joinResult', - data: { - success: false, - error: 'Password wrong', - }, - }); - return; - } + // Setup our router + if (staticPath) { + console.log('Serving static files at', staticPath); + router.use(express.static(staticPath)); } else { - log({ socketId: socket.id, message: `Creating room: ${roomId}` }); - - createRoom({ - id: roomId, - password, - isPartyPausingEnabled: desiredPartyPausingEnabled, - isAutoHostEnabled: desiredAutoHostEnabled, - hostId: socket.id, - }); - } - - addUserToRoom({ - socketId: socket.id, - roomId, - desiredUsername, - thumb, - playerProduct, - }); - - log({ socketId: socket.id, message: `join "${roomId}"` }); - - updateUserPlayerState({ - socketId: socket.id, state, time, duration, playbackRate, - }); - - updateUserSyncFlexibility({ - socketId: socket.id, - syncFlexibility, - }); - - updateUserMedia({ - socketId: socket.id, - media, - }); - - // Broadcast user joined to everyone but this - emitAdjustedUserDataToRoom({ - exceptSocketId: socket.id, - eventName: 'userJoined', - userData: getRoomUserData(socket.id), - }); - - emitToSocket({ - socketId: socket.id, - eventName: 'joinResult', - data: { - success: true, - ...getJoinData({ roomId, socketId: socket.id }), - }, - }); -}; - -const disconnect = ({ socket }) => { - log({ socketId: socket.id, message: 'disconnect' }); - - if (isUserInARoom(socket.id)) { - removeUserAndUpdateRoom(socket.id); - } - - clearSocketLatencyInterval(socket.id); - removeSocketLatencyData(socket.id); -}; - -const transferHost = ({ socket, data: desiredHostId }) => { - if (!isUserInARoom(socket.id) || !isUserHost(socket.id)) { - socket.disconnect(true); - return; - } - - const roomId = getUserRoomId(socket.id); - if (!isUserInRoom({ roomId, socketId: desiredHostId })) { - socket.disconnect(true); - return; - } - - log({ - socketId: socket.id, - message: `Transferring host to: [${desiredHostId}] ${getRoomUserData(desiredHostId).username}`, - }); - makeUserHost(desiredHostId); - announceNewHost({ - roomId, - hostId: desiredHostId, - }); -}; - -const emitPlayerStateUpdateToRoom = (socketId) => { - const { - updatedAt, state, time, duration, playbackRate, - } = getRoomUserData(socketId); - - emitAdjustedUserDataToRoom({ - eventName: 'playerStateUpdate', - exceptSocketId: socketId, - userData: { - updatedAt, - state, - time, - duration, - playbackRate, - }, - }); -}; - -const playerStateUpdate = ({ - socket, data: { - state, time, duration, playbackRate, - }, -}) => { - if (!isUserInARoom(socket.id)) { - socket.disconnect(true); - return; - } - - updateUserPlayerState({ - socketId: socket.id, state, time, duration, playbackRate, - }); - - emitPlayerStateUpdateToRoom(socket.id); -}; - -const emitMediaUpdateToRoom = ({ socketId, makeHost }) => { - const { - updatedAt, state, time, duration, playbackRate, media, - } = getRoomUserData(socketId); - - emitAdjustedUserDataToRoom({ - eventName: 'mediaUpdate', - exceptSocketId: socketId, - userData: { - updatedAt, - state, - time, - duration, - playbackRate, - media, - makeHost, - }, - }); -}; - -const mediaUpdate = ({ - socket, data: { - state, time, duration, playbackRate, media, userInitiated, - }, -}) => { - if (!isUserInARoom(socket.id)) { - socket.disconnect(true); - return; - } - - updateUserPlayerState({ - socketId: socket.id, state, time, duration, playbackRate, - }); - - updateUserMedia({ - socketId: socket.id, - media, - }); - - const makeHost = userInitiated && !isUserHost(socket.id) - && isAutoHostEnabledInSocketRoom(socket.id); - - if (makeHost) { - // Emit to user that they are host now - makeUserHost(socket.id); - emitToSocket({ - socketId: socket.id, - eventName: 'newHost', - data: socket.id, - }); - - log({ - socketId: socket.id, - message: 'Making host because user initiated media change', + router.get('/', (req, res) => { + res.send('You\'ve connected to the SLServer, you\'re probably looking for the webapp.'); }); } - emitMediaUpdateToRoom({ socketId: socket.id, makeHost }); -}; - -const slPong = ({ socket, data: secret }) => { - const expectedSecret = getSocketPingSecret(socket.id); - if (expectedSecret === null || secret !== expectedSecret) { - log({ - socketId: socket.id, - message: `Incorrect secret. Expected "${expectedSecret}", got "${secret}"`, - }); - - socket.disconnect(true); - return; - } - - updateSocketLatency(socket.id); - - setSocketLatencyIntervalId({ - socketId: socket.id, - intervalId: setTimeout(() => { - sendPing(socket.id); - }, config.get('ping_interval')), - }); -}; - -const sendMessage = ({ socket, data: text }) => { - if (!isUserInARoom(socket.id)) { - socket.disconnect(true); - return; - } - - emitToUserRoomExcept({ - eventName: 'newMessage', - data: { - text, - senderId: socket.id, - }, - exceptSocketId: socket.id, - }); -}; - -const setPartyPausingEnabled = ({ socket, data: isPartyPausingEnabled }) => { - if (!isUserInARoom(socket.id) || !isUserHost(socket.id)) { - socket.disconnect(true); - return; - } - - log({ - socketId: socket.id, - message: `set party pausing to: ${isPartyPausingEnabled}`, - }); - - setIsPartyPausingEnabledInSocketRoom({ socketId: socket.id, isPartyPausingEnabled }); - - // Emitting to everyone including sender as an ack that it went through - emitToSocketRoom({ - socketId: socket.id, - eventName: 'setPartyPausingEnabled', - data: isPartyPausingEnabled, - }); -}; - -const setAutoHostEnabled = ({ socket, data: isAutoHostEnabled }) => { - if (!isUserInARoom(socket.id) || !isUserHost(socket.id)) { - socket.disconnect(true); - return; - } - - log({ - socketId: socket.id, - message: `set auto host to: ${isAutoHostEnabled}`, - }); - - setIsAutoHostEnabledInSocketRoom({ socketId: socket.id, isAutoHostEnabled }); - - // Emitting to everyone including sender as an ack that it went through - emitToSocketRoom({ - socketId: socket.id, - eventName: 'setAutoHostEnabled', - data: isAutoHostEnabled, - }); -}; - -const partyPause = ({ socket, data: isPause }) => { - if (!isUserInARoom(socket.id) || !isPartyPausingEnabledInSocketRoom(socket.id)) { - socket.disconnect(true); - return; - } - - emitToSocketRoom({ - socketId: socket.id, - eventName: 'partyPause', - data: { - senderId: socket.id, - isPause, - }, + router.get('/health', (req, res) => { + res.json(getHealth()); }); -}; -const syncFlexibilityUpdate = ({ socket, data: syncFlexibility }) => { - if (!isUserInARoom(socket.id)) { - socket.disconnect(true); - return; - } - - updateUserSyncFlexibility({ - socketId: socket.id, - syncFlexibility, - }); - - emitToUserRoomExcept({ - eventName: 'syncFlexibilityUpdate', - data: { - syncFlexibility, - id: socket.id, - }, - exceptSocketId: socket.id, + server.listen(port, () => { + console.log('SyncLounge Server successfully started on port', port); + console.log('Running with base URL:', baseUrl); }); }; -server.on('connection', (socket) => { - const forwardedHeader = socket.handshake.headers['x-forwarded-for']; - const addressInfo = forwardedHeader - ? `${forwardedHeader} (${socket.conn.remoteAddres})` - : socket.conn.remoteAddres; - - log({ socketId: socket.id, message: `connection: ${addressInfo}` }); - initSocketLatencyData(socket.id); - sendPing(socket.id); - - const registerEvent = ({ eventName, handler }) => { - socket.on(eventName, (data) => { - handler({ socket, data }); - }); - }; - - // Register handlers - registerEvent({ eventName: 'join', handler: join }); - registerEvent({ eventName: 'slPong', handler: slPong }); - registerEvent({ eventName: 'playerStateUpdate', handler: playerStateUpdate }); - registerEvent({ eventName: 'mediaUpdate', handler: mediaUpdate }); - registerEvent({ eventName: 'syncFlexibilityUpdate', handler: syncFlexibilityUpdate }); - registerEvent({ eventName: 'transferHost', handler: transferHost }); - registerEvent({ eventName: 'sendMessage', handler: sendMessage }); - registerEvent({ eventName: 'setPartyPausingEnabled', handler: setPartyPausingEnabled }); - registerEvent({ eventName: 'setAutoHostEnabled', handler: setAutoHostEnabled }); - registerEvent({ eventName: 'partyPause', handler: partyPause }); - registerEvent({ eventName: 'disconnect', handler: disconnect }); -}); - -export default server; +export default socketServer;