rcs 2.0.19 → 2.0.20.pre.rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.fern/metadata.json +5 -6
  3. data/lib/pinnacle/calls/client.rb +359 -0
  4. data/lib/pinnacle/calls/types/calls_download_recording_request.rb +13 -0
  5. data/lib/pinnacle/calls/types/calls_list_events_request.rb +15 -0
  6. data/lib/pinnacle/calls/types/calls_list_request.rb +25 -0
  7. data/lib/pinnacle/calls/types/create_call_params.rb +17 -0
  8. data/lib/pinnacle/calls/types/create_stream_token_params.rb +17 -0
  9. data/lib/pinnacle/calls/types/update_call_params.rb +13 -0
  10. data/lib/pinnacle/client.rb +11 -1
  11. data/lib/pinnacle/forms/client.rb +1 -1
  12. data/lib/pinnacle/network/client.rb +34 -0
  13. data/lib/pinnacle/network/requests/client.rb +94 -0
  14. data/lib/pinnacle/network/requests/types/list_network_requests_params.rb +21 -0
  15. data/lib/pinnacle/network/silent_auth/client.rb +107 -0
  16. data/lib/pinnacle/network/silent_auth/types/silent_auth_check_request.rb +15 -0
  17. data/lib/pinnacle/network/silent_auth/types/silent_auth_request.rb +17 -0
  18. data/lib/pinnacle/network/sim_swap/client.rb +55 -0
  19. data/lib/pinnacle/network/sim_swap/types/sim_swap_request.rb +15 -0
  20. data/lib/pinnacle/network/subscriber_match/client.rb +55 -0
  21. data/lib/pinnacle/network/subscriber_match/types/subscriber_match_request.rb +15 -0
  22. data/lib/pinnacle/pinnacle_client.rb +8 -0
  23. data/lib/pinnacle/types/agent_summary_carrier_launches.rb +1 -1
  24. data/lib/pinnacle/types/call.rb +31 -0
  25. data/lib/pinnacle/types/call_direction.rb +12 -0
  26. data/lib/pinnacle/types/call_event.rb +27 -0
  27. data/lib/pinnacle/types/call_event_source.rb +14 -0
  28. data/lib/pinnacle/types/call_metadata.rb +23 -0
  29. data/lib/pinnacle/types/call_state.rb +19 -0
  30. data/lib/pinnacle/types/call_status_event.rb +14 -0
  31. data/lib/pinnacle/types/call_status_event_call.rb +17 -0
  32. data/lib/pinnacle/types/call_stream_token.rb +13 -0
  33. data/lib/pinnacle/types/created_call.rb +33 -0
  34. data/lib/pinnacle/types/e_164_phone_number.rb +23 -0
  35. data/lib/pinnacle/types/hosted_silent_auth_response.rb +13 -0
  36. data/lib/pinnacle/types/json_silent_auth_response.rb +13 -0
  37. data/lib/pinnacle/types/list_call_events_response.rb +11 -0
  38. data/lib/pinnacle/types/list_calls_response.rb +11 -0
  39. data/lib/pinnacle/types/list_network_requests_response.rb +13 -0
  40. data/lib/pinnacle/types/network_insight_status.rb +14 -0
  41. data/lib/pinnacle/types/network_request_id.rb +23 -0
  42. data/lib/pinnacle/types/network_request_result.rb +16 -0
  43. data/lib/pinnacle/types/network_request_status.rb +15 -0
  44. data/lib/pinnacle/types/network_request_summary.rb +27 -0
  45. data/lib/pinnacle/types/network_request_type.rb +13 -0
  46. data/lib/pinnacle/types/recording_download.rb +11 -0
  47. data/lib/pinnacle/types/recording_state.rb +14 -0
  48. data/lib/pinnacle/types/silent_auth_check_response.rb +11 -0
  49. data/lib/pinnacle/types/silent_auth_check_response_status.rb +12 -0
  50. data/lib/pinnacle/types/silent_auth_response.rb +15 -0
  51. data/lib/pinnacle/types/sim_swap_response.rb +15 -0
  52. data/lib/pinnacle/types/subscriber_match_attributes.rb +30 -0
  53. data/lib/pinnacle/types/subscriber_match_response.rb +13 -0
  54. data/lib/pinnacle/types/subscriber_match_response_matches.rb +41 -0
  55. data/lib/pinnacle/types/subscriber_match_result.rb +18 -0
  56. data/lib/pinnacle/types/webhook_event_enum.rb +1 -0
  57. data/lib/pinnacle/version.rb +1 -1
  58. data/lib/pinnacle/wrapper/messages/client.rb +4 -1
  59. data/lib/pinnacle/wrapper/voice/client.rb +134 -0
  60. data/lib/pinnacle/wrapper/voice/types.rb +38 -0
  61. data/lib/pinnacle/wrapper/voice/voice_socket.rb +354 -0
  62. data/lib/pinnacle.rb +49 -0
  63. data/reference.md +1903 -696
  64. metadata +75 -3
