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,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ module NetworkRequestId
6
+ # NetworkRequestId is an alias for String
7
+
8
+ # @option str [String]
9
+ #
10
+ # @return [untyped]
11
+ def self.load(str)
12
+ ::JSON.parse(str)
13
+ end
14
+
15
+ # @option value [untyped]
16
+ #
17
+ # @return [String]
18
+ def self.dump(value)
19
+ ::JSON.generate(value)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ # Result from the original check, or null while the request is still pending or no result is available.
6
+ class NetworkRequestResult < Internal::Types::Model
7
+ extend Pinnacle::Internal::Types::Union
8
+
9
+ member -> { Pinnacle::Types::SimSwapResponse }
10
+
11
+ member -> { Pinnacle::Types::SubscriberMatchResponse }
12
+
13
+ member -> { Pinnacle::Types::SilentAuthCheckResponse }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ module NetworkRequestStatus
6
+ extend Pinnacle::Internal::Types::Enum
7
+
8
+ STARTED = "started"
9
+ CHALLENGE_ISSUED = "challenge-issued"
10
+ COMPLETED = "completed"
11
+ FAILED = "failed"
12
+ EXPIRED = "expired"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ class NetworkRequestSummary < Internal::Types::Model
6
+ field :request_id, -> { String }, optional: false, nullable: false, api_name: "requestId"
7
+
8
+ field :type, -> { Pinnacle::Types::NetworkRequestType }, optional: false, nullable: false
9
+
10
+ field :status, -> { Pinnacle::Types::NetworkRequestStatus }, optional: false, nullable: false
11
+
12
+ field :client_ref, -> { String }, optional: false, nullable: true, api_name: "clientRef"
13
+
14
+ field :created_at, -> { String }, optional: false, nullable: false, api_name: "createdAt"
15
+
16
+ field :updated_at, -> { String }, optional: false, nullable: false, api_name: "updatedAt"
17
+
18
+ field :completed_at, -> { String }, optional: false, nullable: true, api_name: "completedAt"
19
+
20
+ field :expires_at, -> { String }, optional: false, nullable: true, api_name: "expiresAt"
21
+
22
+ field :error_message, -> { String }, optional: false, nullable: true, api_name: "errorMessage"
23
+
24
+ field :result, -> { Pinnacle::Types::NetworkRequestResult }, optional: false, nullable: true
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ module NetworkRequestType
6
+ extend Pinnacle::Internal::Types::Enum
7
+
8
+ SILENT_AUTH = "silent-auth"
9
+ SIM_SWAP = "sim-swap"
10
+ SUBSCRIBER_MATCH = "subscriber-match"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ class RecordingDownload < Internal::Types::Model
6
+ field :url, -> { String }, optional: false, nullable: false
7
+
8
+ field :expires_at, -> { String }, optional: false, nullable: false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ module RecordingState
6
+ extend Pinnacle::Internal::Types::Enum
7
+
8
+ PENDING = "PENDING"
9
+ READY = "READY"
10
+ FAILED = "FAILED"
11
+ DELETED = "DELETED"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ class SilentAuthCheckResponse < Internal::Types::Model
6
+ field :request_id, -> { String }, optional: false, nullable: false, api_name: "requestId"
7
+
8
+ field :status, -> { Pinnacle::Types::SilentAuthCheckResponseStatus }, optional: false, nullable: false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ module SilentAuthCheckResponseStatus
6
+ extend Pinnacle::Internal::Types::Enum
7
+
8
+ COMPLETED = "completed"
9
+ FAILED = "failed"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ class SilentAuthResponse < Internal::Types::Model
6
+ extend Pinnacle::Internal::Types::Union
7
+
8
+ discriminant :format
9
+
10
+ member -> { Pinnacle::Types::HostedSilentAuthResponse }, key: "HOSTED"
11
+
12
+ member -> { Pinnacle::Types::JsonSilentAuthResponse }, key: "JSON_"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ class SimSwapResponse < Internal::Types::Model
6
+ field :request_id, -> { String }, optional: false, nullable: false, api_name: "requestId"
7
+
8
+ field :status, -> { Pinnacle::Types::NetworkInsightStatus }, optional: false, nullable: false
9
+
10
+ field :sim_swapped, -> { Internal::Types::Boolean }, optional: false, nullable: true, api_name: "simSwapped"
11
+
12
+ field :latest_sim_swap_at, -> { String }, optional: false, nullable: true, api_name: "latestSimSwapAt"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ # User-provided subscriber details to compare with the carrier record. Provide at least one non-empty field.
6
+ class SubscriberMatchAttributes < Internal::Types::Model
7
+ field :id_document, -> { String }, optional: true, nullable: false, api_name: "idDocument"
8
+
9
+ field :given_name, -> { String }, optional: true, nullable: false, api_name: "givenName"
10
+
11
+ field :family_name, -> { String }, optional: true, nullable: false, api_name: "familyName"
12
+
13
+ field :street_name, -> { String }, optional: true, nullable: false, api_name: "streetName"
14
+
15
+ field :street_number, -> { String }, optional: true, nullable: false, api_name: "streetNumber"
16
+
17
+ field :postal_code, -> { String }, optional: true, nullable: false, api_name: "postalCode"
18
+
19
+ field :locality, -> { String }, optional: true, nullable: false
20
+
21
+ field :region, -> { String }, optional: true, nullable: false
22
+
23
+ field :country, -> { String }, optional: true, nullable: false
24
+
25
+ field :street_number_suffix, -> { String }, optional: true, nullable: false, api_name: "streetNumberSuffix"
26
+
27
+ field :birthdate, -> { String }, optional: true, nullable: false
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ class SubscriberMatchResponse < Internal::Types::Model
6
+ field :request_id, -> { String }, optional: false, nullable: false, api_name: "requestId"
7
+
8
+ field :status, -> { Pinnacle::Types::NetworkInsightStatus }, optional: false, nullable: false
9
+
10
+ field :matches, -> { Pinnacle::Types::SubscriberMatchResponseMatches }, optional: false, nullable: false
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ # Match strength for each evaluated subscriber attribute.
6
+ #
7
+ # - `exact`: Carrier record matches the submitted value exactly.
8
+ # - `high`: Strong match with minor formatting or normalization differences.
9
+ # - `partial`: Some parts match, but the value is not a strong match.
10
+ # - `low`: Weak match.
11
+ # - `none`: Carrier record does not match the submitted value.
12
+ # - `unavailable`: Carrier could not evaluate this attribute.
13
+ # - `included-with-address-match`: Attribute was evaluated as part of the overall address match.
14
+ # - `unknown`: Carrier returned a match value Pinnacle does not recognize yet.
15
+ class SubscriberMatchResponseMatches < Internal::Types::Model
16
+ field :id_document, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false, api_name: "idDocument"
17
+
18
+ field :given_name, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false, api_name: "givenName"
19
+
20
+ field :family_name, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false, api_name: "familyName"
21
+
22
+ field :address, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false
23
+
24
+ field :street_name, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false, api_name: "streetName"
25
+
26
+ field :street_number, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false, api_name: "streetNumber"
27
+
28
+ field :postal_code, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false, api_name: "postalCode"
29
+
30
+ field :locality, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false
31
+
32
+ field :region, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false
33
+
34
+ field :country, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false
35
+
36
+ field :street_number_suffix, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false, api_name: "streetNumberSuffix"
37
+
38
+ field :birthdate, -> { Pinnacle::Types::SubscriberMatchResult }, optional: true, nullable: false
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Types
5
+ module SubscriberMatchResult
6
+ extend Pinnacle::Internal::Types::Enum
7
+
8
+ EXACT = "exact"
9
+ HIGH = "high"
10
+ PARTIAL = "partial"
11
+ LOW = "low"
12
+ NONE = "none"
13
+ UNAVAILABLE = "unavailable"
14
+ INCLUDED_WITH_ADDRESS_MATCH = "included-with-address-match"
15
+ UNKNOWN = "unknown"
16
+ end
17
+ end
18
+ end
@@ -10,6 +10,7 @@ module Pinnacle
10
10
  USER_TYPING = "USER.TYPING"
