nostrb 0.1.0.1 → 0.2.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f6159cbd2c246ae573103bd68e56e7cd97b0d3d81d36d8e0816abc5bd23c3c1
4
- data.tar.gz: 8bcf8fe77b3baa61882ea849df549b2800ebbf5e788d55c2c1c00efddc99e1f8
3
+ metadata.gz: a89ed8e93f661c8de971b8cba920363431d8a6a21ba22487877b09c041b25123
4
+ data.tar.gz: 4d2f947fc9244d1db2c003b27c004f7a666fa67e1f3e36283fecfb396228647c
5
5
  SHA512:
6
- metadata.gz: 369121a3fa1b9ead9bc245a3bd5f18ce6a6e2cc3571e1c91019afb4752845de7edbef086b755ad3b90d4b1a072997b2cce8047e19c4db293cc14328332febb56
7
- data.tar.gz: 8b19fa2c2b738e53711fdb4bbdac6a196ce8499b8a212b742be734318a526bd495bed6ddfd97f771c005323ee2583f6207feea8f79850b95e7c3aced24ac3cec
6
+ metadata.gz: 5d0a5f3137c9350da36a431790b3dc18dc3b0d38a32d605ba6bc3ee5c1c9b13950026e0aff037f486f55f6b5d0943323bbd4342de0e558289c7c785f7809c548
7
+ data.tar.gz: 6f835fda78aca5c38d43e05a73af41cc88a13a5ec7a880406bc7b35c9c984cddd0b5ebdce3a56b30faff9a276971584c750c419f010f0f5e52532465c63a2e53
data/Rakefile CHANGED
@@ -5,11 +5,16 @@ Rake::TestTask.new :test do |t|
5
5
  t.warning = true
6
6
  end
7
7
 
8
+ Rake::TestTask.new :test_less do |t|
9
+ t.pattern = "test/{[!r][!e][!l][!a][!y]}*.rb"
10
+ t.warning = true
11
+ end
12
+
8
13
  task :relay do |t|
9
- sh "ruby -I lib examples/relay.rb"
14
+ sh "bundle exec falcon serve --bind wss://localhost:7070"
10
15
  end
11
16
 
12
- task default: [:test, :relay]
17
+ task default: [:test]
13
18
 
14
19
  begin
15
20
  require 'buildar'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0.1
1
+ 0.2.0.1
@@ -0,0 +1,75 @@
1
+ require 'async'
2
+ require 'async/http/endpoint'
3
+ require 'async/websocket/client'
4
+
5
+ require 'nostrb/source'
6
+
7
+ module Nostrb
8
+ class Client
9
+ attr_reader :url, :endpoint, :sid
10
+
11
+ def initialize(relay_url, sid: nil)
12
+ @url = relay_url
13
+ @endpoint = Async::HTTP::Endpoint.parse(@url)
14
+ @sid = sid.nil? ? Source.random_sid : sid
15
+ end
16
+
17
+ def log msg
18
+ warn msg
19
+ end
20
+
21
+ # open, send req, get response, return response
22
+ def single(req)
23
+ Sync do
24
+ Async::WebSocket::Client.connect(@endpoint) do |conn|
25
+ conn.write(Nostrb.json(req))
26
+ conn.flush
27
+ resp = conn.read
28
+ conn.shutdown
29
+ Nostrb.parse(resp.buffer)
30
+ end
31
+ end
32
+ end
33
+
34
+ def publish(signed_event)
35
+ case single(Nostrb::Source.publish(signed_event))
36
+ in ['OK', String => event_id, ok, String => msg]
37
+ log "id mismatch: #{event_id}" unless event_id == signed_event.id
38
+ log msg unless ok
39
+ ok
40
+ in ['NOTICE', String => msg]
41
+ log msg
42
+ end
43
+ end
44
+
45
+ def subscribe(*filters, &blk)
46
+ Sync do
47
+ Async::WebSocket::Client.connect(@endpoint) do |conn|
48
+ conn.write(Nostrb.json(Nostrb::Source.subscribe(@sid, *filters)))
49
+ conn.flush
50
+ eose = false
51
+ while !eose and resp = conn.read
52
+ case Nostrb.parse(resp.buffer)
53
+ in ['EVENT', String => sid, Hash => event]
54
+ log "sid mismatch: #{sid}" unless sid == @sid
55
+ yield event
56
+ in ['EOSE', String => sid]
57
+ log "sid mismatch: #{sid}" unless sid == @sid
58
+ eose = true
59
+ end
60
+ end
61
+ conn.shutdown
62
+ end
63
+ end
64
+ end
65
+
66
+ def close
67
+ case single(Nostrb::Source.close(@sid))
68
+ in ['CLOSED', String => sid, String => msg]
69
+ log "sid mismatch: #{sid}" unless sid == @sid
70
+ log msg unless msg.empty?
71
+ true
72
+ end
73
+ end
74
+ end
75
+ end
data/lib/nostrb/event.rb CHANGED
@@ -19,25 +19,38 @@ module Nostrb
19
19
  tag_values('d', tags).first
