nostr 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +23 -0
- data/CHANGELOG.md +16 -0
- data/README.md +232 -13
- data/Steepfile +14 -0
- data/lib/nostr/client.rb +176 -0
- data/lib/nostr/client_message_type.rb +15 -0
- data/lib/nostr/event.rb +93 -0
- data/lib/nostr/event_fragment.rb +111 -0
- data/lib/nostr/event_kind.rb +35 -0
- data/lib/nostr/filter.rb +172 -0
- data/lib/nostr/key_pair.rb +46 -0
- data/lib/nostr/keygen.rb +78 -0
- data/lib/nostr/relay.rb +43 -0
- data/lib/nostr/subscription.rb +65 -0
- data/lib/nostr/user.rb +92 -0
- data/lib/nostr/version.rb +2 -1
- data/lib/nostr.rb +11 -2
- data/nostr.gemspec +15 -2
- data/sig/nostr/client.rbs +20 -0
- data/sig/nostr/client_message_type.rbs +7 -0
- data/sig/nostr/event.rbs +24 -0
- data/sig/nostr/event_fragment.rbs +12 -0
- data/sig/nostr/event_kind.rbs +8 -0
- data/sig/nostr/filter.rbs +25 -0
- data/sig/nostr/key_pair.rbs +9 -0
- data/sig/nostr/keygen.rbs +13 -0
- data/sig/nostr/relay.rbs +9 -0
- data/sig/nostr/subscription.rbs +9 -0
- data/sig/nostr/user.rbs +24 -0
- data/sig/vendor/ecsda/group/secp256k1.rbs +6 -0
- data/sig/vendor/event_emitter.rbs +9 -0
- data/sig/vendor/event_machine/channel.rbs +18 -0
- data/sig/vendor/event_machine.rbs +69 -0
- data/sig/vendor/schnorr.rbs +4 -0
- metadata +205 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 371ed11c0474fd944cc55a054d553945623f7439f67c55f3eadc564805d2fb11
|
4
|
+
data.tar.gz: f7fbe86119bd7816e7066ea3603263dee7327b06daeb4b92f9e90977f52ef8ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa602354304ce9e77377b80cab60146f51ac8943b1399070a87ce81a5e44582f0a23b50f1736fafd9d7d0f13042b8b7292b9d29684077ad878e2439ddf7970ef
|
7
|
+
data.tar.gz: 83fc3d535a1e35438522779ef7dc3e101b4fea3a3f9874db9457b4f6c61d74469ded6ba973923d731789d3c50d7aa66b1c0770d62451794d45b52720c7928ebf
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2023-01-12 10:00:10 UTC using RuboCop version 1.42.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 2
|
10
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods, CountRepeatedAttributes.
|
11
|
+
Metrics/AbcSize:
|
12
|
+
Max: 23
|
13
|
+
|
14
|
+
# Offense count: 2
|
15
|
+
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods.
|
16
|
+
Metrics/MethodLength:
|
17
|
+
Max: 16
|
18
|
+
|
19
|
+
# Offense count: 4
|
20
|
+
# Configuration parameters: AssignmentOnly.
|
21
|
+
RSpec/InstanceVariable:
|
22
|
+
Exclude:
|
23
|
+
- 'spec/nostr/client_spec.rb'
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## [0.3.0] - 2023-02-15
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- Client compliance wth [NIP-02](https://github.com/nostr-protocol/nips/blob/master/02.md) (manage contact lists)
|
12
|
+
|
13
|
+
## [0.2.0] - 2023-01-12
|
14
|
+
|
15
|
+
### Added
|
16
|
+
|
17
|
+
- [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) compliant client
|
18
|
+
|
7
19
|
## [0.1.0] - 2023-01-06
|
8
20
|
|
9
21
|
- Initial release
|
22
|
+
|
23
|
+
[0.3.0]: https://github.com/wilsonsilva/nostr/compare/v0.2.0...v0.3.0
|
24
|
+
[0.2.0]: https://github.com/wilsonsilva/nostr/compare/v0.1.0...v0.2.0
|
25
|
+
[0.1.0]: https://github.com/wilsonsilva/nostr/compare/7fded5...v0.1.0
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# Nostr
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/nostr)
|
4
|
-
[](https://codeclimate.com/github/wilsonsilva/nostr/maintainability)
|
5
|
+
[](https://codeclimate.com/github/wilsonsilva/nostr/test_coverage)
|
6
6
|
|
7
|
-
Nostr client. Please note that the API is likely to change as the gem is still in development and
|
8
|
-
stable release. Use with caution.
|
7
|
+
Asynchronous Nostr client. Please note that the API is likely to change as the gem is still in development and
|
8
|
+
has not yet reached a stable release. Use with caution.
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
@@ -19,10 +19,212 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
19
19
|
|
20
20
|
## Usage
|
21
21
|
|
22
|
+
### Requiring the gem
|
23
|
+
|
24
|
+
All examples below assume that the gem has been required.
|
25
|
+
|
22
26
|
```ruby
|
23
27
|
require 'nostr'
|
24
28
|
```
|
25
29
|
|
30
|
+
### Generating a keypair
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
keygen = Nostr::Keygen.new
|
34
|
+
keypair = keygen.generate_keypair
|
35
|
+
|
36
|
+
keypair.private_key
|
37
|
+
keypair.public_key
|
38
|
+
```
|
39
|
+
|
40
|
+
### Generating a private key and a public key
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
keygen = Nostr::Keygen.new
|
44
|
+
|
45
|
+
private_key = keygen.generate_private_key
|
46
|
+
public_key = keygen.extract_public_key(private_key)
|
47
|
+
```
|
48
|
+
|
49
|
+
### Connecting to a Relay
|
50
|
+
|
51
|
+
Clients can connect to multiple Relays. In this version, a Client can only connect to a single Relay at a time.
|
52
|
+
|
53
|
+
You may instantiate multiple Clients and multiple Relays.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
client = Nostr::Client.new
|
57
|
+
relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
|
58
|
+
|
59
|
+
client.connect(relay)
|
60
|
+
```
|
61
|
+
|
62
|
+
### WebSocket events
|
63
|
+
|
64
|
+
All communication between clients and relays happen in WebSockets.
|
65
|
+
|
66
|
+
The `:connect` event is fired when a connection with a WebSocket is opened. You must call `Nostr::Client#connect` first.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
client.on :connect do
|
70
|
+
# all the code goes here
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
The `:close` event is fired when a connection with a WebSocket has been closed because of an error.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
client.on :error do |error_message|
|
78
|
+
puts error_message
|
79
|
+
end
|
80
|
+
|
81
|
+
# > Network error: wss://rsslay.fiatjaf.com: Unable to verify the server certificate for 'rsslay.fiatjaf.com'
|
82
|
+
```
|
83
|
+
|
84
|
+
The `:message` event is fired when data is received through a WebSocket.
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
client.on :message do |message|
|
88
|
+
puts message
|
89
|
+
end
|
90
|
+
|
91
|
+
# [
|
92
|
+
# "EVENT",
|
93
|
+
# "d34107357089bfc9882146d3bfab0386",
|
94
|
+
# {
|
95
|
+
# "content":"",
|
96
|
+
# "created_at":1676456512,
|
97
|
+
# "id":"18f63550da74454c5df7caa2a349edc5b2a6175ea4c5367fa4b4212781e5b310",
|
98
|
+
# "kind":3,
|
99
|
+
# "pubkey":"117a121fa41dc2caa0b3d6c5b9f42f90d114f1301d39f9ee96b646ebfee75e36",
|
100
|
+
# "sig":"d171420bd62cf981e8f86f2dd8f8f86737ea2bbe2d98da88db092991d125535860d982139a3c4be39886188613a9912ef380be017686a0a8b74231dc6e0b03cb",
|
101
|
+
# "tags":[
|
102
|
+
# ["p","1cc821cc2d47191b15fcfc0f73afed39a86ac6fb34fbfa7993ee3e0f0186ef7c"]
|
103
|
+
# ]
|
104
|
+
# }
|
105
|
+
# ]
|
106
|
+
```
|
107
|
+
|
108
|
+
The `:close` event is fired when a connection with a WebSocket is closed.
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
client.on :close do |code, reason|
|
112
|
+
# you may attempt to reconnect
|
113
|
+
|
114
|
+
client.connect(relay)
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
### Requesting for events / creating a subscription
|
119
|
+
|
120
|
+
A client can request events and subscribe to new updates after it has established a connection with the Relay.
|
121
|
+
|
122
|
+
You may use a `Nostr::Filter` instance with as many attributes as you wish:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
client.on :connect do
|
126
|
+
filter = Nostr::Filter.new(
|
127
|
+
ids: ['8535d5e2d7b9dc07567f676fbe70428133c9884857e1915f5b1cc6514c2fdff8'],
|
128
|
+
authors: ['ae00f88a885ce76afad5cbb2459ef0dcf0df0907adc6e4dac16e1bfbd7074577'],
|
129
|
+
kinds: [Nostr::EventKind::TEXT_NOTE],
|
130
|
+
e: ["f111593a72cc52a7f0978de5ecf29b4653d0cf539f1fa50d2168fc1dc8280e52"],
|
131
|
+
p: ["f1f9b0996d4ff1bf75e79e4cc8577c89eb633e68415c7faf74cf17a07bf80bd8"],
|
132
|
+
since: 1230981305,
|
133
|
+
until: 1292190341,
|
134
|
+
limit: 420,
|
135
|
+
)
|
136
|
+
|
137
|
+
subscription = client.subscribe('a_random_subscription_id', filter)
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
With just a few:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
client.on :connect do
|
145
|
+
filter = Nostr::Filter.new(kinds: [Nostr::EventKind::TEXT_NOTE])
|
146
|
+
subscription = client.subscribe('a_random_subscription_id', filter)
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
Or omit the filter:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
client.on :connect do
|
154
|
+
subscription = client.subscribe('a_random_subscription_id')
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
Or even omit the subscription id:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
client.on :connect do
|
162
|
+
subscription = client.subscribe('a_random_subscription_id')
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
### Stop previous subscriptions
|
167
|
+
|
168
|
+
You can stop receiving messages from a subscription by calling `#unsubscribe`:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
client.unsubscribe('your_subscription_id')
|
172
|
+
```
|
173
|
+
|
174
|
+
### Publishing an event
|
175
|
+
|
176
|
+
To publish an event you need a keypair.
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
# Set up the private key
|
180
|
+
private_key = 'a630b06e2f883378d0aa335b9adaf7734603e00433350b684fe53e184f08c58f'
|
181
|
+
user = Nostr::User.new(private_key)
|
182
|
+
|
183
|
+
# Create a signed event
|
184
|
+
event = user.create_event(
|
185
|
+
created_at: 1667422587, # optional, defaults to the current time
|
186
|
+
kind: Nostr::EventKind::TEXT_NOTE,
|
187
|
+
tags: [], # optional, defaults to []
|
188
|
+
content: 'Your feedback is appreciated, now pay $8'
|
189
|
+
)
|
190
|
+
|
191
|
+
# Send it to the Relay
|
192
|
+
client.publish(event)
|
193
|
+
```
|
194
|
+
|
195
|
+
### Creating/updating your contact list
|
196
|
+
|
197
|
+
Every new contact list that gets published overwrites the past ones, so it should contain all entries.
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
# Creating a contact list event with 2 contacts
|
201
|
+
update_contacts_event = user.create_event(
|
202
|
+
kind: Nostr::EventKind::CONTACT_LIST,
|
203
|
+
tags: [
|
204
|
+
[
|
205
|
+
"p", # mandatory
|
206
|
+
"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", # public key of the user to add to the contacts
|
207
|
+
"wss://alicerelay.com/", # can be an empty string or can be omitted
|
208
|
+
"alice" # can be an empty string or can be omitted
|
209
|
+
],
|
210
|
+
[
|
211
|
+
"p", # mandatory
|
212
|
+
"3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", # public key of the user to add to the contacts
|
213
|
+
"wss://bobrelay.com/nostr", # can be an empty string or can be omitted
|
214
|
+
"bob" # can be an empty string or can be omitted
|
215
|
+
],
|
216
|
+
],
|
217
|
+
)
|
218
|
+
|
219
|
+
# Send it to the Relay
|
220
|
+
client.publish(update_contacts_event)
|
221
|
+
```
|
222
|
+
|
223
|
+
## NIPS
|
224
|
+
|
225
|
+
- [x] [NIP-01 - Client](https://github.com/nostr-protocol/nips/blob/master/01.md)
|
226
|
+
- [x] [NIP-02 - Client](https://github.com/nostr-protocol/nips/blob/master/02.md)
|
227
|
+
|
26
228
|
## Development
|
27
229
|
|
28
230
|
After checking out the repo, run `bin/setup` to install dependencies.
|
@@ -39,17 +241,34 @@ The health and maintainability of the codebase is ensured through a set of
|
|
39
241
|
Rake tasks to test, lint and audit the gem for security vulnerabilities and documentation:
|
40
242
|
|
41
243
|
```
|
42
|
-
rake
|
43
|
-
rake
|
44
|
-
rake
|
45
|
-
rake
|
46
|
-
rake
|
47
|
-
rake
|
48
|
-
rake
|
49
|
-
rake
|
50
|
-
rake
|
244
|
+
rake build # Build nostr.gem into the pkg directory
|
245
|
+
rake build:checksum # Generate SHA512 checksum if nostr.gem into the checksums directory
|
246
|
+
rake bundle:audit:check # Checks the Gemfile.lock for insecure dependencies
|
247
|
+
rake bundle:audit:update # Updates the bundler-audit vulnerability database
|
248
|
+
rake clean # Remove any temporary products
|
249
|
+
rake clobber # Remove any generated files
|
250
|
+
rake coverage # Run spec with coverage
|
251
|
+
rake install # Build and install nostr.gem into system gems
|
252
|
+
rake install:local # Build and install nostr.gem into system gems without network access
|
253
|
+
rake qa # Test, lint and perform security and documentation audits
|
254
|
+
rake release[remote] # Create a tag, build and push nostr.gem to rubygems.org
|
255
|
+
rake rubocop # Run RuboCop
|
256
|
+
rake rubocop:autocorrect # Autocorrect RuboCop offenses (only when it's safe)
|
257
|
+
rake rubocop:autocorrect_all # Autocorrect RuboCop offenses (safe and unsafe)
|
258
|
+
rake spec # Run RSpec code examples
|
259
|
+
rake verify_measurements # Verify that yardstick coverage is at least 100%
|
260
|
+
rake yard # Generate YARD Documentation
|
261
|
+
rake yard:junk # Check the junk in your YARD Documentation
|
262
|
+
rake yardstick_measure # Measure docs in lib/**/*.rb with yardstick
|
51
263
|
```
|
52
264
|
|
265
|
+
### Type checking
|
266
|
+
|
267
|
+
This gem leverages [RBS](https://github.com/ruby/rbs), a language to describe the structure of Ruby programs. It is
|
268
|
+
used to provide type checking and autocompletion in your editor. Run `bundle exec typeprof FILENAME` to generate
|
269
|
+
an RBS definition for the given Ruby file. And validate all definitions using [Steep](https://github.com/soutaro/steep)
|
270
|
+
with the command `bundle exec steep check`.
|
271
|
+
|
53
272
|
## Contributing
|
54
273
|
|
55
274
|
Bug reports and pull requests are welcome on GitHub at https://github.com/wilsonsilva/nostr.
|
data/Steepfile
ADDED
data/lib/nostr/client.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'event_emitter'
|
4
|
+
require 'faye/websocket'
|
5
|
+
|
6
|
+
module Nostr
|
7
|
+
# Clients can talk with relays and can subscribe to any set of events using a subscription filters.
|
8
|
+
# The filter represents all the set of nostr events that a client is interested in.
|
9
|
+
#
|
10
|
+
# There is no sign-up or account creation for a client. Every time a client connects to a relay, it submits its
|
11
|
+
# subscription filters and the relay streams the "interested events" to the client as long as they are connected.
|
12
|
+
#
|
13
|
+
class Client
|
14
|
+
include EventEmitter
|
15
|
+
|
16
|
+
# Instantiates a new Client
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
#
|
20
|
+
# @example Instantiating a client that logs all the events it sends and receives
|
21
|
+
# client = Nostr::Client.new(debug: true)
|
22
|
+
#
|
23
|
+
def initialize
|
24
|
+
@subscriptions = {}
|
25
|
+
|
26
|
+
initialize_channels
|
27
|
+
end
|
28
|
+
|
29
|
+
# Connects to the Relay's websocket endpoint
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
#
|
33
|
+
# @example Connecting to a relay
|
34
|
+
# relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
|
35
|
+
# client.connect(relay)
|
36
|
+
#
|
37
|
+
# @param [Relay] relay The relay to connect to
|
38
|
+
#
|
39
|
+
# @return [void]
|
40
|
+
#
|
41
|
+
def connect(relay)
|
42
|
+
execute_within_an_em_thread do
|
43
|
+
client = Faye::WebSocket::Client.new(relay.url, [], { tls: { verify_peer: false } })
|
44
|
+
parent_to_child_channel.subscribe { |msg| client.send(msg) }
|
45
|
+
|
46
|
+
client.on :open do
|
47
|
+
child_to_parent_channel.push(type: :open)
|
48
|
+
end
|
49
|
+
|
50
|
+
client.on :message do |event|
|
51
|
+
child_to_parent_channel.push(type: :message, data: event.data)
|
52
|
+
end
|
53
|
+
|
54
|
+
client.on :error do |event|
|
55
|
+
child_to_parent_channel.push(type: :error, message: event.message)
|
56
|
+
end
|
57
|
+
|
58
|
+
client.on :close do |event|
|
59
|
+
child_to_parent_channel.push(type: :close, code: event.code, reason: event.reason)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Subscribes to a set of events using a filter
|
65
|
+
#
|
66
|
+
# @api public
|
67
|
+
#
|
68
|
+
# @example Creating a subscription with no id and no filters
|
69
|
+
# subscription = client.subscribe
|
70
|
+
#
|
71
|
+
# @example Creating a subscription with an ID
|
72
|
+
# subscription = client.subscribe(subscription_id: 'my-subscription')
|
73
|
+
#
|
74
|
+
# @example Subscribing to all events created after a certain time
|
75
|
+
# subscription = client.subscribe(filter: Nostr::Filter.new(since: 1230981305))
|
76
|
+
#
|
77
|
+
# @param subscription_id [String] The subscription id. A random string.
|
78
|
+
# @param filter [Filter] A set of attributes that represent the events that the client is interested in.
|
79
|
+
#
|
80
|
+
# @return [Subscription] The subscription object
|
81
|
+
#
|
82
|
+
def subscribe(subscription_id: SecureRandom.hex, filter: Filter.new)
|
83
|
+
subscriptions[subscription_id] = Subscription.new(id: subscription_id, filter:)
|
84
|
+
parent_to_child_channel.push([ClientMessageType::REQ, subscription_id, filter.to_h].to_json)
|
85
|
+
subscriptions[subscription_id]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Stops a previous subscription
|
89
|
+
#
|
90
|
+
# @api public
|
91
|
+
#
|
92
|
+
# @example Stopping a subscription
|
93
|
+
# client.unsubscribe(subscription.id)
|
94
|
+
#
|
95
|
+
# @example Stopping a subscription
|
96
|
+
# client.unsubscribe('my-subscription')
|
97
|
+
#
|
98
|
+
# @param subscription_id [String] ID of a previously created subscription.
|
99
|
+
#
|
100
|
+
# @return [void]
|
101
|
+
#
|
102
|
+
def unsubscribe(subscription_id)
|
103
|
+
subscriptions.delete(subscription_id)
|
104
|
+
parent_to_child_channel.push([ClientMessageType::CLOSE, subscription_id].to_json)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Sends an event to a Relay
|
108
|
+
#
|
109
|
+
# @api public
|
110
|
+
#
|
111
|
+
# @example Sending an event to a relay
|
112
|
+
# client.publish(event)
|
113
|
+
#
|
114
|
+
# @param event [Event] The event to be sent to a Relay
|
115
|
+
#
|
116
|
+
# @return [void]
|
117
|
+
#
|
118
|
+
def publish(event)
|
119
|
+
parent_to_child_channel.push([ClientMessageType::EVENT, event.to_h].to_json)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# The subscriptions that the client has created
|
125
|
+
#
|
126
|
+
# @api private
|
127
|
+
#
|
128
|
+
# @return [Hash{String=>Subscription}>]
|
129
|
+
#
|
130
|
+
attr_reader :subscriptions
|
131
|
+
|
132
|
+
# The channel that the parent thread uses to send messages to the child thread
|
133
|
+
#
|
134
|
+
# @api private
|
135
|
+
#
|
136
|
+
# @return [EventMachine::Channel]
|
137
|
+
#
|
138
|
+
attr_reader :parent_to_child_channel
|
139
|
+
|
140
|
+
# The channel that the child thread uses to send messages to the parent thread
|
141
|
+
#
|
142
|
+
# @api private
|
143
|
+
#
|
144
|
+
# @return [EventMachine::Channel]
|
145
|
+
#
|
146
|
+
attr_reader :child_to_parent_channel
|
147
|
+
|
148
|
+
# Executes a block of code within the EventMachine thread
|
149
|
+
#
|
150
|
+
# @api private
|
151
|
+
#
|
152
|
+
# @return [Thread]
|
153
|
+
#
|
154
|
+
def execute_within_an_em_thread(&block)
|
155
|
+
Thread.new { EventMachine.run(block) }
|
156
|
+
end
|
157
|
+
|
158
|
+
# Creates the communication channels between threads
|
159
|
+
#
|
160
|
+
# @api private
|
161
|
+
#
|
162
|
+
# @return [void]
|
163
|
+
#
|
164
|
+
def initialize_channels
|
165
|
+
@parent_to_child_channel = EventMachine::Channel.new
|
166
|
+
@child_to_parent_channel = EventMachine::Channel.new
|
167
|
+
|
168
|
+
child_to_parent_channel.subscribe do |msg|
|
169
|
+
emit :connect if msg[:type] == :open
|
170
|
+
emit :message, msg[:data] if msg[:type] == :message
|
171
|
+
emit :error, msg[:message] if msg[:type] == :error
|
172
|
+
emit :close, msg[:code], msg[:reason] if msg[:type] == :close
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nostr
|
4
|
+
# Clients can send 3 types of messages, which must be JSON arrays
|
5
|
+
module ClientMessageType
|
6
|
+
# @return [String] Used to publish events
|
7
|
+
EVENT = 'EVENT'
|
8
|
+
|
9
|
+
# @return [String] Used to request events and subscribe to new updates
|
10
|
+
REQ = 'REQ'
|
11
|
+
|
12
|
+
# @return [String] Used to stop previous subscriptions
|
13
|
+
CLOSE = 'CLOSE'
|
14
|
+
end
|
15
|
+
end
|
data/lib/nostr/event.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nostr
|
4
|
+
# The only object type that exists in Nostr is an event. Events are immutable.
|
5
|
+
class Event < EventFragment
|
6
|
+
# 32-bytes sha256 of the the serialized event data.
|
7
|
+
# To obtain the event.id, we sha256 the serialized event. The serialization is done over the UTF-8 JSON-serialized
|
8
|
+
# string (with no white space or line breaks)
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
#
|
12
|
+
# @example Getting the event id
|
13
|
+
# event.id # => 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460'
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
#
|
17
|
+
attr_reader :id
|
18
|
+
|
19
|
+
# 64-bytes signature of the sha256 hash of the serialized event data, which is
|
20
|
+
# the same as the "id" field
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
#
|
24
|
+
# @example Getting the event signature
|
25
|
+
# event.sig # => ''
|
26
|
+
#
|
27
|
+
# @return [String]
|
28
|
+
#
|
29
|
+
attr_reader :sig
|
30
|
+
|
31
|
+
# Instantiates a new Event
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
#
|
35
|
+
# @example Instantiating a new event
|
36
|
+
# Nostr::Event.new(
|
37
|
+
# id: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
|
38
|
+
# pubkey: '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e',
|
39
|
+
# created_at: 1230981305,
|
40
|
+
# kind: 1,
|
41
|
+
# tags: [],
|
42
|
+
# content: 'Your feedback is appreciated, now pay $8',
|
43
|
+
# sig: '123ac2923b792ce730b3da34f16155470ab13c8f97f9c53eaeb334f1fb3a5dc9a7f643
|
44
|
+
# 937c6d6e9855477638f5655c5d89c9aa5501ea9b578a66aced4f1cd7b3'
|
45
|
+
# )
|
46
|
+
#
|
47
|
+
#
|
48
|
+
# @param id [String] 32-bytes sha256 of the the serialized event data.
|
49
|
+
# @param sig [String] 64-bytes signature of the sha256 hash of the serialized event data, which is
|
50
|
+
# the same as the "id" field
|
51
|
+
#
|
52
|
+
def initialize(id:, sig:, **kwargs)
|
53
|
+
super(**kwargs)
|
54
|
+
|
55
|
+
@id = id
|
56
|
+
@sig = sig
|
57
|
+
end
|
58
|
+
|
59
|
+
# Converts the event to a hash
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
#
|
63
|
+
# @example Converting the event to a hash
|
64
|
+
# event.to_h
|
65
|
+
#
|
66
|
+
# @return [Hash] The event as a hash.
|
67
|
+
#
|
68
|
+
def to_h
|
69
|
+
{
|
70
|
+
id:,
|
71
|
+
pubkey:,
|
72
|
+
created_at:,
|
73
|
+
kind:,
|
74
|
+
tags:,
|
75
|
+
content:,
|
76
|
+
sig:
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Compares two events. Returns true if all attributes are equal and false otherwise
|
81
|
+
#
|
82
|
+
# @api public
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# event1 == event2 # => true
|
86
|
+
#
|
87
|
+
# @return [Boolean] True if all attributes are equal and false otherwise
|
88
|
+
#
|
89
|
+
def ==(other)
|
90
|
+
to_h == other.to_h
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|