@@ -0,0 +1,354 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "securerandom"
5
+ require "timeout"
6
+
7
+ require_relative "types"
8
+
9
+ module Pinnacle
10
+ module Wrapper
11
+ module Voice
12
+ class VoiceSocket
13
+ attr_accessor :call, :call_id
14
+ attr_reader :socket
15
+
16
+ def initialize(socket, create_socket: nil, reconnect: nil)
17
+ @socket = socket
18
+ @create_socket = create_socket
19
+ @reconnect = normalize_reconnect_options(reconnect || {})
20
+ @listeners = Hash.new { |hash, key| hash[key] = [] }
21
+ @pending_acks = {}
22
+ @closed_by_user = false
23
+ @reconnect_attempts = 0
24
+ @reconnect_thread = nil
25
+ bind_socket
26
+ end
27
+
28
+ def ready_state
29
+ if @socket.respond_to?(:ready_state)
30
+ @socket.ready_state
31
+ elsif @socket.respond_to?(:readyState)
32
+ @socket.readyState
33
+ else
34
+ 0
35
+ end
36
+ end
37
+
38
+ def open?
39
+ ready_state == 1
40
+ end
41
+
42
+ def wait_until_open(timeout_ms: 10_000)
43
+ return if open?
44
+
45
+ mutex = Mutex.new
46
+ condition = ConditionVariable.new
47
+ error = nil
48
+ done = false
49
+ unsubscribers = []
50
+
51
+ cleanup = -> do
52
+ unsubscribers.each(&:call)
53
+ end
54
+ finish = ->(next_error) do
55
+ mutex.synchronize do
56
+ error = next_error
57
+ done = true
58
+ condition.signal
59
+ end
60
+ end
61
+
62
+ unsubscribers << on("open") do
63
+ cleanup.call
64
+ finish.call(nil)
65
+ end
66
+ unsubscribers << on("close") do
67
+ cleanup.call
68
+ finish.call(RuntimeError.new("Voice socket closed before opening."))
69
+ end
70
+ unsubscribers << on("error") do |event|
71
+ cleanup.call
72
+ finish.call(to_error(event))
73
+ end
74
+
75
+ mutex.synchronize do
76
+ condition.wait(mutex, timeout_ms / 1000.0) unless done
77
+ end
78
+ cleanup.call unless done
79
+ raise Timeout::Error, "Timed out waiting for voice socket to open." unless done
80
+ raise error unless error.nil?
81
+ end
82
+
83
+ def connect
84
+ wait_until_open
85
+ end
86
+
87
+ def on(event, &listener)
88
+ @listeners[event] << listener
89
+ -> { @listeners[event].delete(listener) }
90
+ end
91
+
92
+ def send(frame)
93
+ @socket.send(JSON.generate(frame))
94
+ end
95
+
96
+ def command(frame)
97
+ send(frame)
98
+ frame.fetch(:command_id)
99
+ end
100
+
101
+ def command_and_wait(frame, timeout_ms: 10_000)
102
+ waiter = wait_for_ack(frame.fetch(:command_id), timeout_ms: timeout_ms)
103
+ command(frame)
104
+ waiter.call
105
+ end
106
+
107
+ def answer(params = nil, command_id: create_voice_command_id)
108
+ command(with_optional_params(command_frame(command_id, Types::CALL_ANSWER), params))
109
+ end
110
+
111
+ def end_call(params = nil, command_id: create_voice_command_id)
112
+ command(with_optional_params(command_frame(command_id, Types::CALL_END), params))
113
+ end
114
+
115
+ def transfer(params, command_id: create_voice_command_id)
116
+ command(command_frame(command_id, Types::CALL_TRANSFER, params))
117
+ end
118
+
119
+ def start_recording(command_id: create_voice_command_id)
120
+ command(command_frame(command_id, Types::RECORDING_START))
121
+ end
122
+
123
+ def stop_recording(command_id: create_voice_command_id)
124
+ command(command_frame(command_id, Types::RECORDING_STOP))
125
+ end
126
+
127
+ def play_audio(params, command_id: create_voice_command_id)
128
+ command(command_frame(command_id, Types::AUDIO_PLAY, params))
129
+ end
130
+
131
+ def stop_audio(command_id: create_voice_command_id)
132
+ command(command_frame(command_id, Types::AUDIO_STOP))
133
+ end
134
+
135
+ def reduce_noise(params, command_id: create_voice_command_id)
136
+ command(command_frame(command_id, Types::AUDIO_REDUCE_NOISE, params))
137
+ end
138
+
139
+ def get_input(params = nil, command_id: create_voice_command_id)
140
+ command(with_optional_params(command_frame(command_id, Types::INPUT_GET), params))
141
+ end
142
+
143
+ def cancel_input(command_id: create_voice_command_id)
144
+ command(command_frame(command_id, Types::INPUT_CANCEL))
145
+ end
146
+
147
+ def send_dtmf(params, command_id: create_voice_command_id)
148
+ command(command_frame(command_id, Types::DTMF_SEND, params))
149
+ end
150
+
151
+ def update_state(params, command_id: create_voice_command_id)
152
+ command(command_frame(command_id, Types::CALL_UPDATE_STATE, params))
153
+ end
154
+
155
+ def send_media(media)
156
+ send({ event: "media", media: media })
157
+ end
158
+
159
+ def wait_for_ack(command_id, timeout_ms: 10_000)
160
+ raise "Already waiting for ack #{command_id}." if @pending_acks.key?(command_id)
161
+
162
+ mutex = Mutex.new
163
+ condition = ConditionVariable.new
164
+ result = nil
165
+ error = nil
166
+ done = false
167
+ @pending_acks[command_id] = ->(ack, next_error) do
168
+ mutex.synchronize do
169
+ result = ack
170
+ error = next_error
171
+ done = true
172
+ condition.signal
173
+ end
174
+ end
175
+
176
+ -> do
177
+ mutex.synchronize do
178
+ condition.wait(mutex, timeout_ms / 1000.0) unless done
179
+ end
180
+ @pending_acks.delete(command_id)
181
+ raise Timeout::Error, "Timed out waiting for ack #{command_id}." unless done
182
+ raise error unless error.nil?
183
+
184
+ result
185
+ end
186
+ end
187
+
188
+ def close(code = nil, reason = nil)
189
+ @closed_by_user = true
190
+ @reconnect_thread&.kill
191
+ close_socket(@socket, code, reason)
192
+ end
193
+
194
+ def reconnect_now
195
+ raise "Voice socket was not configured with a reconnect factory." if @create_socket.nil?
196
+
197
+ @closed_by_user = false
198
+ @socket = @create_socket.call
199
+ bind_socket
200
+ @reconnect_attempts = 0
201
+ emit("reconnected", @socket)
202
+ self
203
+ end
204
+
205
+ def handle_message(data)
206
+ frame = parse_voice_frame(data)
207
+ emit("frame", frame)
208
+ case frame["event"]
209
+ when "ack"
210
+ resolve_pending_ack(frame)
211
+ emit("ack", frame)
212
+ when "event"
213
+ emit("event", frame)
214
+ when "media"
215
+ emit("media", frame)
216
+ when "connected"
217
+ nil
218
+ else
219
+ raise "Voice socket received an unknown frame event."
220
+ end
221
+ rescue StandardError => e
222
+ emit("error", e)
223
+ end
224
+
225
+ private
226
+
227
+ def bind_socket
228
+ bind_socket_event(@socket, "open") { |event| emit("open", event) }
229
+ bind_socket_event(@socket, "close") { |event| handle_close(event) }
230
+ bind_socket_event(@socket, "error") { |event| emit("error", event) }
231
+ bind_socket_event(@socket, "message") { |event| handle_message(message_data(event)) }
232
+ end
233
+
234
+ def handle_close(event)
235
+ reject_pending_acks(RuntimeError.new("Voice socket closed before an ack was received."))
236
+ emit("close", event)
237
+ schedule_reconnect
238
+ end
239
+
240
+ def resolve_pending_ack(ack)
241
+ resolver = @pending_acks.delete(ack["command_id"])
242
+ resolver&.call(ack, nil)
243
+ end
244
+
245
+ def reject_pending_acks(error)
246
+ @pending_acks.each_value { |resolver| resolver.call(nil, error) }
247
+ @pending_acks.clear
248
+ end
249
+
250
+ def emit(event, payload)
251
+ @listeners[event].each { |listener| listener.call(payload) }
252
+ end
253
+
254
+ def schedule_reconnect
255
+ return if @closed_by_user || !@reconnect[:enabled] || @create_socket.nil? || @reconnect_thread&.alive?
256
+
257
+ if @reconnect_attempts >= @reconnect[:max_attempts]
258
+ emit("error", RuntimeError.new("Voice socket reconnect attempts exhausted."))
259
+ return
260
+ end
261
+
262
+ @reconnect_attempts += 1
263
+ delay_ms = reconnect_delay(@reconnect, @reconnect_attempts)
264
+ emit("reconnecting", { attempt: @reconnect_attempts, delay_ms: delay_ms })
265
+ @reconnect_thread = Thread.new do
266
+ sleep(delay_ms / 1000.0)
267
+ reconnect_now
268
+ rescue StandardError => e
269
+ emit("error", e)
270
+ schedule_reconnect
271
+ end
272
+ end
273
+
274
+ def command_frame(command_id, action, params = nil)
275
+ frame = { event: "command", command_id: command_id, action: action }
276
+ frame[:params] = params unless params.nil?
277
+ frame
278
+ end
279
+
280
+ def with_optional_params(frame, params)
281
+ frame[:params] = params unless params.nil?
282
+ frame
283
+ end
284
+
285
+ def parse_voice_frame(data)
286
+ raise "Voice socket received a non-string frame." unless data.is_a?(String)
287
+
288
+ JSON.parse(data)
289
+ end
290
+
291
+ def bind_socket_event(socket, event, &listener)
292
+ if socket.respond_to?(:add_event_listener)
293
+ socket.add_event_listener(event, &listener)
294
+ elsif socket.respond_to?(:addEventListener)
295
+ socket.addEventListener(event, listener)
296
+ elsif socket.respond_to?(:on)
297
+ socket.on(event, &listener)
298
+ else
299
+ socket.public_send(:"on#{event}=", listener)
300
+ end
301
+ end
302
+
303
+ def message_data(event)
304
+ event.is_a?(Hash) ? event[:data] || event["data"] : event.data
305
+ end
306
+
307
+ def close_socket(socket, code, reason)
308
+ if code.nil? && reason.nil?
309
+ socket.close
310
+ elsif reason.nil?
311
+ socket.close(code)
312
+ else
313
+ socket.close(code, reason)
314
+ end
315
+ end
316
+
317
+ def normalize_reconnect_options(options)
318
+ {
319
+ enabled: options.fetch(:enabled, options.fetch("enabled", false)),
320
+ max_attempts: options.fetch(:max_attempts, options.fetch(:maxAttempts, options.fetch("maxAttempts", 5))),
321
+ initial_delay_ms: options.fetch(
322
+ :initial_delay_ms,
323
+ options.fetch(:initialDelayMs, options.fetch("initialDelayMs", 250))
324
+ ),
325
+ max_delay_ms: options.fetch(:max_delay_ms, options.fetch(:maxDelayMs, options.fetch("maxDelayMs", 5_000))),
326
+ backoff_multiplier: options.fetch(
327
+ :backoff_multiplier,
328
+ options.fetch(:backoffMultiplier, options.fetch("backoffMultiplier", 2))
329
+ )
330
+ }
331
+ end
332
+
333
+ def reconnect_delay(options, attempt)
334
+ delay = options[:initial_delay_ms] * (options[:backoff_multiplier]**[attempt - 1, 0].max)
335
+ [delay, options[:max_delay_ms]].min
336
+ end
337
+
338
+ def create_voice_command_id
339
+ self.class.create_voice_command_id
340
+ end
341
+
342
+ def to_error(error)
343
+ error.is_a?(Exception) ? error : RuntimeError.new("Voice socket error.")
344
+ end
345
+
346
+ class << self
347
+ def create_voice_command_id
348
+ "cmd_#{SecureRandom.uuid}"
349
+ end
350
+ end
351
+ end
352
+ end
353
+ end
354
+ end
data/lib/pinnacle.rb CHANGED
@@ -452,6 +452,24 @@ require_relative "pinnacle/types/dlc_campaign_status"
452
452
  require_relative "pinnacle/types/rcs_campaign_status"
