meshchat 0.8.0 → 0.10.0

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 (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