colossus 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/colossus.rb +95 -0
- data/lib/colossus/configuration.rb +45 -0
- data/lib/colossus/engines/memory/client_session.rb +20 -0
- data/lib/colossus/engines/memory/client_session_store.rb +55 -0
- data/lib/colossus/engines/memory/memory.rb +96 -0
- data/lib/colossus/faye/extension.rb +162 -0
- data/lib/colossus/verifier.rb +31 -0
- data/lib/colossus/version.rb +4 -0
- data/lib/colossus/writer_client.rb +62 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9ea35d4325a20e16ba03e449605e9afb9fef70cb
|
4
|
+
data.tar.gz: 4624e6e7bea9aab4bc858cd81663e7f5f4e28b42
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e4d5d0f2cb89a7a033ab391d5d93c865bc743d5a19934227a134442d25508018249229334559ee48fedd57cc0d15a3fe59e781f3ac39618819ffd841682a3ea3
|
7
|
+
data.tar.gz: ab60db5e7d318ef1f32934dba3e0076773e2b50fafc01f2e3ba689ccdbc7fd5841dab8cedd7e1bc557d80e64534e1fb23328f945571afbec002368d97e3a4e1a
|
data/lib/colossus.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'observer'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
require 'colossus/configuration'
|
5
|
+
require 'colossus/verifier'
|
6
|
+
require 'colossus/writer_client'
|
7
|
+
|
8
|
+
require 'colossus/engines/memory/memory'
|
9
|
+
require 'colossus/engines/memory/client_session'
|
10
|
+
require 'colossus/engines/memory/client_session_store'
|
11
|
+
|
12
|
+
require 'colossus/faye/extension'
|
13
|
+
|
14
|
+
# Top Level Class.
|
15
|
+
# The public API of the Gem.
|
16
|
+
class Colossus
|
17
|
+
include Observable
|
18
|
+
|
19
|
+
attr_reader :engine, :verifier
|
20
|
+
|
21
|
+
# Initialize Colossus
|
22
|
+
#
|
23
|
+
# @param ttl [Integer] the seconds before a user without emitting heartbeat
|
24
|
+
# is considered disconnected
|
25
|
+
#
|
26
|
+
# @param engine [Engine] the engine which implements the needed methods
|
27
|
+
# to work with Colossus
|
28
|
+
#
|
29
|
+
# @return [Colossus]
|
30
|
+
def initialize(ttl = Colossus.config.ttl,
|
31
|
+
engine = Colossus.config.engine,
|
32
|
+
secret = Colossus.config.secret_key,
|
33
|
+
writer_token = Colossus.config.writer_token)
|
34
|
+
@engine = engine.new(ttl.to_i)
|
35
|
+
@engine.add_observer(self)
|
36
|
+
@verifier = Colossus::Verifier.new(secret, writer_token)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Set the status of a user on a specificed client. A client could be
|
40
|
+
# a Websocket session (if the user has 2 tabs opened) or anything else.
|
41
|
+
#
|
42
|
+
# @param user_id [#to_s] The unique identifier of a user
|
43
|
+
# @param client_id [#to_s] The unique identifier of a client
|
44
|
+
# @param status [#to_s] The status of a the user, it can be active,
|
45
|
+
# away or disconnected.
|
46
|
+
#
|
47
|
+
# @return [Boolean] Return true if the status has changed if not false.
|
48
|
+
def set(user_id, client_id, status)
|
49
|
+
engine.set(user_id.to_s, client_id.to_s, status.to_s)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get the status of a specified user, it analyzes the status
|
53
|
+
# of all the sessions.
|
54
|
+
# It returns :
|
55
|
+
#
|
56
|
+
# - active if one or more clients are active.
|
57
|
+
# - away if one or more clients are away.
|
58
|
+
# - disconnected.
|
59
|
+
#
|
60
|
+
# @param user_id [#to_s] The unique identifier of a user
|
61
|
+
#
|
62
|
+
# @return status [String]
|
63
|
+
def get(user_id)
|
64
|
+
engine.get(user_id.to_s)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param user_ids [Array<#to_s>] An array of user ids
|
68
|
+
# (see #get)
|
69
|
+
def get_multi(*user_ids)
|
70
|
+
engine.get_multi(user_ids.map(&:to_s))
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return Hash{String => String} User_id keys, statuses values
|
74
|
+
def get_all
|
75
|
+
engine.get_all
|
76
|
+
end
|
77
|
+
|
78
|
+
# Reset all the data (useful for specs)
|
79
|
+
def reset!
|
80
|
+
engine.reset!
|
81
|
+
end
|
82
|
+
|
83
|
+
# Generate a token for the given user_id
|
84
|
+
def generate_user_token(user_id)
|
85
|
+
verifier.generate_user_token(user_id)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Method used when the engine notify a change
|
89
|
+
#
|
90
|
+
# @!visibility private
|
91
|
+
def update(user_id, status)
|
92
|
+
changed
|
93
|
+
notify_observers(user_id, status)
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Main Colossus module
|
3
|
+
class Colossus
|
4
|
+
# Handles all the configuration
|
5
|
+
class Configuration
|
6
|
+
attr_accessor :ttl, :seconds_before_ttl_check, :engine,
|
7
|
+
:secret_key, :writer_token
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@ttl = 10
|
11
|
+
@seconds_before_ttl_check = 2
|
12
|
+
@engine = Colossus::Engine::Memory
|
13
|
+
@secret_key = ''
|
14
|
+
@writer_token = ''
|
15
|
+
end
|
16
|
+
end # class Configuration
|
17
|
+
|
18
|
+
def self.configure
|
19
|
+
yield(configuration) if block_given?
|
20
|
+
configuration
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configuration
|
24
|
+
@configuration ||= Configuration.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.config
|
28
|
+
configuration
|
29
|
+
end
|
30
|
+
|
31
|
+
# Help ?
|
32
|
+
# rubocop:disable TrivialAccessors
|
33
|
+
def self.configuration=(configuration)
|
34
|
+
@configuration = configuration
|
35
|
+
end
|
36
|
+
# rubocop:enable
|
37
|
+
|
38
|
+
def self.config=(configuration)
|
39
|
+
self.configuration = configuration
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.reset_configuration
|
43
|
+
@configuration = Configuration.new
|
44
|
+
end
|
45
|
+
end # Colossus
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Colossus
|
2
|
+
module Engine
|
3
|
+
class Memory
|
4
|
+
# Represent the status and the information of a given user.
|
5
|
+
class ClientSession
|
6
|
+
attr_reader :status, :last_seen
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@status = 'disconnected'
|
10
|
+
@last_seen = Time.now
|
11
|
+
end
|
12
|
+
|
13
|
+
def status=(given_status)
|
14
|
+
@last_seen = Time.now
|
15
|
+
@status = given_status
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Colossus
|
2
|
+
module Engine
|
3
|
+
class Memory
|
4
|
+
# Represents all the different sessions of a user. It can find
|
5
|
+
# the global status of the user given all the different status.
|
6
|
+
class ClientSessionStore
|
7
|
+
attr_reader :last_status
|
8
|
+
attr_accessor :sessions
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@sessions = Hash.new do |hash, key|
|
12
|
+
hash[key] = Colossus::Engine::Memory::ClientSession.new
|
13
|
+
end
|
14
|
+
@last_status = 'disconnected'
|
15
|
+
end
|
16
|
+
|
17
|
+
def status
|
18
|
+
sessions.values.reduce('disconnected') do |memo, session|
|
19
|
+
case session.status
|
20
|
+
when 'active'
|
21
|
+
session.status
|
22
|
+
when 'away'
|
23
|
+
memo == 'active' ? memo : session.status
|
24
|
+
else
|
25
|
+
memo
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def last_seen
|
31
|
+
sessions.values.reduce(Time.new(0)) do |memo, session|
|
32
|
+
session.last_seen > memo ? session.last_seen : memo
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def status_changed?
|
37
|
+
last_status != status
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](session_id)
|
41
|
+
sessions[session_id]
|
42
|
+
end
|
43
|
+
|
44
|
+
def []=(session_id, session_status)
|
45
|
+
@last_status = status
|
46
|
+
sessions[session_id].status = session_status
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete(session_id)
|
50
|
+
sessions.delete(session_id)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
class Colossus
|
2
|
+
module Engine
|
3
|
+
# The Memory Engine is a non-distributed engine.
|
4
|
+
# Based on EventMachine in order to provide the ttl to
|
5
|
+
# disconnect clients.
|
6
|
+
class Memory
|
7
|
+
include Observable
|
8
|
+
|
9
|
+
attr_reader :client_sessions, :ttl, :mutex
|
10
|
+
|
11
|
+
def initialize(ttl)
|
12
|
+
@client_sessions = Hash.new do |hash, key|
|
13
|
+
hash[key] = Colossus::Engine::Memory::ClientSessionStore.new
|
14
|
+
end
|
15
|
+
@ttl = ttl
|
16
|
+
@mutex = Mutex.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def user_changed(user_id, status)
|
20
|
+
changed
|
21
|
+
notify_observers(user_id, status)
|
22
|
+
end
|
23
|
+
|
24
|
+
def set(user_id, client_id, given_status)
|
25
|
+
mutex.synchronize do
|
26
|
+
if given_status == 'disconnected'
|
27
|
+
client_sessions[user_id].delete(client_id)
|
28
|
+
else
|
29
|
+
client_sessions[user_id][client_id] = given_status
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if client_sessions[user_id].status_changed?
|
34
|
+
status = client_sessions[user_id].status
|
35
|
+
delete(user_id) if status == 'disconnected'
|
36
|
+
user_changed(user_id, status)
|
37
|
+
return true
|
38
|
+
end
|
39
|
+
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def get(user_id)
|
44
|
+
client_sessions[user_id]
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_multi(*user_ids)
|
48
|
+
user_ids.map { |user_id| get(user_id) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_all
|
52
|
+
statuses = {}
|
53
|
+
client_sessions.each_pair do |user_id, session_store|
|
54
|
+
statuses[user_id] = session_store.status
|
55
|
+
end
|
56
|
+
statuses
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete(user_id)
|
60
|
+
mutex.synchronize do
|
61
|
+
client_sessions.delete(user_id)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def reset!
|
66
|
+
mutex.synchronize do
|
67
|
+
@client_sessions = Hash.new do |hash, key|
|
68
|
+
hash[key] = Colossus::Engine::Memory::ClientSessionStore.new
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def new_periodic_ttl
|
74
|
+
secs_ttl = Colossus.config.seconds_before_ttl_check
|
75
|
+
@periodic_ttl = EM::Synchrony.add_periodic_timer(secs_ttl) do
|
76
|
+
client_sessions.each_pair do |user_id, session_store|
|
77
|
+
sessions_dupped = session_store.sessions.dup
|
78
|
+
|
79
|
+
session_store.sessions.each_pair do |session_id, session|
|
80
|
+
if (session.last_seen + ttl) < Time.now
|
81
|
+
sessions_dupped.delete(session_id)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
mutex.synchronize { session_store.sessions = sessions_dupped }
|
86
|
+
|
87
|
+
if (session_store.last_seen + ttl) < Time.now
|
88
|
+
delete(user_id)
|
89
|
+
user_changed(user_id, 'disconnected')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
class Colossus
|
2
|
+
module Faye
|
3
|
+
# Faye extension implementing all the presence, authorization,
|
4
|
+
# authentification and push logic.
|
5
|
+
class Extension
|
6
|
+
attr_reader :colossus, :faye, :faye_client
|
7
|
+
|
8
|
+
VALID_STATUSES = %w(disconnected away active).freeze
|
9
|
+
|
10
|
+
def initialize(faye, colossus = Colossus.new)
|
11
|
+
@colossus = colossus
|
12
|
+
@faye = faye
|
13
|
+
faye.add_extension(self)
|
14
|
+
@faye_client = faye.get_client
|
15
|
+
colossus_faye_extension = Colossus::WriterClient::FayeExtension.
|
16
|
+
new(colossus.verifier.writer_token)
|
17
|
+
faye_client.add_extension(colossus_faye_extension)
|
18
|
+
end
|
19
|
+
|
20
|
+
def incoming(message, _request, callback)
|
21
|
+
if !acceptable?(message)
|
22
|
+
handle_invalid_token(message)
|
23
|
+
message.delete('data')
|
24
|
+
message.delete('ext')
|
25
|
+
elsif message['ext']['user_token']
|
26
|
+
handle_user_action(message)
|
27
|
+
message.delete('data')
|
28
|
+
message.delete('ext')
|
29
|
+
elsif message['ext']['writer_token']
|
30
|
+
handle_server_action(message)
|
31
|
+
message.delete('ext')
|
32
|
+
end
|
33
|
+
|
34
|
+
callback.call(message)
|
35
|
+
end
|
36
|
+
|
37
|
+
def acceptable?(message)
|
38
|
+
message['ext'] &&
|
39
|
+
(message['ext']['user_token'] ||
|
40
|
+
message['ext']['writer_token'])
|
41
|
+
end
|
42
|
+
|
43
|
+
def handle_user_action(message)
|
44
|
+
if message['channel'] == '/meta/subscribe'
|
45
|
+
handle_subscribe(message)
|
46
|
+
elsif message['channel'].start_with?('/meta/')
|
47
|
+
message
|
48
|
+
elsif message['channel'].start_with?('/users/')
|
49
|
+
handle_set_status(message)
|
50
|
+
else
|
51
|
+
message['error'] = 'Unknown Action'
|
52
|
+
end
|
53
|
+
|
54
|
+
message
|
55
|
+
end
|
56
|
+
|
57
|
+
def handle_server_action(message)
|
58
|
+
token = message['ext'] && message['ext']['writer_token']
|
59
|
+
|
60
|
+
unless token && colossus.verifier.verify_writer_token(token)
|
61
|
+
message['error'] = 'Invalid Token'
|
62
|
+
message.delete('data')
|
63
|
+
return message
|
64
|
+
end
|
65
|
+
|
66
|
+
if message['channel'].start_with?('/meta/')
|
67
|
+
message.delete('data')
|
68
|
+
message
|
69
|
+
elsif message['channel'].start_with?('/presences/request/')
|
70
|
+
handle_presence_request(message)
|
71
|
+
elsif message['channel'].start_with?('/presences/response/')
|
72
|
+
handle_presence_response(message)
|
73
|
+
elsif message['channel'].start_with?('/users/')
|
74
|
+
handle_publish(message)
|
75
|
+
else
|
76
|
+
message['error'] = 'Unknown Action'
|
77
|
+
message.delete('data')
|
78
|
+
end
|
79
|
+
|
80
|
+
message
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_presence_request(message)
|
84
|
+
user_ids = message['data'] && message['data']['user_ids']
|
85
|
+
presence_id = message['channel'].partition('/presences/request/').last
|
86
|
+
|
87
|
+
if user_ids && user_ids.is_a?(Array)
|
88
|
+
statuses = colossus.get_multi(user_ids)
|
89
|
+
message['data'].delete('user_ids')
|
90
|
+
faye_client.publish("/presences/response/#{presence_id}", { 'statuses' => Hash[user_ids.zip(statuses)] })
|
91
|
+
message
|
92
|
+
elsif user_ids == nil
|
93
|
+
statuses = colossus.get_all
|
94
|
+
faye_client.publish("/presences/response/#{presence_id}", { 'statuses' => statuses })
|
95
|
+
message
|
96
|
+
else
|
97
|
+
message.delete('data')
|
98
|
+
message['error'] = 'Invalid user_ids data'
|
99
|
+
message
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def handle_presence_response(message)
|
104
|
+
message
|
105
|
+
end
|
106
|
+
|
107
|
+
def handle_publish(message)
|
108
|
+
message
|
109
|
+
end
|
110
|
+
|
111
|
+
def handle_invalid_token(message)
|
112
|
+
message['error'] = 'Invalid Token'
|
113
|
+
message
|
114
|
+
end
|
115
|
+
|
116
|
+
def handle_subscribe(message)
|
117
|
+
token = message['ext']['user_token']
|
118
|
+
user_id = message['subscription'].partition('/users/').last
|
119
|
+
|
120
|
+
if invalid_user_channel?(user_id)
|
121
|
+
message['error'] = 'The only accepted channel_name is users/:user_id'
|
122
|
+
return message
|
123
|
+
end
|
124
|
+
|
125
|
+
unless colossus.verifier.verify_token(token, user_id)
|
126
|
+
message['error'] = 'Invalid Token'
|
127
|
+
end
|
128
|
+
|
129
|
+
message
|
130
|
+
end
|
131
|
+
|
132
|
+
def handle_set_status(message)
|
133
|
+
token = message['ext']['user_token']
|
134
|
+
user_id = message['channel'].partition('/users/').last
|
135
|
+
status = message['data'] && message['data']['status']
|
136
|
+
|
137
|
+
if invalid_user_channel?(user_id)
|
138
|
+
message['error'] = 'The only accepted channel_name is users/:user_id'
|
139
|
+
return message
|
140
|
+
end
|
141
|
+
|
142
|
+
unless status && VALID_STATUSES.include?(status)
|
143
|
+
message['error'] = 'Invalid Status'
|
144
|
+
return message
|
145
|
+
end
|
146
|
+
|
147
|
+
unless colossus.verifier.verify_token(token, user_id)
|
148
|
+
message['error'] = 'Invalid Token'
|
149
|
+
return message
|
150
|
+
end
|
151
|
+
|
152
|
+
colossus.set(user_id, message['clientId'], status)
|
153
|
+
|
154
|
+
message
|
155
|
+
end
|
156
|
+
|
157
|
+
def invalid_user_channel?(user_id)
|
158
|
+
user_id.empty? || user_id.include?('*') || user_id.include?('/')
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Colossus
|
2
|
+
# Implements the verification logic based on SHA1 in order
|
3
|
+
# to avoid timing attacks. (cf Faye doc)
|
4
|
+
class Verifier
|
5
|
+
attr_reader :sha1, :secret, :writer_token
|
6
|
+
|
7
|
+
def initialize(secret = Colossus.config.secret_key,
|
8
|
+
writer_token = Colossus.config.writer_token)
|
9
|
+
@sha1 = OpenSSL::Digest.new('sha1')
|
10
|
+
@secret = secret
|
11
|
+
@writer_token = writer_token
|
12
|
+
end
|
13
|
+
|
14
|
+
def verify_token(token_given, user_id)
|
15
|
+
expected_token = generate_user_token(user_id)
|
16
|
+
expected_hash = Digest::SHA1.hexdigest(expected_token)
|
17
|
+
actual_hash = Digest::SHA1.hexdigest(token_given)
|
18
|
+
expected_hash == actual_hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def verify_writer_token(token_given)
|
22
|
+
expected_hash = Digest::SHA1.hexdigest(writer_token)
|
23
|
+
actual_hash = Digest::SHA1.hexdigest(token_given)
|
24
|
+
expected_hash == actual_hash
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_user_token(user_id)
|
28
|
+
OpenSSL::HMAC.hexdigest(sha1, secret, user_id)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class Colossus
|
2
|
+
class WriterClient
|
3
|
+
attr_reader :url, :writer_token, :time_out
|
4
|
+
|
5
|
+
def initialize(url, writer_token, time_out = 2)
|
6
|
+
@url = url
|
7
|
+
@writer_token = writer_token
|
8
|
+
@time_out = time_out
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_presences(optional_user_ids = nil)
|
12
|
+
user_ids = Array(optional_user_ids) if optional_user_ids
|
13
|
+
unique_token = generate_unique_token
|
14
|
+
EM.synchrony do
|
15
|
+
EM::Synchrony.add_timer(time_out) { raise "Presence request timed out" }
|
16
|
+
EM::Synchrony.sync(faye_client.subscribe("/presences/response/#{unique_token}") { |message| return message['statuses'] })
|
17
|
+
EM::Synchrony.sync(faye_client.publish("/presences/request/#{unique_token}", user_ids))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def push_message(user_ids, message)
|
22
|
+
user_ids = Array(user_ids)
|
23
|
+
EM.synchrony do
|
24
|
+
user_ids.each do |user_id|
|
25
|
+
EM::Synchrony.sync(faye_client.publish("/users/#{user_id}", message))
|
26
|
+
end
|
27
|
+
EM.stop
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def faye_client
|
34
|
+
faye_client = ::Faye::Client.new(url)
|
35
|
+
faye_client.add_extension(FayeExtension.new(writer_token))
|
36
|
+
faye_client
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate_unique_token
|
40
|
+
SecureRandom.hex(8)
|
41
|
+
end
|
42
|
+
|
43
|
+
class FayeExtension
|
44
|
+
attr_reader :token
|
45
|
+
|
46
|
+
def initialize(token)
|
47
|
+
@token = token
|
48
|
+
end
|
49
|
+
|
50
|
+
def incoming(message, callback)
|
51
|
+
callback.call(message)
|
52
|
+
end
|
53
|
+
|
54
|
+
def outgoing(message, callback)
|
55
|
+
message['ext'] ||= {}
|
56
|
+
message['ext']['writer_token'] = 'WRITER_TOKEN'
|
57
|
+
|
58
|
+
callback.call(message)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: colossus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- antoinelyset
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faye
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: em-synchrony
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
description: Colossus is a Push and Presence pure Ruby server. It uses Faye internally.
|
42
|
+
email:
|
43
|
+
- antoinelyset+github@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/colossus.rb
|
49
|
+
- lib/colossus/configuration.rb
|
50
|
+
- lib/colossus/engines/memory/client_session.rb
|
51
|
+
- lib/colossus/engines/memory/client_session_store.rb
|
52
|
+
- lib/colossus/engines/memory/memory.rb
|
53
|
+
- lib/colossus/faye/extension.rb
|
54
|
+
- lib/colossus/verifier.rb
|
55
|
+
- lib/colossus/version.rb
|
56
|
+
- lib/colossus/writer_client.rb
|
57
|
+
homepage: https://github.com/antoinelyset/colossus
|
58
|
+
licenses: []
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.2.2
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: Colossus, Web Push & Presence made easy.
|
80
|
+
test_files: []
|
81
|
+
has_rdoc:
|