20
20
  end
21
21
 
22
+ def self.freeze_tags(tags)
23
+ tags.each { |a|
24
+ a.each { |s| s.freeze }
25
+ a.freeze
26
+ }
27
+ tags.freeze
28
+ end
29
+
22
30
  attr_reader :content, :kind, :tags, :pk
23
31
 
24
32
  def initialize(content = '', kind: 1, tags: [], pk:)
25
- @content = Nostrb.txt!(content)
33
+ @content = Nostrb.txt!(content) # frozen
26
34
  @kind = Nostrb.kind!(kind)
27
35
  @tags = Nostrb.tags!(tags)
28
- @pk = Nostrb.key!(pk)
36
+ @pk = Nostrb.key!(pk) # frozen
29
37
  end
30
38
 
31
39
  alias_method :to_s, :content
32
40
 
33
41
  def serialize(created_at)
34
- [0, self.pubkey, Nostrb.int!(created_at), @kind, @tags, @content]
42
+ [0, self.pubkey, Nostrb.int!(created_at), @kind, @tags, @content].freeze
43
+ end
44
+
45
+ def freeze
46
+ Event.freeze_tags(@tags)
47
+ self
35
48
  end
36
49
 
37
50
  def to_a = serialize(Time.now.to_i)
38
51
  def pubkey = SchnorrSig.bin2hex(@pk)
39
52
  def digest(created_at) = Event.digest(serialize(created_at))
40
- def sign(sk) = SignedEvent.new(self, sk)
53
+ def sign(sk) = SignedEvent.new(self.freeze, sk)
41
54
 
42
55
  #
43
56
  # Tags
@@ -82,10 +95,11 @@ module Nostrb
82
95
  Nostrb.pubkey!(parsed.fetch("pubkey"))
83
96
  Nostrb.kind!(parsed.fetch("kind"))
84
97
  Nostrb.tags!(parsed.fetch("tags"))
98
+ Event.freeze_tags(parsed['tags'])
85
99
  Nostrb.int!(parsed.fetch("created_at"))
86
100
  Nostrb.id!(parsed.fetch("id"))
87
101
  Nostrb.sig!(parsed.fetch("sig"))
88
- parsed
102
+ parsed.freeze
89
103
  end
90
104
 
91
105
  def self.digest(valid) = Nostrb.digest(Nostrb.json(serialize(valid)))
@@ -96,7 +110,7 @@ module Nostrb
96
110
  valid["created_at"],
97
111
  valid["kind"],
98
112
  valid["tags"],
99
- valid["content"], ]
113
+ valid["content"], ].freeze
100
114
  end
101
115
 
102
116
  # Validate the id (optional) and signature
@@ -127,8 +141,8 @@ module Nostrb
127
141
  def initialize(event, sk)
128
142
  @event = Nostrb.check!(event, Event)
129
143
  @created_at = Time.now.to_i
130
- @digest = @event.digest(@created_at)
131
- @signature = SchnorrSig.sign(Nostrb.key!(sk), @digest)
144
+ @digest = @event.digest(@created_at).freeze
145
+ @signature = SchnorrSig.sign(Nostrb.key!(sk), @digest).freeze
132
146
  end
133
147
 
134
148
  def content = @event.content
@@ -138,8 +152,8 @@ module Nostrb
138
152
  def to_s = @event.to_s
139
153
  def serialize = @event.serialize(@created_at)
140
154
 
141
- def id = SchnorrSig.bin2hex(@digest)
142
- def sig = SchnorrSig.bin2hex(@signature)
155
+ def id = SchnorrSig.bin2hex(@digest).freeze
156
+ def sig = SchnorrSig.bin2hex(@signature).freeze
143
157
 
144
158
  def to_h
145
159
  Hash[ "content" => @event.content,
@@ -148,7 +162,7 @@ module Nostrb
148
162
  "pubkey" => @event.pubkey,
149
163
  "created_at" => @created_at,
150
164
  "id" => self.id,
151
- "sig" => self.sig ]
165
+ "sig" => self.sig ].freeze
152
166
  end
153
167
  end
154
168
  end
data/lib/nostrb/filter.rb CHANGED
@@ -71,6 +71,10 @@ module Nostrb
71
71
  @limit = nil
72
72
  end
73
73
 
74
+ def to_s
75
+ self.to_h.to_s
76
+ end
77
+
74
78
  def add_ids(*event_ids)
75
79
  @ids += event_ids.each { |id| Nostrb.id!(id) }
76
80
  end
@@ -138,7 +142,7 @@ module Nostrb
138
142
  h["since"] = @since unless @since.nil?
139
143
  h["until"] = @until unless @until.nil?
