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