1 if(typeof(socketbug) === 'undefined')
  2 {
  3 	/**
  4 	 * Socketbug - Web Socket Remote Debugging
  5 	 * 
  6 	 * Copyright (c) 2011 Manifest Interactive, LLC
  7 	 *
  8 	 * Licensed under the LGPL v3 licenses.
  9 	 *
 10 	 * @version v0.2.1 ( 7/4/2011 )
 11 	 *
 12 	 * @author <a href="http://www.socketbug.com">Website</a>
 13 	 * @author <a href="http://www.vimeo.com/user7532036/videos">Video Tutorials ( HD )</a>
 14 	 * @author <a href="http://www.twitter.com/socketbug_dev">Twitter</a>
 15 	 * @author <a href="http://github.com/manifestinteractive/socketbug">Source Code</a>
 16 	 * @author <a href="http://socketbug.userecho.com">Support & Feature Requests</a>
 17 	 *
 18 	 * @namespace Socketbug Server 
 19 	 */
 20 	socketbug = function()
 21 	{
 22 		/**
 23 		 * Socketbug Configuration
 24 		 * 
 25 		 * @type Object
 26 		 * 	 
 27 		 * @example <b>_socketbug._config._server_port</b> ( Number ): <b>8080</b>
 28 		 * 
 29 		 * This is your Socketbugs Server Port Number Where Socket.IO is Running
 30 		 * 
 31 		 * @example <b>_socketbug._config._authorized_only._groups</b> ( Boolean ): <b>true</b>
 32 		 * 
 33 		 * Should Group Authentication Be Required? If this is set to true 
 34 		 * then all connections will have their Group ID authenticated on 
 35 		 * whatever server you specify below
 36 		 * 
 37 		 * @example <b>_socketbug._config._authorized_only._applications</b> ( Boolean ): <b>true</b>
 38 		 * 
 39 		 * Should Application Authentication Be Required? If this is set to 
 40 		 * true then all connections will have their Application ID 
 41 		 * authenticated on whatever server you specify below
 42 		 * 
 43 		 * @example <b>_socketbug._config._authorize._host</b> ( String ): <b>www.mywebsite.com</b>
 44 		 * 
 45 		 * Host URL ( NOTE: do not add http:// or a trailing / ) to 
 46 		 * Authentication  Script. This can be any server.  It does not 
 47 		 * have to be on the same  server that Socketbug is installed on.
 48 		 * 
 49 		 * @example <b>_socketbug._config._authorize._port</b> ( Number ): <b>80</b>
 50 		 * 
 51 		 * Port Number for your Authentication Script
 52 		 * 
 53 		 * @example <b>_socketbug._config._authorize._path</b> ( String ): <b>/path/to/my/auth.script</b>
 54 		 * 
 55 		 * Path from root of Authentication Script. Basically, this 
 56 		 * gets appended to _socketbug._config._authorize._host to 
 57 		 * make a complete URL. ( NOTE: This should start with a / )
 58 		 * 
 59 		 * @example <b>_socketbug._config._encryption_salt</b> ( String ): <b>Go0dp4$sW0rd</b>
 60 		 * 
 61 		 * SALT you would like to use for MD5 Hashing.  This is the SAME SALT 
 62 		 * you will use throughout your Socketbug Communications.
 63 		 * 
 64 		 * @example <b>_socketbug._groups</b> ( Object )
 65 		 * 
 66 		 * Object to Store Socketbug Groups.  You will not need to 
 67 		 * mess with this, it's just for storing connection 
 68 		 * info on Socketbug.
 69 		 * 
 70 		 * @example <b>_socketbug._applications</b> ( Object )
 71 		 * 
 72 		 * Object to Store Socketbug Applications.  You will not 
 73 		 * need to mess with this, it's just for storing connection 
 74 		 * info on Socketbug.
 75 		 * 
 76 		 * @example <b>_socketbug._clients</b> ( Object )
 77 		 * 
 78 		 * Object to Store Socketbug Clients.  You will not need 
 79 		 * to mess with this, it's just for storing connection 
 80 		 * info on Socketbug.
 81 		 */
 82 		var _socketbug = 
 83 		{
 84 			'_config': 
 85 			{
 86 				'_server_port': 8080,
 87 				'_authorized_only': 
 88 				{
 89 					'_groups': true,
 90 					'_applications': true
 91 				},
 92 				'_authorize': 
 93 				{
 94 					'_host': 'localhost',
 95 					'_port': 80,
 96 					'_path': '/socketbug/client/example/auth/validate.php'
 97 				},
 98 				'_encryption_salt': 'Ch4ng3^M3'
 99 			},
100 			'_groups': {},
101 			'_applications': {},
102 			'_clients': {}
103 		};
104 
105 		/* ================================================== */
106 		/*   Should Not Need to Change Anything Below Here    */
107 		/* ================================================== */
108 
109 		/** Basic Functionality to Manage GUID's */
110 		var GUID={is_valid:function(value){if(typeof(value)=='undefined'){return false;}if(value.length!==36){return false;}rGx=new RegExp("\\b(?:[A-F0-9]{8})(?:-[A-F0-9]{4}){3}-(?:[A-F0-9]{12})\\b");return (rGx.exec(value)!=null);},create:function(){if(arguments.length==1&&this.is_valid(arguments[0])){value=arguments[0];return value;}var res=[];var hv;var rgx=new RegExp("[2345]");for(var i=0;i<8;i++){hv=(((1+Math.random())*0x10000)|0).toString(16).substring(1);if(rgx.exec(i.toString())!=null){if(i==3){hv="6"+hv.substr(1,3);}res.push("-");}res.push(hv.toUpperCase());}value=res.join('');return value;}};
111 
112 		/** Node.js Requirements */
113 		var http = require('http'),
114 		    crypto = require('crypto');
115 
116 		/** Setup Socket.io to Listen to Server */
117 		var io = require('socket.io').listen(_socketbug._config._server_port);
118 
119 		/** Configure Socket.IO */
120 		io.configure('production', function () {
121 			io.set('polling duration', 20);
122 			io.set('log level', 1);
123 		});
124 		io.configure('development', function () {
125 			io.set('polling duration', 60);
126 			io.set('log level', 3);
127 		});
128 		io.configure(function () {
129 			io.set('polling duration', 60);
130 			io.set('log level', 2);
131 		});
132 
133 		/** Build a Socket to Isolate the Application Layer */
134 		var sb_manager = io
135 			.of('/sb_manager')
136 			.on('connection', function (socket)
137 			{
138 				/** 
139 				* @function disconnect
140 				* 
141 				* Do some House Keeping to Clean Up Stored Data
142 				*/
143 				socket.on('disconnect', function ()
144 				{
145 					/** Manage Groups on Client Disconnect */
146 					if(_socketbug._groups)
147 					{
148 						try
149 						{
150 							/** Determine Group Client belongs to */
151 							var my_group = _socketbug._clients[socket.id].group.id;
152 				
153 							/** Keep track of Clients in the Group */
154 							var group_clients = 0;
155 					
156 							/** Check if Client Group Exists on Socketbug */
157 							if(typeof(_socketbug._applications[my_application]) != 'undefined')
158 							{				
159 								/** Loop through Group Clients and remove Disconnecting Client */
160 								for(var i=0; i<_socketbug._groups[my_group].clients.length; i++)
161 								{
162 									/** This is the Client that's Disconnecting */
163 									if(_socketbug._groups[my_group].clients[i] == socket.id)
164 									{
165 										/** Remove this Client from the Group */
166 										_socketbug._groups[my_group].clients.splice(i, 1);
167 									}
168 									/** There are other Clients in the Group */
169 									else
170 									{
171 										group_clients++;
172 									}
173 								}
174 				
175 								/** Delete the Group if all Connected Clients from Socketbug are Gone */
176 								if(group_clients == 0)
177 								{
178 									delete _socketbug._groups[my_group];
179 								}
180 							}
181 						}
182 						catch(error)
183 						{
184 							console.error(error);
185 						}
186 					}
187 			
188 					/** Manage Applications on Client Disconnect */
189 					if(_socketbug._applications)
190 					{
191 						try
192 						{
193 							/** Determine Application Client belongs to */
194 							var my_application = _socketbug._clients[socket.id].application.id;
195 				
196 							/** Keep track of Clients in the Application */
197 							var application_clients = 0;
198 					
199 							/** Check if Client Application Exists on Socketbug */
200 							if(typeof(_socketbug._applications[my_application]) != 'undefined')
201 							{
202 								/** Loop through Application Clients and remove Disconnecting Client */
203 								for(var i=0; i<_socketbug._applications[my_application].clients.length; i++)
204 								{
205 									/** This is the Client that's Disconnecting */
206 									if(_socketbug._applications[my_application].clients[i] == socket.id)
207 									{
208 										/** Remove this Client from the Application */
209 										_socketbug._applications[my_application].clients.splice(i, 1);
210 									}
211 									/** There are other Clients in the Application */
212 									else
213 									{
214 										application_clients++;
215 									}
216 								}
217 				
218 								/** Delete the Application if all Connected Clients from Socketbug are Gone */
219 								if(application_clients == 0)
220 								{
221 									delete _socketbug._applications[my_application];
222 								}
223 							}
224 						}
225 						catch(error)
226 						{
227 							console.error(error);
228 						}
229 					}
230 			
231 					/** Manage Clients on Disconnect */
232 					if(_socketbug._clients)
233 					{
234 						try
235 						{
236 							delete _socketbug._clients[socket.id];
237 						}
238 						catch(error)
239 						{
240 							console.error(error);
241 						}
242 					}
243 				});
244 		
245 				/** 
246 				* @function connection_manager
247 				* 
248 				* Manage Group Connections
249 				* 
250 				* @param {Object} group Group Data
251 				* @param {Object} application Application Data
252 				* @param {Object} client Client Data
253 				* @param {Function} callback Callback Handler
254 				*/
255 				socket.on('connection_manager', function (group, application, client, callback)
256 				{	
257 					var check_auth = false;
258 			
259 					/* Add Client to Client List */
260 					_socketbug._clients[socket.id] = { 
261 						'client': {
262 							'id': socket.id,
263 							'name': ''
264 						},
265 						'group': group, 
266 						'application': application,
267 						'authenticated': {
268 							'group': null,
269 							'application': null,
270 						}
271 					};
272 			
273 					/** Check if this Group is Already Connected to Socketbug */
274 					if (_socketbug._groups[group.id])
275 					{
276 						sb_manager.socket(socket.id).emit('manager_response', 'Group Already Connected and Approved for Socketbug Use', 'log');
277 					}
278 					else if(_socketbug._config._authorized_only._groups)
279 					{
280 						sb_manager.socket(socket.id).emit('manager_response', 'Socketbug Server Group Authorization Required', 'warn');
281 						check_auth = true;
282 					}
283 			
284 					/** Check if this Application is Already Connected to Socketbug */
285 					if (_socketbug._applications[application.id])
286 					{
287 						sb_manager.socket(socket.id).emit('manager_response', 'Application Already Connected and Approved for Socketbug Use');
288 					}
289 					else if(_socketbug._config._authorized_only._applications)
290 					{
291 						sb_manager.socket(socket.id).emit('manager_response', 'Socketbug Server Application Authorization Required', 'warn');
292 						check_auth = true;
293 					}
294 			
295 					if(check_auth)
296 					{
297 						sb_manager.socket(socket.id).emit('manager_response', 'Checking Authorization...', 'info');
298 				
299 						var path = _socketbug._config._authorize._path + '?cb=';
300 						path += (_socketbug._config._authorized_only._groups) ? '&group_id='+group.id:'';
301 						path += (_socketbug._config._authorized_only._applications) ? '&application_id='+application.id:'';
302 					
303 						/** Setup Options for Socketbug Authorization for Group ID's */
304 						var options = 
305 						{
306 					  		host: _socketbug._config._authorize._host,
307 					  		port: _socketbug._config._authorize._port,
308 					  		path: path
309 						};
310 		
311 						/** Establish Connection and Make request */
312 						http.get(options, function(response)
313 						{
314 					        /** Create Variable to Store Returned JSON */
315 							var data = '';
316 				
317 							/** Build the Response in Pieces Should the JSON be Large */
318 					        response.on('data', function(chunk)
319 							{
320 					            data += chunk;
321 					        });
322 		
323 							/** Start Validation now that JSON is done Loading */
324 					        response.on('end', function()
325 							{
326 								try
327 								{
328 									/** Build Expected Response */
329 									var _expected = (_socketbug._config._authorized_only._groups) ? crypto.createHash('md5').update(_socketbug._config._encryption_salt + 'Valid Group ID: ' + group.id).digest("hex") : '';
330 									_expected += (_socketbug._config._authorized_only._applications) ? crypto.createHash('md5').update(_socketbug._config._encryption_salt + 'Valid Application ID: ' + application.id).digest("hex") : '';
331 					
332 									/** Convert JSON String to Javascript Object */
333 									var json = JSON.parse(data);
334 					
335 									/** Verify that the JSON Response Matched Exactly what was Expected */
336 									if(json.response === _expected)
337 									{
338 										authorize_group(socket, group);
339 										authorize_application(socket, application);
340 									}
341 									/** JSON Resonse Did NOT Match */
342 									else
343 									{
344 										if(_socketbug._config._authorized_only._groups && json.group.valid)
345 										{
346 											authorize_group(socket, group);
347 										}
348 										else
349 										{
350 											/** Add this Group to the Unauthorized Group List */
351 											_socketbug._clients[socket.id].authenticated.group = false;
352 							
353 											/** Communicate Unsuccesful Connection */
354 											sb_manager.socket(socket.id).emit('manager_response', 'Group NOT Approved for Socketbug Use', 'error');
355 										}
356 								
357 										if(_socketbug._config._authorized_only._applications && json.application.valid)
358 										{
359 											authorize_application(socket, application);
360 										}
361 										else
362 										{
363 											/** Add this Group to the Unauthorized Group List */
364 											_socketbug._clients[socket.id].authenticated.application = false;
365 							
366 											/* Communicate Unsuccesful Connection */
367 											sb_manager.socket(socket.id).emit('manager_response', 'Application NOT Approved for Socketbug Use', 'error');
368 										}
369 									}
370 							
371 									/** Send Authentication Failures */
372 									authorization(socket.id);
373 								}
374 								catch(message)
375 								{
376 									/** Communicate Unsuccesful Connection */
377 									//socket.emit('manager_response', message, 'error');
378 								}
379 							});
380 					    });
381 					}
382 					else
383 					{
384 						authorize_group(socket, group);
385 						authorize_application(socket, application);
386 					}
387 			
388 					/** Execute Callback Handler */
389 					if(typeof(callback) == 'function')
390 					{
391 						callback(socket.id);
392 					}
393 			
394 				});
395 			}
396 		);
397 
398 		/** Build a Socket to Isolate the Application Layer */
399 		var sb_application = io
400 			.of('/sb_application')
401 			.on('connection', function (socket)
402 			{
403 				/** 
404 				* @function execute_js
405 				* 
406 				* Capture Console's Remote Javascript Exection Request
407 				* 
408 				* @param {String} javascript Javascript to Execute
409 				* @param {Function} callback Callback Handler
410 				*/
411 				socket.on('execute_js', function (javascript, callback)
412 				{
413 					try
414 					{
415 						/** Fetch Clients Application ID */
416 						var my_application = _socketbug._clients[socket.id].application.id;
417 			
418 						/** Loop through Clients in Matching Application to Send Debug Message */
419 						for(var i=0; i<_socketbug._applications[my_application].clients.length; i++)
420 						{
421 							/** Fetch Client ID for Other Connected Users */
422 							var app_client = _socketbug._applications[my_application].clients[i];
423 					
424 							/** See if Client is Authorized */
425 							if(authorization(app_client))
426 							{
427 								/** Send to Specific Client */
428 								sb_application.socket(app_client).emit('execute_js', javascript);
429 						
430 								/** Execute Callback Handler */
431 								if(typeof(callback) == 'function')
432 								{
433 									callback();
434 								}
435 							}
436 						}
437 					}
438 					catch(error)
439 					{
440 						console.error(error);
441 					}
442 				});
443 		
444 				/** 
445 				* @function view_source
446 				* 
447 				* Capture Applications Remote View Source Response
448 				* 
449 				* @param {Function} callback Callback Handler
450 				*/
451 				socket.on('view_source', function (callback)
452 				{
453 					try
454 					{
455 						/** Fetch Clients Application ID */
456 						var my_application = _socketbug._clients[socket.id].application.id;
457 			
458 						/** Loop through Clients in Matching Application to Send Debug Message */
459 						for(var i=0; i<_socketbug._applications[my_application].clients.length; i++)
460 						{
461 							/** Fetch Client ID for Other Connected Users */
462 							var app_client = _socketbug._applications[my_application].clients[i];
463 							
464 							/** See if Client is Authorized */
465 							if(authorization(app_client) && app_client != socket.id)
466 							{
467 								/** Send to Specific Client */
468 								sb_application.socket(app_client).emit('fetch_source');
469 						
470 								/** Execute Callback Handler */
471 								if(typeof(callback) == 'function')
472 								{
473 									callback();
474 								}
475 							}
476 						}
477 					}
478 					catch(error)
479 					{
480 						console.error(error);
481 					}
482 				});
483 			}
484 		);
485 
486 		/** Build a Socket to Isolate the Application Layer */
487 		var sb_console = io
488 			.of('/sb_console')
489 			.on('connection', function (socket)
490 			{
491 				/** 
492 				* @function debug
493 				* 
494 				* Capture Client Message Event
495 				* 
496 				* @param {String} level Debug Level
497 				* @param {Object} data Data to Debug
498 				* @param {Function} callback Callback Handler
499 				*/
500 				socket.on('debug', function (level, data, callback)
501 				{
502 					try
503 					{
504 						/** Fetch Clients Application ID */
505 						var my_application = _socketbug._clients[socket.id].application.id;
506 			
507 						/** Loop through Clients in Matching Application to Send Debug Message */
508 						for(var i=0; i<_socketbug._applications[my_application].clients.length; i++)
509 						{
510 							/** Fetch Client ID for Other Connected Users */
511 							var app_client = _socketbug._applications[my_application].clients[i];
512 					
513 							/** See if Client is Authorized */
514 							if(authorization(app_client))
515 							{
516 								/** Send to Specific Client */
517 								sb_console.socket(app_client).emit('application_debug', level, data[0]);
518 						
519 								/** Execute Callback Handler */
520 								if(typeof(callback) == 'function')
521 								{
522 									callback();
523 								}
524 							}
525 						}
526 					}
527 					catch(error)
528 					{
529 						console.error('Error Authenticating Remote Debug Message');
530 						console.error(error);
531 					}
532 				});
533 		
534 				/** 
535 				* @function view_source
536 				* 
537 				* Capture Console's Remote View Source Request 
538 				* 
539 				* @param {String} source_code
540 				*/
541 				socket.on('view_source', function (source_code)
542 				{
543 					try
544 					{
545 						/** Fetch Clients Application ID */
546 						var my_application = _socketbug._clients[socket.id].application.id;
547 						
548 						/** Loop through Clients in Matching Application to Send Debug Message */
549 						for(var i=0; i<_socketbug._applications[my_application].clients.length; i++)
550 						{
551 							/** Fetch Client ID for Other Connected Users */
552 							var app_client = _socketbug._applications[my_application].clients[i];
553 					
554 							/** See if Client is Authorized */
555 							if(authorization(app_client) && app_client != socket.id)
556 							{
557 								/** Send to Specific Client */
558 								sb_console.socket(app_client).emit('view_source', source_code);
559 							}
560 						}
561 					}
562 					catch(error)
563 					{
564 						console.error(error);
565 					}
566 				});
567 			}
568 		);
569 	
570 		/**
571 		 * Check Authorization of Session ID
572 		 * 
573 		 * @param {String} session_id This is the Socket.IO Session ID for Socketbug
574 		 * 
575 		 * @return Boolean
576 		 */ 
577 		function authorization(session_id)
578 		{
579 			/** Check if Either Group or Application Authentication is False.  It is NULL by default and will only be FALSE if authentication failed. */
580 			if(_socketbug._clients[session_id].authenticated.application !== false && _socketbug._clients[session_id].authenticated.group !== false)
581 			{
582 				return true;
583 			}
584 			/** Authorization Failed */
585 			else
586 			{
587 				sb_manager.socket(session_id).emit('authentication_failed', _socketbug._clients[session_id].authenticated.application, _socketbug._clients[session_id].authenticated.group);
588 		
589 				return false;
590 			}
591 		}
592 	
593 		/**
594 		 * Check Authorization of Group ID
595 		 * 
596 		 * @param {Object} socket This is the Socket used by Socketbug
597 		 * @param {String} group This is the Group ID for Socketbug
598 		 */
599 		function authorize_group(socket, group)
600 		{
601 			/** Set Flag to Indicate if Group is Connected on Socketbug */
602 			var group_found = false;
603 	
604 			/** Loop through Connected Groups */
605 			for(var group_id in _socketbug._groups)
606 			{
607 				if(group_id == group.id)
608 				{
609 					group_found = true;
610 				}
611 		  	}
612 
613 			/** This is a New Group */
614 			if( !group_found)
615 			{
616 				_socketbug._groups[group.id] = { 
617 			
618 					/** Add Group Data */
619 					'data': { 
620 						'id': group.id, 
621 						'name': group.name 
622 					}, 
623 			
624 					/** Add Client to Group */
625 					'clients': [socket.id] 
626 				};
627 			}
628 			/** Add New Client to Existing Group */
629 			else
630 			{	
631 				_socketbug._groups[group.id].clients.push(socket.id);
632 			}
633 	
634 			/** Tell Socketbug that Client can Communicate with Group */
635 			_socketbug._clients[socket.id].authenticated.group = true;
636 	
637 			sb_manager.socket(socket.id).emit('manager_response', 'Group Authorized for Socketbug Use', 'info');
638 		}
639 	
640 		/**
641 		 * Check Authorization of Application ID
642 		 * 
643 		 * @param {Object} socket This is the Socket used by Socketbug
644 		 * @param {String} application This is the Application ID for Socketbug
645 		 */
646 		function authorize_application(socket, application)
647 		{
648 			/** Set Flag to Indicate if Application is Connected on Socketbug */
649 			var application_found = false;
650 	
651 			/** Loop through Connected Applications */
652 			for(var application_id in _socketbug._applications)
653 			{
654 				if(application_id == application.id)
655 				{
656 					application_found = true;
657 				}
658 		  	}
659 
660 			/** This is a New Application */
661 			if( !application_found)
662 			{
663 				_socketbug._applications[application.id] = { 
664 			
665 					/** Add Application Data */
666 					'data': { 
667 						'id': application.id, 
668 						'name': application.name 
669 					}, 
670 			
671 					/** Add Client to Application */
672 					'clients': [socket.id] 
673 				};
674 			}
675 			/** Add New Client to Existing Application */
676 			else
677 			{	
678 				_socketbug._applications[application.id].clients.push(socket.id);
679 			}
680 	
681 			/** Tell Socketbug that Client can Communicate with Application */
682 			_socketbug._clients[socket.id].authenticated.application = true;
683 	
684 			sb_manager.socket(socket.id).emit('manager_response', 'Application Authorized for Socketbug Use', 'info');
685 		}
686 	}();
687 }