1# frozen_string_literal: true
  2
  3require "blather"
  4require "blather/stanza/iq/command"
  5require "value_semantics/monkey_patched"
  6
  7require_relative "customer_fwd"
  8require_relative "not_loaded"
  9require_relative "form_to_h"
 10
 11class BackendSgx
 12	COMMANDS_DISCO_NODE = "http://jabber.org/protocol/commands"
 13
 14	class CanceledError < StandardError; end
 15
 16	using FormToH
 17
 18	value_semantics do
 19		jid Blather::JID
 20		creds HashOf(Symbol => String)
 21		from_jid Blather::JID
 22		ogm_url Either(String, nil, NotLoaded)
 23		fwd Either(CustomerFwd, nil, NotLoaded)
 24		transcription_enabled Either(Bool(), NotLoaded)
 25		registered? Either(Blather::Stanza::Iq::IBR, FalseClass, NotLoaded)
 26	end
 27
 28	def register!(tel)
 29		iq = ibr
 30		iq.phone = tel
 31		IQ_MANAGER.write(iq).then do
 32			REDIS.set("catapult_jid-#{tel}", from_jid.to_s)
 33		end
 34	end
 35
 36	def deregister!
 37		ibr = Blather::Stanza::Iq::IBR.new(:set, @jid)
 38		ibr.from = from_jid
 39		ibr.remove!
 40		IQ_MANAGER.write(ibr)
 41	end
 42
 43	def stanza(s)
 44		s.dup.tap do |stanza|
 45			stanza.to = stanza.to.with(
 46				domain: jid.domain,
 47				node: jid.node || stanza.to.node
 48			)
 49			stanza.from = from_jid.with(resource: stanza.from&.resource)
 50		end
 51	end
 52
 53	def set_ogm_url(url)
 54		REDIS.set("catapult_ogm_url-#{from_jid}", url)
 55	end
 56
 57	def tn_portable?
 58		iq = Blather::Stanza::DiscoItems.new(:get, COMMANDS_DISCO_NODE)
 59		iq.to = jid
 60		iq.from = from_jid
 61		IQ_MANAGER.write(iq).then { |reply|
 62			reply.items.any? { |item| item.node == "set-port-out-pin" }
 63		}.catch { |e|
 64			Sentry.capture_exception(e)
 65			false
 66		}
 67	end
 68
 69	def set_port_out_pin(pin)
 70		cmd = build_port_out_command(:execute)
 71
 72		IQ_MANAGER.write(cmd).then { |reply|
 73			session_id = reply.command[:sessionid]
 74			submit_cmd = build_submit_form(pin, session_id)
 75
 76			IQ_MANAGER.write(submit_cmd).then { |submit_reply|
 77				validate_submit_reply!(submit_reply)
 78			}.catch { |e|
 79				handle_pin_submission_error(e)
 80			}
 81		}
 82	end
 83
 84protected
 85
 86	def ibr
 87		s = Blather::Stanza::Iq::IBR.new(:set, jid)
 88		s.from = from_jid
 89		s.nick = creds[:account]
 90		s.username = creds[:username]
 91		s.password = creds[:password]
 92		s
 93	end
 94
 95	def build_submit_form(pin, session_id)
 96		build_port_out_command(:complete, session_id: session_id).tap { |iq|
 97			iq.form.type = :submit
 98			iq.form.fields = [
 99				{ var: "pin", value: pin, type: "text-private" },
100				{ var: "confirm_pin", value: pin, type: "text-private" }
101			]
102		}
103	end
104
105	def build_port_out_command(action, session_id: nil)
106		Blather::Stanza::Iq::Command.new.tap { |iq|
107			iq.to = jid
108			iq.from = from_jid
109			iq.node = "set-port-out-pin"
110			iq.action = action
111			iq.sessionid = session_id if session_id
112		}
113	end
114
115	def validate_submit_reply!(submit_reply)
116		sub_text = submit_reply.note&.text
117		case submit_reply.status
118		when :completed
119			raise sub_text if submit_reply.note&.[]("type") == "error"
120		when :canceled
121			raise CanceledError, reply.note&.text
122		else
123			raise sub_text
124		end
125	end
126
127	def handle_pin_submission_error(e)
128		if e.is_a?(Blather::StanzaError) || e.is_a?(RuntimeError)
129			EMPromise.reject(e)
130		else
131			Sentry.capture_exception(e)
132			EMPromise.reject(
133				RuntimeError.new(
134					"Unable to communicate with service. Please try again later."
135				)
136			)
137		end
138	end
139end