140
144
  h["limit"] = @limit unless @limit.nil?
141
- h
145
+ h.freeze
142
146
  end
143
147
  end
144
148
  end
data/lib/nostrb/json.rb CHANGED
@@ -14,6 +14,6 @@ module Nostrb
14
14
  space_before: '',
15
15
  }
16
16
 
17
- def self.parse(json) = JSON.parse(json, **JSON_OPTIONS)
18
- def self.json(object) = JSON.generate(object, **JSON_OPTIONS)
17
+ def self.parse(json) = JSON.parse(json, **JSON_OPTIONS).freeze
18
+ def self.json(object) = JSON.generate(object, **JSON_OPTIONS).freeze
19
19
  end
data/lib/nostrb/oj.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'oj'
2
2
 
3
3
  module Nostrb
4
- def self.parse(json) = Oj.load(json, mode: :strict)
5
- def self.json(object) = Oj.dump(object, mode: :strict)
4
+ def self.parse(json) = Oj.load(json, mode: :strict).freeze
5
+ def self.json(object) = Oj.dump(object, mode: :strict).freeze
6
6
  end
data/lib/nostrb/relay.rb CHANGED
@@ -14,7 +14,7 @@ require 'set' # jruby wants this
14
14
  # for replaceable events with same timestamp, lowest id wins
15
15
 
16
16
  module Nostrb
17
- class Server
17
+ class Relay
18
18
  def self.event(sid, event) = ["EVENT", Nostrb.sid!(sid), event.to_h]
19
19
  def self.ok(eid, msg = "", ok: true)
20
20
  ["OK", Nostrb.id!(eid), !!ok, ok ? Nostrb.txt!(msg) : Nostrb.help!(msg)]
@@ -58,10 +58,10 @@ module Nostrb
58
58
  when 'CLOSE'
59
59
  [handle_close(Nostrb.sid!(a[1]))]
60
60
  else
61
- [Server.notice("unexpected: #{a[0].inspect}")]
61
+ [Relay.notice("unexpected: #{a[0].inspect}")]
62
62
  end
63
63
  rescue StandardError => e
64
- [Server.error(e)]
64
+ [Relay.error(e)]
65
65
  end
66
66
  end
67
67
 
@@ -70,7 +70,7 @@ module Nostrb
70
70
  begin
71
71
  hsh = SignedEvent.validate!(hsh)
72
72
  rescue Nostrb::Error, KeyError, RuntimeError => e
73
- return Server.error(e)
73
+ return Relay.error(e)
74
74
  end
75
75
 
76
76
  eid = hsh.fetch('id')
@@ -94,11 +94,11 @@ module Nostrb
94
94
  raise(SignedEvent::Error, "kind: #{hsh['kind']}")
95
95
  end
96
96
 
97
- Server.ok(eid)
97
+ Relay.ok(eid)
98
98
  rescue SignedEvent::Error => e
99
- Server.ok(eid, Server.message(e), ok: false)
99
+ Relay.ok(eid, Relay.message(e), ok: false)
100
100
  rescue Nostrb::Error, KeyError, RuntimeError => e
101
- Server.error(e)
101
+ Relay.error(e)
102
102
  end
103
103
  end
104
104
 
@@ -124,19 +124,19 @@ module Nostrb
124
124
 
125
125
  filters.each { |f|
126
126
  @reader.process_events(f).each { |h|
127
- responses << Server.event(sid, h) if f.match? h
127
+ responses << Relay.event(sid, h) if f.match? h
128
128
  }
129
129
  @reader.process_r_events(f).each { |h|
130
- responses << Server.event(sid, h) if f.match? h
130
+ responses << Relay.event(sid, h) if f.match? h
131
131
  }
132
132
  }
133
133
  responses = responses.to_a
134
- responses << Server.eose(sid)
134
+ responses << Relay.eose(sid)
135
135
  end
136
136
 
137
137
  # single response
138
138
  def handle_close(sid)
139
- Server.closed(sid, "reason: CLOSE requested")
139
+ Relay.closed(sid, "reason: CLOSE requested")
140
140
  end
141
141
  end
142
142
  end
data/lib/nostrb/sequel.rb CHANGED
@@ -4,7 +4,7 @@ require 'nostrb/sqlite'
4
4
  module Nostrb
5
5
  module Sequel
6
6
  class Storage < SQLite::Storage
7
- FILENAME = 'sequel.db'
7
+ FILENAME = 'sequel.tmp.db'
8
8
  TABLES = [:events, :tags, :r_events, :r_tags]
9
9
 
10
10
  def self.schema_line(col, cfg)
@@ -77,7 +77,7 @@ module Nostrb
77
77
  end
78
78
 
79
79
  def create_tables
80
- @db.create_table :events do
80
+ @db.create_table :events, strict: true do
81
81
  text :content, null: false
82
82
  int :kind, null: false
