adammck-rubysms 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +50 -0
- data/bin/rubysms-drb-client +168 -0
- data/lib/drb-client.glade +90 -0
- data/lib/rubysms.rb +39 -0
- data/lib/rubysms/application.rb +207 -0
- data/lib/rubysms/backend.rb +61 -0
- data/lib/rubysms/backend/cellphone.ico +0 -0
- data/lib/rubysms/backend/drb.rb +49 -0
- data/lib/rubysms/backend/gsm.rb +116 -0
- data/lib/rubysms/backend/http.rb +414 -0
- data/lib/rubysms/errors.rb +17 -0
- data/lib/rubysms/logger.rb +69 -0
- data/lib/rubysms/message/incoming.rb +58 -0
- data/lib/rubysms/message/outgoing.rb +40 -0
- data/lib/rubysms/person.rb +47 -0
- data/lib/rubysms/router.rb +198 -0
- data/lib/rubysms/thing.rb +38 -0
- data/rubysms.gemspec +47 -0
- metadata +99 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: noet
|
3
|
+
|
4
|
+
|
5
|
+
module SMS::Backend
|
6
|
+
|
7
|
+
# TODO: doc
|
8
|
+
def self.create(klass, label=nil, *args)
|
9
|
+
|
10
|
+
# if a class name was passed (rather
|
11
|
+
# than a real class), attempt to load
|
12
|
+
# the ruby source, and resolve the name
|
13
|
+
unless klass.is_a?(Class)
|
14
|
+
begin
|
15
|
+
src = File.dirname(__FILE__) +\
|
16
|
+
"/backend/#{klass.to_s.downcase}.rb"
|
17
|
+
require src
|
18
|
+
|
19
|
+
# if the backend couldn't be required, re-
|
20
|
+
# raise the error with a more useful message
|
21
|
+
rescue LoadError
|
22
|
+
raise LoadError.new(
|
23
|
+
"Couldn't load #{klass.inspect} " +\
|
24
|
+
"backend from: #{src}")
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
klass = SMS::Backend.const_get(klass)
|
29
|
+
|
30
|
+
# if the constant couldn't be found,
|
31
|
+
# re-raise with a more useful message
|
32
|
+
rescue NameError
|
33
|
+
raise LoadError.new(
|
34
|
+
"Loaded #{klass.inspect} backend from " +\
|
35
|
+
"#{src}, but the SMS::Backend::#{klass} "+\
|
36
|
+
"class was not defined")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# create an instance of this backend,
|
41
|
+
# passing along the (optional) arguments
|
42
|
+
inst = klass.new(*args)
|
43
|
+
|
44
|
+
# apply the label, if one were provided.
|
45
|
+
# if not, the backend will provide its own
|
46
|
+
inst.label = label unless\
|
47
|
+
label.nil?
|
48
|
+
|
49
|
+
inst
|
50
|
+
end
|
51
|
+
|
52
|
+
class Base < SMS::Thing
|
53
|
+
|
54
|
+
# This method should be called (via super)
|
55
|
+
# by all backends before sending a message,
|
56
|
+
# so it can be logged, and apps are notified
|
57
|
+
def send_sms(msg)
|
58
|
+
router.outgoing(msg)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
Binary file
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: noet
|
3
|
+
|
4
|
+
|
5
|
+
require "drb.rb"
|
6
|
+
|
7
|
+
|
8
|
+
module SMS::Backend
|
9
|
+
class DRB < Base
|
10
|
+
DRB_PORT = 1370
|
11
|
+
|
12
|
+
def start
|
13
|
+
begin
|
14
|
+
|
15
|
+
# start the DRb service, listening for connections
|
16
|
+
# from the RubySMS virtual device (virtual-device.rb)
|
17
|
+
drb = DRb.start_service("druby://localhost:#{DRB_PORT}", self)
|
18
|
+
log ["Started DRb Offline Backend", "URI: #{drb.uri}"], :init
|
19
|
+
|
20
|
+
# a hash to store incoming
|
21
|
+
# connections from drb clients
|
22
|
+
@injectors = {}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def send_sms(msg)
|
27
|
+
to = msg.phone_number
|
28
|
+
super
|
29
|
+
|
30
|
+
# if this is the first time that we
|
31
|
+
# have communicated with this DRb
|
32
|
+
# client, then initialize the object
|
33
|
+
unless @injectors.include?(to)
|
34
|
+
drbo = DRbObject.new_with_uri("druby://localhost:#{DRB_PORT}#{to}")
|
35
|
+
@injectors[to] = drbo
|
36
|
+
end
|
37
|
+
|
38
|
+
@injectors[to].incoming(msg.text)
|
39
|
+
end
|
40
|
+
|
41
|
+
# called from another ruby process, via
|
42
|
+
# drb, to simulate an incoming sms message
|
43
|
+
def incoming(sender, text)
|
44
|
+
router.incoming(
|
45
|
+
SMS::Incoming.new(
|
46
|
+
self, sender, Time.now, text))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: noet
|
3
|
+
|
4
|
+
|
5
|
+
# attempt to load rubygsm using relative paths first,
|
6
|
+
# so we can easily run on the trunk by cloning from
|
7
|
+
# github. the dir structure should look something like:
|
8
|
+
#
|
9
|
+
# projects
|
10
|
+
# - rubygsms
|
11
|
+
# - rubygsm
|
12
|
+
begin
|
13
|
+
dev_dir = "#{dir}/../../../rubygsm"
|
14
|
+
dev_path = "#{dev_dir}/lib/rubygsm.rb"
|
15
|
+
require File.expand_path(dev_path)
|
16
|
+
|
17
|
+
rescue LoadError
|
18
|
+
begin
|
19
|
+
|
20
|
+
# couldn't load via relative
|
21
|
+
# path, so try loading the gem
|
22
|
+
require "rubygems"
|
23
|
+
require "rubygsm"
|
24
|
+
|
25
|
+
rescue LoadError
|
26
|
+
|
27
|
+
# nothing worked, so re-raise
|
28
|
+
# with more useful information
|
29
|
+
raise LoadError.new(
|
30
|
+
"Couldn't load RubyGSM relatively (tried: " +\
|
31
|
+
"#{dev_path.inspect}) or via RubyGems")
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
module SMS::Backend
|
38
|
+
|
39
|
+
# Provides an interface between RubyGSM and RubySMS,
|
40
|
+
# which allows RubySMS to send real SMS in an abstract
|
41
|
+
# fashion, which can be replicated by other backends.
|
42
|
+
# This backend is probably the thinnest layer between
|
43
|
+
# applications and the network, since the backend API
|
44
|
+
# (first implemented here) was based on RubyGSM.
|
45
|
+
class GSM < Base
|
46
|
+
|
47
|
+
# just store the arguments until the
|
48
|
+
# backend is ready to be started
|
49
|
+
def initialize(port=:auto, pin=nil)
|
50
|
+
@port = port
|
51
|
+
@pin = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def start
|
55
|
+
|
56
|
+
# lock the threads during modem initialization,
|
57
|
+
# simply to avoid the screen log being mixed up
|
58
|
+
Thread.exclusive do
|
59
|
+
begin
|
60
|
+
@gsm = ::Gsm::Modem.new(@port)
|
61
|
+
@gsm.use_pin(@pin) unless @pin.nil?
|
62
|
+
@gsm.receive method(:incoming)
|
63
|
+
|
64
|
+
#bands = @gsm.bands_available.join(", ")
|
65
|
+
#log "Using GSM Band: #{@gsm.band}MHz"
|
66
|
+
#log "Modem supports: #{bands}"
|
67
|
+
|
68
|
+
log "Waiting for GSM network..."
|
69
|
+
str = @gsm.wait_for_network
|
70
|
+
log "Signal strength is: #{str}"
|
71
|
+
|
72
|
+
# couldn't open the port. this usually means
|
73
|
+
# that the modem isn't plugged in to it...
|
74
|
+
rescue Errno::ENOENT, ArgumentError
|
75
|
+
log "Couldn't open #{@port}", :err
|
76
|
+
raise IOError
|
77
|
+
|
78
|
+
# something else went wrong
|
79
|
+
# while initializing the modem
|
80
|
+
rescue ::Gsm::Modem::Error => err
|
81
|
+
log ["Couldn't initialize the modem",
|
82
|
+
"RubyGSM Says: #{err.desc}"], :err
|
83
|
+
raise RuntimeError
|
84
|
+
end
|
85
|
+
|
86
|
+
# rubygsm didn't blow up?!
|
87
|
+
log "Started GSM Backend", :init
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def send_sms(msg)
|
92
|
+
super
|
93
|
+
|
94
|
+
# send the message to the modem via rubygsm, and log
|
95
|
+
# if it failed. TODO: needs moar info from rubygsm
|
96
|
+
# on *why* sending failed
|
97
|
+
unless @gsm.send_sms(msg.recipient.phone_number, msg.text)
|
98
|
+
log "Message sending FAILED", :warn
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# called back by rubygsm when an incoming
|
103
|
+
# message arrives, which we will pass on
|
104
|
+
# to rubysms to dispatch to applications
|
105
|
+
def incoming(msg)
|
106
|
+
|
107
|
+
# NOTE: the msg argument is a GSM::Incoming
|
108
|
+
# object from RubyGSM, NOT the more useful
|
109
|
+
# SMS::Incoming object from RubySMS
|
110
|
+
|
111
|
+
router.incoming(
|
112
|
+
SMS::Incoming.new(
|
113
|
+
self, msg.sender, msg.sent, msg.text))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,414 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: noet
|
3
|
+
|
4
|
+
|
5
|
+
require "rubygems"
|
6
|
+
require "rack"
|
7
|
+
|
8
|
+
|
9
|
+
module SMS::Backend
|
10
|
+
|
11
|
+
# Provides a low-tech HTML webUI to inject mock SMS messages into
|
12
|
+
# RubySMS, and receive responses. This is usually used during app
|
13
|
+
# development, to provide a cross-platform method of simulating
|
14
|
+
# a two-way conversation with the SMS backend(s). Note, though,
|
15
|
+
# that there is no technical difference between the SMS::Incoming
|
16
|
+
# and SMS::Outgoing objects created by this backend, and those
|
17
|
+
# created by "real" incoming messages via the GSM backend.
|
18
|
+
#
|
19
|
+
# The JSON API used internally by this backend also be used by
|
20
|
+
# other HTML applications to communicate with RubySMS, but that
|
21
|
+
# is quite obscure, and isn't very well documented yet. Also, it
|
22
|
+
# sporadically changes without warning. May The Force be with you.
|
23
|
+
class HTTP < Base
|
24
|
+
HTTP_PORT = 1270
|
25
|
+
MT_URL = "http://ajax.googleapis.com/ajax/libs/mootools/1.2.1/mootools-yui-compressed.js"
|
26
|
+
attr_reader :msg_log
|
27
|
+
|
28
|
+
def initialize(port=HTTP_PORT, mootools_url=MT_URL)
|
29
|
+
@app = RackApp.new(self, mootools_url)
|
30
|
+
@port = port
|
31
|
+
|
32
|
+
# initialize the log, which returns empty
|
33
|
+
# arrays (new session) for unknown keys
|
34
|
+
# to avoid initializing sessions all over
|
35
|
+
@msg_log = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Starts a thread-blocking Mongrel to serve
|
39
|
+
# SMS::Backend::HTTP::RackApp, and never returns.
|
40
|
+
def start
|
41
|
+
|
42
|
+
# add a screen log message, which is kind of
|
43
|
+
# a lie, because we haven't started anything yet
|
44
|
+
uri = "http://localhost:#{@port}"
|
45
|
+
log ["Started HTTP Offline Backend", "URI: #{uri}"], :init
|
46
|
+
|
47
|
+
# this is goodbye
|
48
|
+
Rack::Handler::Mongrel.run(
|
49
|
+
@app, :Port=>@port)
|
50
|
+
end
|
51
|
+
|
52
|
+
# outgoing message from RubySMS (probably
|
53
|
+
# in response to an incoming, but maybe a
|
54
|
+
# blast or other unsolicited message). do
|
55
|
+
# nothing except add it to the log, for it
|
56
|
+
# to be picked up next time someone looks
|
57
|
+
def send_sms(msg)
|
58
|
+
s = msg.recipient.phone_number
|
59
|
+
t = msg.text
|
60
|
+
|
61
|
+
# allow RubySMS to notify the router
|
62
|
+
# this is a giant ugly temporary hack
|
63
|
+
super
|
64
|
+
|
65
|
+
# init the message log for
|
66
|
+
# this session if necessary
|
67
|
+
@msg_log[s] = []\
|
68
|
+
unless @msg_log.has_key?(s)
|
69
|
+
|
70
|
+
# add the outgoing message to the log
|
71
|
+
msg_id = @msg_log[s].push\
|
72
|
+
[t.object_id.abs.to_s, "out", t]
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
# This simple Rack application handles the few
|
79
|
+
# HTTP requests that this backend will serve:
|
80
|
+
#
|
81
|
+
# GET / -- redirect to a random blank session
|
82
|
+
# GET /123456.json -- export session 123456 as JSON data
|
83
|
+
# GET /123456 -- view session 123456 (actually a
|
84
|
+
# static HTML page which fetches
|
85
|
+
# the data via javascript+json)
|
86
|
+
# POST /123456/send -- add a message to session 123456
|
87
|
+
class RackApp
|
88
|
+
def initialize(http_backend, mootools_url)
|
89
|
+
@backend = http_backend
|
90
|
+
@mt_url = mootools_url
|
91
|
+
|
92
|
+
# generate the html to be returned by replacing the
|
93
|
+
# variables in the constant with our instance vars
|
94
|
+
@html = HTML.sub(/%(\w+)%/) do
|
95
|
+
instance_variable_get("@#{$1}")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def call(env)
|
100
|
+
req = Rack::Request.new(env)
|
101
|
+
path = req.path_info
|
102
|
+
|
103
|
+
if req.get?
|
104
|
+
|
105
|
+
# serve GET /
|
106
|
+
# for requests not containing a session id, generate a random
|
107
|
+
# new one (between 111111 and 999999) and redirect back to it
|
108
|
+
if path == "/"
|
109
|
+
while true
|
110
|
+
|
111
|
+
# randomize a session id, and stop looping if
|
112
|
+
# it's empty - this is just to avoid accidentally
|
113
|
+
# jumping into someone elses session (although
|
114
|
+
# that's allowed, if explicly requested)
|
115
|
+
new_session = (111111 + rand(888888)).to_s
|
116
|
+
break unless @backend.msg_log.has_key?(new_session)
|
117
|
+
end
|
118
|
+
|
119
|
+
return [
|
120
|
+
301,
|
121
|
+
{"location" => "/#{new_session}"},
|
122
|
+
"Redirecting to session #{new_session}"]
|
123
|
+
|
124
|
+
# serve GET /123456
|
125
|
+
elsif m = path.match(/^\/\d{6}$/)
|
126
|
+
|
127
|
+
# just so render the static HTML content (the
|
128
|
+
# log contents are rendered via JSON, above)
|
129
|
+
return [200, {"content-type" => "text/html"}, @html]
|
130
|
+
|
131
|
+
# serve GET /123456.json
|
132
|
+
elsif m = path.match(/^\/(\d{6})\.json$/)
|
133
|
+
msgs = @backend.msg_log[m.captures[0]] || []
|
134
|
+
|
135
|
+
return [
|
136
|
+
200,
|
137
|
+
{"content-type" => "application/json"},
|
138
|
+
"[" + (msgs.collect { |msg| msg.inspect }.join(", ")) + "]"]
|
139
|
+
|
140
|
+
# serve GET /favicon.ico
|
141
|
+
# as if YOU'VE never wasted
|
142
|
+
# a minute on frivolous crap
|
143
|
+
elsif path == "/favicon.ico"
|
144
|
+
icon = File.dirname(__FILE__) + "/cellphone.ico"
|
145
|
+
return [200, {"content-type" => "image/x-ico"}, File.read(icon)]
|
146
|
+
end
|
147
|
+
|
148
|
+
# serve POST /123456/send
|
149
|
+
elsif (m = path.match(/^\/(\d{6})\/send$/)) && req.post?
|
150
|
+
t = req.POST["msg"]
|
151
|
+
s = m.captures[0]
|
152
|
+
|
153
|
+
# init the message log for
|
154
|
+
# this session if necessary
|
155
|
+
@backend.msg_log[s] = []\
|
156
|
+
unless @backend.msg_log.has_key?(s)
|
157
|
+
|
158
|
+
# log the incoming message, so it shows
|
159
|
+
# up in the two-way "conversation"
|
160
|
+
msg_id = @backend.msg_log[s].push\
|
161
|
+
[t.object_id.abs.to_s, "in", t]
|
162
|
+
|
163
|
+
# push the incoming message
|
164
|
+
# into RubySMS, to distribute
|
165
|
+
# to each application
|
166
|
+
@backend.router.incoming(
|
167
|
+
SMS::Incoming.new(
|
168
|
+
@backend, s, Time.now, t))
|
169
|
+
|
170
|
+
# acknowledge POST with
|
171
|
+
# the new message ID
|
172
|
+
return [
|
173
|
+
200,
|
174
|
+
{"content-type" => "text/plain" },
|
175
|
+
t.object_id.abs.to_s]
|
176
|
+
end
|
177
|
+
|
178
|
+
# nothing else is valid. not 404, because it might be
|
179
|
+
# an invalid method, and i can't be arsed right now.
|
180
|
+
[500, {"content-type" => "text/plain" }, "FAIL."]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
SMS::Backend::HTTP::HTML = <<EOF
|
187
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
188
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
189
|
+
<html>
|
190
|
+
<head>
|
191
|
+
<title>RubySMS Virtual Device</title>
|
192
|
+
<script id="mt" type="text/javascript" src="%mt_url%"></script>
|
193
|
+
<style type="text/css">
|
194
|
+
|
195
|
+
/* remove m+p from most elements,
|
196
|
+
* without resetting form elements */
|
197
|
+
body, h1, #log, #log li, form {
|
198
|
+
margin: 0;
|
199
|
+
padding: 0;
|
200
|
+
}
|
201
|
+
|
202
|
+
body {
|
203
|
+
line-height: 1;
|
204
|
+
font: 8pt sans-serif;
|
205
|
+
background: #eef;
|
206
|
+
padding: 2em;
|
207
|
+
}
|
208
|
+
|
209
|
+
body.framed {
|
210
|
+
background: transparent;
|
211
|
+
padding: 0;
|
212
|
+
}
|
213
|
+
|
214
|
+
body.framed #wrapper {
|
215
|
+
position: absolute;
|
216
|
+
width: 100%;
|
217
|
+
bottom: 0;
|
218
|
+
left: 0;
|
219
|
+
|
220
|
+
}
|
221
|
+
|
222
|
+
#wrapper div {
|
223
|
+
padding: 0.5em;
|
224
|
+
background: #33a7d2;
|
225
|
+
}
|
226
|
+
|
227
|
+
h1 {
|
228
|
+
font-size: 100%;
|
229
|
+
color: #fff;
|
230
|
+
}
|
231
|
+
|
232
|
+
#log {
|
233
|
+
height: 14em;
|
234
|
+
overflow-y: scroll;
|
235
|
+
font-family: monospace;
|
236
|
+
background: #fff;
|
237
|
+
margin: 0.5em 0;
|
238
|
+
}
|
239
|
+
|
240
|
+
#log li {
|
241
|
+
line-height: 1.4;
|
242
|
+
list-style: none;
|
243
|
+
white-space: pre;
|
244
|
+
padding: 0.5em;
|
245
|
+
}
|
246
|
+
|
247
|
+
#log li.in {
|
248
|
+
color: #888;
|
249
|
+
border-top: 1px dotted #000;
|
250
|
+
}
|
251
|
+
|
252
|
+
/* don't show the divider
|
253
|
+
* at the top of the log */
|
254
|
+
#log li:first-child.in {
|
255
|
+
border-top: 0; }
|
256
|
+
|
257
|
+
#log li.out {
|
258
|
+
color: #000;
|
259
|
+
background: #f8f8f8;
|
260
|
+
}
|
261
|
+
|
262
|
+
/* messages prior to the latest
|
263
|
+
* are dim, to enhance readability */
|
264
|
+
#log li.old {
|
265
|
+
border-top: 0;
|
266
|
+
color: #ddd;
|
267
|
+
}
|
268
|
+
|
269
|
+
#log li.error {
|
270
|
+
color: #f00;
|
271
|
+
}
|
272
|
+
|
273
|
+
form { }
|
274
|
+
|
275
|
+
form input {
|
276
|
+
-moz-box-sizing: border-box;
|
277
|
+
width: 100%;
|
278
|
+
}
|
279
|
+
</style>
|
280
|
+
</head>
|
281
|
+
<body>
|
282
|
+
<div id="wrapper">
|
283
|
+
<div>
|
284
|
+
<h1>RubySMS Virtual Device</h1>
|
285
|
+
|
286
|
+
<ul id="log">
|
287
|
+
</ul>
|
288
|
+
|
289
|
+
<form id="send" method="post">
|
290
|
+
<input type="text" id="msg" name="msg" />
|
291
|
+
<!--<input type="submit" value="Send" />-->
|
292
|
+
</form>
|
293
|
+
</div>
|
294
|
+
</div>
|
295
|
+
|
296
|
+
<script type="text/javascript">
|
297
|
+
/* if mootools wasn't loaded (ie, the internet at this shitty
|
298
|
+
* african hotel is broken again), just throw up a warning */
|
299
|
+
if(typeof(MooTools) == "undefined") {
|
300
|
+
var err = [
|
301
|
+
"Couldn't load MooTools from: " + document.getElementById("mt").src,
|
302
|
+
"This interface will not work without it, because I'm a lazy programmer. Sorry."
|
303
|
+
].join("\\n");
|
304
|
+
document.getElementById("log").innerHTML = '<li class="error">' + err + '</li>';
|
305
|
+
|
306
|
+
} else {
|
307
|
+
window.addEvent("domready", function() {
|
308
|
+
|
309
|
+
/* if this window is not the top-level
|
310
|
+
* window (ie, it has been included in
|
311
|
+
* an iframe), then add a body class
|
312
|
+
* to style things slightly differently */
|
313
|
+
if (window != top) {
|
314
|
+
$(document.body).addClass("framed");
|
315
|
+
}
|
316
|
+
|
317
|
+
// extract the session id from the URI
|
318
|
+
var session_id = location.pathname.replace(/[^0-9]/g, "");
|
319
|
+
|
320
|
+
/* for storing the timeout, so we
|
321
|
+
* can ensure that only one fetch
|
322
|
+
* is running at a time */
|
323
|
+
var timeout = null;
|
324
|
+
|
325
|
+
// the scrolling message log
|
326
|
+
var log = $("log");
|
327
|
+
|
328
|
+
/* function to be called when it is time
|
329
|
+
* to update the log by polling the server */
|
330
|
+
var update = function(msg_id) {
|
331
|
+
$clear(timeout);
|
332
|
+
|
333
|
+
new Request.JSON({
|
334
|
+
"method": "get",
|
335
|
+
"url": "/" + session_id + ".json",
|
336
|
+
"onSuccess": function(json) {
|
337
|
+
var dimmed_old = false;
|
338
|
+
|
339
|
+
json.each(function(msg) {
|
340
|
+
var msg_id = "msg-" + msg[0];
|
341
|
+
|
342
|
+
/* iterate the items returned by the JSON request, and append
|
343
|
+
* any new messages to the message log, in order of receipt */
|
344
|
+
if ($(msg_id) == null) {
|
345
|
+
|
346
|
+
/* before adding new messages, add a class
|
347
|
+
* to the existing messages, to dim them */
|
348
|
+
if (!dimmed_old) {
|
349
|
+
log.getElements("li").addClass("old");
|
350
|
+
dimmed_old = true;
|
351
|
+
}
|
352
|
+
|
353
|
+
/* create the new element, and inject it into
|
354
|
+
* the log (msg[1] contains "in" or "out"). */
|
355
|
+
new Element("li", {
|
356
|
+
"text": ((msg[2] == "") ? "<blank>" : msg[2]),
|
357
|
+
"class": msg[1],
|
358
|
+
"id": msg_id
|
359
|
+
}).inject(log);
|
360
|
+
}
|
361
|
+
});
|
362
|
+
|
363
|
+
/* if the update function was called in response
|
364
|
+
* to an outgoing message (via the #send.onComplete
|
365
|
+
* event, below), a msg_id will have been returned
|
366
|
+
* by the POST request. this msg should now be in
|
367
|
+
* the log, so scroll to it, so we can quickly see
|
368
|
+
* the response */
|
369
|
+
if (msg_id != null) {
|
370
|
+
var msg_el = $("msg-" + msg_id);
|
371
|
+
if (msg_el != null) {
|
372
|
+
|
373
|
+
log.scrollTo(0, msg_el.getPosition(log)["y"]);
|
374
|
+
}
|
375
|
+
|
376
|
+
/* if it was called independantly, just scroll to
|
377
|
+
* the bottom of the log (Infinity doesn't work!) */
|
378
|
+
} else {
|
379
|
+
log.scrollTo(0, 9999);
|
380
|
+
}
|
381
|
+
|
382
|
+
/* call again in 30 seconds, to check for
|
383
|
+
* unsolicited messages once in a while */
|
384
|
+
timeout = update.delay(30000);
|
385
|
+
}
|
386
|
+
}).send();
|
387
|
+
};
|
388
|
+
|
389
|
+
/* when a message is posted via AJAX,
|
390
|
+
* reload the load to include it */
|
391
|
+
$("send").set("send", {
|
392
|
+
"url": "/" + session_id + "/send",
|
393
|
+
"onComplete": update
|
394
|
+
|
395
|
+
/* submit the form via ajax,
|
396
|
+
* and cancel the full-page */
|
397
|
+
}).addEvent("submit", function(ev) {
|
398
|
+
this.send();
|
399
|
+
ev.stop();
|
400
|
+
|
401
|
+
/* clear the text entry field to
|
402
|
+
* make way for the next message */
|
403
|
+
$("msg").value = "";
|
404
|
+
});
|
405
|
+
|
406
|
+
/* update the log now, in case there
|
407
|
+
* is already anything in the log */
|
408
|
+
update();
|
409
|
+
});
|
410
|
+
}
|
411
|
+
</script>
|
412
|
+
</body>
|
413
|
+
</html>
|
414
|
+
EOF
|