vinesmod 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
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