83
83
  text :tags, null: false
@@ -89,8 +89,7 @@ module Nostrb
89
89
  text :sig, null: false
90
90
  end
91
91
 
92
- @db.create_table :tags do
93
- # text :event_id, null: false # fk
92
+ @db.create_table :tags, strict: true do
94
93
  foreign_key :event_id, :events,
95
94
  key: :id,
96
95
  type: :text,
@@ -103,7 +102,7 @@ module Nostrb
103
102
  text :json, null: false
104
103
  end
105
104
 
106
- @db.create_table :r_events do
105
+ @db.create_table :r_events, strict: true do
107
106
  text :content, null: false
108
107
  int :kind, null: false
109
108
  text :tags, null: false
@@ -120,7 +119,7 @@ module Nostrb
120
119
  }
121
120
  end
122
121
 
123
- @db.create_table :r_tags do
122
+ @db.create_table :r_tags, strict: true do
124
123
  foreign_key :r_event_id, :r_events,
125
124
  key: :id,
126
125
  type: :text,
@@ -145,7 +144,7 @@ module Nostrb
145
144
  'pubkey' => event_row.fetch(:pubkey),
146
145
  'created_at' => event_row.fetch(:created_at),
147
146
  'id' => event_row.fetch(:id),
148
- 'sig' => event_row.fetch(:sig), ]
147
+ 'sig' => event_row.fetch(:sig), ].freeze
149
148
  end
150
149
 
151
150
  def self.event_clauses(filter)
data/lib/nostrb/source.rb CHANGED
@@ -11,30 +11,30 @@ module Nostrb
11
11
  #######################
12
12
  # Client Requests
13
13
 
14
- def self.publish(signed) = ["EVENT", signed.to_h]
14
+ def self.publish(signed) = ["EVENT", signed.to_h].freeze
15
15
 
16
16
  def self.subscribe(sid, *filters)
17
17
  ["REQ", Nostrb.sid!(sid), *filters.map { |f|
18
18
  Nostrb.check!(f, Filter).to_h
19
- }]
19
+ }].freeze
20
20
  end
21
21
 
22
- def self.close(sid) = ["CLOSE", Nostrb.sid!(sid)]
22
+ def self.close(sid) = ["CLOSE", Nostrb.sid!(sid)].freeze
23
23
 
24
24
  #######################
25
25
  # Utils / Init
26
26
 
27
27
  def self.random_sid
28
- SchnorrSig.bin2hex Random.bytes(32)
28
+ SchnorrSig.bin2hex(Random.bytes(32)).freeze
29
29
  end
30
30
 
31
31
  attr_reader :pk
32
32
 
33
33
  def initialize(pk)
34
- @pk = Nostrb.key!(pk)
34
+ @pk = Nostrb.key!(pk).freeze
35
35
  end
36
36
 
37
- def pubkey = SchnorrSig.bin2hex(@pk)
37
+ def pubkey = SchnorrSig.bin2hex(@pk).freeze
38
38
 
39
39
  ############################
40
40
  # Event Creation
data/lib/nostrb/sqlite.rb CHANGED
@@ -112,7 +112,7 @@ module Nostrb
112
112
  MB = KB * 1024
113
113
  GB = MB * 1024
114
114
 
115
- FILENAME = 'tmp.db'
115
+ FILENAME = 'sqlite.tmp.db'
116
116
  CONFIG = {
117
117
  default_transaction_mode: :immediate,
118
118
  }
@@ -241,7 +241,7 @@ CREATE TABLE events (content TEXT NOT NULL,
241
241
  pubkey TEXT NOT NULL,
242
242
  created_at INT NOT NULL,
243
243
  id TEXT PRIMARY KEY NOT NULL,
244
- sig TEXT NOT NULL)
244
+ sig TEXT NOT NULL) STRICT
245
245
  SQL
246
246
 
247
247
  @db.execute <<SQL
@@ -250,7 +250,7 @@ CREATE TABLE tags (event_id TEXT NOT NULL REFERENCES events (id)
250
250
  created_at INT NOT NULL,
251
251
  tag TEXT NOT NULL,
252
252
  value TEXT NOT NULL,
253
- json TEXT NOT NULL)
253
+ json TEXT NOT NULL) STRICT
254
254
  SQL
255
255
 
256
256
  @db.execute <<SQL
@@ -261,7 +261,7 @@ CREATE TABLE r_events (content TEXT NOT NULL,
261
261
  pubkey TEXT NOT NULL,
262
262
  created_at INT NOT NULL,
263
263
  id TEXT PRIMARY KEY NOT NULL,
264
- sig TEXT NOT NULL)
264
+ sig TEXT NOT NULL) STRICT
265
265
  SQL
266
266
 
267
267
  @db.execute <<SQL