453
453
  require_relative "pinnacle/types/phone_number_status_enum"
454
454
  require_relative "pinnacle/types/phone_number_status"
455
+ require_relative "pinnacle/types/e_164_phone_number"
456
+ require_relative "pinnacle/types/network_request_id"
457
+ require_relative "pinnacle/types/network_insight_status"
458
+ require_relative "pinnacle/types/sim_swap_response"
459
+ require_relative "pinnacle/types/subscriber_match_attributes"
460
+ require_relative "pinnacle/types/subscriber_match_result"
461
+ require_relative "pinnacle/types/subscriber_match_response_matches"
462
+ require_relative "pinnacle/types/subscriber_match_response"
463
+ require_relative "pinnacle/types/hosted_silent_auth_response"
464
+ require_relative "pinnacle/types/json_silent_auth_response"
465
+ require_relative "pinnacle/types/silent_auth_response"
466
+ require_relative "pinnacle/types/silent_auth_check_response_status"
467
+ require_relative "pinnacle/types/silent_auth_check_response"
468
+ require_relative "pinnacle/types/network_request_type"
469
+ require_relative "pinnacle/types/network_request_status"
470
+ require_relative "pinnacle/types/network_request_result"
471
+ require_relative "pinnacle/types/network_request_summary"
472
+ require_relative "pinnacle/types/list_network_requests_response"
455
473
  require_relative "pinnacle/types/create_url_options"
