nostrb 0.1.0.1 → 0.2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +7 -2
- data/VERSION +1 -1
- data/lib/nostrb/client.rb +75 -0
- data/lib/nostrb/event.rb +25 -11
- data/lib/nostrb/filter.rb +5 -1
- data/lib/nostrb/json.rb +2 -2
- data/lib/nostrb/oj.rb +2 -2
- data/lib/nostrb/relay.rb +11 -11
- data/lib/nostrb/sequel.rb +6 -7
- data/lib/nostrb/source.rb +6 -6
- data/lib/nostrb/sqlite.rb +20 -19
- data/lib/nostrb.rb +34 -30
- data/test/relay.rb +41 -33
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a89ed8e93f661c8de971b8cba920363431d8a6a21ba22487877b09c041b25123
|
4
|
+
data.tar.gz: 4d2f947fc9244d1db2c003b27c004f7a666fa67e1f3e36283fecfb396228647c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 "
|
14
|
+
sh "bundle exec falcon serve --bind wss://localhost:7070"
|
10
15
|
end
|
11
16
|
|
12
|
-
task default: [:test
|
17
|
+
task default: [:test]
|
13
18
|
|
14
19
|
begin
|
15
20
|
require 'buildar'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
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
|
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
|
-
[
|
61
|
+
[Relay.notice("unexpected: #{a[0].inspect}")]
|
62
62
|
end
|
63
63
|
rescue StandardError => e
|
64
|
-
[
|
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
|
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
|
-
|
97
|
+
Relay.ok(eid)
|
98
98
|
rescue SignedEvent::Error => e
|
99
|
-
|
99
|
+
Relay.ok(eid, Relay.message(e), ok: false)
|
100
100
|
rescue Nostrb::Error, KeyError, RuntimeError => e
|
101
|
-
|
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 <<
|
127
|
+
responses << Relay.event(sid, h) if f.match? h
|
128
128
|
}
|
129
129
|
@reader.process_r_events(f).each { |h|
|
130
|
-
responses <<
|
130
|
+
responses << Relay.event(sid, h) if f.match? h
|
131
131
|
}
|
132
132
|
}
|
133
133
|
responses = responses.to_a
|
134
|
-
responses <<
|
134
|
+
responses << Relay.eose(sid)
|
135
135
|
end
|
136
136
|
|
137
137
|
# single response
|
138
138
|
def handle_close(sid)
|
139
|
-
|
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
|
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
|
-
|
406
|
-
valid["tags"]
|
407
|
-
|
408
|
-
|
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
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
109
|
-
|
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
|
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 =
|
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 =
|
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 =
|
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
|
-
|
57
|
+
Relay.ok(Test::SIGNED.id, "", ok: false)
|
58
58
|
}.must_raise FormatError
|
59
|
-
expect {
|
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 =
|
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 =
|
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 =
|
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(
|
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(
|
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 =
|
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 =
|
110
|
-
expect(s).must_be_kind_of
|
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 =
|
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 =
|
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 =
|
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 =
|
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
|
224
|
+
a = Source.publish(Test::SIGNED).dup
|
225
225
|
a[0] = 'NONSENSE'
|
226
|
-
responses =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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.
|
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
|