@@ -270,7 +270,7 @@ CREATE TABLE r_tags (r_event_id TEXT NOT NULL REFERENCES r_events (id)
270
270
  created_at INT NOT NULL,
271
271
  tag TEXT NOT NULL,
272
272
  value TEXT NOT NULL,
273
- json TEXT NOT NULL)
273
+ json TEXT NOT NULL) STRICT
274
274
  SQL
275
275
  end
276
276
 
@@ -394,6 +394,10 @@ SQL
394
394
  end
395
395
 
396
396
  class Writer < Storage
397
+ def self.serialize_tags(valid)
398
+ valid.merge("tags" => Nostrb.json(valid["tags"]))
399
+ end
400
+
397
401
  # a valid hash, as returned from SignedEvent.validate!
398
402
  def add_event(valid)
399
403
  @add_event ||= @db.prepare("INSERT INTO events
@@ -402,12 +406,10 @@ SQL
402
406
  @add_tag ||= @db.prepare("INSERT INTO tags
403
407
  VALUES (:event_id, :created_at,
404
408
  :tag, :value, :json)")
405
- tags = valid["tags"]
406
- valid["tags"] = Nostrb.json(tags)
407
- @add_event.execute(valid) # insert event
408
- tags.each { |a| # insert tags
409
- @add_tag.execute(event_id: valid.fetch('id'),
410
- created_at: valid.fetch('created_at'),
409
+ @add_event.execute(Writer.serialize_tags(valid)) # insert event
410
+ valid["tags"].each { |a| # insert tags
411
+ @add_tag.execute(event_id: valid['id'],
412
+ created_at: valid['created_at'],
411
413
  tag: a[0],
412
414
  value: a[1],
413
415
  json: Nostrb.json(a))
@@ -423,14 +425,13 @@ SQL
423
425
  @add_rtag ||= @db.prepare("INSERT INTO r_tags
424
426
  VALUES (:r_event_id, :created_at,
425
427
  :tag, :value, :json)")
426
- tags = valid.fetch('tags')
427
- d_tags = tags.select { |a| a[0] == 'd' }
428
- valid['d_tag'] = d_tags.empty? ? nil : d_tags[0][1]
429
- valid['tags'] = Nostrb.json(tags)
430
- @add_r_event.execute(valid) # upsert event
431
- tags.each { |a| # insert tags
432
- @add_rtag.execute(r_event_id: valid.fetch('id'),
433
- created_at: valid.fetch('created_at'),
428
+ tags = valid['tags']
429
+ record = Writer.serialize_tags(valid)
430
+ record['d_tag'] = Event.d_tag(tags)
431
+ @add_r_event.execute(record) # upsert event
432
+ tags.each { |a| # insert tags
433
+ @add_rtag.execute(r_event_id: valid['id'],
434
+ created_at: valid['created_at'],
434
435
  tag: a[0],
435
436
  value: a[1],
436
437
  json: Nostrb.json(a))
data/lib/nostrb.rb CHANGED
@@ -7,14 +7,12 @@ rescue LoadError
7
7
  end
8
8
 
9
9
  module Nostrb
10
- GEMS = %w[rbsecp256k1 oj sqlite3 sequel]
11
-
12
10
  class Error < RuntimeError; end
13
11
  class SizeError < Error; end
14
12
  class FormatError < Error; end
15
13
 
16
14
  # return 32 bytes binary
17
- def self.digest(str) = Digest::SHA256.digest(str)
15
+ def self.digest(str) = Digest::SHA256.digest(str).freeze
18
16
 
19
17
  def self.check!(val, cls)
20
18
  val.is_a?(cls) ? val : raise(TypeError, "#{cls} expected: #{val.inspect}")
@@ -45,7 +43,7 @@ module Nostrb
45
43
  end
46
44
  raise(SizeError, str.length) if !length.nil? and str.length != length
47
45
  raise(SizeError, str.length) if !max.nil? and str.length > max
48
- str
46
+ str.freeze
49
47
  end
50
48
 
51
49
  def self.bin!(str, length: nil, max: nil)
@@ -58,6 +56,7 @@ module Nostrb
58
56
  str!(str, binary: false, length: length, max: max)
59
57
  end
60
58
  def self.pubkey!(str) = txt!(str, length: 64)
59
+
61
60
  def self.id!(str) = txt!(str, length: 64)
62
61
  def self.sid!(str) = txt!(str, max: 64)
63
62
  def self.sig!(str) = txt!(str, length: 128)
@@ -73,39 +72,44 @@ module Nostrb
73
72
  ary!(ary, max: 9999).each { |a| ary!(a, max: 99).each { |s| txt!(s) } }
74
73
  end
75
74
 
76
- def self.rbsecp256k1?
77
- begin
78
- require 'rbsecp256k1'; Secp256k1
79
- rescue LoadError, NameError
80
- false
75
+ # optional dependencies
76
+ module Optional
77
+ GEMS = %w[rbsecp256k1 oj sqlite3 sequel]
78
+
79
+ def self.rbsecp256k1?
80
+ begin
81
+ require 'rbsecp256k1'; ::Secp256k1
82
+ rescue LoadError, NameError
83
+ false
84
+ end
81
85
  end
82
- end
83
86
 
84
- def self.oj?
85
- begin
86
- require 'oj'; Oj
87
- rescue LoadError, NameError
88
- false
87
+ def self.oj?
88
+ begin
89
+ require 'oj'; ::Oj
90
+ rescue LoadError, NameError
91
+ false
92
+ end
89
93
  end
90
- end
91
94
 
92
- def self.sqlite3?
93
- begin
94
- require 'sqlite3'; SQLite3
95
- rescue LoadError, NameError
96
- false
95
+ def self.sqlite3?
96
+ begin
97
+ require 'sqlite3'; ::SQLite3
98
+ rescue LoadError, NameError
99
+ false
100
+ end
97
101
  end
98
- end
99
102
 
100
- def self.sequel?
101
- begin
102
- require 'sequel'; Sequel
103
- rescue LoadError, NameError
104
- false
103
+ def self.sequel?
104
+ begin
105
+ require 'sequel'; ::Sequel
106
+ rescue LoadError, NameError
107
+ false
108
+ end
105
109
  end
106
- end
107
110
 
108
- def self.gem_check
109
- GEMS.map { |gem| [gem, self.send("#{gem}?")] }.to_h
111
+ def self.gem_check
112
+ GEMS.map { |gem| [gem, self.send("#{gem}?")] }.to_h
113
+ end
110
114
  end
111
115
  end
data/test/relay.rb CHANGED
@@ -12,7 +12,7 @@ DB_FILE = ENV['INPUT_DB_FILE'] || 'testing.db'
12
12
  # use SQLite backing
13
13
  SQLite::Setup.new(DB_FILE).setup
14
14
 
15
- describe Server do
15
+ describe Relay do
16
16
  def valid_response!(resp)
17
17
  types = ["EVENT", "OK", "EOSE", "CLOSED", "NOTICE"]
18
18
  expect(resp).must_be_kind_of Array
@@ -26,7 +26,7 @@ describe Server do
26
26
  describe "class functions" do
27
27
  it "has an EVENT response, given subscriber_id and requested event" do
28
28
  sid = '1234'
29
- resp = Server.event(sid, Test::SIGNED)
29
+ resp = Relay.event(sid, Test::SIGNED)
30
30
  valid_response!(resp)
31
31
  expect(resp[0]).must_equal "EVENT"
32
32
  expect(resp[1]).must_equal sid
@@ -36,7 +36,7 @@ describe Server do
36
36
 
37
37
  it "has an OK response, given an event_id" do
38
38
  # positive ok
39
- resp = Server.ok(Test::SIGNED.id)
39
+ resp = Relay.ok(Test::SIGNED.id)
40
40
  valid_response!(resp)
41
41
  expect(resp[0]).must_equal "OK"
42
42
  expect(resp[1]).must_equal Test::SIGNED.id
@@ -44,7 +44,7 @@ describe Server do
44
44
  expect(resp[3]).must_be_kind_of String # empty by default
45
45
 
46
46
  # negative ok
47
- resp = Server.ok(Test::SIGNED.id, "error: testing", ok: false)
47
+ resp = Relay.ok(Test::SIGNED.id, "error: testing", ok: false)
48
48
  valid_response!(resp)
49
49
  expect(resp[0]).must_equal "OK"
50
50
  expect(resp[1]).must_equal Test::SIGNED.id
@@ -54,14 +54,14 @@ describe Server do
54
54
 
55
55
  # ok:false requires nonempty message
56
56
  expect {
57
- Server.ok(Test::SIGNED.id, "", ok: false)
57
+ Relay.ok(Test::SIGNED.id, "", ok: false)
58
58
  }.must_raise FormatError
59
- expect { Server.ok(Test::SIGNED.id, ok: false) }.must_raise FormatError
59
+ expect { Relay.ok(Test::SIGNED.id, ok: false) }.must_raise FormatError
60
60
  end
61
61
 
62
62
  it "has an EOSE response to conclude a series of EVENT responses" do
63
63
  sid = '1234'
64
- resp = Server.eose(sid)
64
+ resp = Relay.eose(sid)
65
65
  valid_response!(resp)
66
66
  expect(resp[0]).must_equal "EOSE"
67
67
  expect(resp[1]).must_equal sid
@@ -70,7 +70,7 @@ describe Server do
70
70
  it "has a CLOSED response to shut down a subscriber" do
71
71
  sid = '1234'
72
72
  msg = "closed: bye"
73
- resp = Server.closed(sid, msg)
73
+ resp = Relay.closed(sid, msg)
74
74
  valid_response!(resp)
75
75
  expect(resp[0]).must_equal "CLOSED"
76
76
  expect(resp[1]).must_equal sid
@@ -80,7 +80,7 @@ describe Server do
80
80
  it "has a NOTICE response to provide any message to the user" do
81
81
  msg = "all i ever really wanna do is get nice, " +
82
82
  "get loose and goof this little slice of life"
83
- resp = Server.notice(msg)
83
+ resp = Relay.notice(msg)
84
84
  valid_response!(resp)
85
85
  expect(resp[0]).must_equal "NOTICE"
86
86
  expect(resp[1]).must_equal msg
@@ -89,16 +89,16 @@ describe Server do
89
89
  it "formats Exceptions to a common string representation" do
90
90
  r = RuntimeError.new("stuff")
91
91
  expect(r).must_be_kind_of Exception
92
- expect(Server.message(r)).must_equal "RuntimeError: stuff"
92
+ expect(Relay.message(r)).must_equal "RuntimeError: stuff"
93
93
 
94
94
  e = Nostrb::Error.new("things")
95
95
  expect(e).must_be_kind_of Exception
96
- expect(Server.message(e)).must_equal "Error: things"
96
+ expect(Relay.message(e)).must_equal "Error: things"
97
97
  end
98
98
 
99
99
  it "uses NOTICE to return errors" do
100
100
  e = RuntimeError.new "stuff"
101
- resp = Server.error(e)
101
+ resp = Relay.error(e)
102
102
  valid_response!(resp)
103
103
  expect(resp[0]).must_equal "NOTICE"
104
104
  expect(resp[1]).must_equal "RuntimeError: stuff"
@@ -106,14 +106,14 @@ describe Server do
106
106
  end
107
107
 
108
108
  it "has no initialization parameters" do
109
- s = Server.new(DB_FILE)
110
- expect(s).must_be_kind_of Server
109
+ s = Relay.new(DB_FILE)
110
+ expect(s).must_be_kind_of Relay
111
111
  end
112
112
 
113
113
  # respond OK: true
114
114
  it "has a single response to EVENT requests" do
115
115
  json = Nostrb.json(Source.publish(Test::SIGNED))
116
- responses = Server.new(DB_FILE).ingest(json)
116
+ responses = Relay.new(DB_FILE).ingest(json)
117
117
  expect(responses).must_be_kind_of Array
118
118
  expect(responses.length).must_equal 1
119
119
 
@@ -126,7 +126,7 @@ describe Server do
126
126
 
127
127
  # store and retrieve with a subscription filter
128
128
  it "stores inbound events" do
129
- s = Server.new(DB_FILE)
129
+ s = Relay.new(DB_FILE)
130
130
  sk, pk = SchnorrSig.keypair
131
131
  e = Event.new('sqlite', pk: pk).sign(sk)
132
132
  resp = s.ingest Nostrb.json(Source.publish(e))
@@ -152,7 +152,7 @@ describe Server do
152
152
  end
153
153
 
154
154
  it "has multiple responses to REQ requets" do
155
- s = Server.new(DB_FILE)
155
+ s = Relay.new(DB_FILE)
156
156
  sk, pk = SchnorrSig.keypair
157
157
  e = Event.new('first', pk: pk).sign(sk)
158
158
  resp = s.ingest Nostrb.json(Source.publish(e))
@@ -204,7 +204,7 @@ describe Server do
204
204
  end
205
205
 
206
206
  it "has a single response to CLOSE requests" do
207
- s = Server.new(DB_FILE)
207
+ s = Relay.new(DB_FILE)
208
208
  sid = Test::EVENT.pubkey
209
209
  responses = s.ingest(Nostrb.json(Source.close(sid)))
210
210
 
@@ -221,9 +221,9 @@ describe Server do
221
221
  describe "error handling" do
222
222
  # invalid request type
223
223
  it "handles unknown unknown request types with an error notice" do
224
- a = Source.publish Test::SIGNED
224
+ a = Source.publish(Test::SIGNED).dup
225
225
  a[0] = 'NONSENSE'
226
- responses = Server.new(DB_FILE).ingest(Nostrb.json(a))
226
+ responses = Relay.new(DB_FILE).ingest(Nostrb.json(a))
227
227
  expect(responses).must_be_kind_of Array
228
228
  expect(responses.length).must_equal 1
229
229
 
@@ -236,10 +236,10 @@ describe Server do
236
236
 
237
237
  # replace leading open brace with space
238
238
  it "handles JSON parse errors with an error notice" do
239
- j = Nostrb.json(Nostrb::Source.publish(Test::SIGNED))
239
+ j = Nostrb.json(Nostrb::Source.publish(Test::SIGNED)).dup
240
240
  expect(j[9]).must_equal '{'
241
241
  j[9] = ' '
242
- resp = Server.new(DB_FILE).ingest(j)
242
+ resp = Relay.new(DB_FILE).ingest(j)
243
243
  expect(resp).must_be_kind_of Array
244
244
  expect(resp.length).must_equal 1
245
245
 
@@ -251,11 +251,12 @@ describe Server do
251
251
 
252
252
  # add "stuff":"things"
253
253
  it "handles unexpected fields with an error notice" do
254
- a = Nostrb::Source.publish(Test::SIGNED)
254
+ a = Nostrb::Source.publish(Test::SIGNED).dup
255
255
  expect(a[1]).must_be_kind_of Hash
256
+ a[1] = a[1].dup
256
257
  a[1]["stuff"] = "things"
257
258
 
258
- resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
259
+ resp = Relay.new(DB_FILE).ingest(Nostrb.json(a))
259
260
  expect(resp).must_be_kind_of Array
260
261
  expect(resp.length).must_equal 1
261
262
 
@@ -269,9 +270,11 @@ describe Server do
269
270
  it "handles missing fields with an error notice" do
270
271
  a = Nostrb::Source.publish(Test::SIGNED)
271
272
  expect(a[1]).must_be_kind_of Hash
273
+ a = a.dup
274
+ a[1] = a[1].dup
272
275
  a[1].delete("tags")
273
276
 
274
- resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
277
+ resp = Relay.new(DB_FILE).ingest(Nostrb.json(a))
275
278
  expect(resp).must_be_kind_of Array
276
279
  expect(resp.length).must_equal 1
277
280
 
@@ -283,11 +286,12 @@ describe Server do
283
286
 
284
287
  # cut "id" in half
285
288
  it "handles field format errors with an error notice" do
286
- a = Nostrb::Source.publish(Test::SIGNED)
289
+ a = Nostrb::Source.publish(Test::SIGNED).dup
287
290
  expect(a[1]).must_be_kind_of Hash
291
+ a[1] = a[1].dup
288
292
  a[1]["id"] = a[1]["id"].slice(0, 32)
289
293
 
290
- resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
294
+ resp = Relay.new(DB_FILE).ingest(Nostrb.json(a))
291
295
  expect(resp).must_be_kind_of Array
292
296
  expect(resp.length).must_equal 1
293
297
 
@@ -301,9 +305,11 @@ describe Server do
301
305
  it "handles invalid signature with OK:false" do
302
306
  a = Nostrb::Source.publish(Test::SIGNED)
303
307
  expect(a[1]).must_be_kind_of Hash
308
+ a = a.dup
309
+ a[1] = a[1].dup
304
310
  a[1]["sig"] = SchnorrSig.bin2hex(Random.bytes(64))
305
311
 
306
- resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
312
+ resp = Relay.new(DB_FILE).ingest(Nostrb.json(a))
307
313
  expect(resp).must_be_kind_of Array
308
314
  expect(resp.length).must_equal 1
309
315
 
@@ -318,16 +324,17 @@ describe Server do
318
324
 
319
325
  # "id" and "sig" spoofed from another event
320
326
  it "handles spoofed id with OK:false" do
321
- orig = Source.publish(Test.new_event('orig'))
322
- spoof = Source.publish(Test::SIGNED)
327
+ orig = Source.publish(Test.new_event('orig')).dup
328
+ spoof = Source.publish(Test::SIGNED).dup
323
329
 
330
+ orig[1] = orig[1].dup
324
331
  orig[1]["id"] = spoof[1]["id"]
325
332
  orig[1]["sig"] = spoof[1]["sig"]
326
333
 
327
334
  # now sig and id agree with each other, but not orig's content/metadata
328
335
  # the signature should verify, but the id should not
329
336
 
330
- resp = Server.new(DB_FILE).ingest(Nostrb.json(orig))
337
+ resp = Relay.new(DB_FILE).ingest(Nostrb.json(orig))
331
338
  expect(resp).must_be_kind_of Array
332
339
  expect(resp.length).must_equal 1
333
340
 
@@ -342,10 +349,11 @@ describe Server do
342
349
 
343
350
  # random "id"
344
351
  it "handles invalid id with OK:false" do
345
- a = Source.publish(Test::SIGNED)
352
+ a = Source.publish(Test::SIGNED).dup
353
+ a[1] = a[1].dup
346
354
  a[1]["id"] = SchnorrSig.bin2hex(Random.bytes(32))
347
355
 
348
- resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
356
+ resp = Relay.new(DB_FILE).ingest(Nostrb.json(a))
349
357
  expect(resp).must_be_kind_of Array
350
358
  expect(resp.length).must_equal 1
351
359
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nostrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.1
4
+ version: 0.2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rick Hull
@@ -33,6 +33,7 @@ files:
33
33
  - Rakefile
34
34
  - VERSION
35
35
  - lib/nostrb.rb
36
+ - lib/nostrb/client.rb
36
37
  - lib/nostrb/event.rb
37
38
  - lib/nostrb/filter.rb
38
39
  - lib/nostrb/json.rb