11
11
  FORM_SUBMISSION = "FORM.SUBMISSION"
12
12
  CAMPAIGN_STATUS = "CAMPAIGN.STATUS"
13
+ CALL_STATUS = "CALL.STATUS"
13
14
  end
14
15
  end
15
16
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pinnacle
4
- VERSION = "2.0.19"
4
+ VERSION = "2.0.20.pre.rc.2"
5
5
  end
@@ -5,6 +5,7 @@ require_relative "../../types/message_event"
5
5
  require_relative "../../types/user_event"
6
6
  require_relative "../../types/form_submission_event"
7
7
  require_relative "../../types/campaign_status_event"
8
+ require_relative "../../types/call_status_event"
8
9
 
9
10
  module Pinnacle
10
11
  module Wrapper
@@ -23,7 +24,7 @@ module Pinnacle
23
24
  # @param request [Hash] Request with :headers and :body keys
24
25
  # @param secret [String, nil] Webhook secret (falls back to PINNACLE_SIGNING_SECRET env var)
25
26
  #
26
- # @return [Pinnacle::Types::MessageEvent, Pinnacle::Types::UserEvent, Pinnacle::Types::FormSubmissionEvent, Pinnacle::Types::CampaignStatusEvent]
27
+ # @return [Pinnacle::Types::MessageEvent, Pinnacle::Types::UserEvent, Pinnacle::Types::FormSubmissionEvent, Pinnacle::Types::CampaignStatusEvent, Pinnacle::Types::CallStatusEvent]
27
28
  # @raise [Pinnacle::Errors::UnauthorizedError] If webhook signature is invalid or missing
