vinesmod 0.4.5

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.
Files changed (151) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +19 -0
  3. data/README.md +43 -0
  4. data/Rakefile +57 -0
  5. data/bin/vines +93 -0
  6. data/conf/certs/README +39 -0
  7. data/conf/certs/ca-bundle.crt +3366 -0
  8. data/conf/config.rb +149 -0
  9. data/lib/vines.rb +197 -0
  10. data/lib/vines/cluster.rb +246 -0
  11. data/lib/vines/cluster/connection.rb +26 -0
  12. data/lib/vines/cluster/publisher.rb +55 -0
  13. data/lib/vines/cluster/pubsub.rb +92 -0
  14. data/lib/vines/cluster/sessions.rb +125 -0
  15. data/lib/vines/cluster/subscriber.rb +108 -0
  16. data/lib/vines/command/bcrypt.rb +12 -0
  17. data/lib/vines/command/cert.rb +50 -0
  18. data/lib/vines/command/init.rb +68 -0
  19. data/lib/vines/command/register.rb +27 -0
  20. data/lib/vines/command/restart.rb +12 -0
  21. data/lib/vines/command/schema.rb +24 -0
  22. data/lib/vines/command/start.rb +28 -0
  23. data/lib/vines/command/stop.rb +18 -0
  24. data/lib/vines/command/unregister.rb +27 -0
  25. data/lib/vines/config.rb +213 -0
  26. data/lib/vines/config/host.rb +119 -0
  27. data/lib/vines/config/port.rb +132 -0
  28. data/lib/vines/config/pubsub.rb +108 -0
  29. data/lib/vines/contact.rb +111 -0
  30. data/lib/vines/daemon.rb +78 -0
  31. data/lib/vines/error.rb +150 -0
  32. data/lib/vines/jid.rb +95 -0
  33. data/lib/vines/kit.rb +35 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza.rb +175 -0
  37. data/lib/vines/stanza/iq.rb +48 -0
  38. data/lib/vines/stanza/iq/auth.rb +18 -0
  39. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  40. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  41. data/lib/vines/stanza/iq/error.rb +16 -0
  42. data/lib/vines/stanza/iq/ping.rb +16 -0
  43. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  44. data/lib/vines/stanza/iq/query.rb +10 -0
  45. data/lib/vines/stanza/iq/register.rb +42 -0
  46. data/lib/vines/stanza/iq/result.rb +16 -0
  47. data/lib/vines/stanza/iq/roster.rb +140 -0
  48. data/lib/vines/stanza/iq/session.rb +17 -0
  49. data/lib/vines/stanza/iq/vcard.rb +56 -0
  50. data/lib/vines/stanza/iq/version.rb +25 -0
  51. data/lib/vines/stanza/message.rb +43 -0
  52. data/lib/vines/stanza/presence.rb +156 -0
  53. data/lib/vines/stanza/presence/error.rb +23 -0
  54. data/lib/vines/stanza/presence/probe.rb +37 -0
  55. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  56. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  57. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  58. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  59. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  60. data/lib/vines/stanza/pubsub.rb +22 -0
  61. data/lib/vines/stanza/pubsub/create.rb +39 -0
  62. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  63. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  64. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  65. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  66. data/lib/vines/storage.rb +188 -0
  67. data/lib/vines/storage/local.rb +165 -0
  68. data/lib/vines/storage/null.rb +39 -0
  69. data/lib/vines/storage/sql.rb +260 -0
  70. data/lib/vines/store.rb +94 -0
  71. data/lib/vines/stream.rb +247 -0
  72. data/lib/vines/stream/client.rb +84 -0
  73. data/lib/vines/stream/client/auth.rb +74 -0
  74. data/lib/vines/stream/client/auth_restart.rb +29 -0
  75. data/lib/vines/stream/client/bind.rb +72 -0
  76. data/lib/vines/stream/client/bind_restart.rb +24 -0
  77. data/lib/vines/stream/client/closed.rb +13 -0
  78. data/lib/vines/stream/client/ready.rb +17 -0
  79. data/lib/vines/stream/client/session.rb +210 -0
  80. data/lib/vines/stream/client/start.rb +27 -0
  81. data/lib/vines/stream/client/tls.rb +38 -0
  82. data/lib/vines/stream/component.rb +58 -0
  83. data/lib/vines/stream/component/handshake.rb +26 -0
  84. data/lib/vines/stream/component/ready.rb +23 -0
  85. data/lib/vines/stream/component/start.rb +19 -0
  86. data/lib/vines/stream/http.rb +157 -0
  87. data/lib/vines/stream/http/auth.rb +22 -0
  88. data/lib/vines/stream/http/bind.rb +32 -0
  89. data/lib/vines/stream/http/bind_restart.rb +37 -0
  90. data/lib/vines/stream/http/ready.rb +29 -0
  91. data/lib/vines/stream/http/request.rb +172 -0
  92. data/lib/vines/stream/http/session.rb +120 -0
  93. data/lib/vines/stream/http/sessions.rb +65 -0
  94. data/lib/vines/stream/http/start.rb +23 -0
  95. data/lib/vines/stream/parser.rb +78 -0
  96. data/lib/vines/stream/sasl.rb +92 -0
  97. data/lib/vines/stream/server.rb +150 -0
  98. data/lib/vines/stream/server/auth.rb +13 -0
  99. data/lib/vines/stream/server/auth_restart.rb +13 -0
  100. data/lib/vines/stream/server/final_restart.rb +21 -0
  101. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  102. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  103. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  104. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  105. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  106. data/lib/vines/stream/server/outbound/start.rb +20 -0
  107. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  108. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  109. data/lib/vines/stream/server/ready.rb +24 -0
  110. data/lib/vines/stream/server/start.rb +13 -0
  111. data/lib/vines/stream/server/tls.rb +13 -0
  112. data/lib/vines/stream/state.rb +60 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +5 -0
  116. data/lib/vines/xmpp_server.rb +43 -0
  117. data/vines.gemspec +36 -0
  118. data/web/404.html +51 -0
  119. data/web/apple-touch-icon.png +0 -0
  120. data/web/chat/coffeescripts/chat.coffee +362 -0
  121. data/web/chat/coffeescripts/init.coffee +15 -0
  122. data/web/chat/index.html +16 -0
  123. data/web/chat/javascripts/app.js +1 -0
  124. data/web/chat/stylesheets/chat.css +144 -0
  125. data/web/favicon.png +0 -0
  126. data/web/lib/coffeescripts/button.coffee +25 -0
  127. data/web/lib/coffeescripts/contact.coffee +32 -0
  128. data/web/lib/coffeescripts/filter.coffee +49 -0
  129. data/web/lib/coffeescripts/layout.coffee +30 -0
  130. data/web/lib/coffeescripts/login.coffee +68 -0
  131. data/web/lib/coffeescripts/logout.coffee +5 -0
  132. data/web/lib/coffeescripts/navbar.coffee +84 -0
  133. data/web/lib/coffeescripts/notification.coffee +14 -0
  134. data/web/lib/coffeescripts/router.coffee +40 -0
  135. data/web/lib/coffeescripts/session.coffee +229 -0
  136. data/web/lib/coffeescripts/transfer.coffee +106 -0
  137. data/web/lib/images/dark-gray.png +0 -0
  138. data/web/lib/images/default-user.png +0 -0
  139. data/web/lib/images/light-gray.png +0 -0
  140. data/web/lib/images/logo-large.png +0 -0
  141. data/web/lib/images/logo-small.png +0 -0
  142. data/web/lib/images/white.png +0 -0
  143. data/web/lib/javascripts/base.js +12 -0
  144. data/web/lib/javascripts/icons.js +110 -0
  145. data/web/lib/javascripts/jquery.cookie.js +91 -0
  146. data/web/lib/javascripts/jquery.js +4 -0
  147. data/web/lib/javascripts/raphael.js +6 -0
  148. data/web/lib/javascripts/strophe.js +1 -0
  149. data/web/lib/stylesheets/base.css +385 -0
  150. data/web/lib/stylesheets/login.css +68 -0
  151. metadata +423 -0
