diff --git a/.gitignore b/.gitignore index c2658d7d1b31848c3b71960543cb0368e56cd4c7..b9470778764f72c5257a3361590d2994547f90e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules/ +dist/ diff --git a/camera-server/models/validation-error.js b/camera-server/models/validation-error.js deleted file mode 100644 index 1a1d68177b800b00d29026e22d4981df2df2d7e3..0000000000000000000000000000000000000000 --- a/camera-server/models/validation-error.js +++ /dev/null @@ -1,7 +0,0 @@ -class ValidationError extends Error { - constructor(message) { - super(message); - } -} - -module.exports = ValidationError; diff --git a/camera-server/package-lock.json b/camera-server/package-lock.json index 205ee3b0a703b161a4c2011708aade170adb9a2b..252583e7d00971d3407530ada61fb6b7ca54f817 100644 --- a/camera-server/package-lock.json +++ b/camera-server/package-lock.json @@ -19,11 +19,40 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.9.tgz", "integrity": "sha512-zurD1ibz21BRlAOIKP8yhrxlqKx6L9VCwkB5kMiP6nZAhoF5MvC7qS1qPA7nRcr1GJolfkQC7/EAL4hdYejLtg==" }, + "@types/engine.io": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/engine.io/-/engine.io-3.1.4.tgz", + "integrity": "sha512-98rXVukLD6/ozrQ2O80NAlWDGA4INg+tqsEReWJldqyi2fulC9V7Use/n28SWgROXKm6003ycWV4gZHoF8GA6w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "14.14.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.12.tgz", "integrity": "sha512-ASH8OPHMNlkdjrEdmoILmzFfsJICvhBsFfAum4aKZ/9U4B6M6tTmTPh+f3ttWdD74CEGV5XvXWkbyfSdXaTd7g==" }, + "@types/socket.io": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.12.tgz", + "integrity": "sha512-oStc5VFkpb0AsjOxQUj9ztX5Iziatyla/rjZTYbFGoVrrKwd+JU2mtxk7iSl5RGYx9WunLo6UXW1fBzQok/ZyA==", + "dev": true, + "requires": { + "@types/engine.io": "*", + "@types/node": "*", + "@types/socket.io-parser": "*" + } + }, + "@types/socket.io-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/socket.io-parser/-/socket.io-parser-2.2.1.tgz", + "integrity": "sha512-+JNb+7N7tSINyXPxAJb62+NcpC1x/fPn7z818W4xeNCdPTp6VsO/X8fCsg6+ug4a56m1v9sEiTIIUKVupcHOFQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", diff --git a/camera-server/package.json b/camera-server/package.json index 698779ddd43e9742486126ef07bdfbc3eeabd10e..04faac0e99822e2e6d9afbf4dbf2334e9b65378b 100644 --- a/camera-server/package.json +++ b/camera-server/package.json @@ -7,5 +7,8 @@ }, "dependencies": { "socket.io": "^3.0.4" + }, + "devDependencies": { + "@types/socket.io": "^2.1.12" } } diff --git a/camera-server/server.js b/camera-server/server.js deleted file mode 100644 index 08bd485ccf63c549461ed9b585be41327edaab58..0000000000000000000000000000000000000000 --- a/camera-server/server.js +++ /dev/null @@ -1,342 +0,0 @@ -const ValidationError = require('./models/validation-error'); - -const readline = require('readline'); -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false -}); - -let port = 5000; -if (process.env.PORT) { - port = +process.env.PORT; - console.log('Using port ' + port + ' from PORT environment variable'); -} else { - console.log('Got no PORT environment variable - using default port ' + port); -} - -let cameraSlots = 4; -if (process.env.CAMERA_SLOTS) { - cameraSlots = +process.env.CAMERA_SLOTS; - console.log('Using camera count ' + cameraSlots + ' from CAMERA_SLOTS environment variable'); -} else { - console.log('Got no CAMERA_SLOTS environment variable - using default count of ' + cameraSlots); -} - -const io = require('socket.io')(port); - -const visibilityCommands = ['hide', 'show']; -const geometryCommands = [ - 'set_geometry_relative_to_window', - 'set_geometry_relative_to_canvas' -]; -const internalCommands = [ - 'activate_slot', - 'deactivate_slot', - 'refresh_token' -]; - -let cameraStates = []; -for (let i = 0; i < cameraSlots; i++) { - cameraStates.push({ - slotActive: false, - token: null, - feedActive: false, - feedId: null, - senderSocketId: null, - visibility: { - command: 'show', - params: [] - }, - geometry: { - command: 'set_geometry_relative_to_canvas', - params: ['rb', '0', '0', '200', '200'] - } - }); -} - -const emitNewFeed = (slot) => { - const cameraState = cameraStates[slot]; - io.emit('new_feed', { - slot, - feedId: cameraState.feedId, - visibility: cameraState.visibility, - geometry: cameraState.geometry - }); -}; - -const emitRemoveFeed = (slot) => { - io.emit('remove_feed', { slot }); -}; - -const handleSetFeedId = (socket, data, fn) => { - let success = true; - let message = ''; - - try { - const slot = socket.cameraSlot; - const currentCameraState = cameraStates[slot]; - - if (currentCameraState.token !== socket.cameraSlotToken) { - console.log('Error: Got set_feed_id event for slot ' + slot + ' with an old token'); - throw new ValidationError('The provided token is not valid anymore - the feed is not transmitted'); - } - - if (currentCameraState.feedActive) { - console.log('Error: Got set_feed_id event for slot ' + slot + ' which already has an active feed'); - throw new ValidationError('There is already somebody using this slot'); - } - - if (data == null) { - console.log('Error: Got set_feed_id event for slot ' + slot + ' without data'); - throw new ValidationError('Could not get feed id because no data was provided'); - } - - const feedId = data.feedId; - if (feedId == null) { - console.log('Error: Got set_feed_id event without a feed id on slot ' + slot); - throw new ValidationError('No feed id was provided'); - } - - console.log('Setting feed id of slot ' + slot + ' to ' + feedId); - message = 'Successfully set feed id - you are now using this slot'; - - currentCameraState.feedActive = true; - currentCameraState.feedId = feedId; - currentCameraState.senderSocketId = socket.id; - - emitNewFeed(slot); - } catch (e) { - if (e instanceof ValidationError) { - success = false; - message = e.message; - } else { - throw e; - } - } - - fn({ success, message }); -}; - -const handleSenderDisconnect = (socket, reason) => { - const slot = socket.cameraSlot; - if (slot != null) { - const currentCameraState = cameraStates[slot]; - if (currentCameraState.feedActive && socket.id === currentCameraState.senderSocketId) { - console.log('Sender on slot ' + slot + ' disconnected - Clearing slot'); - currentCameraState.feedActive = false; - currentCameraState.feedId = null; - currentCameraState.senderSocketId = null; - - emitRemoveFeed(slot); - } - } -}; - -const registerSenderHandlers = (socket) => { - socket.on('set_feed_id', handleSetFeedId.bind(null, socket)); - socket.on('disconnect', handleSenderDisconnect.bind(null, socket)); -}; - -const handleSenderInit = (socket, data, fn) => { - let success = true; - let message = ''; - try { - const slotStr = data.slot; - if (isNaN(slotStr)) { - console.log('Error: Got socket connection with slot ' + slotStr + ' that cannot be parsed to a number'); - throw new ValidationError('Slot ' + slotStr + ' cannot be parsed to number'); - } - - const slot = parseInt(slotStr); - if (slot < 0 || slot > cameraStates.length - 1) { - console.log('Error: Got socket connection with slot ' + slot + ' which is not in the list of slots'); - throw new ValidationError('Slot ' + slot + ' is not in the list of slots'); - } - - const currentCameraState = cameraStates[slot]; - if (!currentCameraState.slotActive) { - console.log('Error: Got socket connection for inactive slot ' + slot); - throw new ValidationError('Slot ' + slot + ' is not active'); - } - - const token = data.token; - if (currentCameraState.token !== token) { - console.log('Error: Got socket connecion with wrong token ' + token + ' for slot ' + slot); - throw new ValidationError('Invalid token'); - } - - console.log('Got sender socket connection on slot ' + slot); - - message = 'Socket authenticated'; - socket.cameraSlot = slot; - socket.cameraSlotToken = data.token; - - registerSenderHandlers(socket); - } catch (e) { - if (e instanceof ValidationError) { - success = false; - message = e.message; - } else { - throw e; - } - } - - fn({ success, message }); -}; - -const handleQueryState = (fn) => { - console.log('Got state query from socket'); - let response = {}; - for (let i = 0; i < cameraStates.length; i++) { - const cameraState = cameraStates[i]; - if (cameraState.feedActive) { - response[i] = { - feedId: cameraState.feedId, - visibility: cameraState.visibility, - geometry: cameraState.geometry - }; - } - } - fn(response); -} - -io.on('connection', (socket) => { - socket.on('query_state', handleQueryState); - - socket.on('sender_init', handleSenderInit.bind(null, socket)); -}); - -const handleInternalCommand = (command, slot, params) => { - const currentCameraState = cameraStates[slot]; - switch (command) { - case 'activate_slot': - if (currentCameraState.slotActive) { - console.log('Error: Tried to activate active slot ' + slot); - return; - } - if (params.length === 0) { - console.log('Error while activating slot ' + slot + ' - Got no token parameter'); - return; - } - currentCameraState.token = params[0]; - currentCameraState.slotActive = true; - break; - case 'deactivate_slot': - if (!currentCameraState.slotActive) { - console.log('Error: Tried to deactivate inactive slot ' + slot ); - return; - } - console.log('Deactivating slot ' + slot); - emitRemoveFeed(slot); - - currentCameraState.slotActive = false; - currentCameraState.token = null; - currentCameraState.feedActive = false; - currentCameraState.feedId = null; - currentCameraState.senderSocketId = null; - break; - case 'refresh_token': - if (!currentCameraState.slotActive) { - console.log('Error: Tried to refresh token for inactive slot ' + slot); - return; - } - if (params.length === 0) { - console.log('Error while refreshing token for slot ' + slot + ' - Got no token parameter'); - console.log('Keeping old token'); - return; - } - console.log('Refreshing token for slot ' + slot); - currentCameraState.token = params[0]; - break; - default: - console.log('Error: handleInternalCommand got unknown command ' + command); - break; - } -}; - -const handleCommand = (line) => { - let emitCommand = false; - - console.log('Got command from stdin:', line); - const params = line.split(' '); - const command = params.shift(); - if (params.length === 0) { - console.log('Error: Got no slot to apply the command on'); - return; - } - const slotStr = params.shift(); - if (isNaN(slotStr)) { - console.log('Error: Could not parse slot ' + slotStr + ' to an integer'); - return; - } - const slot = parseInt(slotStr); - console.log('command:', command); - console.log('slot:', slot); - console.log('params:', params); - - if (slot < 0 || slot > cameraStates.length - 1) { - console.log(`Error: Got invalid slot number ${slot}. There are ${cameraStates.length} camera slots.`); - return; - } - - const currentCameraState = cameraStates[slot]; - - if (visibilityCommands.includes(command)) { - currentCameraState.visibility = { - command, - params - }; - emitCommand = true; - } else if (geometryCommands.includes(command)) { - currentCameraState.geometry = { - command, - params - }; - emitCommand = true; - } else if (internalCommands.includes(command)) { - handleInternalCommand(command, slot, params); - } else { - console.log('Command "' + command + '" is not a valid command'); - return; - } - - console.log('new cameraState:', currentCameraState); - - if (currentCameraState.feedActive && emitCommand) { - io.emit('command', { - slot, - command, - params - }); - } -} - -rl.on('line', handleCommand); - -const cleanup = () => { - console.log('cleanup'); - io.emit('remove_all_feeds'); -}; - -const exitHandler = (options, exitCode) => { - if (options.cleanup) cleanup(); - if (exitCode || exitCode === 0) console.log(exitCode); - if (options.exit) process.exit(); -} - -// do something when app is closing -process.on('exit', exitHandler.bind(null, { cleanup:true })); - -// catches ctrl+c event -process.on('SIGINT', exitHandler.bind(null, { exit:true })); - -// catches "kill pid" (for example: nodemon restart) -process.on('SIGUSR1', exitHandler.bind(null, { exit:true })); -process.on('SIGUSR2', exitHandler.bind(null, { exit:true })); - -// catches uncaught exceptions -process.on('uncaughtException', exitHandler.bind(null, { exit:true })); - -// catches termination -process.on('SIGTERM', exitHandler.bind(null, { exit:true })); diff --git a/camera-server/src/models/camera-slot-state.ts b/camera-server/src/models/camera-slot-state.ts new file mode 100644 index 0000000000000000000000000000000000000000..9795178209fa54457e1a91e81e2eb35b41b6628e --- /dev/null +++ b/camera-server/src/models/camera-slot-state.ts @@ -0,0 +1,22 @@ +export interface CommandDescriptor { + command: string; + params: string[]; +} + +type NullableString = string | null; + +export class CameraSlotState { + slotActive = false; + token: NullableString = null; + feedActive = false; + feedId: NullableString = null; + senderSocketId: NullableString = null; + visibility: CommandDescriptor = { + command: 'show', + params: [] + }; + geometry: CommandDescriptor = { + command: 'set_geometry_relative_to_canvas', + params: ['rb', '0', '0', '200', '200'] + }; +} \ No newline at end of file diff --git a/camera-server/src/models/sender-socket.ts b/camera-server/src/models/sender-socket.ts new file mode 100644 index 0000000000000000000000000000000000000000..f468674525456a634e9d746d3e56d44ae997c97a --- /dev/null +++ b/camera-server/src/models/sender-socket.ts @@ -0,0 +1,6 @@ +import { Socket } from 'socket.io'; + +export interface SenderSocket extends Socket { + cameraSlot: number; + cameraSlotToken: string; +}; diff --git a/camera-server/src/models/validation-error.ts b/camera-server/src/models/validation-error.ts new file mode 100644 index 0000000000000000000000000000000000000000..f822453f7d5a7c792429637bf4215b01b8d02a9b --- /dev/null +++ b/camera-server/src/models/validation-error.ts @@ -0,0 +1,5 @@ +export class ValidationError extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/camera-server/src/server.ts b/camera-server/src/server.ts new file mode 100644 index 0000000000000000000000000000000000000000..0dae5876176e96ddcff2e48a6c147389df3ef398 --- /dev/null +++ b/camera-server/src/server.ts @@ -0,0 +1,411 @@ +import { Server as SocketIOServer, Socket } from 'socket.io'; + +import { mountCleanupLogic } from './util/cleanup'; +import { ValidationError } from './models/validation-error'; +import { CameraSlotState, CommandDescriptor } from './models/camera-slot-state'; +import { SenderSocket } from './models/sender-socket'; + +const readline = require('readline'); +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}); + +let port = 5000; +if (process.env.PORT) { + port = +process.env.PORT; + console.log('Using port ' + port + ' from PORT environment variable'); +} else { + console.log( + 'Got no PORT environment variable - using default port ' + port + ); +} + +let cameraSlots = 4; +if (process.env.CAMERA_SLOTS) { + cameraSlots = +process.env.CAMERA_SLOTS; + console.log( + 'Using camera count ' + + cameraSlots + + ' from CAMERA_SLOTS environment variable' + ); +} else { + console.log( + 'Got no CAMERA_SLOTS environment variable - using default count of ' + + cameraSlots + ); +} + +const io = new SocketIOServer(port); + +const visibilityCommands = ['hide', 'show']; +const geometryCommands = [ + 'set_geometry_relative_to_window', + 'set_geometry_relative_to_canvas' +]; +const internalCommands = ['activate_slot', 'deactivate_slot', 'refresh_token']; + +let cameraSlotState: CameraSlotState[] = []; +for (let i = 0; i < cameraSlots; i++) { + cameraSlotState.push(new CameraSlotState()); +} + +const emitNewFeed = (slot: number) => { + const cameraState = cameraSlotState[slot]; + io.emit('new_feed', { + slot, + feedId: cameraState.feedId, + visibility: cameraState.visibility, + geometry: cameraState.geometry + }); +}; + +const emitRemoveFeed = (slot: number) => { + io.emit('remove_feed', { slot }); +}; + +const handleSetFeedId = ( + socket: SenderSocket, + data: null | { feedId?: string }, + fn: Function +) => { + let success = true; + let message = ''; + + try { + const slot = socket.cameraSlot; + const currentCameraState = cameraSlotState[slot]; + + if (currentCameraState.token !== socket.cameraSlotToken) { + console.log( + 'Error: Got set_feed_id event for slot ' + + slot + + ' with an old token' + ); + throw new ValidationError( + 'The provided token is not valid anymore - the feed is not transmitted' + ); + } + + if (currentCameraState.feedActive) { + console.log( + 'Error: Got set_feed_id event for slot ' + + slot + + ' which already has an active feed' + ); + throw new ValidationError( + 'There is already somebody using this slot' + ); + } + + if (data == null) { + console.log( + 'Error: Got set_feed_id event for slot ' + + slot + + ' without data' + ); + throw new ValidationError( + 'Could not get feed id because no data was provided' + ); + } + + const feedId = data.feedId; + if (feedId == null) { + console.log( + 'Error: Got set_feed_id event without a feed id on slot ' + slot + ); + throw new ValidationError('No feed id was provided'); + } + + console.log('Setting feed id of slot ' + slot + ' to ' + feedId); + message = 'Successfully set feed id - you are now using this slot'; + + currentCameraState.feedActive = true; + currentCameraState.feedId = feedId; + currentCameraState.senderSocketId = socket.id; + + emitNewFeed(slot); + } catch (e) { + if (e instanceof ValidationError) { + success = false; + message = e.message; + } else { + throw e; + } + } + + fn({ success, message }); +}; + +const handleSenderDisconnect = (socket: SenderSocket, _: string) => { + const slot = socket.cameraSlot; + if (slot != null) { + const currentCameraState = cameraSlotState[slot]; + if ( + currentCameraState.feedActive && + socket.id === currentCameraState.senderSocketId + ) { + console.log( + 'Sender on slot ' + slot + ' disconnected - Clearing slot' + ); + currentCameraState.feedActive = false; + currentCameraState.feedId = null; + currentCameraState.senderSocketId = null; + + emitRemoveFeed(slot); + } + } +}; + +const registerSenderHandlers = (socket: SenderSocket) => { + socket.on('set_feed_id', handleSetFeedId.bind(null, socket)); + socket.on('disconnect', handleSenderDisconnect.bind(null, socket)); +}; + +const handleSenderInit = ( + socket: SenderSocket, + data: null | { slot?: string; token?: string }, + fn: Function +) => { + let success = true; + let message = ''; + try { + if (data == null) { + console.log('Error: Got socket connection without data'); + throw new ValidationError('No data provided'); + } + + const slotStr = data.slot; + if (slotStr == null) { + console.log('Error: Got socket connection without a slot'); + throw new ValidationError('No slot provided'); + } + + const slot = parseInt(slotStr); + if (isNaN(slot)) { + console.log( + 'Error: Got socket connection with slot ' + + slotStr + + ' that cannot be parsed to a number' + ); + throw new ValidationError( + 'Slot ' + slotStr + ' cannot be parsed to number' + ); + } + if (slot < 0 || slot > cameraSlotState.length - 1) { + console.log( + 'Error: Got socket connection with slot ' + + slot + + ' which is not in the list of slots' + ); + throw new ValidationError( + 'Slot ' + slot + ' is not in the list of slots' + ); + } + + const slotState = cameraSlotState[slot]; + if (!slotState.slotActive) { + console.log( + 'Error: Got socket connection for inactive slot ' + slot + ); + throw new ValidationError('Slot ' + slot + ' is not active'); + } + + const token = data.token; + if (token == null) { + console.log('Error: Got socket connection without token'); + throw new ValidationError('No token provided'); + } + if (slotState.token !== token) { + console.log( + 'Error: Got socket connecion with wrong token ' + + token + + ' for slot ' + + slot + ); + throw new ValidationError('Invalid token'); + } + + console.log('Got sender socket connection on slot ' + slot); + + message = 'Socket authenticated'; + socket.cameraSlot = slot; + socket.cameraSlotToken = token; + + registerSenderHandlers(socket); + } catch (e) { + if (e instanceof ValidationError) { + success = false; + message = e.message; + } else { + throw e; + } + } + + fn({ success, message }); +}; + +const handleQueryState = (fn: Function) => { + console.log('Got state query from socket'); + let response: { + [index: number]: { + feedId: string | null; + visibility: CommandDescriptor; + geometry: CommandDescriptor; + }; + } = {}; + for (let i = 0; i < cameraSlotState.length; i++) { + const cameraState = cameraSlotState[i]; + if (cameraState.feedActive) { + response[i] = { + feedId: cameraState.feedId, + visibility: cameraState.visibility, + geometry: cameraState.geometry + }; + } + } + fn(response); +}; + +io.on('connection', (socket: Socket) => { + socket.on('query_state', handleQueryState); + + socket.on('sender_init', handleSenderInit.bind(null, socket)); +}); + +const handleInternalCommand = ( + command: string, + slot: number, + params: string[] +) => { + const currentCameraState = cameraSlotState[slot]; + switch (command) { + case 'activate_slot': + if (currentCameraState.slotActive) { + console.log('Error: Tried to activate active slot ' + slot); + return; + } + if (params.length === 0) { + console.log( + 'Error while activating slot ' + + slot + + ' - Got no token parameter' + ); + return; + } + currentCameraState.token = params[0]; + currentCameraState.slotActive = true; + break; + case 'deactivate_slot': + if (!currentCameraState.slotActive) { + console.log('Error: Tried to deactivate inactive slot ' + slot); + return; + } + console.log('Deactivating slot ' + slot); + emitRemoveFeed(slot); + + currentCameraState.slotActive = false; + currentCameraState.token = null; + currentCameraState.feedActive = false; + currentCameraState.feedId = null; + currentCameraState.senderSocketId = null; + break; + case 'refresh_token': + if (!currentCameraState.slotActive) { + console.log( + 'Error: Tried to refresh token for inactive slot ' + slot + ); + return; + } + if (params.length === 0) { + console.log( + 'Error while refreshing token for slot ' + + slot + + ' - Got no token parameter' + ); + console.log('Keeping old token'); + return; + } + console.log('Refreshing token for slot ' + slot); + currentCameraState.token = params[0]; + break; + default: + console.log( + 'Error: handleInternalCommand got unknown command ' + command + ); + break; + } +}; + +const handleCommand = (line: string) => { + let emitCommand = false; + + console.log('Got command from stdin:', line); + const params = line.split(' '); + + const command = params.shift(); + if (command == null) { + console.log('Error: Got malformed line with no command'); + return; + } + + const slotStr = params.shift(); + if (slotStr == null) { + console.log('Error: Got no slot to apply the command on'); + return; + } + + const slot = parseInt(slotStr); + if (isNaN(slot)) { + console.log( + 'Error: Could not parse slot ' + slotStr + ' to an integer' + ); + return; + } + if (slot < 0 || slot > cameraSlotState.length - 1) { + console.log( + `Error: Got invalid slot number ${slot}. There are ${cameraSlotState.length} camera slots.` + ); + return; + } + + console.log('command:', command); + console.log('slot:', slot); + console.log('params:', params); + + const currentCameraState = cameraSlotState[slot]; + + if (visibilityCommands.includes(command)) { + currentCameraState.visibility = { + command, + params + }; + emitCommand = true; + } else if (geometryCommands.includes(command)) { + currentCameraState.geometry = { + command, + params + }; + emitCommand = true; + } else if (internalCommands.includes(command)) { + handleInternalCommand(command, slot, params); + } else { + console.log('Command "' + command + '" is not a valid command'); + return; + } + + console.log('new cameraState:', currentCameraState); + + if (currentCameraState.feedActive && emitCommand) { + io.emit('command', { + slot, + command, + params + }); + } +}; + +rl.on('line', handleCommand); + +mountCleanupLogic(io); diff --git a/camera-server/src/util/cleanup.ts b/camera-server/src/util/cleanup.ts new file mode 100644 index 0000000000000000000000000000000000000000..7154e5caced184f47cd6a775a69bd85093b6cc26 --- /dev/null +++ b/camera-server/src/util/cleanup.ts @@ -0,0 +1,35 @@ +import { Server as SocketIOServer } from 'socket.io'; + +interface ExitHandlerOptions { + cleanup?: boolean; + exit?: boolean; +} + +export const mountCleanupLogic = (io: SocketIOServer) => { + const cleanup = () => { + console.log('cleanup'); + io.emit('remove_all_feeds'); + }; + + const exitHandler = (options: ExitHandlerOptions, exitCode: number) => { + if (options.cleanup) cleanup(); + if (exitCode || exitCode === 0) console.log(exitCode); + if (options.exit) process.exit(); + } + + // do something when app is closing + process.on('exit', exitHandler.bind(null, { cleanup:true })); + + // catches ctrl+c event + process.on('SIGINT', exitHandler.bind(null, { exit:true })); + + // catches "kill pid" (for example: nodemon restart) + process.on('SIGUSR1', exitHandler.bind(null, { exit:true })); + process.on('SIGUSR2', exitHandler.bind(null, { exit:true })); + + // catches uncaught exceptions + process.on('uncaughtException', exitHandler.bind(null, { exit:true })); + + // catches termination + process.on('SIGTERM', exitHandler.bind(null, { exit:true })); +} \ No newline at end of file diff --git a/camera-server/test-server.js b/camera-server/test-server.js deleted file mode 100644 index 7424f6b0f2d3d981d2fd42642823763d6324fb57..0000000000000000000000000000000000000000 --- a/camera-server/test-server.js +++ /dev/null @@ -1,28 +0,0 @@ -const readline = require('readline'); -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false -}); - -const io = require('socket.io')(5005); - -io.on('connection', (socket) => { - socket.emit('command', { message: 'Hello, world!' }); - - socket.on('query', (param1) => { - console.log('query:', param1); - }); - - rl.on('line', function(line) { - console.log('Got command from stdin:', line); - const params = line.split(' '); - const command = params.shift(); - console.log('command:', command); - console.log('params:', params); - socket.emit('command', { - command, - params - }); - }); -}); diff --git a/camera-server/tsconfig.json b/camera-server/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..bcced0b35ae18d5673410b1bcbdb8e30dbaacf0d --- /dev/null +++ b/camera-server/tsconfig.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "lib": [ + "dom", + "es6", + "es2017", + "esnext.asynciterable" + ], + "sourceMap": true, + "outDir": "./dist", + "moduleResolution": "node", + "removeComments": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + "baseUrl": "." + }, + "exclude": [ + "node_modules" + ], + "include": [ + "./src/**/*.ts" + ] +} \ No newline at end of file