diff --git a/Nibriboard/ClientFiles/BoardWindow.js b/Nibriboard/ClientFiles/BoardWindow.js
index fe967f5..19567d6 100644
--- a/Nibriboard/ClientFiles/BoardWindow.js
+++ b/Nibriboard/ClientFiles/BoardWindow.js
@@ -189,8 +189,6 @@ class BoardWindow extends EventEmitter
 		
 		// Handle the HandshakeResponse when it comes in
 		this.rippleLink.on("HandshakeResponse", this.handleHandshakeResponse.bind(this));
-		// Handle other clients' state updates
-		this.rippleLink.on("ClientStates", this.handlePeerUpdates.bind(this));
 		// Handle the plane change confirmations
 		this.rippleLink.on("PlaneChangeOk", this.handlePlaneChangeOk.bind(this));
 	}
@@ -307,34 +305,6 @@ class BoardWindow extends EventEmitter
 		context.restore();
 	}
 	
-	renderOthers(canvas, context)
-	{
-		context.save();
-		
-		for (let [otherClientId, otherClient] of this.otherClients)
-		{
-			// TODO: Filter rendering by working out if this client is actually inside our viewport or not here
-			context.save();
-			context.translate(otherClient.CursorPosition.x, otherClient.CursorPosition.y);
-			
-			context.beginPath();
-			// Horizontal line
-			context.moveTo(-this.otherCursorRadius, 0);
-			context.lineTo(this.otherCursorRadius, 0);
-			// Vertical line
-			context.moveTo(0, -this.otherCursorRadius);
-			context.lineTo(0, this.otherCursorRadius);
-			
-			context.strokeStyle = otherClient.Colour;
-			context.lineWidth = this.otherCursorWidth
-			context.stroke();
-			
-			context.restore();
-		}
-		
-		context.restore();
-	}
-	
 	/**
 	 * Updates the canvas size to match the current viewport size.
 	 */
@@ -410,29 +380,6 @@ class BoardWindow extends EventEmitter
 		this.gridSize = message.GridSize;
 		console.info(`Plane changed to ${this.currentPlaneName} with a grid size of ${this.gridSize} successfully.`);
 	}
-	
-	/**
-	 * Handles peer update messages recieved from the server via the RippleLink.
-	 */
-	handlePeerUpdates(message) {
-		// Update our knowledge about other clients
-		for (let otherClient of message.ClientStates) {
-			// If this client is new, emit an event about it
-			if(!this.otherClients.has(otherClient.Id)) {
-				// Convert the raw object into a class instance
-				let otherClientObj = OtherClient.FromRaw(otherClient);
-				this.otherClients.set(otherClientObj.Id, otherClientObj);
-				
-				this.emit("OtherClientConnect", otherClient);
-			}
-			else { // If not, emit a normal update message about it
-				this.emit("OtherClientUpdate", otherClient);
-			} 
-			
-			// Get the OtherClient instance to pull in the rest of the data
-			this.otherClients.get(otherClient.Id).update(otherClient);
-		}
-	}
 }
 
 export default BoardWindow;
