meshchat 0.8.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +39 -11
  3. data/lib/meshchat.rb +48 -42
  4. data/lib/meshchat/configuration.rb +14 -0
  5. data/lib/meshchat/configuration/app_config.rb +63 -0
  6. data/lib/meshchat/configuration/database.rb +41 -0
  7. data/lib/meshchat/{config → configuration}/hash_file.rb +7 -6
  8. data/lib/meshchat/configuration/identity.rb +79 -0
  9. data/lib/meshchat/{config → configuration}/settings.rb +22 -26
  10. data/lib/meshchat/debug.rb +69 -0
  11. data/lib/meshchat/encryption.rb +7 -2
  12. data/lib/meshchat/encryption/aes_rsa.rb +2 -1
  13. data/lib/meshchat/encryption/passthrough.rb +5 -3
  14. data/lib/meshchat/locale/en.yml +14 -0
  15. data/lib/meshchat/models/node.rb +140 -0
  16. data/lib/meshchat/network.rb +19 -0
  17. data/lib/meshchat/network/dispatcher.rb +83 -0
  18. data/lib/meshchat/network/errors.rb +11 -0
  19. data/lib/meshchat/network/incoming.rb +13 -0
  20. data/lib/meshchat/network/incoming/message_decryptor.rb +51 -0
  21. data/lib/meshchat/network/incoming/message_processor.rb +75 -0
  22. data/lib/meshchat/network/incoming/request_processor.rb +30 -0
  23. data/lib/meshchat/network/local.rb +12 -0
  24. data/lib/meshchat/network/local/connection.rb +58 -0
  25. data/lib/meshchat/network/local/server.rb +69 -0
  26. data/lib/meshchat/network/message.rb +34 -0
  27. data/lib/meshchat/network/message/base.rb +139 -0
  28. data/lib/meshchat/network/message/chat.rb +9 -0
  29. data/lib/meshchat/network/message/disconnect.rb +21 -0
  30. data/lib/meshchat/network/message/emote.rb +9 -0
  31. data/lib/meshchat/network/message/factory.rb +80 -0
  32. data/lib/meshchat/network/message/node_list.rb +75 -0
  33. data/lib/meshchat/network/message/node_list_diff.rb +18 -0
  34. data/lib/meshchat/network/message/node_list_hash.rb +32 -0
  35. data/lib/meshchat/network/message/ping.rb +31 -0
  36. data/lib/meshchat/network/message/ping_reply.rb +12 -0
  37. data/lib/meshchat/network/message/whisper.rb +28 -0
  38. data/lib/meshchat/network/remote.rb +13 -0
  39. data/lib/meshchat/network/remote/connection.rb +28 -0
  40. data/lib/meshchat/network/remote/relay.rb +109 -0
  41. data/lib/meshchat/network/remote/relay_pool.rb +52 -0
  42. data/lib/meshchat/ui.rb +13 -0
  43. data/lib/meshchat/ui/cli.rb +48 -0
  44. data/lib/meshchat/ui/cli/base.rb +39 -0
  45. data/lib/meshchat/ui/cli/input_factory.rb +50 -0
  46. data/lib/meshchat/ui/cli/keyboard_line_input.rb +14 -0
  47. data/lib/meshchat/ui/command.rb +51 -0
  48. data/lib/meshchat/ui/command/base.rb +77 -0
  49. data/lib/meshchat/ui/command/bind.rb +47 -0
  50. data/lib/meshchat/ui/command/chat.rb +31 -0
  51. data/lib/meshchat/ui/command/config.rb +37 -0
  52. data/lib/meshchat/ui/command/emote.rb +23 -0
  53. data/lib/meshchat/ui/command/exit.rb +16 -0
  54. data/lib/meshchat/ui/command/help.rb +20 -0
  55. data/lib/meshchat/ui/command/identity.rb +16 -0
  56. data/lib/meshchat/ui/command/import.rb +42 -0
  57. data/lib/meshchat/ui/command/irb.rb +22 -0
  58. data/lib/meshchat/ui/command/offline.rb +23 -0
  59. data/lib/meshchat/ui/command/online.rb +18 -0
  60. data/lib/meshchat/ui/command/ping.rb +65 -0
  61. data/lib/meshchat/ui/command/ping_all.rb +19 -0
  62. data/lib/meshchat/ui/command/send_disconnect.rb +20 -0
  63. data/lib/meshchat/ui/command/server.rb +22 -0
  64. data/lib/meshchat/ui/command/share.rb +16 -0
  65. data/lib/meshchat/ui/command/whisper.rb +40 -0
  66. data/lib/meshchat/ui/display.rb +78 -0
  67. data/lib/meshchat/ui/display/base.rb +58 -0
  68. data/lib/meshchat/ui/display/manager.rb +59 -0
  69. data/lib/meshchat/ui/notifier.rb +9 -0
  70. data/lib/meshchat/ui/notifier/base.rb +33 -0
  71. data/lib/meshchat/version.rb +3 -2
  72. metadata +150 -80
  73. data/lib/meshchat/cli.rb +0 -188
  74. data/lib/meshchat/cli/base.rb +0 -13
  75. data/lib/meshchat/cli/input.rb +0 -37
  76. data/lib/meshchat/command/base.rb +0 -80
  77. data/lib/meshchat/command/bind.rb +0 -44
  78. data/lib/meshchat/command/chat.rb +0 -30
  79. data/lib/meshchat/command/config.rb +0 -34
  80. data/lib/meshchat/command/emote.rb +0 -20
  81. data/lib/meshchat/command/exit.rb +0 -13
  82. data/lib/meshchat/command/help.rb +0 -17
  83. data/lib/meshchat/command/identity.rb +0 -13
  84. data/lib/meshchat/command/import.rb +0 -41
  85. data/lib/meshchat/command/init.rb +0 -34
  86. data/lib/meshchat/command/irb.rb +0 -23
  87. data/lib/meshchat/command/listen.rb +0 -13
  88. data/lib/meshchat/command/offline.rb +0 -20
  89. data/lib/meshchat/command/online.rb +0 -15
  90. data/lib/meshchat/command/ping.rb +0 -65
  91. data/lib/meshchat/command/ping_all.rb +0 -15
  92. data/lib/meshchat/command/send_disconnect.rb +0 -15
  93. data/lib/meshchat/command/server.rb +0 -20
  94. data/lib/meshchat/command/share.rb +0 -13
  95. data/lib/meshchat/command/stop_listening.rb +0 -13
  96. data/lib/meshchat/command/whisper.rb +0 -38
  97. data/lib/meshchat/database.rb +0 -30
  98. data/lib/meshchat/display.rb +0 -33
  99. data/lib/meshchat/display/base.rb +0 -60
  100. data/lib/meshchat/display/manager.rb +0 -55
  101. data/lib/meshchat/instance.rb +0 -40
  102. data/lib/meshchat/message.rb +0 -41
  103. data/lib/meshchat/message/base.rb +0 -97
  104. data/lib/meshchat/message/chat.rb +0 -19
  105. data/lib/meshchat/message/disconnect.rb +0 -13
  106. data/lib/meshchat/message/emote.rb +0 -9
  107. data/lib/meshchat/message/node_list.rb +0 -63
  108. data/lib/meshchat/message/node_list_diff.rb +0 -15
  109. data/lib/meshchat/message/node_list_hash.rb +0 -33
  110. data/lib/meshchat/message/ping.rb +0 -32
  111. data/lib/meshchat/message/ping_reply.rb +0 -9
  112. data/lib/meshchat/message/relay.rb +0 -43
  113. data/lib/meshchat/message/whisper.rb +0 -36
  114. data/lib/meshchat/models/entry.rb +0 -104
  115. data/lib/meshchat/net/client.rb +0 -83
  116. data/lib/meshchat/net/listener/errors.rb +0 -11
  117. data/lib/meshchat/net/listener/request.rb +0 -48
  118. data/lib/meshchat/net/listener/request_processor.rb +0 -50
  119. data/lib/meshchat/net/listener/server.rb +0 -114
  120. data/lib/meshchat/net/request.rb +0 -29
  121. data/lib/meshchat/notifier/base.rb +0 -31
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ module Incoming
5
+ # decodes an encrypted message and handles it.
6
+ # also update's the info of the sender
7
+ class MessageProcessor
8
+ attr_reader :_network, :_location
9
+ attr_reader :_message_factory, :_message_dispatcher
10
+
11
+ def initialize(network: NETWORK_LOCAL, message_dispatcher: nil, location: nil)
12
+ @_network = network
13
+ @_message_dispatcher = message_dispatcher
14
+ @_message_factory = message_dispatcher._message_factory
15
+ @_location = location
16
+ end
17
+
18
+ # @param [String] encoded_message - the encrypted message as a string
19
+ def process(encoded_message)
20
+ request = MessageDecryptor.new(encoded_message, _message_factory)
21
+ message = request.message
22
+
23
+ Debug.receiving_message(message)
24
+
25
+ # show the message to the user, and update the information
26
+ # we have on the sender, so that we may reply to the
27
+ # correct location
28
+ update_sender_info(request._json)
29
+ Display.present_message message
30
+ end
31
+
32
+ # @param [String] encoded_message - the encrypted message as a string
33
+ def update_sender_info(json)
34
+ sender = json['sender']
35
+ # Note that sender['location'] should always reference
36
+ # the sender's local network address
37
+ network_location = sender['location']
38
+
39
+ # if the sender isn't currently marked as active,
40
+ # perform the server list exchange
41
+ node = Node.find_by_uid(sender['uid'])
42
+ raise Errors::Forbidden, 'node not found' if node.nil?
43
+
44
+ # if we are receiving a message from a node we had previously
45
+ # known to be offline, we need to do the node list hash dance
46
+ # with them to see if they know of any new members to the network
47
+ unless node.online?
48
+ node.update(on_local_network: true) if is_processing_for_local?
49
+ node.update(on_relay: true) if is_processing_for_relay?
50
+
51
+ nlh = _message_factory.create(Message::NODE_LIST_HASH)
52
+ _message_dispatcher.send_message(node: node, message: nlh)
53
+ end
54
+
55
+ # update the node's location/alias
56
+ # as they can change this info willy nilly
57
+ attributes = {
58
+ location_on_network: network_location,
59
+ alias_name: sender['alias']
60
+ }
61
+ attributes[:location_of_relay] = _location if is_processing_for_relay?
62
+ node.update(attributes)
63
+ end
64
+
65
+ def is_processing_for_relay?
66
+ _network == NETWORK_RELAY
67
+ end
68
+
69
+ def is_processing_for_local?
70
+ _network != NETWORK_RELAY
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ module Incoming
5
+ # all this does is pull the encrypted message out of
6
+ # the received request
7
+ class RequestProcessor
8
+ attr_reader :_message_processor
9
+
10
+ def initialize(network: NETWORK_LOCAL, message_dispatcher: nil, location: nil)
11
+ @_message_processor = MessageProcessor.new(
12
+ network: network,
13
+ message_dispatcher: message_dispatcher,
14
+ location: location)
15
+ end
16
+
17
+ # @param [String] request_body - the encrypted message as a json string
18
+ def process(request_body)
19
+ encoded_message = parse_content(request_body)
20
+ _message_processor.process(encoded_message)
21
+ end
22
+
23
+ def parse_content(content)
24
+ content = JSON.parse(content) if content.is_a?(String)
25
+ content['message']
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ module Local
5
+ extend ActiveSupport::Autoload
6
+ eager_autoload do
7
+ autoload :Server
8
+ autoload :Connection
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ require 'em-http-request'
3
+
4
+ module Meshchat
5
+ module Network
6
+ module Local
7
+ class Connection
8
+ attr_reader :_message_factory, :_message_dispatcher
9
+
10
+ def initialize(dispatcher, message_factory)
11
+ @_message_factory = message_factory
12
+ @_message_dispatcher = dispatcher
13
+
14
+ # async, won't prevent us from sending
15
+ start_server
16
+ end
17
+
18
+ def start_server
19
+ port = APP_CONFIG.user['port']
20
+ Display.info "listening on port #{port}"
21
+ EM.start_server('0.0.0.0', port,
22
+ Network::Local::Server, _message_dispatcher)
23
+ end
24
+
25
+ # @param [Node] node - the node describing the person you're sending a message to
26
+ # @param [JSON] encrypted_message - the message intended for the person at the location
27
+ # @param [Block] error_callback - what to do in case of failure
28
+ def send_message(node, encrypted_message, &error_callback)
29
+ payload = payload_for(encrypted_message)
30
+ create_http_request(node.location_on_network, payload, &error_callback)
31
+ end
32
+
33
+ def create_http_request(location, payload, &error_callback)
34
+ # TODO: what about https?
35
+ # maybe do the regex match: /https?:\/\//
36
+ location = 'http://' + location unless location.include?('http://')
37
+ http = EventMachine::HttpRequest.new(location).post(
38
+ body: payload,
39
+ head: {
40
+ 'Accept' => 'application/json',
41
+ 'Content-Type' => 'application/json'
42
+ })
43
+
44
+ http.errback &error_callback
45
+ # example things available in the callback
46
+ # p http.response_header.status
47
+ # p http.response_header
48
+ # p http.response
49
+ http.callback {}
50
+ end
51
+
52
+ def payload_for(encrypted_message)
53
+ { message: encrypted_message }.to_json
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ require 'evma_httpserver'
3
+
4
+ module Meshchat
5
+ module Network
6
+ module Local
7
+ # This is created every request
8
+ class Server < EM::Connection
9
+ include EM::HttpServer
10
+ attr_reader :_message_dispatcher, :_request_processor
11
+
12
+ OK = 200
13
+ BAD_REQUEST = 400
14
+ NOT_AUTHORIZED = 401
15
+ FORBIDDEN = 403
16
+ SERVER_ERROR = 500
17
+
18
+ def initialize(message_dispatcher)
19
+ @_message_dispatcher = message_dispatcher
20
+ @_request_processor = Incoming::RequestProcessor.new(
21
+ network: NETWORK_LOCAL,
22
+ message_dispatcher: message_dispatcher)
23
+ end
24
+
25
+ def process_http_request
26
+ # the http request details are available via the following instance variables:
27
+ # @http_protocol
28
+ # @http_request_method
29
+ # @http_cookie
30
+ # @http_if_none_match
31
+ # @http_content_type
32
+ # @http_path_info
33
+ # @http_request_uri
34
+ # @http_query_string
35
+ # @http_post_content
36
+ # @http_headers
37
+ # view what instance variables are available thorugh the
38
+ # instance_variables method
39
+
40
+ process(@http_post_content)
41
+ build_response
42
+ end
43
+
44
+ def process(raw)
45
+ # decode, etc
46
+ _request_processor.process(raw)
47
+ rescue Errors::NotAuthorized
48
+ build_response NOT_AUTHORIZED
49
+ rescue Errors::Forbidden
50
+ build_response FORBIDDEN
51
+ rescue Errors::BadRequest
52
+ build_response BAD_REQUEST
53
+ rescue => e
54
+ Display.error e.message
55
+ Display.error e.backtrace.join("\n")
56
+ build_response SERVER_ERROR, e.message
57
+ end
58
+
59
+ def build_response(s = OK, message = '')
60
+ response = EM::DelegatedHttpResponse.new(self)
61
+ response.status = s
62
+ response.content_type 'text/json'
63
+ response.content = message
64
+ response.send_response
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ module Message
5
+ extend ActiveSupport::Autoload
6
+
7
+ # @see https://github.com/neuravion/mesh-chat/blob/master/message-types.md
8
+ CHAT = 'chat'
9
+ EMOTE = 'emote'
10
+ PING = 'ping'
11
+ PING_REPLY = 'pingreply'
12
+ WHISPER = 'whisper'
13
+ DISCONNECT = 'disconnect'
14
+
15
+ NODE_LIST = 'nodelist'
16
+ NODE_LIST_HASH = 'nodelisthash'
17
+ NODE_LIST_DIFF = 'nodelistdiff'
18
+
19
+ eager_autoload do
20
+ autoload :Base
21
+ autoload :Chat
22
+ autoload :Emote
23
+ autoload :Ping
24
+ autoload :PingReply
25
+ autoload :Disconnect
26
+ autoload :Whisper
27
+ autoload :NodeList
28
+ autoload :NodeListDiff
29
+ autoload :NodeListHash
30
+ autoload :Factory
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ module Message
5
+ # NOTE:
6
+ # #display: shows the message
7
+ # should be used locally, before *sending* a message
8
+ # #handle: processing logic for the message
9
+ # should be used when receiving a message, and there
10
+ # needs to be a response right away
11
+ # #respond: where the actual logic for the response goes
12
+ class Base
13
+ attr_accessor :payload,
14
+ :_message, :_sender_name, :_sender_location, :_sender_uid,
15
+ :_time_received,
16
+ :_message_dispatcher, :_message_factory
17
+
18
+ # @param [String] message
19
+ # @param [Hash] sender
20
+ # @param [Hash] payload all paramaters for a received message
21
+ # @param [MeshChat::Network::Dispatcher] message_dispatcher optionally overrides the default payload
22
+ # @param [MeshChat::Message::Factory] message_factory the message factory
23
+ def initialize(
24
+ message: '',
25
+ sender: {},
26
+ payload: {},
27
+ message_dispatcher: nil,
28
+ message_factory: nil)
29
+
30
+ if payload.present?
31
+ @payload = payload.deep_stringify_keys
32
+ else
33
+ @_message = message
34
+ @_sender_name = sender['alias']
35
+ @_sender_location = sender['location']
36
+ @_sender_uid = sender['uid']
37
+ @_time_received = Time.now.iso8601
38
+ end
39
+
40
+ @_message_dispatcher = message_dispatcher
41
+ @_message_factory = message_factory
42
+ end
43
+
44
+ def payload
45
+ @payload ||= {
46
+ 'type' => type,
47
+ 'message' => _message,
48
+ 'client' => client,
49
+ 'client_version' => client_version,
50
+ 'time_sent' => _time_received,
51
+ 'sender' => {
52
+ 'alias' => _sender_name,
53
+ 'location' => _sender_location,
54
+ 'uid' => _sender_uid
55
+ }
56
+ }
57
+ end
58
+
59
+ def type
60
+ @type ||= Factory::TYPES.invert[self.class]
61
+ end
62
+
63
+ def message
64
+ _message || payload['message']
65
+ end
66
+
67
+ def sender
68
+ payload['sender']
69
+ end
70
+
71
+ def sender_name
72
+ _sender_name || sender['alias']
73
+ end
74
+
75
+ def sender_location
76
+ _sender_location || sender['location']
77
+ end
78
+
79
+ def sender_uid
80
+ _sender_uid || sender['uid']
81
+ end
82
+
83
+ def time_received
84
+ _time_received || payload['time_sent']
85
+ end
86
+
87
+ def time_received_as_date
88
+ DateTime.parse(time_received) if time_received
89
+ end
90
+
91
+ def client
92
+ APP_CONFIG[:client_name]
93
+ end
94
+
95
+ def client_version
96
+ APP_CONFIG[:client_version]
97
+ end
98
+
99
+ # shows the message
100
+ # should be used locally, before *sending* a message
101
+ def display
102
+ {
103
+ time: time_received_as_date,
104
+ from: sender_name,
105
+ message: message
106
+ }
107
+ end
108
+
109
+ # processing logic for the message
110
+ # should be used when receiving a message, and there
111
+ # needs to be a response right away.
112
+ # this may call display, if the response is always to be displayed
113
+ def handle
114
+ display
115
+ end
116
+
117
+ # Most message types aren't going to need to have an
118
+ # immediate response.
119
+ def respond
120
+ end
121
+
122
+ # this message should be called immediately
123
+ # before sending to the whomever
124
+ def render
125
+ payload.to_json
126
+ end
127
+
128
+ alias_method :jsonized_payload, :render
129
+
130
+ def encrypt_for(node)
131
+ result = jsonized_payload
132
+ public_key = node.public_key
133
+ result = Encryption.encrypt(result, public_key) if node.public_key
134
+ Base64.strict_encode64(result)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ module Message
5
+ class Chat < Base
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module Meshchat
3
+ module Network
4
+ module Message
5
+ class Disconnect < Base
6
+ def display
7
+ location = payload['sender']['location']
8
+ uid = payload['sender']['uid']
9
+ name = payload['sender']['alias']
10
+ node = Node.find_by_uid(uid)
11
+ if node
12
+ node.update(on_local_network: false)
13
+ node.update(on_relay: false)
14
+ end
15
+
16
+ "#{name}@#{location} has disconnected"
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end