Class: SMS::Backend::HTTP

Inherits:
Base show all
Defined in:
lib/rubysms/backend/http.rb

Overview

Provides a low-tech HTML webUI to inject mock SMS messages into RubySMS, and receive responses. This is usually used during app development, to provide a cross-platform method of simulating a two-way conversation with the SMS backend(s). Note, though, that there is no technical difference between the SMS::Incoming and SMS::Outgoing objects created by this backend, and those created by “real” incoming messages via the GSM backend.

The JSON API used internally by this backend also be used by other HTML applications to communicate with RubySMS, but that is quite obscure, and isn’t very well documented yet. Also, it sporadically changes without warning. May The Force be with you.

Defined Under Namespace

Classes: RackApp

Constant Summary collapse

HTTP_PORT =
1270
MT_URL =
"http://ajax.googleapis.com/ajax/libs/mootools/1.2.1/mootools-yui-compressed.js"
HTML =
<<EOF
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
	<head>
		<title>RubySMS Virtual Device</title>
		<script id="mt" type="text/javascript" src="%mt_url%"></script>
		<style type="text/css">
			
			/* remove m+p from most elements,
			 * without resetting form elements */
			body, h1, #log, #log li, form {
				margin: 0;
				padding: 0;
			}
			
			body {
				line-height: 1;
				font: 8pt sans-serif;
				background: #eef;
				padding: 2em;
			}
			
				body.framed {
					background: transparent;
					padding: 0;
				}
				
				body.framed #wrapper {
					position: absolute;
					width: 100%;
					bottom: 0;
					left: 0;
					
				}
			
			#wrapper div {
				padding: 0.5em;
				background: #33a7d2;
			}
			
				h1 {
					font-size: 100%;
					color: #fff;
				}
				
				#log {
					height: 14em;
					overflow-y: scroll;
					font-family: monospace;
					background: #fff;
					margin: 0.5em 0;
				}
			
					#log li {
						line-height: 1.4;
						list-style: none;
						white-space: pre;
						padding: 0.5em;
					}
					
						#log li.in {
							color: #888;
							border-top: 1px dotted #000;
						}
							
							/* don't show the divider
							 * at the top of the log */
							#log li:first-child.in {
								border-top: 0; }
						
						#log li.out {
							color: #000;
							background: #f8f8f8;
						}
						
						/* messages prior to the latest
						 * are dim, to enhance readability */
						#log li.old {
							border-top: 0;
							color: #ddd;
						}
						
						#log li.error {
							color: #f00;
						}
					
				form { }
					
					form input {
						-moz-box-sizing: border-box;
						width: 100%;
					}
		</style>
	</head>
	<body>
		<div id="wrapper">
			<div>
				<h1>RubySMS Virtual Device</h1>
			
				<ul id="log">
				</ul>
			
				<form id="send" method="post">
					<input type="text" id="msg" name="msg" />
					<!--<input type="submit" value="Send" />-->
				</form>
			</div>
		</div>
		
		<script type="text/javascript">
			/* if mootools wasn't loaded (ie, the internet at this shitty
			 * african hotel is broken again), just throw up a warning */
			if(typeof(MooTools) == "undefined") {
				var err = [
					"Couldn't load MooTools from: " + document.getElementById("mt").src,
					"This interface will not work without it, because I'm a lazy programmer. Sorry."
				].join("\\n");
				document.getElementById("log").innerHTML = '<li class="error">' + err + '</li>';

			} else {
				window.addEvent("domready", function() {
					
					/* if this window is not the top-level
					 * window (ie, it has been included in
					 * an iframe), then add a body class
					 * to style things slightly differently */
					if (window != top) {
						$(document.body).addClass("framed");
					}
					
					// extract the session id from the URI
					var session_id = location.pathname.replace(/[^0-9]/g, "");
			
					/* for storing the timeout, so we
					 * can ensure that only one fetch
					 * is running at a time */
					var timeout = null;
					
					// the scrolling message log
					var log = $("log");
			
					/* function to be called when it is time
					 * to update the log by polling the server */
					var update = function(msg_id) {
						$clear(timeout);
						
						new Request.JSON({
							"method": "get",
							"url": "/" + session_id + ".json",
							"onSuccess": function(json) {
								var dimmed_old = false;
								
								json.each(function(msg) {
									var msg_id = "msg-" + msg[0];
									
									/* iterate the items returned by the JSON request, and append
									 * any new messages to the message log, in order of receipt */
									if ($(msg_id) == null) {
									
										/* before adding new messages, add a class
										 * to the existing messages, to dim them */
										if (!dimmed_old) {
											log.getElements("li").addClass("old");
											dimmed_old = true;
										}
										
										/* create the new element, and inject it into
										 * the log (msg[1] contains "in" or "out"). */
										new Element("li", {
											"text": ((msg[2] == "") ? "<blank>" : msg[2]),
											"class": msg[1],
											"id": msg_id
										}).inject(log);
									}
								});
								
								/* if the update function was called in response
								 * to an outgoing message (via the #send.onComplete
								 * event, below), a msg_id will have been returned
								 * by the POST request. this msg should now be in
								 * the log, so scroll to it, so we can quickly see
								 * the response */
								if (msg_id != null) {
									var msg_el = $("msg-" + msg_id);
									if (msg_el != null) {
										
										log.scrollTo(0, msg_el.getPosition(log)["y"]);
									}

								/* if it was called independantly, just scroll to
								 * the bottom of the log (Infinity doesn't work!) */
								} else {
									log.scrollTo(0, 9999);
								}
								
								/* call again in 30 seconds, to check for
								 * unsolicited messages once in a while */
								timeout = update.delay(30000);
							}
						}).send();
					};
			
					/* when a message is posted via AJAX,
					 * reload the load to include it */
					$("send").set("send", {
						"url": "/" + session_id + "/send",
						"onComplete": function() {
							update.delay(100); }
				
					/* submit the form via ajax,
					 * and cancel the full-page */
					}).addEvent("submit", function(ev) {
						this.send();
						ev.stop();
						
						/* clear the text entry field to
						 * make way for the next message */
						$("msg").value = "";
					});
					
					/* update the log now, in case there
					 * is already anything in the log */
					update();
				});
			}
		</script>
	</body>
