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