diff --git a/camera-server/src/config/config.ts b/camera-server/src/config/config.ts
index 5f75e8d4070905afcef4a9345844729d0aab2254..84ecd9df53d3936dfb407df5272e6964a17a79e9 100644
--- a/camera-server/src/config/config.ts
+++ b/camera-server/src/config/config.ts
@@ -4,6 +4,11 @@ import * as path from 'path';
 interface Config {
     port: number;
     cameraSlots: number;
+}
+
+// Required to access config with config[key]
+// But only an object of type Config should be returned
+interface IndexableConfig extends Config {
     // Change type once non-number values are added
     [key: string]: number;
 }
@@ -26,7 +31,7 @@ if (configPath) {
     console.log('Got not CONFIG_PATH environment variable');
 }
 
-const config: Config = {
+const indexableConfig: IndexableConfig = {
     port: 5000,
     cameraSlots: 4
 };
@@ -43,11 +48,11 @@ if (fileContent) {
         console.log(`Reading config at ${path.resolve(configPath!)}`);
         // Overwrite default values with values of read config
         Object.keys(readConfig).forEach((key) => {
-            if (config.hasOwnProperty(key)) {
-                const expectedType = typeof config[key];
+            if (indexableConfig.hasOwnProperty(key)) {
+                const expectedType = typeof indexableConfig[key];
                 const readType = typeof readConfig[key];
                 if (expectedType === readType) {
-                    config[key] = readConfig[key];
+                    indexableConfig[key] = readConfig[key];
                 } else {
                     console.log(
                         `Error: Read config propety '${key}' is of type ${readType}, but type ${expectedType} was expected`
@@ -66,6 +71,8 @@ if (fileContent) {
     }
 }
 
+const config = indexableConfig as Config;
+
 console.log('Using config:', config);
 
 export { config };
diff --git a/camera-server/src/io-interface/handlers/input-handlers.ts b/camera-server/src/io-interface/handlers/input-handlers.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5a2a097e508549d038b488af3382e2a46134d6f1
--- /dev/null
+++ b/camera-server/src/io-interface/handlers/input-handlers.ts
@@ -0,0 +1,142 @@
+import { socketIO } from '../../socket-io/socket-io';
+import { cameraSlotState } from '../../state/camera-slot-state';
+import { emitRemoveFeed } from '../../socket-io/handlers/common-handlers';
+
+const visibilityCommands = ['hide', 'show'];
+const geometryCommands = [
+    'set_geometry_relative_to_window',
+    'set_geometry_relative_to_canvas'
+];
+const internalCommands = ['activate_slot', 'deactivate_slot', 'refresh_token'];
+
+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;
+    }
+};
+
+export 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) {
+        socketIO.emit('command', {
+            slot,
+            command,
+            params
+        });
+    }
+};
diff --git a/camera-server/src/io-interface/readline-interface.ts b/camera-server/src/io-interface/readline-interface.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a6c6a03cf00b0f7e8d3771c9fe04ab8bf435adee
--- /dev/null
+++ b/camera-server/src/io-interface/readline-interface.ts
@@ -0,0 +1,7 @@
+import * as readline from 'readline';
+
+export const readlineInterface = readline.createInterface({
+    input: process.stdin,
+    output: process.stdout,
+    terminal: false
+});
diff --git a/camera-server/src/models/command-descriptor.ts b/camera-server/src/models/command-descriptor.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1a65bf10defc6ff5e8ecad0aa70615e39b718a82
--- /dev/null
+++ b/camera-server/src/models/command-descriptor.ts
@@ -0,0 +1,4 @@
+export interface CommandDescriptor {
+    command: string;
+    params: string[];
+}
diff --git a/camera-server/src/server.ts b/camera-server/src/server.ts
index 16d5d1f151668ceceb73cedcb0d017a02a4dbd57..e2ec3a48bbba527993f5537d64d4bb63a107aa2c 100644
--- a/camera-server/src/server.ts
+++ b/camera-server/src/server.ts
@@ -1,387 +1,18 @@
-import { Server as SocketIOServer, Socket } from 'socket.io';
+import { 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';
-import { config } from './config/config';
+import { socketIO } from './socket-io/socket-io';
+import { handleSenderInit } from './socket-io/handlers/sender-handlers';
+import { handleQueryState } from './socket-io/handlers/common-handlers';
+import { readlineInterface } from './io-interface/readline-interface';
+import { handleCommand } from './io-interface/handlers/input-handlers';
+import { registerCleanupLogic } from './util/cleanup';
 
-const readline = require('readline');
-const rl = readline.createInterface({
-    input: process.stdin,
-    output: process.stdout,
-    terminal: false
-});
-
-const io = new SocketIOServer(config.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 < config.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) => {
+socketIO.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);
+readlineInterface.on('line', handleCommand);
 
-mountCleanupLogic(io);
+registerCleanupLogic();
diff --git a/camera-server/src/socket-io/handlers/common-handlers.ts b/camera-server/src/socket-io/handlers/common-handlers.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d3b56691f50c5f2502530aaa2afca3a841b9470b
--- /dev/null
+++ b/camera-server/src/socket-io/handlers/common-handlers.ts
@@ -0,0 +1,39 @@
+import { socketIO } from '../socket-io';
+import { cameraSlotState } from '../../state/camera-slot-state';
+import { CommandDescriptor } from '../../models/command-descriptor';
+
+export const emitNewFeed = (slot: number) => {
+    const cameraState = cameraSlotState[slot];
+    socketIO.emit('new_feed', {
+        slot,
+        feedId: cameraState.feedId,
+        visibility: cameraState.visibility,
+        geometry: cameraState.geometry
+    });
+};
+
+export const emitRemoveFeed = (slot: number) => {
+    socketIO.emit('remove_feed', { slot });
+};
+
+export 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);
+};
diff --git a/camera-server/src/socket-io/handlers/sender-handlers.ts b/camera-server/src/socket-io/handlers/sender-handlers.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ae4b7fb0afdc4fe40ab6b2b0002087d539d82332
--- /dev/null
+++ b/camera-server/src/socket-io/handlers/sender-handlers.ts
@@ -0,0 +1,185 @@
+import { cameraSlotState } from '../../state/camera-slot-state';
+import { emitNewFeed, emitRemoveFeed } from './common-handlers';
+import { SenderSocket } from '../../models/sender-socket';
+import { ValidationError } from '../../models/validation-error';
+
+const handleSetFeedId = (
+    socket: SenderSocket,
+    data: null | { feedId?: string },
+    fn: Function
+) => {
+    let success = true;
+    let message = '';
+
+    try {
+        const slot = socket.cameraSlot;
+        const currentSlotState = cameraSlotState[slot];
+
+        if (currentSlotState.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 (currentSlotState.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';
+
+        currentSlotState.feedActive = true;
+        currentSlotState.feedId = feedId;
+        currentSlotState.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 currentSlotState = cameraSlotState[slot];
+        if (
+            currentSlotState.feedActive &&
+            socket.id === currentSlotState.senderSocketId
+        ) {
+            console.log(
+                'Sender on slot ' + slot + ' disconnected - Clearing slot'
+            );
+            currentSlotState.feedActive = false;
+            currentSlotState.feedId = null;
+            currentSlotState.senderSocketId = null;
+
+            emitRemoveFeed(slot);
+        }
+    }
+};
+
+const registerSenderHandlers = (socket: SenderSocket) => {
+    socket.on('set_feed_id', handleSetFeedId.bind(null, socket));
+    socket.on('disconnect', handleSenderDisconnect.bind(null, socket));
+};
+
+export 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 });
+};
diff --git a/camera-server/src/socket-io/socket-io.ts b/camera-server/src/socket-io/socket-io.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7de123197b9efd6e62b246bb15035d7de0dfe47d
--- /dev/null
+++ b/camera-server/src/socket-io/socket-io.ts
@@ -0,0 +1,5 @@
+import { Server as SocketIOServer } from 'socket.io';
+
+import { config } from '../config/config';
+
+export const socketIO = new SocketIOServer(config.port);
diff --git a/camera-server/src/models/camera-slot-state.ts b/camera-server/src/state/camera-slot-state.ts
similarity index 56%
rename from camera-server/src/models/camera-slot-state.ts
rename to camera-server/src/state/camera-slot-state.ts
index 9795178209fa54457e1a91e81e2eb35b41b6628e..d4501752be17ea1dd36d5c8287a885903ebb4d5b 100644
--- a/camera-server/src/models/camera-slot-state.ts
+++ b/camera-server/src/state/camera-slot-state.ts
@@ -1,11 +1,9 @@
-export interface CommandDescriptor {
-    command: string;
-    params: string[];
-}
+import { config } from '../config/config';
+import { CommandDescriptor } from '../models/command-descriptor';
 
 type NullableString = string | null;
 
-export class CameraSlotState {
+class SingleCameraSlotState {
     slotActive = false;
     token: NullableString = null;
     feedActive = false;
@@ -19,4 +17,12 @@ export class CameraSlotState {
         command: 'set_geometry_relative_to_canvas',
         params: ['rb', '0', '0', '200', '200']
     };
-}
\ No newline at end of file
+}
+
+const cameraSlotState: SingleCameraSlotState[] = [];
+
+for (let i = 0; i < config.cameraSlots; i++) {
+    cameraSlotState.push(new SingleCameraSlotState());
+}
+
+export { cameraSlotState };
diff --git a/camera-server/src/util/cleanup.ts b/camera-server/src/util/cleanup.ts
index 7154e5caced184f47cd6a775a69bd85093b6cc26..5cc867a4fff2a245f63c13f0d2357f968917c951 100644
--- a/camera-server/src/util/cleanup.ts
+++ b/camera-server/src/util/cleanup.ts
@@ -1,35 +1,35 @@
-import { Server as SocketIOServer } from 'socket.io';
+import { socketIO } from '../socket-io/socket-io';
 
 interface ExitHandlerOptions {
     cleanup?: boolean;
     exit?: boolean;
 }
 
-export const mountCleanupLogic = (io: SocketIOServer) => {
-    const cleanup = () => {
-        console.log('cleanup');
-        io.emit('remove_all_feeds');
-    };
+const cleanup = () => {
+    console.log('cleanup');
+    socketIO.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();
-    }
+const exitHandler = (options: ExitHandlerOptions, exitCode: number) => {
+    if (options.cleanup) cleanup();
+    if (exitCode || exitCode === 0) console.log(exitCode);
+    if (options.exit) process.exit();
+};
 
+export const registerCleanupLogic = () => {
     // do something when app is closing
-    process.on('exit', exitHandler.bind(null, { cleanup:true }));
+    process.on('exit', exitHandler.bind(null, { cleanup: true }));
 
     // catches ctrl+c event
-    process.on('SIGINT', exitHandler.bind(null, { exit:true }));
+    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 }));
+    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 }));
+    process.on('uncaughtException', exitHandler.bind(null, { exit: true }));
 
     // catches termination
-    process.on('SIGTERM', exitHandler.bind(null, { exit:true }));
-}
\ No newline at end of file
+    process.on('SIGTERM', exitHandler.bind(null, { exit: true }));
+};