</html>
EOF

Instance Attribute Summary collapse

Attributes inherited from Thing

#label, #router

Instance Method Summary collapse

Methods inherited from Thing

#incoming, #outgoing, #stop

Constructor Details

#initialize(port = HTTP_PORT, mootools_url = MT_URL) ⇒ HTTP

Returns a new instance of HTTP.



28
29
30
31
32
33
34
35
36
# File 'lib/rubysms/backend/http.rb', line 28

def initialize(port=HTTP_PORT, mootools_url=MT_URL)
	@app = RackApp.new(self, mootools_url)
	@port = port
	
	# initialize the log, which returns empty
	# arrays (new session) for unknown keys
	# to avoid initializing sessions all over
	@msg_log = {}
end

Instance Attribute Details

#msg_logObject (readonly)

Returns the value of attribute msg_log.



26
27
28
# File 'lib/rubysms/backend/http.rb', line 26

def msg_log
  @msg_log
end

Instance Method Details

#send_sms(msg) ⇒ Object

outgoing message from RubySMS (probably in response to an incoming, but maybe a blast or other unsolicited message). do nothing except add it to the log, for it to be picked up next time someone looks



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/rubysms/backend/http.rb', line 57

def send_sms(msg)
	s = msg.recipient.phone_number
	t = msg.text
	
	# allow RubySMS to notify the router
	# this is a giant ugly temporary hack
	super
	
	# init the message log for
	# this session if necessary
	@msg_log[s] = []\
		unless @msg_log.has_key?(s)
	
	# add the outgoing message to the log
	msg_id = @msg_log[s].push\
		[t.object_id.abs.to_s, "out", t]
end

#startObject

Starts a thread-blocking Mongrel to serve SMS::Backend::HTTP::RackApp, and never returns.



40
41
42
43
44
45
46
47
48
49
50
# File 'lib/rubysms/backend/http.rb', line 40

def start
	
	# add a screen log message, which is kind of
	# a lie, because we haven't started anything yet
	uri = "http://localhost:#{@port}/"
	log ["Started HTTP Offline Backend", "URI: #{uri}"], :init
	
	# this is goodbye
	Rack::Handler::Mongrel.run(
		@app, :Port=>@port)
end