data/conf/config.rb ADDED
@@ -0,0 +1,149 @@
1
+ # encoding: UTF-8
2
+
3
+ # This is the Vines XMPP server configuration file. Restart the server with
4
+ # 'vines restart' after updating this file.
5
+
6
+ Vines::Config.configure do
7
+ # Set the logging level to debug, info, warn, error, or fatal. The debug
8
+ # level logs all XML sent and received by the server.
9
+ log :info
10
+
11
+ # Set the directory in which to look for virtual hosts' TLS certificates.
12
+ # This is optional and defaults to the conf/certs directory created during
13
+ # `vines init`.
14
+ certs 'conf/certs'
15
+
16
+ # Each host element below is a virtual host domain name that this server will
17
+ # service. Hosts can share storage configurations or use separate databases.
18
+ # TLS encryption is mandatory so each host must have a <domain>.crt and
19
+ # <domain>.key file in the conf/certs directory. A self-signed certificate can
20
+ # be generated for a virtual host domain with the 'vines cert <domain.tld>'
21
+ # command. Change the example, 'wonderland.lit', domain name to your actual
22
+ # domain.
23
+ #
24
+ # The private_storage attribute allows clients to store XML fragments
25
+ # on the server, using the XEP-0049 Private XML Storage feature.
26
+ #
27
+ # The pubsub attribute defines the XEP-0060 Publish-Subscribe services hosted
28
+ # at these virtual host domains. In the example below, pubsub services are
29
+ # available at games.wonderland.lit and scores.wonderland.lit as well as
30
+ # games.verona.lit and scores.verona.lit.
31
+ #
32
+ # Shared storage example:
33
+ # host 'verona.lit', 'wonderland.lit' do
34
+ # private_storage false
35
+ # cross_domain_messages false
36
+ # storage 'fs' do
37
+ # dir 'data'
38
+ # end
39
+ # components 'tea' => 'secr3t',
40
+ # 'cake' => 'passw0rd'
41
+ # pubsub 'games', 'scores'
42
+ # end
43
+
44
+ host 'wonderland.lit' do
45
+ cross_domain_messages false
46
+ private_storage false
47
+ storage 'fs' do
48
+ dir 'data'
49
+ end
50
+ # components 'tea' => 'secr3t',
51
+ # 'cake' => 'passw0rd'
52
+ # pubsub 'games', 'scores'
53
+ end
54
+
55
+ # Configure the client-to-server port. The max_resources_per_account attribute
56
+ # limits how many concurrent connections one user can have to the server.
57
+ client '0.0.0.0', 5222 do
58
+ max_stanza_size 65536
59
+ max_resources_per_account 5
60
+ end
61
+
62
+ # Configure the server-to-server port. The max_stanza_size attribute should be
63
+ # much larger than the setting for client-to-server. Add domain names to the
64
+ # 'hosts' white-list attribute to allow those servers to connect. Any connection
65
+ # attempt from a host not in this list will be denied.
66
+ server '0.0.0.0', 5269 do
67
+ max_stanza_size 131072
68
+ hosts []
69
+ end
70
+
71
+ # Configure the built-in HTTP server that serves static files and responds to
72
+ # XEP-0124 BOSH requests. This allows HTTP clients to connect to
73
+ # the XMPP server.
74
+ #
75
+ # The root attribute defines the web server's document root (default 'web/').
76
+ # It will only serve files out of this directory.
77
+ #
78
+ # The bind attribute defines the URL to which BOSH clients must POST their
79
+ # XMPP stanza requests (default /xmpp).
80
+ #
81
+ # The vroute attribute defines the value of the vroute cookie sent in each
82
+ # response that uniquely identifies this HTTP server. Reverse proxy servers
83
+ # (nginx/apache) can use this cookie to implement sticky sessions. This is
84
+ # only needed if the server is clustered behind a reverse proxy.
85
+ http '0.0.0.0', 5280 do
86
+ bind '/xmpp'
87
+ max_stanza_size 65536
88
+ max_resources_per_account 5
89
+ root 'web'
90
+ vroute ''
91
+ end
92
+
93
+ # Configure the XEP-0114 external component port. Component sub-domains and
94
+ # their passwords are defined with their virtual host entries above.
95
+ component '0.0.0.0', 5347 do
96
+ max_stanza_size 131072
97
+ end
98
+
99
+ # Configure the redis connection used to form a cluster of server instances,
100
+ # serving the same chat domains across many different machines.
101
+ #cluster do
102
+ # host 'redis.wonderland.lit'
103
+ # port 6379
104
+ # database 0
105
+ # password ''
106
+ #end
107
+ end
108
+
109
+ # Available storage implementations:
110
+
111
+ #storage 'fs' do
112
+ # dir 'data'
113
+ #end
114
+
115
+ #storage 'couchdb' do
116
+ # host 'localhost'
117
+ # port 6984
118
+ # database 'xmpp'
119
+ # tls true
120
+ # username ''
121
+ # password ''
122
+ #end
123
+
124
+ #storage 'mongodb' do
125
+ # host 'localhost', 27017
126
+ # host 'localhost', 27018 # optional, connects to replica set
127
+ # database 'xmpp'
128
+ # tls true
129
+ # username ''
130
+ # password ''
131
+ # pool 5
132
+ #end
133
+
134
+ #storage 'redis' do
135
+ # host 'localhost'
136
+ # port 6379
137
+ # database 0
138
+ # password ''
139
+ #end
140
+
141
+ #storage 'sql' do
142
+ # adapter 'postgresql'
143
+ # host 'localhost'
144
+ # port 5432
145
+ # database 'xmpp'
146
+ # username ''
147
+ # password ''
148
+ # pool 5
149
+ #end
data/lib/vines.rb ADDED
@@ -0,0 +1,197 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ NAMESPACES = {
5
+ :stream => 'http://etherx.jabber.org/streams'.freeze,
6
+ :client => 'jabber:client'.freeze,
7
+ :server => 'jabber:server'.freeze,
8
+ :component => 'jabber:component:accept'.freeze,
9
+ :roster => 'jabber:iq:roster'.freeze,
10
+ :register => 'jabber:iq:register'.freeze,
11
+ :non_sasl => 'jabber:iq:auth'.freeze,
12
+ :storage => 'jabber:iq:private'.freeze,
13
+ :version => 'jabber:iq:version'.freeze,
14
+ :sasl => 'urn:ietf:params:xml:ns:xmpp-sasl'.freeze,
15
+ :tls => 'urn:ietf:params:xml:ns:xmpp-tls'.freeze,
16
+ :bind => 'urn:ietf:params:xml:ns:xmpp-bind'.freeze,
17
+ :session => 'urn:ietf:params:xml:ns:xmpp-session'.freeze,
18
+ :ping => 'urn:xmpp:ping'.freeze,
19
+ :pubsub => 'http://jabber.org/protocol/pubsub'.freeze,
20
+ :pubsub_event => 'http://jabber.org/protocol/pubsub#event'.freeze,
21
+ :pubsub_create => 'http://jabber.org/protocol/pubsub#create-nodes'.freeze,
22
+ :pubsub_delete => 'http://jabber.org/protocol/pubsub#delete-nodes'.freeze,
23
+ :pubsub_instant => 'http://jabber.org/protocol/pubsub#instant-nodes'.freeze,
24
+ :pubsub_item_ids => 'http://jabber.org/protocol/pubsub#item-ids'.freeze,
25
+ :pubsub_publish => 'http://jabber.org/protocol/pubsub#publish'.freeze,
26
+ :pubsub_subscribe => 'http://jabber.org/protocol/pubsub#subscribe'.freeze,
27
+ :disco_items => 'http://jabber.org/protocol/disco#items'.freeze,
28
+ :disco_info => 'http://jabber.org/protocol/disco#info'.freeze,
29
+ :http_bind => 'http://jabber.org/protocol/httpbind'.freeze,
30
+ :bosh => 'urn:xmpp:xbosh'.freeze,
31
+ :vcard => 'vcard-temp'.freeze,
32
+ :si => 'http://jabber.org/protocol/si'.freeze,
33
+ :byte_streams => 'http://jabber.org/protocol/bytestreams'.freeze
34
+ }.freeze
35
+
36
+ module Log
37
+ @@logger = nil
38
+ def log
39
+ unless @@logger
40
+ @@logger = Logger.new(STDOUT)
41
+ @@logger.level = Logger::INFO
42
+ @@logger.progname = 'vines'
43
+ @@logger.formatter = Class.new(Logger::Formatter) do
44
+ def initialize
45
+ @time = "%Y-%m-%dT%H:%M:%SZ".freeze
46
+ @fmt = "[%s] %5s -- %s: %s\n".freeze
47
+ end
48
+ def call(severity, time, program, msg)
49
+ @fmt % [time.utc.strftime(@time), severity, program, msg2str(msg)]
50
+ end
51
+ end.new
52
+ end
53
+ @@logger
54
+ end
55
+ end
56
+ end
57
+
58
+ %w[
59
+ active_record
60
+ base64
61
+ bcrypt
62
+ digest/sha1
63
+ em-http
64
+ em-hiredis
65
+ eventmachine
66
+ fiber
67
+ fileutils
68
+ http/parser
69
+ logger
70
+ nokogiri
71
+ openssl
72
+ resolv
73
+ set
74
+ socket
75
+ uri
76
+ yaml
77
+ UPnP
78
+
79
+ vines/log
80
+ vines/jid
81
+
82
+ vines/stanza
83
+ vines/stanza/iq
84
+ vines/stanza/iq/query
85
+ vines/stanza/iq/auth
86
+ vines/stanza/iq/disco_info
87
+ vines/stanza/iq/disco_items
88
+ vines/stanza/iq/error
89
+ vines/stanza/iq/ping
90
+ vines/stanza/iq/private_storage
91
+ vines/stanza/iq/register
92
+ vines/stanza/iq/result
93
+ vines/stanza/iq/roster
94
+ vines/stanza/iq/session
95
+ vines/stanza/iq/vcard
96
+ vines/stanza/iq/version
97
+ vines/stanza/message
98
+ vines/stanza/presence
99
+ vines/stanza/presence/error
100
+ vines/stanza/presence/probe
101
+ vines/stanza/presence/subscribe
102
+ vines/stanza/presence/subscribed
103
+ vines/stanza/presence/unavailable
104
+ vines/stanza/presence/unsubscribe
105
+ vines/stanza/presence/unsubscribed
106
+ vines/stanza/pubsub
107
+ vines/stanza/pubsub/create
108
+ vines/stanza/pubsub/delete
109
+ vines/stanza/pubsub/publish
110
+ vines/stanza/pubsub/subscribe
111
+ vines/stanza/pubsub/unsubscribe
112
+
113
+ vines/storage
114
+ vines/storage/local
115
+ vines/storage/null
116
+ vines/storage/sql
117
+
118
+ vines/config
119
+ vines/config/host
120
+ vines/config/port
121
+ vines/config/pubsub
122
+
123
+ vines/store
124
+ vines/contact
125
+ vines/daemon
126
+ vines/error
127
+ vines/kit
128
+ vines/router
129
+ vines/token_bucket
130
+ vines/user
131
+ vines/version
132
+ vines/xmpp_server
133
+
134
+ vines/cluster
135
+ vines/cluster/connection
136
+ vines/cluster/publisher
137
+ vines/cluster/pubsub
138
+ vines/cluster/sessions
139
+ vines/cluster/subscriber
140
+
141
+ vines/stream
142
+ vines/stream/sasl
143
+ vines/stream/state
144
+ vines/stream/parser
145
+
146
+ vines/stream/client
147
+ vines/stream/client/session
148
+ vines/stream/client/start
149
+ vines/stream/client/tls
150
+ vines/stream/client/auth_restart
151
+ vines/stream/client/auth
152
+ vines/stream/client/bind_restart
153
+ vines/stream/client/bind
154
+ vines/stream/client/ready
155
+ vines/stream/client/closed
156
+
157
+ vines/stream/component
158
+ vines/stream/component/start
159
+ vines/stream/component/handshake
160
+ vines/stream/component/ready
161
+
162
+ vines/stream/http
163
+ vines/stream/http/session
164
+ vines/stream/http/sessions
165
+ vines/stream/http/request
166
+ vines/stream/http/start
167
+ vines/stream/http/auth
168
+ vines/stream/http/bind_restart
169
+ vines/stream/http/bind
170
+ vines/stream/http/ready
171
+
172
+ vines/stream/server
173
+ vines/stream/server/start
174
+ vines/stream/server/tls
175
+ vines/stream/server/auth_restart
176
+ vines/stream/server/auth
177
+ vines/stream/server/final_restart
178
+ vines/stream/server/ready
179
+
180
+ vines/stream/server/outbound/start
181
+ vines/stream/server/outbound/tls
182
+ vines/stream/server/outbound/tls_result
183
+ vines/stream/server/outbound/auth_restart
184
+ vines/stream/server/outbound/auth
185
+ vines/stream/server/outbound/auth_result
186
+ vines/stream/server/outbound/final_restart
187
+ vines/stream/server/outbound/final_features
188
+
189
+ vines/command/bcrypt
190
+ vines/command/cert
191
+ vines/command/init
192
+ vines/command/restart
193
+ vines/command/schema
194
+ vines/command/start
195
+ vines/command/stop
196
+ vines/command/register
197
+ ].each {|f| require f }
@@ -0,0 +1,246 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ # Server instances may be connected to one another in a cluster so they
5
+ # can host a single chat domain, or set of domains, across many servers,
6
+ # transparently to users. A redis database is used for the session routing
7
+ # table, mapping JIDs to their node's location. Redis pubsub channels are
8
+ # used to communicate amongst nodes.
9
+ #
10
+ # Using a shared in-memory cache, like redis, rather than synchronizing the
11
+ # cache to each node, allows us to add cluster nodes dynamically, without
12
+ # updating all other nodes' config files. It also greatly reduces the amount
13
+ # of memory required by the chat server processes.
14
+ class Cluster
15
+ include Vines::Log
16
+
17
+ attr_reader :id
18
+
19
+ %w[host port database password].each do |name|
20
+ define_method(name) do |*args|
21
+ if args.first
22
+ @connection.send("#{name}=", args.first)
23
+ else
24
+ @connection.send(name)
25
+ end
26
+ end
27
+ end
28
+
29
+ def initialize(config, &block)
30
+ @config, @id = config, Kit.uuid
31
+ @connection = Connection.new
32
+ @sessions = Sessions.new(self)
33
+ @publisher = Publisher.new(self)
34
+ @subscriber = Subscriber.new(self)
35
+ @pubsub = PubSub.new(self)
36
+ instance_eval(&block)
37
+ end
38
+
39
+ # Join this node to the cluster by broadcasting its state to the
40
+ # other nodes, subscribing to redis channels, and scheduling periodic
41
+ # heartbeat broadcasts. This method must be called after initialize
42
+ # or this node will not be a cluster member.
43
+ def start
44
+ @connection.connect
45
+ @publisher.broadcast(:online)
46
+ @subscriber.subscribe
47
+
48
+ EM.add_periodic_timer(1) { heartbeat }
49
+
50
+ at_exit do
51
+ @publisher.broadcast(:offline)
52
+ @sessions.delete_all(@id)
53
+ end
54
+ end
55
+
56
+ # Returns any streams hosted at remote nodes for these JIDs. The streams act
57
+ # like normal EM::Connections, but are actually proxies that route stanzas
58
+ # over redis pubsub channels to remote nodes.
59
+ def remote_sessions(*jids)
60
+ @sessions.find(*jids).map do |session|
61
+ StreamProxy.new(self, session)
62
+ end
63
+ end
64
+
65
+ # Persist the user's session to the shared redis cache so that other cluster
66
+ # nodes can locate the node hosting this user's connection and route messages
67
+ # to them.
68
+ def save_session(jid, attrs)
69
+ @sessions.save(jid, attrs)
70
+ end
71
+
72
+ # Remove this user from the cluster routing table so that no further stanzas
73
+ # may be routed to them. This must be called when the user's session is
74
+ # terminated, either by logout or stream disconnect.
75
+ def delete_session(jid)
76
+ @sessions.delete(jid)
77
+ end
78
+
79
+ # Remove all user sessions from the routing table associated with the
80
+ # given node ID. Cluster nodes call this themselves during normal shutdown.
81
+ # However, if a node dies without being properly shutdown, the other nodes
82
+ # will cleanup its sessions when they detect the node is offline.
83
+ def delete_sessions(node)
84
+ @sessions.delete_all(node)
85
+ end
86
+
87
+ # Notify the session store that this node is still alive. The node
88
+ # broadcasts its current time, so all cluster members' clocks don't
89
+ # necessarily need to be in sync.
90
+ def poke(node, time)
91
+ @sessions.poke(node, time)
92
+ end
93
+
94
+ # Send the stanza to the node hosting the user's session. The stanza is
95
+ # published to the channel to which the remote node is listening for
96
+ # messages.
97
+ def route(stanza, node)
98
+ @publisher.route(stanza, node)
99
+ end
100
+
101
+ # Notify the remote node that the user's roster has changed and it should
102
+ # reload the user from storage.
103
+ def update_user(jid, node)
104
+ @publisher.update_user(jid, node)
105
+ end
106
+
107
+ # Return the shared redis connection for most queries to use.
108
+ def connection
109
+ @connection.connect
110
+ end
111
+
112
+ # Create a new redis connection.
113
+ def connect
114
+ @connection.create
115
+ end
116
+
117
+ # Turn an asynchronous redis query into a blocking call by pausing the
118
+ # fiber in which this code is running. Return the result of the query
119
+ # from this method, rather than passing it to a callback block.
120
+ def query(name, *args)
121
+ fiber, yielding = Fiber.current, true
122
+ req = connection.send(name, *args)
123
+ req.errback { fiber.resume rescue yielding = false }
124
+ req.callback {|response| fiber.resume(response) }
125
+ Fiber.yield if yielding
126
+ end
127
+
128
+ # Return the connected streams for this user, without any proxy streams
129
+ # to remote cluster nodes (locally connected streams only).
130
+ def connected_resources(jid)
131
+ @config.router.connected_resources(jid, jid, false)
132
+ end
133
+
134
+ # Return the Storage implementation for this domain or nil if the
135
+ # domain is not hosted here.
136
+ def storage(domain)
137
+ @config.storage(domain)
138
+ end
139
+
140
+ # Create a pubsub topic (a.k.a. node), in the given domain, to which
141
+ # messages may be published. The domain argument will be one of the
142
+ # configured pubsub subdomains in conf/config.rb (e.g. games.wonderland.lit,
143
+ # topics.wonderland.lit, etc).
144
+ def add_pubsub_node(domain, node)
145
+ @pubsub.add_node(domain, node)
146
+ end
147
+
148
+ # Remove a pubsub topic so messages may no longer be broadcast to it.
149
+ def delete_pubsub_node(domain, node)
150
+ @pubsub.delete_node(domain, node)
151
+ end
152
+
153
+ # Subscribe the JID to the pubsub topic so it will receive any messages
154
+ # published to it.
155
+ def subscribe_pubsub(domain, node, jid)
156
+ @pubsub.subscribe(domain, node, jid)
157
+ end
158
+
159
+ # Unsubscribe the JID from the pubsub topic, deregistering its interest
160
+ # in receiving any messages published to it.
161
+ def unsubscribe_pubsub(domain, node, jid)
162
+ @pubsub.unsubscribe(domain, node, jid)
163
+ end
164
+
165
+ # Unsubscribe the JID from all pubsub topics. This is useful when the
166
+ # JID's session ends by logout or disconnect.
167
+ def unsubscribe_all_pubsub(domain, jid)
168
+ @pubsub.unsubscribe_all(domain, jid)
169
+ end
170
+
171
+ # Return true if the pubsub topic exists and messages may be published to it.
172
+ def pubsub_node?(domain, node)
173
+ @pubsub.node?(domain, node)
174
+ end
175
+
176
+ # Return true if the JID is a registered subscriber to the pubsub topic and
177
+ # messages published to it should be routed to the JID.
178
+ def pubsub_subscribed?(domain, node, jid)
179
+ @pubsub.subscribed?(domain, node, jid)
180
+ end
181
+
182
+ # Return a list of JIDs subscribed to the pubsub topic.
183
+ def pubsub_subscribers(domain, node)
184
+ @pubsub.subscribers(domain, node)
185
+ end
186
+
187
+ private
188
+
189
+ # Call this method once per second to broadcast this node's heartbeat and
190
+ # expire stale user sessions. This method must not raise exceptions or the
191
+ # timer will stop.
192
+ def heartbeat
193
+ @publisher.broadcast(:heartbeat)
194
+ @sessions.expire
195
+ rescue => e
196
+ log.error("Cluster session cleanup failed: #{e}")
197
+ end
198
+
199
+ # StreamProxy behaves like an EM::Connection so that stanzas may be sent to
200
+ # remote nodes just as they are to locally connected streams. The rest of the
201
+ # system doesn't know or care that these "streams" send their traffic over
202
+ # redis pubsub channels.
203
+ class StreamProxy
204
+ attr_reader :user
205
+
206
+ def initialize(cluster, session)
207
+ @cluster, @user = cluster, UserProxy.new(cluster, session)
208
+ @node, @available, @interested, @presence =
209
+ session.values_at('node', 'available', 'interested', 'presence')
210
+
211
+ unless @presence.nil? || @presence.empty?
212
+ @presence = Nokogiri::XML(@presence).root rescue nil
213
+ end
214
+ end
215
+
216
+ def available?
217
+ @available
218
+ end
219
+
220
+ def interested?
221
+ @interested
222
+ end
223
+
224
+ def last_broadcast_presence
225
+ @presence
226
+ end
227
+
228
+ def write(stanza)
229
+ @cluster.route(stanza, @node)
230
+ end
231
+ end
232
+
233
+ # Proxy User#update_from calls to remote cluster nodes over redis
234
+ # pubsub channels.
235
+ class UserProxy < User
236
+ def initialize(cluster, session)
237
+ super(jid: session['jid'])
238
+ @cluster, @node = cluster, session['node']
239
+ end
240
+
241
+ def update_from(user)
242
+ @cluster.update_user(@jid.bare, @node)
243
+ end
244
+ end
245
+ end
246
+ end