telnyx 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.rubocop.yml +30 -1
- data/.travis.yml +16 -7
- data/Gemfile +1 -0
- data/Guardfile +4 -0
- data/README.md +2 -2
- data/VERSION +1 -1
- data/lib/telnyx.rb +4 -1
- data/lib/telnyx/api_operations/nested_resource.rb +19 -2
- data/lib/telnyx/api_operations/param_wrapper.rb +24 -0
- data/lib/telnyx/available_phone_number.rb +2 -0
- data/lib/telnyx/call.rb +38 -0
- data/lib/telnyx/conferences.rb +19 -0
- data/lib/telnyx/messaging_profile.rb +6 -15
- data/lib/telnyx/number_reservation.rb +9 -0
- data/lib/telnyx/util.rb +7 -0
- data/lib/telnyx/webhook.rb +14 -10
- data/telnyx.gemspec +11 -3
- data/test/telnyx/api_operations_test.rb +48 -1
- data/test/telnyx/available_phone_number_test.rb +5 -0
- data/test/telnyx/call_control_test.rb +157 -0
- data/test/telnyx/conferences_test.rb +76 -0
- data/test/telnyx/number_reservation_test.rb +8 -0
- data/test/telnyx/webhook_test.rb +71 -29
- data/test/test_helper.rb +2 -2
- metadata +31 -7
- data/.gitattributes +0 -4
- data/.rubocop_todo.yml +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf0a68b78d4fd1ab92bebdeeca23496359d7b3de38296e9dbdd22aa271147c1a
|
4
|
+
data.tar.gz: 231d2e6e1e002234e8560b7f954588ed2a156e10f41c51a34dc62737eb9203b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 205abab8d0d437ed164f84346f96172f1d8b19bbe5f2e5d66ce0a14d6d09573dd117399a01b7663a03f3e0611140f5c2a0b39a16350b65c47022b80be675db2e
|
7
|
+
data.tar.gz: 3659223c70977575d8d7a81e994d210b0aa37af8d05c24821d5742c1067cdb0a0aad04c3f89dadb93e33bb709887ea3b8a7ca3935714aeacb97d730307cbfcd1
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
inherit_from: .rubocop_todo.yml
|
2
1
|
|
3
2
|
AllCops:
|
4
3
|
DisplayCopNames: true
|
@@ -30,3 +29,33 @@ Style/StringLiterals:
|
|
30
29
|
|
31
30
|
Style/TrailingCommaInLiteral:
|
32
31
|
EnforcedStyleForMultiline: consistent_comma
|
32
|
+
|
33
|
+
Metrics/AbcSize:
|
34
|
+
Max: 52
|
35
|
+
|
36
|
+
Metrics/BlockLength:
|
37
|
+
Max: 498
|
38
|
+
|
39
|
+
Metrics/ClassLength:
|
40
|
+
Max: 659
|
41
|
+
|
42
|
+
# Offense count: 11
|
43
|
+
Metrics/CyclomaticComplexity:
|
44
|
+
Max: 15
|
45
|
+
|
46
|
+
Metrics/LineLength:
|
47
|
+
Max: 310
|
48
|
+
|
49
|
+
Metrics/ParameterLists:
|
50
|
+
Max: 7
|
51
|
+
|
52
|
+
Metrics/PerceivedComplexity:
|
53
|
+
Max: 17
|
54
|
+
|
55
|
+
Style/ClassVars:
|
56
|
+
Exclude:
|
57
|
+
- 'lib/telnyx/telnyx_object.rb'
|
58
|
+
- 'test/telnyx/api_resource_test.rb'
|
59
|
+
|
60
|
+
Style/Documentation:
|
61
|
+
Enabled: false
|
data/.travis.yml
CHANGED
@@ -1,23 +1,32 @@
|
|
1
1
|
language: ruby
|
2
2
|
|
3
|
+
|
3
4
|
rvm:
|
4
5
|
- 2.1
|
5
6
|
- 2.2
|
6
7
|
- 2.3
|
7
8
|
- 2.4
|
8
9
|
- 2.5
|
9
|
-
-
|
10
|
+
- 2.6
|
11
|
+
|
12
|
+
matrix:
|
13
|
+
include:
|
14
|
+
- rvm: jruby-head
|
15
|
+
jdk: oraclejdk11
|
16
|
+
|
10
17
|
|
11
18
|
notifications:
|
12
19
|
email:
|
13
20
|
on_success: never
|
21
|
+
slack:
|
22
|
+
secure: AMXcZSwIL8SRZQ+opSFrlvNKoUXv1rZkWDgorBmz+BEHwGKWgVzYcZ4GwD5p6Z5uNdtvl9FZz6oLvvAcbW6CblbAPM+f2qLVDlZ/sazpOK8l6QIo7X86U3SuwJicY2CbbKvqN/A3u23Bbvf5u4djvm5oc73qASZY/RJHxm2xcmD57+z6hY12AvWtLJN95BsjVZ9RHXy8/qkJehqGnzSi9VGojNmd0voU9UrxJU0xS10kBA7dQFCCf+NZv9utguyFfAATpa9JTlD1a8QiB2fzvdPkBym1bnqr3nQPk5rNbgiFHf14OIlq7C2jwaNNoB1dDpkT/Vfvmn5EHzBDZQ30PrVpq9uNgQg45pOIMXp9ZLY0zYi/Gzk5tF/lTKUxk5evJ2+2Dtmzv4mzbk98pvGrA+MIkSXuYy6GHZuXanb3OQ5y42dSYVdy1c+WHdbYx1LOJSEGtALr9ADyjDu9KAu2eJMnmGQ14cJarl/33BF4UzCRKpPxV5CwOqI82+fK9pNiW0CLijfxpkFr9aaxViVsf43r5Ag12Jqme18IWCGJ1P5sMEo6bz/Gp4BuVMXQtYExorK+fWkrm1Wus6HGINlRonUswJ9LJ995M384j6KyP1121MJuiPAc1AdNqS1C992j/cDoUDlxsxW9HTX15nGoM712w00wNrj/vdQt0TlmENo=
|
14
23
|
|
15
24
|
sudo: false
|
16
25
|
|
17
26
|
env:
|
18
27
|
global:
|
19
28
|
# If changing this number, please also change it in `test/test_helper.rb`.
|
20
|
-
- TELNYX_MOCK_VERSION=0.
|
29
|
+
- TELNYX_MOCK_VERSION=0.2.0
|
21
30
|
|
22
31
|
cache:
|
23
32
|
directories:
|
@@ -29,14 +38,14 @@ before_install:
|
|
29
38
|
# Unpack and start telnyx-mock so that the test suite can talk to it
|
30
39
|
- |
|
31
40
|
if [ ! -d "telnyx-mock/telnyx-mock_${TELNYX_MOCK_VERSION}" ]; then
|
32
|
-
mkdir -p telnyx-mock
|
33
|
-
curl -L "https://github.com/telnyx/telnyx-mock/releases/download/v${TELNYX_MOCK_VERSION}/telnyx-mock_${TELNYX_MOCK_VERSION}_linux_amd64.tar.gz" -o "telnyx-mock/telnyx-mock_${TELNYX_MOCK_VERSION}_linux_amd64.tar.gz"
|
34
|
-
tar -zxf "telnyx-mock/telnyx-mock_${TELNYX_MOCK_VERSION}_linux_amd64.tar.gz" -C "telnyx-mock
|
41
|
+
mkdir -p telnyx-mock/${TELNYX_MOCK_VERSION}/
|
42
|
+
curl -L "https://github.com/team-telnyx/telnyx-mock/releases/download/v${TELNYX_MOCK_VERSION}/telnyx-mock_${TELNYX_MOCK_VERSION}_linux_amd64.tar.gz" -o "telnyx-mock/telnyx-mock_${TELNYX_MOCK_VERSION}_linux_amd64.tar.gz"
|
43
|
+
tar -zxf "telnyx-mock/telnyx-mock_${TELNYX_MOCK_VERSION}_linux_amd64.tar.gz" -C "telnyx-mock/${TELNYX_MOCK_VERSION}/"
|
35
44
|
fi
|
36
45
|
- |
|
37
|
-
telnyx-mock
|
46
|
+
telnyx-mock/${TELNYX_MOCK_VERSION}/telnyx-mock > /dev/null &
|
38
47
|
TELNYX_MOCK_PID=$!
|
39
|
-
- export PATH="${PATH}:${PWD}/telnyx-mock
|
48
|
+
- export PATH="${PATH}:${PWD}/telnyx-mock/${TELNYX_MOCK_VERSION}"
|
40
49
|
|
41
50
|
script:
|
42
51
|
- bundle exec rake
|
data/Gemfile
CHANGED
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -19,7 +19,7 @@ The library also provides other features. For example:
|
|
19
19
|
|
20
20
|
## Documentation
|
21
21
|
|
22
|
-
See the [API docs](https://developers.telnyx.com/docs/v2/overview).
|
22
|
+
See the [API docs](https://developers.telnyx.com/docs/api/v2/overview).
|
23
23
|
|
24
24
|
## Installation
|
25
25
|
|
@@ -56,7 +56,7 @@ value:
|
|
56
56
|
|
57
57
|
``` ruby
|
58
58
|
require "telnyx"
|
59
|
-
Telnyx.api_key = "
|
59
|
+
Telnyx.api_key = "YOUR_API_KEY"
|
60
60
|
|
61
61
|
# list messaging profiles
|
62
62
|
Telnyx::MessagingProfile.list()
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
0.0.2
|
data/lib/telnyx.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Telnyx Ruby bindings
|
4
|
-
# API spec at https://developers.telnyx.com
|
4
|
+
# API spec at https://developers.telnyx.com
|
5
5
|
require "cgi"
|
6
6
|
require "faraday"
|
7
7
|
require "json"
|
@@ -23,6 +23,7 @@ require "telnyx/api_operations/list"
|
|
23
23
|
require "telnyx/api_operations/nested_resource"
|
24
24
|
require "telnyx/api_operations/request"
|
25
25
|
require "telnyx/api_operations/save"
|
26
|
+
require "telnyx/api_operations/param_wrapper"
|
26
27
|
|
27
28
|
# API resource support classes
|
28
29
|
require "telnyx/errors"
|
@@ -35,6 +36,8 @@ require "telnyx/api_resource"
|
|
35
36
|
require "telnyx/singleton_api_resource"
|
36
37
|
require "telnyx/webhook"
|
37
38
|
|
39
|
+
require "telnyx/call"
|
40
|
+
require "telnyx/conferences"
|
38
41
|
require "telnyx/number_order"
|
39
42
|
require "telnyx/number_reservation"
|
40
43
|
require "telnyx/message"
|
@@ -9,18 +9,30 @@ module Telnyx
|
|
9
9
|
# For examle, a transfer gains the static methods for reversals so that the
|
10
10
|
# methods `.create_reversal`, `.retrieve_reversal`, `.update_reversal`,
|
11
11
|
# etc. all become available.
|
12
|
+
# rubocop:disable Metrics/AbcSize
|
13
|
+
# rubocop:disable Metrics/MethodLength
|
12
14
|
module NestedResource
|
13
|
-
def nested_resource_class_methods(resource, path: nil, operations: nil)
|
15
|
+
def nested_resource_class_methods(resource, path: nil, operations: nil, instance_methods: {})
|
14
16
|
path ||= "#{resource}s"
|
17
|
+
path = Array(path).map { |el| CGI.escape(el) }.join("/")
|
15
18
|
raise ArgumentError, "operations array required" if operations.nil?
|
16
19
|
|
17
20
|
resource_url_method = :"#{resource}s_url"
|
18
21
|
define_singleton_method(resource_url_method) do |id, nested_id = nil|
|
19
|
-
url = "#{resource_url}/#{CGI.escape(id)}/#{
|
22
|
+
url = "#{resource_url}/#{CGI.escape(id)}/#{path}"
|
20
23
|
url += "/#{CGI.escape(nested_id)}" unless nested_id.nil?
|
21
24
|
url
|
22
25
|
end
|
23
26
|
|
27
|
+
# proxy class method to custom instance method
|
28
|
+
define_instance_method = lambda do |target_name, operation|
|
29
|
+
return unless instance_methods.keys.include? operation
|
30
|
+
|
31
|
+
define_method(instance_methods[operation] || target_name) do |*opts|
|
32
|
+
self.class.send(target_name, id, *opts)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
24
36
|
operations.each do |operation|
|
25
37
|
case operation
|
26
38
|
when :create
|
@@ -29,30 +41,35 @@ module Telnyx
|
|
29
41
|
resp, opts = request(:post, url, params, opts)
|
30
42
|
Util.convert_to_telnyx_object(resp.data, opts)
|
31
43
|
end
|
44
|
+
define_instance_method.call(:"create_#{resource}", operation)
|
32
45
|
when :retrieve
|
33
46
|
define_singleton_method(:"retrieve_#{resource}") do |id, nested_id, opts = {}|
|
34
47
|
url = send(resource_url_method, id, nested_id)
|
35
48
|
resp, opts = request(:get, url, {}, opts)
|
36
49
|
Util.convert_to_telnyx_object(resp.data, opts)
|
37
50
|
end
|
51
|
+
define_instance_method.call(:"retrieve_#{resource}", operation)
|
38
52
|
when :update
|
39
53
|
define_singleton_method(:"update_#{resource}") do |id, nested_id, params = {}, opts = {}|
|
40
54
|
url = send(resource_url_method, id, nested_id)
|
41
55
|
resp, opts = request(:patch, url, params, opts)
|
42
56
|
Util.convert_to_telnyx_object(resp.data, opts)
|
43
57
|
end
|
58
|
+
define_instance_method.call(:"update_#{resource}", operation)
|
44
59
|
when :delete
|
45
60
|
define_singleton_method(:"delete_#{resource}") do |id, nested_id, params = {}, opts = {}|
|
46
61
|
url = send(resource_url_method, id, nested_id)
|
47
62
|
resp, opts = request(:delete, url, params, opts)
|
48
63
|
Util.convert_to_telnyx_object(resp.data, opts)
|
49
64
|
end
|
65
|
+
define_instance_method.call(:"delete_#{resource}", operation)
|
50
66
|
when :list
|
51
67
|
define_singleton_method(:"list_#{resource}s") do |id, params = {}, opts = {}|
|
52
68
|
url = send(resource_url_method, id)
|
53
69
|
resp, opts = request(:get, url, params, opts)
|
54
70
|
Util.convert_to_telnyx_object(resp.data, opts)
|
55
71
|
end
|
72
|
+
define_instance_method.call(:"list_#{resource}s", operation)
|
56
73
|
else
|
57
74
|
raise ArgumentError, "Unknown operation: #{operation.inspect}"
|
58
75
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Telnyx
|
4
|
+
module APIOperations
|
5
|
+
# Intercepts request params passed to api operation methods and wraps them in a single param.
|
6
|
+
# Usage:
|
7
|
+
# class << self
|
8
|
+
# prepend Telnyx::ParamWrapper
|
9
|
+
# wrap :list, 'filter'
|
10
|
+
# end
|
11
|
+
module ParamWrapper
|
12
|
+
protected
|
13
|
+
|
14
|
+
def wrap(method_name, wrapper)
|
15
|
+
define_singleton_method(method_name) do |filters = {}, opts = {}|
|
16
|
+
return super(filters, opts) if filters.keys == [wrapper]
|
17
|
+
|
18
|
+
filters = { wrapper => filters }
|
19
|
+
super filters, opts
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/telnyx/call.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Telnyx
|
4
|
+
class Call < APIResource
|
5
|
+
extend Telnyx::APIOperations::Create
|
6
|
+
extend Telnyx::APIOperations::NestedResource
|
7
|
+
|
8
|
+
def id
|
9
|
+
call_control_id if defined? call_control_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def id=(val)
|
13
|
+
initialize_from({ call_control_id: val }, {}, true)
|
14
|
+
end
|
15
|
+
|
16
|
+
%w[call_leg_id call_session_id].each do |attribute|
|
17
|
+
define_method attribute do
|
18
|
+
send(attribute) if respond_to?(attribute)
|
19
|
+
end
|
20
|
+
define_method attribute + "=" do |val|
|
21
|
+
initialize_from({ attribute.to_sym => val }, {}, true)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
ACTIONS = %w[reject answer hangup bridge speak fork_start fork_stop
|
26
|
+
gather_using_audio gather_using_speak playback_start
|
27
|
+
playback_stop record_start record_stop send_dtmf transfer].freeze
|
28
|
+
|
29
|
+
ACTIONS.each do |action|
|
30
|
+
nested_resource_class_methods action,
|
31
|
+
path: ["actions", action],
|
32
|
+
operations: [:create],
|
33
|
+
instance_methods: { create: action }
|
34
|
+
end
|
35
|
+
|
36
|
+
OBJECT_NAME = "call".freeze
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Telnyx
|
4
|
+
class Conferences < APIResource
|
5
|
+
extend APIOperations::List
|
6
|
+
extend APIOperations::Create
|
7
|
+
extend APIOperations::NestedResource
|
8
|
+
|
9
|
+
ACTIONS = %w[join mute unmute hold unhold].freeze
|
10
|
+
|
11
|
+
ACTIONS.each do |action|
|
12
|
+
nested_resource_class_methods action,
|
13
|
+
path: ["actions", action],
|
14
|
+
operations: [:create],
|
15
|
+
instance_methods: { create: action }
|
16
|
+
end
|
17
|
+
OBJECT_NAME = "conference".freeze
|
18
|
+
end
|
19
|
+
end
|
@@ -11,22 +11,13 @@ module Telnyx
|
|
11
11
|
OBJECT_NAME = "messaging_profile".freeze
|
12
12
|
|
13
13
|
nested_resource_class_methods :phone_number,
|
14
|
-
operations: %i[list]
|
14
|
+
operations: %i[list],
|
15
|
+
instance_methods: { list: "phone_numbers" }
|
15
16
|
nested_resource_class_methods :sender_id,
|
16
|
-
operations: %i[list]
|
17
|
+
operations: %i[list],
|
18
|
+
instance_methods: { list: "sender_ids" }
|
17
19
|
nested_resource_class_methods :short_code,
|
18
|
-
operations: %i[list]
|
19
|
-
|
20
|
-
def phone_numbers(params = {}, opts = {})
|
21
|
-
self.class.list_phone_numbers(id, params, opts)
|
22
|
-
end
|
23
|
-
|
24
|
-
def sender_ids(params = {}, opts = {})
|
25
|
-
self.class.list_sender_ids(id, params, opts)
|
26
|
-
end
|
27
|
-
|
28
|
-
def short_codes(params = {}, opts = {})
|
29
|
-
self.class.list_short_codes(id, params, opts)
|
30
|
-
end
|
20
|
+
operations: %i[list],
|
21
|
+
instance_methods: { list: "short_codes" }
|
31
22
|
end
|
32
23
|
end
|
@@ -4,8 +4,17 @@ module Telnyx
|
|
4
4
|
class NumberReservation < APIResource
|
5
5
|
extend Telnyx::APIOperations::List
|
6
6
|
extend Telnyx::APIOperations::Create
|
7
|
+
extend Telnyx::APIOperations::NestedResource
|
7
8
|
include Telnyx::APIOperations::Save
|
8
9
|
|
10
|
+
nested_resource_class_methods :extend,
|
11
|
+
path: %w[actions extend],
|
12
|
+
operations: [:create],
|
13
|
+
instance_methods: { create: "extend_number" }
|
14
|
+
class << self
|
15
|
+
alias extend_number create_extend
|
16
|
+
end
|
17
|
+
|
9
18
|
OBJECT_NAME = "number_reservation".freeze
|
10
19
|
end
|
11
20
|
end
|
data/lib/telnyx/util.rb
CHANGED
@@ -48,9 +48,16 @@ module Telnyx
|
|
48
48
|
PublicKey::OBJECT_NAME => PublicKey,
|
49
49
|
NumberOrder::OBJECT_NAME => NumberOrder,
|
50
50
|
NumberReservation::OBJECT_NAME => NumberReservation,
|
51
|
+
Call::OBJECT_NAME => Call,
|
52
|
+
Conferences::OBJECT_NAME => Conferences,
|
51
53
|
}
|
52
54
|
end
|
53
55
|
|
56
|
+
def self.push_object_class(key, klass)
|
57
|
+
object_classes
|
58
|
+
@object_classes[key] = klass
|
59
|
+
end
|
60
|
+
|
54
61
|
# Converts a hash of fields or an array of hashes into a +TelnyxObject+ or
|
55
62
|
# array of +TelnyxObject+s. These new objects will be created as a concrete
|
56
63
|
# type as dictated by their `record_type` field (e.g. a `record_type` value of
|
data/lib/telnyx/webhook.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "openssl"
|
4
4
|
require "base64"
|
5
|
+
require "ed25519"
|
5
6
|
|
6
7
|
module Telnyx
|
7
8
|
module Webhook
|
@@ -35,31 +36,34 @@ module Telnyx
|
|
35
36
|
# Returns true otherwise
|
36
37
|
def self.verify(payload, signature_header, timestamp, tolerance: nil)
|
37
38
|
signature = Base64.decode64(signature_header)
|
39
|
+
timestamp = timestamp.to_i
|
38
40
|
signed_payload = "#{timestamp}|#{payload}"
|
39
41
|
|
40
|
-
|
42
|
+
if tolerance && timestamp < Time.now.to_f - tolerance
|
41
43
|
raise SignatureVerificationError.new(
|
42
|
-
"
|
43
|
-
|
44
|
+
"Timestamp outside the tolerance zone (#{Time.at(timestamp)})",
|
45
|
+
signature_header, http_body: payload
|
44
46
|
)
|
45
47
|
end
|
46
48
|
|
47
|
-
|
49
|
+
begin
|
50
|
+
verify_key.verify(signature, signed_payload)
|
51
|
+
rescue Ed25519::VerifyError
|
48
52
|
raise SignatureVerificationError.new(
|
49
|
-
"
|
50
|
-
|
53
|
+
"Signature is invalid and does not match the payload",
|
54
|
+
signature, http_body: payload
|
51
55
|
)
|
52
56
|
end
|
53
57
|
|
54
58
|
true
|
55
59
|
end
|
56
60
|
|
57
|
-
def self.
|
58
|
-
@
|
61
|
+
def self.verify_key
|
62
|
+
@verify_key ||= reload_verify_key
|
59
63
|
end
|
60
64
|
|
61
|
-
def self.
|
62
|
-
@
|
65
|
+
def self.reload_verify_key
|
66
|
+
@verify_key = Ed25519::VerifyKey.new(Base64.decode64(ENV.fetch("TELNYX_PUBLIC_KEY")))
|
63
67
|
end
|
64
68
|
end
|
65
69
|
end
|
data/telnyx.gemspec
CHANGED
@@ -6,17 +6,25 @@ require "telnyx/version"
|
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "telnyx"
|
9
|
-
s.version =
|
9
|
+
s.version = '0.0.2'
|
10
10
|
s.required_ruby_version = ">= 2.1.0"
|
11
11
|
s.summary = "Ruby bindings for the Telnyx API"
|
12
|
-
s.description = "Telnyx. See https://
|
12
|
+
s.description = "Telnyx enables anyone to deliver enterprise-grade real-time communications over the internet. See https://telnyx.com for details."
|
13
13
|
s.author = "Telnyx"
|
14
14
|
s.email = "support@telnyx.com"
|
15
|
-
s.homepage = "https://developers.telnyx.com
|
15
|
+
s.homepage = "https://developers.telnyx.com"
|
16
16
|
s.license = "MIT"
|
17
17
|
|
18
|
+
s.metadata = {
|
19
|
+
"documentation_uri" => "https://developers.telnyx.com/docs/api/v2/overview",
|
20
|
+
"github_repo" => "ssh://github.com/team-telnyx/telnyx-ruby",
|
21
|
+
"homepage_uri" => "https://telnyx.com",
|
22
|
+
"source_code_uri" => "https://github.com/team-telnyx/telnyx-ruby",
|
23
|
+
}
|
24
|
+
|
18
25
|
s.add_dependency("faraday", "~> 0.13")
|
19
26
|
s.add_dependency("net-http-persistent", "~> 3.0")
|
27
|
+
s.add_dependency("ed25519", "~> 1")
|
20
28
|
|
21
29
|
s.files = `git ls-files`.split("\n")
|
22
30
|
s.test_files = `git ls-files -- test/*`.split("\n")
|
@@ -37,8 +37,10 @@ module Telnyx
|
|
37
37
|
class MainResource < APIResource
|
38
38
|
extend Telnyx::APIOperations::NestedResource
|
39
39
|
OBJECT_NAME = "mainresource".freeze
|
40
|
+
Telnyx::Util.push_object_class OBJECT_NAME, self
|
40
41
|
nested_resource_class_methods :nested,
|
41
|
-
operations: %i[create retrieve update delete list]
|
42
|
+
operations: %i[create retrieve update delete list],
|
43
|
+
instance_methods: { create: nil, retrieve: nil, update: nil, delete: nil, list: nil }
|
42
44
|
end
|
43
45
|
|
44
46
|
should "define a create method" do
|
@@ -49,6 +51,15 @@ module Telnyx
|
|
49
51
|
assert_equal "bar", nested_resource.foo
|
50
52
|
end
|
51
53
|
|
54
|
+
should "define a create instance method" do
|
55
|
+
stub_request(:post, "#{Telnyx.api_base}/v2/mainresources/id/nesteds")
|
56
|
+
.with(body: { foo: "bar" })
|
57
|
+
.to_return(body: JSON.generate(id: "nested_id", object: "nested", foo: "bar"))
|
58
|
+
resource = Telnyx::Util.convert_to_telnyx_object(id: "id", record_type: "mainresource")
|
59
|
+
nested_resource = resource.create_nested(foo: "bar")
|
60
|
+
assert_equal "bar", nested_resource.foo
|
61
|
+
end
|
62
|
+
|
52
63
|
should "define a retrieve method" do
|
53
64
|
stub_request(:get, "#{Telnyx.api_base}/v2/mainresources/id/nesteds/nested_id")
|
54
65
|
.to_return(body: JSON.generate(id: "nested_id", object: "nested", foo: "bar"))
|
@@ -56,6 +67,14 @@ module Telnyx
|
|
56
67
|
assert_equal "bar", nested_resource.foo
|
57
68
|
end
|
58
69
|
|
70
|
+
should "define a retrieve instance method" do
|
71
|
+
stub_request(:get, "#{Telnyx.api_base}/v2/mainresources/id/nesteds/nested_id")
|
72
|
+
.to_return(body: JSON.generate(id: "nested_id", object: "nested", foo: "bar"))
|
73
|
+
resource = Telnyx::Util.convert_to_telnyx_object(id: "id", record_type: "mainresource")
|
74
|
+
nested_resource = resource.retrieve_nested("nested_id")
|
75
|
+
assert_equal "bar", nested_resource.foo
|
76
|
+
end
|
77
|
+
|
59
78
|
should "define an update method" do
|
60
79
|
stub_patch = stub_request(:patch, "#{Telnyx.api_base}/v2/mainresources/id/nesteds/nested_id")
|
61
80
|
.with(body: { foo: "baz" })
|
@@ -65,6 +84,16 @@ module Telnyx
|
|
65
84
|
assert_equal "baz", nested_resource.foo
|
66
85
|
end
|
67
86
|
|
87
|
+
should "define an update instance method" do
|
88
|
+
stub_patch = stub_request(:patch, "#{Telnyx.api_base}/v2/mainresources/id/nesteds/nested_id")
|
89
|
+
.with(body: { foo: "baz" })
|
90
|
+
.to_return(body: JSON.generate(id: "nested_id", object: "nested", foo: "baz"))
|
91
|
+
resource = Telnyx::Util.convert_to_telnyx_object(id: "id", record_type: "mainresource")
|
92
|
+
nested_resource = resource.update_nested("nested_id", foo: "baz")
|
93
|
+
assert_requested(stub_patch)
|
94
|
+
assert_equal "baz", nested_resource.foo
|
95
|
+
end
|
96
|
+
|
68
97
|
should "define a delete method" do
|
69
98
|
stub_request(:delete, "#{Telnyx.api_base}/v2/mainresources/id/nesteds/nested_id")
|
70
99
|
.to_return(body: JSON.generate(id: "nested_id", object: "nested", deleted: true))
|
@@ -72,6 +101,14 @@ module Telnyx
|
|
72
101
|
assert_equal true, nested_resource.deleted
|
73
102
|
end
|
74
103
|
|
104
|
+
should "define a delete instance method" do
|
105
|
+
stub_request(:delete, "#{Telnyx.api_base}/v2/mainresources/id/nesteds/nested_id")
|
106
|
+
.to_return(body: JSON.generate(id: "nested_id", object: "nested", deleted: true))
|
107
|
+
resource = Telnyx::Util.convert_to_telnyx_object(id: "id", record_type: "mainresource")
|
108
|
+
nested_resource = resource.delete_nested("nested_id")
|
109
|
+
assert_equal true, nested_resource.deleted
|
110
|
+
end
|
111
|
+
|
75
112
|
should "define a list method" do
|
76
113
|
stub_get = stub_request(:get, "#{Telnyx.api_base}/v2/mainresources/id/nesteds")
|
77
114
|
.to_return(body: JSON.generate(data: [{ record_type: "foo" }]))
|
@@ -80,6 +117,16 @@ module Telnyx
|
|
80
117
|
assert nested_resources.is_a?(ListObject)
|
81
118
|
assert nested_resources.data.is_a?(Array)
|
82
119
|
end
|
120
|
+
|
121
|
+
should "define a list instance method" do
|
122
|
+
stub_get = stub_request(:get, "#{Telnyx.api_base}/v2/mainresources/id/nesteds")
|
123
|
+
.to_return(body: JSON.generate(data: [{ record_type: "foo" }]))
|
124
|
+
resource = Telnyx::Util.convert_to_telnyx_object(id: "id", record_type: "mainresource")
|
125
|
+
nested_resources = resource.list_nesteds
|
126
|
+
assert_requested(stub_get)
|
127
|
+
assert nested_resources.is_a?(ListObject)
|
128
|
+
assert nested_resources.data.is_a?(Array)
|
129
|
+
end
|
83
130
|
end
|
84
131
|
end
|
85
132
|
end
|
@@ -10,5 +10,10 @@ module Telnyx
|
|
10
10
|
assert available_phone_numbers.data.is_a?(Array)
|
11
11
|
assert available_phone_numbers.first.is_a?(Telnyx::AvailablePhoneNumber)
|
12
12
|
end
|
13
|
+
|
14
|
+
should "accept params for list" do
|
15
|
+
Telnyx::AvailablePhoneNumber.list phone_number: { starts_with: "2&&", ends_with: "ABC" }
|
16
|
+
assert_requested(:get, /#{"#{Telnyx.api_base}/v2/available_phone_numbers"}/)
|
17
|
+
end
|
13
18
|
end
|
14
19
|
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../test_helper"
|
4
|
+
require "securerandom"
|
5
|
+
|
6
|
+
module Telnyx
|
7
|
+
class CallControlTest < Test::Unit::TestCase
|
8
|
+
setup do
|
9
|
+
@call = create_call
|
10
|
+
end
|
11
|
+
context "call instance" do
|
12
|
+
should "be correct class" do
|
13
|
+
assert create_call.is_a? Telnyx::Call
|
14
|
+
end
|
15
|
+
|
16
|
+
should "be initialized with data" do
|
17
|
+
refute_nil @call.call_control_id
|
18
|
+
refute_nil @call.call_leg_id
|
19
|
+
refute_nil @call.call_session_id
|
20
|
+
refute_nil @call.is_alive
|
21
|
+
refute_nil @call.record_type
|
22
|
+
end
|
23
|
+
|
24
|
+
should "have generated instance methods methods" do
|
25
|
+
assert defined? @call.reject
|
26
|
+
assert defined? @call.answer
|
27
|
+
assert defined? @call.hangup
|
28
|
+
assert defined? @call.bridge
|
29
|
+
assert defined? @call.speak
|
30
|
+
assert defined? @call.fork_start
|
31
|
+
assert defined? @call.fork_stop
|
32
|
+
assert defined? @call.gather_using_audio
|
33
|
+
assert defined? @call.gather_using_speak
|
34
|
+
assert defined? @call.playback_start
|
35
|
+
assert defined? @call.playback_stop
|
36
|
+
assert defined? @call.record_start
|
37
|
+
assert defined? @call.record_stop
|
38
|
+
assert defined? @call.send_dtmf
|
39
|
+
assert defined? @call.transfer
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "ojbect created through #new" do
|
44
|
+
should "get and set call_control_id through alias" do
|
45
|
+
call = Call.new
|
46
|
+
refute call.id
|
47
|
+
call.id = "123"
|
48
|
+
assert_equal "123", call.id
|
49
|
+
end
|
50
|
+
|
51
|
+
should "have initialize_object accessors" do
|
52
|
+
call = Call.new
|
53
|
+
call.id = SecureRandom.base64(20)
|
54
|
+
call.call_leg_id = SecureRandom.base64(20)
|
55
|
+
call.call_session_id = SecureRandom.base64(20)
|
56
|
+
|
57
|
+
assert call.id
|
58
|
+
assert call.call_leg_id
|
59
|
+
assert call.call_session_id
|
60
|
+
end
|
61
|
+
|
62
|
+
should "send all commands" do
|
63
|
+
@call = Call.new
|
64
|
+
@call.id = "1234"
|
65
|
+
@call.reject
|
66
|
+
assert_requested :post, format_url(@call, "reject")
|
67
|
+
@call.answer
|
68
|
+
assert_requested :post, format_url(@call, "answer")
|
69
|
+
@call.hangup
|
70
|
+
assert_requested :post, format_url(@call, "hangup")
|
71
|
+
@call.bridge call_control_id: SecureRandom.base64(20)
|
72
|
+
assert_requested :post, format_url(@call, "bridge")
|
73
|
+
@call.speak language: "en-US", voice: "female", payload: "Telnyx call control test"
|
74
|
+
assert_requested :post, format_url(@call, "speak")
|
75
|
+
@call.fork_start call_control_id: SecureRandom.base64(20)
|
76
|
+
assert_requested :post, format_url(@call, "fork_start")
|
77
|
+
@call.fork_stop
|
78
|
+
assert_requested :post, format_url(@call, "fork_stop")
|
79
|
+
@call.gather_using_audio audio_url: "https://audio.example.com"
|
80
|
+
assert_requested :post, format_url(@call, "gather_using_audio")
|
81
|
+
@call.gather_using_speak language: "en-US", voice: "female", payload: "Telnyx call control test"
|
82
|
+
assert_requested :post, format_url(@call, "gather_using_speak")
|
83
|
+
@call.playback_start audio_url: "https://audio.example.com"
|
84
|
+
assert_requested :post, format_url(@call, "playback_start")
|
85
|
+
@call.playback_stop
|
86
|
+
assert_requested :post, format_url(@call, "playback_stop")
|
87
|
+
@call.send_dtmf digits: "1www2WABCDw9"
|
88
|
+
assert_requested :post, format_url(@call, "send_dtmf")
|
89
|
+
@call.transfer to: "+15552223333"
|
90
|
+
assert_requested :post, format_url(@call, "transfer")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "commands" do
|
95
|
+
should "reject" do
|
96
|
+
@call.reject
|
97
|
+
assert_requested :post, format_url(@call, "reject")
|
98
|
+
end
|
99
|
+
should "answer" do
|
100
|
+
@call.answer
|
101
|
+
assert_requested :post, format_url(@call, "answer")
|
102
|
+
end
|
103
|
+
should "hangup" do
|
104
|
+
@call.hangup
|
105
|
+
assert_requested :post, format_url(@call, "hangup")
|
106
|
+
end
|
107
|
+
should "bridge" do
|
108
|
+
@call.bridge call_control_id: SecureRandom.base64(20)
|
109
|
+
assert_requested :post, format_url(@call, "bridge")
|
110
|
+
end
|
111
|
+
should "speak" do
|
112
|
+
@call.speak language: "en-US", voice: "female", payload: "Telnyx call control test"
|
113
|
+
assert_requested :post, format_url(@call, "speak")
|
114
|
+
end
|
115
|
+
should "start fork" do
|
116
|
+
@call.fork_start call_control_id: SecureRandom.base64(20)
|
117
|
+
assert_requested :post, format_url(@call, "fork_start")
|
118
|
+
end
|
119
|
+
should "stop fork" do
|
120
|
+
@call.fork_stop
|
121
|
+
assert_requested :post, format_url(@call, "fork_stop")
|
122
|
+
end
|
123
|
+
should "gather using audio" do
|
124
|
+
@call.gather_using_audio audio_url: "https://audio.example.com"
|
125
|
+
assert_requested :post, format_url(@call, "gather_using_audio")
|
126
|
+
end
|
127
|
+
should "gather using speak" do
|
128
|
+
@call.gather_using_speak language: "en-US", voice: "female", payload: "Telnyx call control test"
|
129
|
+
assert_requested :post, format_url(@call, "gather_using_speak")
|
130
|
+
end
|
131
|
+
should "playback start" do
|
132
|
+
@call.playback_start audio_url: "https://audio.example.com"
|
133
|
+
assert_requested :post, format_url(@call, "playback_start")
|
134
|
+
end
|
135
|
+
should "playback stop" do
|
136
|
+
@call.playback_stop
|
137
|
+
assert_requested :post, format_url(@call, "playback_stop")
|
138
|
+
end
|
139
|
+
should "send dtmf" do
|
140
|
+
@call.send_dtmf digits: "1www2WABCDw9"
|
141
|
+
assert_requested :post, format_url(@call, "send_dtmf")
|
142
|
+
end
|
143
|
+
should "transfer" do
|
144
|
+
@call.transfer to: "+15552223333"
|
145
|
+
assert_requested :post, format_url(@call, "transfer")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def create_call
|
150
|
+
Telnyx::Call.create connection_id: "12345", to: "+15550001111", from: "+15550002222"
|
151
|
+
end
|
152
|
+
|
153
|
+
def format_url(call, action)
|
154
|
+
"#{Telnyx.api_base}/v2/calls/#{call.call_control_id}/actions/#{action}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../test_helper"
|
4
|
+
|
5
|
+
module Telnyx
|
6
|
+
class ConferencesTest < Test::Unit::TestCase
|
7
|
+
setup do
|
8
|
+
@call = create_call
|
9
|
+
@conference = Conferences.create call_control_id: @call.id, name: "conference!"
|
10
|
+
end
|
11
|
+
should "create conference" do
|
12
|
+
assert_requested :post, "#{Telnyx.api_base}/v2/conferences"
|
13
|
+
assert_kind_of Conferences, @conference
|
14
|
+
end
|
15
|
+
|
16
|
+
should "list conferences" do
|
17
|
+
conferences = Conferences.list
|
18
|
+
|
19
|
+
assert_requested :get, "#{Telnyx.api_base}/v2/conferences"
|
20
|
+
assert_kind_of ListObject, conferences
|
21
|
+
assert_kind_of Conferences, conferences.first
|
22
|
+
end
|
23
|
+
|
24
|
+
should "have nested command instance methods" do
|
25
|
+
assert defined? @conference.join
|
26
|
+
assert defined? @conference.mute
|
27
|
+
assert defined? @conference.unmute
|
28
|
+
assert defined? @conference.unhold
|
29
|
+
end
|
30
|
+
|
31
|
+
context "commands" do
|
32
|
+
should "join" do
|
33
|
+
stub = stub_request(:post, format_url(@conference, "join"))
|
34
|
+
.to_return(body: JSON.generate(result: "ok"))
|
35
|
+
@conference.join
|
36
|
+
assert_requested stub
|
37
|
+
end
|
38
|
+
|
39
|
+
should "mute" do
|
40
|
+
stub = stub_request(:post, format_url(@conference, "mute"))
|
41
|
+
.to_return(body: JSON.generate(result: "ok"))
|
42
|
+
@conference.mute
|
43
|
+
assert_requested stub
|
44
|
+
end
|
45
|
+
|
46
|
+
should "unmute" do
|
47
|
+
stub = stub_request(:post, format_url(@conference, "unmute"))
|
48
|
+
.to_return(body: JSON.generate(result: "ok"))
|
49
|
+
@conference.unmute
|
50
|
+
assert_requested stub
|
51
|
+
end
|
52
|
+
|
53
|
+
should "hold" do
|
54
|
+
stub = stub_request(:post, format_url(@conference, "hold"))
|
55
|
+
.to_return(body: JSON.generate(result: "ok"))
|
56
|
+
@conference.hold
|
57
|
+
assert_requested stub
|
58
|
+
end
|
59
|
+
|
60
|
+
should "unhold" do
|
61
|
+
stub = stub_request(:post, format_url(@conference, "unhold"))
|
62
|
+
.to_return(body: JSON.generate(result: "ok"))
|
63
|
+
@conference.unhold
|
64
|
+
assert_requested stub
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_call
|
69
|
+
Telnyx::Call.create connection_id: "12345", to: "+15550001111", from: "+15550002222"
|
70
|
+
end
|
71
|
+
|
72
|
+
def format_url(conf, action)
|
73
|
+
"#{Telnyx.api_base}/v2/conferences/#{conf.id}/actions/#{action}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -8,5 +8,13 @@ module Telnyx
|
|
8
8
|
assert_kind_of Telnyx::ListObject, number_reservations
|
9
9
|
assert_kind_of Telnyx::NumberReservation, number_reservations.first
|
10
10
|
end
|
11
|
+
|
12
|
+
should "call extend" do
|
13
|
+
number_reservation = Telnyx::NumberReservation.list.first
|
14
|
+
stub = stub_request(:post, "#{Telnyx.api_base}/v2/number_reservations/#{number_reservation.id}/actions/extend")
|
15
|
+
.to_return(body: JSON.generate(id: "123"))
|
16
|
+
number_reservation.extend_number
|
17
|
+
assert_requested stub
|
18
|
+
end
|
11
19
|
end
|
12
20
|
end
|
data/test/telnyx/webhook_test.rb
CHANGED
@@ -1,48 +1,95 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require ::File.expand_path("../../test_helper", __FILE__)
|
4
|
+
require "securerandom"
|
4
5
|
|
5
6
|
module Telnyx
|
6
7
|
class WebhookTest < Test::Unit::TestCase
|
7
8
|
EVENT_PAYLOAD = <<-PAYLOAD.freeze
|
8
9
|
{
|
9
10
|
"data": {
|
10
|
-
"
|
11
|
-
"id": "
|
12
|
-
"
|
13
|
-
"created_at": "2018-02-02T22:25:27.521992Z",
|
11
|
+
"event_type": "message.received",
|
12
|
+
"id": "39547d14-fe28-4759-8650-0a974f80a612",
|
13
|
+
"occurred_at": "2019-07-30T23:07:36.628+00:00",
|
14
14
|
"payload": {
|
15
|
-
"
|
16
|
-
|
15
|
+
"completed_at": null,
|
16
|
+
"cost": null,
|
17
|
+
"direction": "inbound",
|
18
|
+
"encoding": "GSM-7",
|
19
|
+
"errors": [],
|
20
|
+
"from": {
|
21
|
+
"carrier": "T-Mobile USA",
|
22
|
+
"line_type": "long_code",
|
23
|
+
"phone_number": "+15745203340",
|
24
|
+
"status": "webhook_delivered"
|
25
|
+
},
|
26
|
+
"id": "1e9ee507-56a5-4d1a-b26b-db3cebcc267b",
|
27
|
+
"media": [],
|
28
|
+
"messaging_profile_id": "13c22476-7a37-4b79-b432-e629fc3b529c",
|
29
|
+
"organization_id": "4fc14b9e-5f17-493d-bb3d-01a8e575566d",
|
30
|
+
"parts": 1,
|
31
|
+
"received_at": "2019-07-30T23:07:36.623+00:00",
|
32
|
+
"record_type": "message",
|
33
|
+
"sent_at": null,
|
34
|
+
"text": "And again",
|
35
|
+
"to": "+18446087849",
|
36
|
+
"type": "SMS",
|
37
|
+
"valid_until": null,
|
38
|
+
"webhook_failover_url": null,
|
39
|
+
"webhook_url": "http://webhook.site/b041b025-0111-49dc-be4f-6b5f4bca31e4"
|
40
|
+
},
|
41
|
+
"record_type": "event"
|
42
|
+
},
|
43
|
+
"meta": {
|
44
|
+
"attempt": 1,
|
45
|
+
"delivered_to": "http://webhook.site/b041b025-0111-49dc-be4f-6b5f4bca31e4"
|
17
46
|
}
|
18
47
|
}
|
19
48
|
PAYLOAD
|
20
49
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAwy/jPkkgBo7oQermYujjAmSqN+aHNg+D4K85lKn6T3khJ8O2\nt/FrgN5qSGqg+0U5hoIHZflEon28lbLdf6gZjPeKQ2a24w5zroR6e4MM00RyJWA6\nMWXdo6Tn6xqKMYuT8LffEJGnXCH4yTIkxAVDyK0dfewhtrlpmW5ojXcDCrZ3Oo1o\n588PLNwSIuQwU7wHZwOLglWxFt6LZ9Ps8zYfQNH/pXNczf1E4rGZ1QxrzqFbndvj\nCE5VDRhULhycT/X0H2EMvNgHsDQk4OhENnzoCal3vO5+P9MgC7NSZCR8Ubebq0ta\nnL5dj5GGYyjWmeq3QhfDLX2mTpIv/B0e8+hg8QIDAQABAoIBAQCNwoP6wsVdvgD1\njxNQlu/41v/Bpc5h9xbC4sChNmqzubfY144nPlHjwKXUfoz4sag8Bsg0ybuNgGCt\nIME6a+5SsZ5boYgGlIJ0J4eFmQKBll6IwsDBC8jTh3thB1+C6GrEE+cQc5jnk0zL\nY33MWD6IyyJ2SD+cJEGLy+JnjB5LckGCQXWPQXwvpIKgGmFoLQzHCKfeKHZ3olB8\nC1+YKrQzLtyuuH9obDWxRSrqI5gOI/76PWmo+weNa4OrfFtBf5O9bo5OD17ilIT/\nuNpxb/7rOkpwU9x6D00/D/S7ecCdVoL2yBB5L635TNQKXxhvdSmBg1ceLlztwsUL\nOHIlglTZAoGBAOY3wyincm8iAUxLE+Z3AeTD94pjND4g2JXFF9E7UDxgRD6E3n38\nubNRdAMkxDmDYgyIOZsebykMadQ2vNiWqTjOBr6hxyQMFutHWrIJOU+peFCepn8u\nNX3Xg44l7KcwwqX5svoqgFl1FKwNpBOSo50oGX4lAgqtjYqEeMInfjZ7AoGBANkL\nz0wBNAr9oXsc0BN2WkQXB34RU/WcrymhxHfc+ZRzRShk9LOdBTuMYnj4rtjdG849\nJDDWlMk7UlzGjI07G5aT+n8Aq69BhV0IARC9PafTncE6G3sHswAQudHiurLflP9C\noj6kTakunrq8Kgj3Q1p6Ie+Hv7E01A3D0Difr4CDAoGAXORrLuBB4G3MMEirAvdK\nIFCidYiJ7/e47NXWQmq4eWQupTtfu15aX+yh7xLKypok2gGtnNWu7NVBbouXr507\nMtyPBCSrAfSO2uizw9rM8UPkdENP00mF8/0d7CGJV/zozafve9niaDZB3Rqz9eHZ\nevRPNQMhy8Uzs4y4XT8qQjkCgYBNuLjmkpe8R86Hc23fSkZQk56POk1CanUfB1p/\nQZXt3skpCd3GY7f39vFcOFEEP0kxtRs8kdp9pMx9hGvYNw5OAXd1+xt/iorjIXag\nM+PcMR8QjmpAyCUFJPglfHc2jnGgZpAKtnNI3fThEXhL9Z8cyxdT2tx97FjzBOeP\nHz+NWQKBgQCU0bSxTp2rbOCxHosQ/GDDTY0JkQ2z5q1SkibSiEnyAZ3yCHpXZRD7\nsa5BWs4qlasSKmxdmT9xgRDAL6CJH6kJizF3UIaIPOvPjIroOa7Mk1OFNbOi6Cao\n0LcWp5w1I2r5g7sOIRM/AcS3yVT5RJO4KB8WyDOvxCfP8cFsTacZmQ==\n-----END RSA PRIVATE KEY-----\n".freeze
|
25
|
-
# rubocop:enable Metrics/LineLength
|
50
|
+
def timestamp
|
51
|
+
@timestamp ||= Time.now.to_i
|
52
|
+
end
|
26
53
|
|
27
54
|
def generate_signature(opts = {})
|
28
|
-
opts[:timestamp] ||=
|
55
|
+
opts[:timestamp] ||= timestamp
|
29
56
|
opts[:payload] ||= EVENT_PAYLOAD
|
30
|
-
opts[:private_key] ||=
|
57
|
+
opts[:private_key] ||= signing_key
|
58
|
+
|
59
|
+
stamped_payload = "#{opts[:timestamp]}|#{opts[:payload]}"
|
60
|
+
Base64.encode64 opts[:private_key].sign(stamped_payload)
|
61
|
+
end
|
31
62
|
|
32
|
-
|
33
|
-
|
34
|
-
Base64.encode64(signature)
|
63
|
+
def signing_key
|
64
|
+
@signing_key ||= Ed25519::SigningKey.generate
|
35
65
|
end
|
36
66
|
|
37
67
|
def setup
|
38
68
|
super
|
39
|
-
ENV["TELNYX_PUBLIC_KEY"] =
|
69
|
+
ENV["TELNYX_PUBLIC_KEY"] = Base64.encode64(signing_key.verify_key.to_bytes)
|
70
|
+
Telnyx::Webhook::Signature.reload_verify_key
|
71
|
+
end
|
72
|
+
|
73
|
+
context "generated test keys" do
|
74
|
+
should "sign and verify" do
|
75
|
+
message = "foobar"
|
76
|
+
signature = signing_key.sign message
|
77
|
+
verify_key = Ed25519::VerifyKey.new(Base64.decode64(ENV.fetch("TELNYX_PUBLIC_KEY")))
|
78
|
+
assert verify_key.verify(signature, message)
|
79
|
+
end
|
80
|
+
|
81
|
+
should "create and decode signature" do
|
82
|
+
message = "lorem ipsum"
|
83
|
+
stamped_message = "#{timestamp}|#{message}"
|
84
|
+
signature = generate_signature(payload: message)
|
85
|
+
verify_key = signing_key.verify_key
|
86
|
+
assert verify_key.verify(Base64.decode64(signature), stamped_message)
|
87
|
+
end
|
40
88
|
end
|
41
89
|
|
42
90
|
context ".construct_event" do
|
43
91
|
should "return an Event instance from a valid JSON payload and valid signature header" do
|
44
|
-
|
45
|
-
signature = generate_signature(timestamp: timestamp)
|
92
|
+
signature = generate_signature
|
46
93
|
event = Telnyx::Webhook.construct_event(EVENT_PAYLOAD, signature, timestamp)
|
47
94
|
assert event.is_a?(Telnyx::Event)
|
48
95
|
end
|
@@ -50,15 +97,13 @@ module Telnyx
|
|
50
97
|
should "raise a JSON::ParserError from an invalid JSON payload" do
|
51
98
|
assert_raises JSON::ParserError do
|
52
99
|
payload = "this is not valid JSON"
|
53
|
-
|
54
|
-
signature = generate_signature(payload: payload, timestamp: timestamp)
|
100
|
+
signature = generate_signature payload: payload
|
55
101
|
Telnyx::Webhook.construct_event(payload, signature, timestamp)
|
56
102
|
end
|
57
103
|
end
|
58
104
|
|
59
105
|
should "raise a SignatureVerificationError from a valid JSON payload and an invalid signature header" do
|
60
|
-
signature =
|
61
|
-
timestamp = Time.now.to_i
|
106
|
+
signature = SecureRandom.base64(64)
|
62
107
|
assert_raises Telnyx::SignatureVerificationError do
|
63
108
|
Telnyx::Webhook.construct_event(EVENT_PAYLOAD, signature, timestamp)
|
64
109
|
end
|
@@ -67,15 +112,14 @@ module Telnyx
|
|
67
112
|
|
68
113
|
context ".verify" do
|
69
114
|
should "raise a SignatureVerificationError when the signature does not have the expected format" do
|
70
|
-
signature =
|
115
|
+
signature = SecureRandom.base64(64)
|
71
116
|
e = assert_raises(Telnyx::SignatureVerificationError) do
|
72
|
-
Telnyx::Webhook::Signature.verify(EVENT_PAYLOAD, signature,
|
117
|
+
Telnyx::Webhook::Signature.verify(EVENT_PAYLOAD, signature, timestamp)
|
73
118
|
end
|
74
119
|
assert_match("Signature is invalid and does not match the payload", e.message)
|
75
120
|
end
|
76
121
|
|
77
122
|
should "raise a SignatureVerificationError when there are no valid signatures for the payload" do
|
78
|
-
timestamp = Time.now.to_i
|
79
123
|
signature = generate_signature(payload: "foo", timestamp: timestamp)
|
80
124
|
e = assert_raises(Telnyx::SignatureVerificationError) do
|
81
125
|
Telnyx::Webhook::Signature.verify(EVENT_PAYLOAD, signature, timestamp)
|
@@ -84,16 +128,14 @@ module Telnyx
|
|
84
128
|
end
|
85
129
|
|
86
130
|
should "raise a SignatureVerificationError when the timestamp is not within the tolerance" do
|
87
|
-
timestamp = Time.now.to_i - 15
|
88
131
|
signature = generate_signature(timestamp: Time.now.to_i - 15)
|
89
132
|
e = assert_raises(Telnyx::SignatureVerificationError) do
|
90
|
-
Telnyx::Webhook::Signature.verify(EVENT_PAYLOAD, signature, timestamp, tolerance: 10)
|
133
|
+
Telnyx::Webhook::Signature.verify(EVENT_PAYLOAD, signature, timestamp - 15, tolerance: 10)
|
91
134
|
end
|
92
135
|
assert_match("Timestamp outside the tolerance zone", e.message)
|
93
136
|
end
|
94
137
|
|
95
138
|
should "return true when the signature is valid and the timestamp is within the tolerance" do
|
96
|
-
timestamp = Time.now.to_i
|
97
139
|
signature = generate_signature
|
98
140
|
assert(Telnyx::Webhook::Signature.verify(EVENT_PAYLOAD, signature, timestamp, tolerance: 10))
|
99
141
|
end
|
data/test/test_helper.rb
CHANGED
@@ -17,7 +17,7 @@ require ::File.expand_path("../test_data", __FILE__)
|
|
17
17
|
require ::File.expand_path("../telnyx_mock", __FILE__)
|
18
18
|
|
19
19
|
# If changing this number, please also change it in `.travis.yml`.
|
20
|
-
MOCK_MINIMUM_VERSION = "0.
|
20
|
+
MOCK_MINIMUM_VERSION = "0.2.0".freeze
|
21
21
|
MOCK_PORT = Telnyx::TelnyxMock.start
|
22
22
|
|
23
23
|
# Disable all real network connections except those that are outgoing to
|
@@ -53,7 +53,7 @@ module Test
|
|
53
53
|
include Mocha
|
54
54
|
|
55
55
|
setup do
|
56
|
-
Telnyx.api_key = "KEYSUPERSECRET"
|
56
|
+
Telnyx.api_key = "KEYSUPERSECRET" # mock server expects this exact string
|
57
57
|
Telnyx.api_base = "http://localhost:#{MOCK_PORT}"
|
58
58
|
|
59
59
|
# stub_connect
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: telnyx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Telnyx
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -38,18 +38,31 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '3.0'
|
41
|
-
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ed25519
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
55
|
+
description: Telnyx enables anyone to deliver enterprise-grade real-time communications
|
56
|
+
over the internet. See https://telnyx.com for details.
|
42
57
|
email: support@telnyx.com
|
43
58
|
executables:
|
44
59
|
- telnyx-console
|
45
60
|
extensions: []
|
46
61
|
extra_rdoc_files: []
|
47
62
|
files:
|
48
|
-
- ".gitattributes"
|
49
63
|
- ".github/ISSUE_TEMPLATE.md"
|
50
64
|
- ".gitignore"
|
51
65
|
- ".rubocop.yml"
|
52
|
-
- ".rubocop_todo.yml"
|
53
66
|
- ".travis.yml"
|
54
67
|
- CHANGELOG.md
|
55
68
|
- CONTRIBUTORS
|
@@ -65,10 +78,13 @@ files:
|
|
65
78
|
- lib/telnyx/api_operations/delete.rb
|
66
79
|
- lib/telnyx/api_operations/list.rb
|
67
80
|
- lib/telnyx/api_operations/nested_resource.rb
|
81
|
+
- lib/telnyx/api_operations/param_wrapper.rb
|
68
82
|
- lib/telnyx/api_operations/request.rb
|
69
83
|
- lib/telnyx/api_operations/save.rb
|
70
84
|
- lib/telnyx/api_resource.rb
|
71
85
|
- lib/telnyx/available_phone_number.rb
|
86
|
+
- lib/telnyx/call.rb
|
87
|
+
- lib/telnyx/conferences.rb
|
72
88
|
- lib/telnyx/errors.rb
|
73
89
|
- lib/telnyx/event.rb
|
74
90
|
- lib/telnyx/list_object.rb
|
@@ -93,6 +109,8 @@ files:
|
|
93
109
|
- test/telnyx/api_operations_test.rb
|
94
110
|
- test/telnyx/api_resource_test.rb
|
95
111
|
- test/telnyx/available_phone_number_test.rb
|
112
|
+
- test/telnyx/call_control_test.rb
|
113
|
+
- test/telnyx/conferences_test.rb
|
96
114
|
- test/telnyx/errors_test.rb
|
97
115
|
- test/telnyx/list_object_test.rb
|
98
116
|
- test/telnyx/message_test.rb
|
@@ -112,10 +130,14 @@ files:
|
|
112
130
|
- test/telnyx_test.rb
|
113
131
|
- test/test_data.rb
|
114
132
|
- test/test_helper.rb
|
115
|
-
homepage: https://developers.telnyx.com
|
133
|
+
homepage: https://developers.telnyx.com
|
116
134
|
licenses:
|
117
135
|
- MIT
|
118
|
-
metadata:
|
136
|
+
metadata:
|
137
|
+
documentation_uri: https://developers.telnyx.com/docs/api/v2/overview
|
138
|
+
github_repo: ssh://github.com/team-telnyx/telnyx-ruby
|
139
|
+
homepage_uri: https://telnyx.com
|
140
|
+
source_code_uri: https://github.com/team-telnyx/telnyx-ruby
|
119
141
|
post_install_message:
|
120
142
|
rdoc_options: []
|
121
143
|
require_paths:
|
@@ -141,6 +163,8 @@ test_files:
|
|
141
163
|
- test/telnyx/api_operations_test.rb
|
142
164
|
- test/telnyx/api_resource_test.rb
|
143
165
|
- test/telnyx/available_phone_number_test.rb
|
166
|
+
- test/telnyx/call_control_test.rb
|
167
|
+
- test/telnyx/conferences_test.rb
|
144
168
|
- test/telnyx/errors_test.rb
|
145
169
|
- test/telnyx/list_object_test.rb
|
146
170
|
- test/telnyx/message_test.rb
|
data/.gitattributes
DELETED
data/.rubocop_todo.yml
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
# This configuration was generated by
|
2
|
-
# `rubocop --auto-gen-config`
|
3
|
-
# on 2018-07-19 14:22:24 +0200 using RuboCop version 0.50.0.
|
4
|
-
# The point is for the user to remove these configuration records
|
5
|
-
# one by one as the offenses are removed from the code base.
|
6
|
-
# Note that changes in the inspected code, or installation of new
|
7
|
-
# versions of RuboCop, may require this file to be generated again.
|
8
|
-
|
9
|
-
# Offense count: 19
|
10
|
-
Metrics/AbcSize:
|
11
|
-
Max: 52
|
12
|
-
|
13
|
-
# Offense count: 27
|
14
|
-
# Configuration parameters: CountComments, ExcludedMethods.
|
15
|
-
Metrics/BlockLength:
|
16
|
-
Max: 498
|
17
|
-
|
18
|
-
# Offense count: 8
|
19
|
-
# Configuration parameters: CountComments.
|
20
|
-
Metrics/ClassLength:
|
21
|
-
Max: 659
|
22
|
-
|
23
|
-
# Offense count: 11
|
24
|
-
Metrics/CyclomaticComplexity:
|
25
|
-
Max: 15
|
26
|
-
|
27
|
-
# Offense count: 278
|
28
|
-
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
29
|
-
# URISchemes: http, https
|
30
|
-
Metrics/LineLength:
|
31
|
-
Max: 310
|
32
|
-
|
33
|
-
# Offense count: 6
|
34
|
-
# Configuration parameters: CountKeywordArgs.
|
35
|
-
Metrics/ParameterLists:
|
36
|
-
Max: 7
|
37
|
-
|
38
|
-
# Offense count: 5
|
39
|
-
Metrics/PerceivedComplexity:
|
40
|
-
Max: 17
|
41
|
-
|
42
|
-
# Offense count: 2
|
43
|
-
Style/ClassVars:
|
44
|
-
Exclude:
|
45
|
-
- 'lib/telnyx/telnyx_object.rb'
|
46
|
-
- 'test/telnyx/api_resource_test.rb'
|
47
|
-
|
48
|
-
# Offense count: 56
|
49
|
-
Style/Documentation:
|
50
|
-
Enabled: false
|