456
474
  require_relative "pinnacle/types/pinnacle_url_config"
457
475
  require_relative "pinnacle/types/shortened_url"
@@ -487,6 +505,18 @@ require_relative "pinnacle/types/failed_sender"
487
505
  require_relative "pinnacle/types/attach_webhook_response_webhook"
488
506
  require_relative "pinnacle/types/attach_webhook_result"
489
507
  require_relative "pinnacle/types/detach_webhook_result"
508
+ require_relative "pinnacle/types/call_state"
509
+ require_relative "pinnacle/types/call_direction"
510
+ require_relative "pinnacle/types/recording_state"
511
+ require_relative "pinnacle/types/call"
512
+ require_relative "pinnacle/types/list_calls_response"
513
+ require_relative "pinnacle/types/call_metadata"
514
+ require_relative "pinnacle/types/created_call"
515
+ require_relative "pinnacle/types/call_event_source"
516
+ require_relative "pinnacle/types/call_event"
517
+ require_relative "pinnacle/types/list_call_events_response"
518
+ require_relative "pinnacle/types/call_stream_token"
519
+ require_relative "pinnacle/types/recording_download"
490
520
  require_relative "pinnacle/types/list_forms_response"
491
521
  require_relative "pinnacle/types/list_form_submissions_response"
