1# frozen_string_literal: true
 2
 3class BlatherClient < Blather::Client
 4	def handle_data(stanza)
 5		EMPromise.resolve(nil).then {
 6			with_sentry(stanza) do |scope|
 7				Thread.current[:log] = ::LOG.child(
 8					transaction: scope.transaction_name
 9				)
10				super
11			rescue StandardError => e
12				handle_error(scope, stanza, e)
13			end
14		}.catch { |e| panic(e) }
15	end
16
17	def with_sentry(stanza)
18		Sentry.clone_hub_to_current_thread
19
20		Sentry.with_scope do |scope|
21			setup_scope(stanza, scope)
22			yield scope
23		ensure
24			scope.get_transaction&.then do |tx|
25				tx.set_status("ok") unless tx.status
26				tx.finish
27			end
28		end
29	end
30
31	def setup_scope(stanza, scope)
32		name = stanza.respond_to?(:node) ? stanza.node : stanza.name
33		scope.clear_breadcrumbs
34		scope.set_transaction_name(name)
35		scope.set_user(jid: stanza.from.stripped.to_s)
36
37		transaction = Sentry.start_transaction(
38			name: name,
39			op: "blather.handle_data"
40		)
41		scope.set_span(transaction) if transaction
42	end
43
44	def handle_error(scope, stanza, e)
45		log.error(
46			"Error raised during #{scope.transaction_name}: " \
47			"#{e.class}",
48			e
49		)
50		Sentry.capture_exception(e) unless e.is_a?(Sentry::Error)
51		scope.get_transaction&.set_status("internal_error")
52		return if e.respond_to?(:replied?) && e.replied?
53
54		write stanza.as_error("internal-server-error", :cancel)
55	end
56
57	def call_handler(handler, guards, stanza)
58		result = if guards.first.respond_to?(:to_str)
59			found = stanza.find(*guards)
60			throw :pass if found.empty?
61
62			handler.call(stanza, found)
63		else
64			throw :pass if guarded?(guards, stanza)
65
66			handler.call(stanza)
67		end
68
69		return result unless result.is_a?(Promise)
70
71		result.sync
72	end
73end