adammck-rubysms 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|