177 lines
5.1 KiB
Ruby
177 lines
5.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'event_emitter'
|
|
require 'faye/websocket'
|
|
|
|
module Nostr
|
|
# Clients can talk with relays and can subscribe to any set of events using a subscription filters.
|
|
# The filter represents all the set of nostr events that a client is interested in.
|
|
#
|
|
# There is no sign-up or account creation for a client. Every time a client connects to a relay, it submits its
|
|
# subscription filters and the relay streams the "interested events" to the client as long as they are connected.
|
|
#
|
|
class Client
|
|
include EventEmitter
|
|
|
|
# Instantiates a new Client
|
|
#
|
|
# @api public
|
|
#
|
|
# @example Instantiating a client that logs all the events it sends and receives
|
|
# client = Nostr::Client.new(debug: true)
|
|
#
|
|
def initialize
|
|
@subscriptions = {}
|
|
|
|
initialize_channels
|
|
end
|
|
|
|
# Connects to the Relay's websocket endpoint
|
|
#
|
|
# @api public
|
|
#
|
|
# @example Connecting to a relay
|
|
# relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
|
|
# client.connect(relay)
|
|
#
|
|
# @param [Relay] relay The relay to connect to
|
|
#
|
|
# @return [void]
|
|
#
|
|
def connect(relay)
|
|
execute_within_an_em_thread do
|
|
client = Faye::WebSocket::Client.new(relay.url, [], { tls: { verify_peer: false } })
|
|
parent_to_child_channel.subscribe { |msg| client.send(msg) }
|
|
|
|
client.on :open do
|
|
child_to_parent_channel.push(type: :open)
|
|
end
|
|
|
|
client.on :message do |event|
|
|
child_to_parent_channel.push(type: :message, data: event.data)
|
|
end
|
|
|
|
client.on :error do |event|
|
|
child_to_parent_channel.push(type: :error, message: event.message)
|
|
end
|
|
|
|
client.on :close do |event|
|
|
child_to_parent_channel.push(type: :close, code: event.code, reason: event.reason)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Subscribes to a set of events using a filter
|
|
#
|
|
# @api public
|
|
#
|
|
# @example Creating a subscription with no id and no filters
|
|
# subscription = client.subscribe
|
|
#
|
|
# @example Creating a subscription with an ID
|
|
# subscription = client.subscribe(subscription_id: 'my-subscription')
|
|
#
|
|
# @example Subscribing to all events created after a certain time
|
|
# subscription = client.subscribe(filter: Nostr::Filter.new(since: 1230981305))
|
|
#
|
|
# @param subscription_id [String] The subscription id. A random string.
|
|
# @param filter [Filter] A set of attributes that represent the events that the client is interested in.
|
|
#
|
|
# @return [Subscription] The subscription object
|
|
#
|
|
def subscribe(subscription_id: SecureRandom.hex, filter: Filter.new)
|
|
subscriptions[subscription_id] = Subscription.new(id: subscription_id, filter:)
|
|
parent_to_child_channel.push([ClientMessageType::REQ, subscription_id, filter.to_h].to_json)
|
|
subscriptions[subscription_id]
|
|
end
|
|
|
|
# Stops a previous subscription
|
|
#
|
|
# @api public
|
|
#
|
|
# @example Stopping a subscription
|
|
# client.unsubscribe(subscription.id)
|
|
#
|
|
# @example Stopping a subscription
|
|
# client.unsubscribe('my-subscription')
|
|
#
|
|
# @param subscription_id [String] ID of a previously created subscription.
|
|
#
|
|
# @return [void]
|
|
#
|
|
def unsubscribe(subscription_id)
|
|
subscriptions.delete(subscription_id)
|
|
parent_to_child_channel.push([ClientMessageType::CLOSE, subscription_id].to_json)
|
|
end
|
|
|
|
# Sends an event to a Relay
|
|
#
|
|
# @api public
|
|
#
|
|
# @example Sending an event to a relay
|
|
# client.publish(event)
|
|
#
|
|
# @param event [Event] The event to be sent to a Relay
|
|
#
|
|
# @return [void]
|
|
#
|
|
def publish(event)
|
|
parent_to_child_channel.push([ClientMessageType::EVENT, event.to_h].to_json)
|
|
end
|
|
|
|
private
|
|
|
|
# The subscriptions that the client has created
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [Hash{String=>Subscription}>]
|
|
#
|
|
attr_reader :subscriptions
|
|
|
|
# The channel that the parent thread uses to send messages to the child thread
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [EventMachine::Channel]
|
|
#
|
|
attr_reader :parent_to_child_channel
|
|
|
|
# The channel that the child thread uses to send messages to the parent thread
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [EventMachine::Channel]
|
|
#
|
|
attr_reader :child_to_parent_channel
|
|
|
|
# Executes a block of code within the EventMachine thread
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [Thread]
|
|
#
|
|
def execute_within_an_em_thread(&block)
|
|
Thread.new { EventMachine.run(block) }
|
|
end
|
|
|
|
# Creates the communication channels between threads
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [void]
|
|
#
|
|
def initialize_channels
|
|
@parent_to_child_channel = EventMachine::Channel.new
|
|
@child_to_parent_channel = EventMachine::Channel.new
|
|
|
|
child_to_parent_channel.subscribe do |msg|
|
|
emit :connect if msg[:type] == :open
|
|
emit :message, msg[:data] if msg[:type] == :message
|
|
emit :error, msg[:message] if msg[:type] == :error
|
|
emit :close, msg[:code], msg[:reason] if msg[:type] == :close
|
|
end
|
|
end
|
|
end
|
|
end
|