492
522
  require_relative "pinnacle/types/form_definition"
@@ -529,6 +559,8 @@ require_relative "pinnacle/types/campaign_status_event_carrier_launches_carriers
529
559
  require_relative "pinnacle/types/campaign_status_event_carrier_launches_verification"
530
560
  require_relative "pinnacle/types/campaign_status_event_carrier_launches"
531
561
  require_relative "pinnacle/types/campaign_status_event"
562
+ require_relative "pinnacle/types/call_status_event_call"
563
+ require_relative "pinnacle/types/call_status_event"
532
564
  require_relative "pinnacle/types/form_submission_event_conversation"
533
565
  require_relative "pinnacle/types/form_submission_event_form"
534
566
  require_relative "pinnacle/types/form_submission_answer"
@@ -573,6 +605,13 @@ require_relative "pinnacle/webhooks/types/get_webhook_params"
573
605
  require_relative "pinnacle/webhooks/types/list_webhooks_params"
574
606
  require_relative "pinnacle/webhooks/types/attach_webhook_params"
575
607
  require_relative "pinnacle/webhooks/types/detach_webhook_params"
608
+ require_relative "pinnacle/calls/client"
609
+ require_relative "pinnacle/calls/types/calls_list_request"
610
+ require_relative "pinnacle/calls/types/create_call_params"
611
+ require_relative "pinnacle/calls/types/update_call_params"
612
+ require_relative "pinnacle/calls/types/calls_list_events_request"
613
+ require_relative "pinnacle/calls/types/create_stream_token_params"
614
+ require_relative "pinnacle/calls/types/calls_download_recording_request"
576
615
  require_relative "pinnacle/forms/client"