28
29
  # @raise [Pinnacle::Errors::ClientError] If request body cannot be parsed
29
30
  def process(request, secret: nil)
@@ -66,6 +67,8 @@ module Pinnacle
66
67
  Pinnacle::Types::FormSubmissionEvent.coerce(parsed)
67
68
  when "CAMPAIGN.STATUS"
68
69
  Pinnacle::Types::CampaignStatusEvent.coerce(parsed)
70
+ when "CALL.STATUS"
71
+ Pinnacle::Types::CallStatusEvent.coerce(parsed)
69
72
  else
70
73
  Pinnacle::Types::MessageEvent.coerce(parsed)
71
74
  end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../calls/client"
4
+ require_relative "voice_socket"
5
+
6
+ module Pinnacle
7
+ module Wrapper
8
+ module Voice
9
+ class Client
10
+ DEFAULT_RECONNECT = { enabled: true }.freeze
11
+
12
+ class DefaultSocket
13
+ attr_reader :ready_state
14
+
15
+ def initialize(url, protocols = nil)
16
+ @ready_state = 0
17
+ @listeners = Hash.new { |hash, key| hash[key] = [] }
18
+ headers = if protocols.nil?
19
+ {}
20
+ else
21
+ { "Sec-WebSocket-Protocol" => Array(protocols).join(", ") }
22
+ end
23
+ @socket = WebSocket::Client::Simple.connect(url, headers: headers)
24
+ wrapper = self
25
+ @socket.on(:open) do |event|
26
+ wrapper.__send__(:dispatch_open, event)
27
+ end
28
+ @socket.on(:message) { |message| wrapper.__send__(:emit, "message", { data: message.data }) }
29
+ @socket.on(:error) { |error| wrapper.__send__(:emit, "error", error) }
30
+ @socket.on(:close) do |event|
31
+ wrapper.__send__(:dispatch_close, event)
32
+ end
33
+ end
34
+
35
+ def add_event_listener(event, &listener)
36
+ @listeners[event.to_s] << listener
37
+ end
38
+
39
+ def send(data)
40
+ @socket.send(data)
41
+ end
42
+
43
+ def close(*)
44
+ @socket.close
45
+ end
46
+
47
+ private
48
+
49
+ def dispatch_open(event)
50
+ @ready_state = 1
51
+ emit("open", event)
52
+ end
53
+
54
+ def dispatch_close(event)
55
+ @ready_state = 3
56
+ emit("close", event)
57
+ end
58
+
59
+ def emit(event, payload)
60
+ @listeners[event].each { |listener| listener.call(payload) }
61
+ end
62
+ end
63
+
64
+ def initialize(client:)
65
+ @calls = Pinnacle::Calls::Client.new(client: client)
66
+ end
67
+
68
+ def create_and_connect(
69
+ to:,
70
+ from:,
71
+ record: nil,
72
+ metadata: nil,
73
+ token: nil,
74
+ protocols: nil,
75
+ reconnect: nil,
76
+ socket: nil,
77
+ request_options: {}
78
+ )
79
+ call_params = { to: to, from: from }
80
+ call_params[:record] = record unless record.nil?
81
+ call_params[:metadata] = metadata unless metadata.nil?
82
+ call = @calls.create(**call_params, request_options: request_options)
83
+ connection = connect(
84
+ call_id: call.id,
85
+ token: token,
86
+ protocols: protocols,
87
+ reconnect: DEFAULT_RECONNECT.merge(reconnect || {}),
88
+ socket: socket
89
+ )
90
+ connection.call = call
91
+ connection.call_id = call.id
92
+ connection
93
+ end
94
+
95
+ def connect(call_id:, token: nil, protocols: nil, reconnect: nil, socket: nil)
96
+ socket_class = socket || default_socket
97
+ token_params = token || {}
98
+ stream_token = @calls.create_stream_token(**token_params, id: call_id)
99
+ create_socket = -> do
100
+ next_token = @calls.create_stream_token(**token_params, id: call_id)
101
+ new_socket(socket_class, next_token.stream_url, protocols)
102
+ end
103
+
104
+ VoiceSocket.new(
105
+ new_socket(socket_class, stream_token.stream_url, protocols),
106
+ create_socket: create_socket,
107
+ reconnect: DEFAULT_RECONNECT.merge(reconnect || {})
108
+ )
109
+ end
110
+
111
+ def connect_stream(stream_url, protocols: nil, reconnect: nil, socket: nil)
112
+ socket_class = socket || default_socket
113
+ create_socket = -> { new_socket(socket_class, stream_url, protocols) }
114
+ VoiceSocket.new(create_socket.call, create_socket: create_socket, reconnect: reconnect)
115
+ end
116
+
117
+ private
118
+
119
+ def default_socket
120
+ require "websocket-client-simple" unless defined?(::WebSocket::Client::Simple)
121
+ DefaultSocket
122
+ rescue LoadError => e
123
+ raise e, "The rcs gem could not load its websocket-client-simple dependency. Reinstall rcs or pass socket: to client.voice.connect."
124
+ end
125
+
126
+ def new_socket(socket_class, stream_url, protocols)
127
+ return socket_class.new(stream_url) if protocols.nil?
128
+
129
+ socket_class.new(stream_url, protocols)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pinnacle
4
+ module Wrapper
5
+ module Voice
6
+ module Types
7
+ VOICE_COMMAND_ACTIONS = [
8
+ CALL_ANSWER = "call.answer",
9
+ CALL_END = "call.end",
10
+ CALL_TRANSFER = "call.transfer",
11
+ RECORDING_START = "recording.start",
12
+ RECORDING_STOP = "recording.stop",
13
+ AUDIO_PLAY = "audio.play",
14
+ AUDIO_STOP = "audio.stop",
15
+ AUDIO_REDUCE_NOISE = "audio.reduce_noise",
16
+ INPUT_GET = "input.get",
17
+ INPUT_CANCEL = "input.cancel",
18
+ DTMF_SEND = "dtmf.send",
19
+ CALL_UPDATE_STATE = "call.update_state"
20
+ ].freeze
21
+
22
+ USER_BUSY = "user_busy"
23
+ CALL_REJECTED = "call_rejected"
24
+
25
+ NOISE_DEFAULT = "default"
26
+ NOISE_KRISP = "krisp"
27
+ NOISE_AICOUSTICS = "aicoustics"
28
+
29
+ DIRECTION_INBOUND = "inbound"
30
+ DIRECTION_OUTBOUND = "outbound"
31
+ DIRECTION_BOTH = "both"
32
+
33
+ TRACK_INBOUND = "inbound"
34
+ TRACK_OUTBOUND = "outbound"
35
+ end
36
+ end
37
+ end
38
+ end