diff --git a/Nibriboard/ClientFiles/ClientManager.js b/Nibriboard/ClientFiles/ClientManager.js
new file mode 100644
index 0000000..9d608bc
--- /dev/null
+++ b/Nibriboard/ClientFiles/ClientManager.js
@@ -0,0 +1,119 @@
+"use strict";
+
+window.EventEmitter = require("event-emitter-es6");
+
+import OtherClient from './OtherClient';
+
+class ClientManager extends EventEmitter
+{
+	constructor(inRippleLink)
+	{
+		this.otherClients = new Map();
+		
+		// Handle other clients' state updates
+		this.rippleLink.on("ClientStates", this.handlePeerUpdates.bind(this));
+		// Handle line start events from other clients
+		this.rippleLink.on("LineStartReflection", this.handleOtherLineStart.bind(this));
+		// Handle line part events from other clients
+		this.rippleLink.on("LinePartReflection", this.handleOtherLinePart.bind(this));
+		// Handle line complete events from other clients
+		this.rippleLink.on("LineCompleteReflection", this.handleOtherLineComplete.bind(this));
+	}
+	
+	render(canvas, context)
+	{
+		context.save();
+		
+		for (let [otherClientId, otherClient] of this.otherClients)
+		{
+			// TODO: Filter rendering by working out if this client is actually inside our viewport or not here
+			context.save();
+			context.translate(otherClient.CursorPosition.x, otherClient.CursorPosition.y);
+			
+			context.beginPath();
+			// Horizontal line
+			context.moveTo(-this.otherCursorRadius, 0);
+			context.lineTo(this.otherCursorRadius, 0);
+			// Vertical line
+			context.moveTo(0, -this.otherCursorRadius);
+			context.lineTo(0, this.otherCursorRadius);
+			
+			context.strokeStyle = otherClient.Colour;
+			context.lineWidth = this.otherCursorWidth
+			context.stroke();
+			
+			context.restore();
+		}
+		
+		context.restore();
+	}
+	
+	/**
+	 * Handles peer update messages recieved from the server via the RippleLink.
+	 */
+	handlePeerUpdates(message) {
+		// Update our knowledge about other clients
+		for (let otherClient of message.ClientStates) {
+			// If this client is new, emit an event about it
+			if(!this.otherClients.has(otherClient.Id)) {
+				// Convert the raw object into a class instance
+				let otherClientObj = OtherClient.FromRaw(otherClient);
+				this.otherClients.set(otherClientObj.Id, otherClientObj);
+				
+				this.emit("OtherClientConnect", otherClient);
+			}
+			else { // If not, emit a normal update message about it
+				this.emit("OtherClientUpdate", otherClient);
+			} 
+			
+			// Get the OtherClient instance to pull in the rest of the data
+			this.otherClients.get(otherClient.Id).update(otherClient);
+		}
+	}
+	
+	/**
+	 * Fetches an OtherClient instance based on its id.
+	 * @param  {number}			targetId	The id of the OtherClient instance to fetch.
+	 * @return {OtherClient}				The OtherClient instance with the
+	 *                              		specified id.
+	 */
+	get(targetId) {
+		if(!this.otherClients.has(targetId))
+			throw new Exception(`Error: The other client with the id ${targetId} couldn't be found.`);
+		
+		return this.otherClients.get(targetId);
+	}
+	
+	/**
+	 * Handles a line start event from another client.
+	 * @param  {LineStartReflectionMessage} message The line start message event to process.
+	 */
+	handleOtherLineStart(message) {
+		let fromClient = this.get(message.OtherClientId);
+		
+		fromClient.addLine({
+			LineId: message.LineId,
+			Colour: message.LineColour,
+			Width: message.LineWidth
+		});
+	}
+	
+	/**
+	 * Handles a line part event from another client.
+	 * @param  {LinePartReflectionMessage} message The line part reflection message to process.
+	 */
+	handleOtherLinePart(message) {
+		let fromClient = this.get(message.OtherClientId);
+		fromClient.appendLine(message.LineId, message.Points);
+	}
+	
+	/**
+	 * Handles a line complete event from another client.
+	 * @param  {LineCompleteReflectionMessage} message The line complete reflection message to process.
+	 */
+	handleOtherLineComplete(message) {
+		let fromClient = this.get(message.OtherClientId);
+		fromClient.finishLine(message.LineId);
+	}
+	
+}
diff --git a/Nibriboard/ClientFiles/OtherClient.js b/Nibriboard/ClientFiles/OtherClient.js
index 587fd1b..d5717fb 100644
--- a/Nibriboard/ClientFiles/OtherClient.js
+++ b/Nibriboard/ClientFiles/OtherClient.js
@@ -19,6 +19,9 @@ class OtherClient
 		this.Viewport = Rectangle.Zero.clone();
 		// The time this other client's information was last updated.
 		this.LastUpdated = new Date();
+		
+		// The lines that this client is currently drawing.
+		this.currentLines = {};
 	}
 	
 	update(data) {
@@ -34,6 +37,19 @@ class OtherClient
 		this.Viewport.width = data.Viewport.Width;
 		this.Viewport.height = data.Viewport.Height;
 	}
+	
+	/**
+	 * Fetches a line that is currently being drawn by this client with the specified id
+	 * @param  {[type]} lineId [description]
+	 * @return {[type]}        [description]
+	 */
+	fetchLine(lineId)
+	{
+		if(!this.currentLines.hasOwnProperty(lineId))
+			throw new Exception(`Error: A client with the id ${lineId} does not appear to be attached to the OtherClient with the id ${this.Id}`);
+		
+		return this.currentLines[lineId];
+	}
 }
 
 /**