577
616
  require_relative "pinnacle/forms/types/update_form_params"
578
617
  require_relative "pinnacle/forms/types/list_forms_params"
@@ -607,6 +646,16 @@ require_relative "pinnacle/messages/schedules/types/list_scheduled_messages_para
607
646
  require_relative "pinnacle/messages/blasts/client"
608
647
  require_relative "pinnacle/messages/blasts/types/list_blasts_params"
609
648
  require_relative "pinnacle/messages/simulate/client"
649
+ require_relative "pinnacle/network/client"
650
+ require_relative "pinnacle/network/sim_swap/client"
651
+ require_relative "pinnacle/network/sim_swap/types/sim_swap_request"
652
+ require_relative "pinnacle/network/subscriber_match/client"
653
+ require_relative "pinnacle/network/subscriber_match/types/subscriber_match_request"
654
+ require_relative "pinnacle/network/silent_auth/client"
655
+ require_relative "pinnacle/network/silent_auth/types/silent_auth_request"
656
+ require_relative "pinnacle/network/silent_auth/types/silent_auth_check_request"
657
+ require_relative "pinnacle/network/requests/client"
658
+ require_relative "pinnacle/network/requests/types/list_network_requests_params"
610
659
  require_relative "pinnacle/phone_numbers/campaign/client"
611
660
  require_relative "pinnacle/phone_numbers/campaign/types/attach_campaign_params"
612
661
  require_relative "pinnacle/phone_numbers/campaign/types/detach_campaign_params"