telnyx 0.0.1 → 0.0.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.
- 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
|