reach-ruby 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.dockerignore +1 -0
- data/.gitignore +18 -0
- data/.rubocop.yml +58 -0
- data/.rubocop_todo.yml +193 -0
- data/AUTHORS.md +52 -0
- data/CHANGES.md +3 -0
- data/CODE_OF_CONDUCT.md +73 -0
- data/CONTRIBUTING.md +163 -0
- data/Dockerfile +9 -0
- data/Gemfile +3 -0
- data/ISSUE_TEMPLATE.md +30 -0
- data/LICENSE +21 -0
- data/Makefile +34 -0
- data/PULL_REQUEST_TEMPLATE.md +31 -0
- data/README.md +237 -0
- data/Rakefile +10 -0
- data/UPGRADE.md +5 -0
- data/VERSIONS.md +35 -0
- data/advanced-examples/custom-http-client.md +166 -0
- data/examples/examples.rb +23 -0
- data/githooks/pre-commit +1 -0
- data/lib/rack/reach_webhook_authentication.rb +72 -0
- data/lib/reach-ruby/framework/reach_response.rb +19 -0
- data/lib/reach-ruby/framework/request.rb +41 -0
- data/lib/reach-ruby/framework/response.rb +18 -0
- data/lib/reach-ruby/framework/rest/domain.rb +39 -0
- data/lib/reach-ruby/framework/rest/error.rb +51 -0
- data/lib/reach-ruby/framework/rest/helper.rb +11 -0
- data/lib/reach-ruby/framework/rest/page.rb +144 -0
- data/lib/reach-ruby/framework/rest/resource.rb +23 -0
- data/lib/reach-ruby/framework/rest/version.rb +240 -0
- data/lib/reach-ruby/framework/serialize.rb +81 -0
- data/lib/reach-ruby/framework/values.rb +9 -0
- data/lib/reach-ruby/http/http_client.rb +82 -0
- data/lib/reach-ruby/http.rb +5 -0
- data/lib/reach-ruby/rest/api/authentix/.openapi-generator/FILES +10 -0
- data/lib/reach-ruby/rest/api/authentix/.openapi-generator/VERSION +1 -0
- data/lib/reach-ruby/rest/api/authentix/.openapi-generator-ignore +23 -0
- data/lib/reach-ruby/rest/api/authentix/authentication_trial_item.rb +418 -0
- data/lib/reach-ruby/rest/api/authentix/authentication_trial_stat_item.rb +279 -0
- data/lib/reach-ruby/rest/api/authentix/configuration_item/authentication_control_item.rb +214 -0
- data/lib/reach-ruby/rest/api/authentix/configuration_item/authentication_item.rb +449 -0
- data/lib/reach-ruby/rest/api/authentix/configuration_item.rb +583 -0
- data/lib/reach-ruby/rest/api/authentix.rb +72 -0
- data/lib/reach-ruby/rest/api/messaging/.openapi-generator/FILES +2 -0
- data/lib/reach-ruby/rest/api/messaging/.openapi-generator/VERSION +1 -0
- data/lib/reach-ruby/rest/api/messaging/.openapi-generator-ignore +23 -0
- data/lib/reach-ruby/rest/api/messaging/messaging_item.rb +582 -0
- data/lib/reach-ruby/rest/api/messaging.rb +51 -0
- data/lib/reach-ruby/rest/api.rb +50 -0
- data/lib/reach-ruby/rest/client.rb +130 -0
- data/lib/reach-ruby/rest.rb +13 -0
- data/lib/reach-ruby/security/request_validator.rb +149 -0
- data/lib/reach-ruby/util/configuration.rb +25 -0
- data/lib/reach-ruby/version.rb +3 -0
- data/lib/reach-ruby.rb +44 -0
- data/reach-ruby.gemspec +38 -0
- data/sonar-project.properties +13 -0
- metadata +267 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
<!--
|
2
|
+
We appreciate the effort for this pull request but before that please make sure you read the contribution guidelines, then fill out the blanks below.
|
3
|
+
|
4
|
+
Please format the PR title appropriately based on the type of change:
|
5
|
+
<type>[!]: <description>
|
6
|
+
Where <type> is one of: docs, chore, feat, fix, test, misc.
|
7
|
+
Add a '!' after the type for breaking changes (e.g. feat!: new breaking feature).
|
8
|
+
|
9
|
+
**All third-party contributors acknowledge that any contributions they provide will be made under the same open-source license that the open-source project is provided under.**
|
10
|
+
|
11
|
+
Please enter each Issue number you are resolving in your PR after one of the following words [Fixes, Closes, Resolves]. This will auto-link these issues and close them when this PR is merged!
|
12
|
+
e.g.
|
13
|
+
Fixes #1
|
14
|
+
Closes #2
|
15
|
+
-->
|
16
|
+
|
17
|
+
# Fixes #
|
18
|
+
|
19
|
+
A short description of what this PR does.
|
20
|
+
|
21
|
+
### Checklist
|
22
|
+
- [x] I acknowledge that all my contributions will be made under the project's license
|
23
|
+
- [ ] I have made a material change to the repo (functionality, testing, spelling, grammar)
|
24
|
+
- [ ] I have read the [Contribution Guidelines](https://github.com/talkylabs/reach-ruby/blob/main/CONTRIBUTING.md) and my PR follows them
|
25
|
+
- [ ] I have titled the PR appropriately
|
26
|
+
- [ ] I have updated my branch with the main branch
|
27
|
+
- [ ] I have added tests that prove my fix is effective or that my feature works
|
28
|
+
- [ ] I have added the necessary documentation about the functionality in the appropriate .md file
|
29
|
+
- [ ] I have added inline documentation to the code I modified
|
30
|
+
|
31
|
+
If you have questions, please create a GitHub Issue in this repository.
|
data/README.md
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
# reach-ruby
|
2
|
+
|
3
|
+
|
4
|
+
## Documentation
|
5
|
+
|
6
|
+
The documentation for the Reach API can be found [here][apidocs].
|
7
|
+
|
8
|
+
The individual releases [here][refdocs].
|
9
|
+
|
10
|
+
## Versions
|
11
|
+
|
12
|
+
`reach-ruby` uses a modified version of [Semantic Versioning](https://semver.org) for all changes. [See this document](VERSIONS.md) for details.
|
13
|
+
|
14
|
+
### Supported Ruby Versions
|
15
|
+
|
16
|
+
This library supports the following Ruby implementations:
|
17
|
+
|
18
|
+
- Ruby 2.4
|
19
|
+
- Ruby 2.5
|
20
|
+
- Ruby 2.6
|
21
|
+
- Ruby 2.7
|
22
|
+
- Ruby 3.0
|
23
|
+
- Ruby 3.1
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
To install using [Bundler][bundler] grab the latest stable version:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
gem 'reach-ruby', '~> 1.0.0'
|
31
|
+
```
|
32
|
+
|
33
|
+
To manually install `reach-ruby` via [Rubygems][rubygems] simply gem install:
|
34
|
+
|
35
|
+
```bash
|
36
|
+
gem install reach-ruby -v 1.0.0
|
37
|
+
```
|
38
|
+
|
39
|
+
To build and install the development branch yourself from the latest source:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
git clone git@github.com:talkylabs/reach-ruby.git
|
43
|
+
cd reach-ruby
|
44
|
+
make install
|
45
|
+
```
|
46
|
+
|
47
|
+
> **Info**
|
48
|
+
> If the command line gives you an error message that says Permission Denied, try running the above commands with sudo.
|
49
|
+
>
|
50
|
+
> For example: `sudo gem install reach-ruby`
|
51
|
+
|
52
|
+
### Test your installation
|
53
|
+
|
54
|
+
To make sure the installation was successful, try sending yourself an SMS message, like this:
|
55
|
+
|
56
|
+
```rb
|
57
|
+
require "reach-ruby"
|
58
|
+
|
59
|
+
# Your API user and key the web app
|
60
|
+
apiUser = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
61
|
+
apiKey = "your_api_key"
|
62
|
+
|
63
|
+
@client = Reach::REST::Client.new apiUser, apiKey
|
64
|
+
message = @client.messaging.messaging_items.dispatch(
|
65
|
+
body: "Hello from Ruby",
|
66
|
+
dest: "+12345678901", # Text this number
|
67
|
+
src: "+15005550006", # From a valid number
|
68
|
+
)
|
69
|
+
|
70
|
+
puts message.messageId
|
71
|
+
```
|
72
|
+
|
73
|
+
> **Warning**
|
74
|
+
> It's okay to hardcode your credentials when testing locally, but you should use environment variables to keep them secret before committing any code or deploying to production.
|
75
|
+
|
76
|
+
## Usage
|
77
|
+
|
78
|
+
### Authenticate the Client
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
require 'reach-ruby'
|
82
|
+
|
83
|
+
# Your API user and key the web app
|
84
|
+
apiUser = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
85
|
+
apiKey = "your_api_key"
|
86
|
+
|
87
|
+
# Initialize the Reach Client with your credentials
|
88
|
+
@client = Reach::REST::Client.new apiUser, apiKey
|
89
|
+
```
|
90
|
+
|
91
|
+
### Send an SMS
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
@client.messaging.messaging_items.dispatch(
|
95
|
+
src: '+14159341234',
|
96
|
+
dest: '+16105557069',
|
97
|
+
body: 'Hey there!'
|
98
|
+
)
|
99
|
+
```
|
100
|
+
|
101
|
+
### List your SMS Messages
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
@client.messaging.messaging_items.dispatch.list(limit: 20)
|
105
|
+
```
|
106
|
+
|
107
|
+
### Fetch a single SMS message by messageId
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
# put the message sid you want to retrieve here:
|
111
|
+
messageId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
112
|
+
@client.messaging.messaging_items(messageId).fetch
|
113
|
+
```
|
114
|
+
|
115
|
+
### Iterate through records
|
116
|
+
|
117
|
+
The library automatically handles paging for you. Collections, such as `messaging_items`, have `list` and stream methods that page under the hood. With both `list` and `stream`, you can specify the number of records you want to receive (`limit`) and the maximum size you want each page fetch to be (`page_size`). The library will then handle the task for you.
|
118
|
+
|
119
|
+
`list` eagerly fetches all records and returns them as a list, whereas `stream` returns an enumerator and lazily retrieves pages of records as you iterate over the collection. You can also page manually using the `page` method.
|
120
|
+
|
121
|
+
|
122
|
+
```rb
|
123
|
+
require 'reach-ruby'
|
124
|
+
|
125
|
+
# Your API user and key the web app
|
126
|
+
apiUser = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
127
|
+
apiKey = "your_api_key"
|
128
|
+
|
129
|
+
@client = Reach::REST::Client.new(apiUser, apiKey)
|
130
|
+
|
131
|
+
@client.messaging.messaging_items.list
|
132
|
+
.each do |msg|
|
133
|
+
puts msg.messageId
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
### Enable Debug logging
|
138
|
+
|
139
|
+
In order to enable debug logging, pass in a 'logger' instance to the client with the level set to at least 'DEBUG'
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
@client = Reach::REST::Client.new apiUser, apiKey
|
143
|
+
myLogger = Logger.new(STDOUT)
|
144
|
+
myLogger.level = Logger::DEBUG
|
145
|
+
@client.logger = myLogger
|
146
|
+
|
147
|
+
@client = Reach::REST::Client.new apiUser, apiKey
|
148
|
+
myLogger = Logger.new('my_log.log')
|
149
|
+
myLogger.level = Logger::DEBUG
|
150
|
+
@client.logger = myLogger
|
151
|
+
```
|
152
|
+
|
153
|
+
### Handle Exceptions {#exceptions}
|
154
|
+
|
155
|
+
If the Reach API returns a 400 or a 500 level HTTP response, the `reach-ruby`
|
156
|
+
library will throw a `Reach::REST::RestError`. 400-level errors are normal
|
157
|
+
during API operation (`“Invalid number”`, `“Cannot deliver SMS to that number”`,
|
158
|
+
for example) and should be handled appropriately.
|
159
|
+
|
160
|
+
```rb
|
161
|
+
require 'reach-ruby'
|
162
|
+
|
163
|
+
# Your API user and key the web app
|
164
|
+
apiUser = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
165
|
+
apiKey = "your_api_key"
|
166
|
+
|
167
|
+
@client = Reach::REST::Client.new apiUser, apiKey
|
168
|
+
|
169
|
+
begin
|
170
|
+
messages = @client.messaging.messaging_items.list(limit: 20)
|
171
|
+
rescue Reach::REST::RestError => e
|
172
|
+
puts e.message
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
### Debug API requests
|
177
|
+
|
178
|
+
To assist with debugging, the library allows you to access the underlying request and response objects. This capability is built into the default HTTP client that ships with the library.
|
179
|
+
|
180
|
+
For example, you can retrieve the status code of the last response like so:
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
require 'rubygems' # Not necessary with ruby 1.9 but included for completeness
|
184
|
+
require 'reach-ruby'
|
185
|
+
|
186
|
+
# Your API user and key the web app
|
187
|
+
apiUser = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
188
|
+
apiKey = "your_api_key"
|
189
|
+
|
190
|
+
@client = Reach::REST::Client.new(apiUser, apiKey)
|
191
|
+
|
192
|
+
@message = @client.messaging.messaging_items.dispatch(
|
193
|
+
dest: '+14158675309',
|
194
|
+
src: '+14258675310',
|
195
|
+
body: 'Ahoy!'
|
196
|
+
)
|
197
|
+
|
198
|
+
# Retrieve the status code of the last response from the HTTP client
|
199
|
+
puts @client.http_client.last_response.status_code
|
200
|
+
```
|
201
|
+
|
202
|
+
### Customize your HTTP Client
|
203
|
+
|
204
|
+
`reach-ruby` uses [Faraday][faraday] to make HTTP requests. You can tell `Reach::REST::Client` to use any of the Faraday adapters like so:
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
@client.http_client.adapter = :typhoeus
|
208
|
+
```
|
209
|
+
|
210
|
+
To use a custom HTTP client with this helper library, please see the [advanced example of how to do so](./advanced-examples/custom-http-client.md).
|
211
|
+
|
212
|
+
To apply customizations such as middleware, you can use the `configure_connection` method like so:
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
@client.http_client.configure_connection do |faraday|
|
216
|
+
faraday.use SomeMiddleware
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
|
221
|
+
|
222
|
+
## Docker Image
|
223
|
+
|
224
|
+
The `Dockerfile` present in this repository and its respective `talkylabs/reach-ruby` Docker image are currently used by Us for testing purposes only.
|
225
|
+
|
226
|
+
## Getting help
|
227
|
+
|
228
|
+
If you've instead found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo!
|
229
|
+
|
230
|
+
[apidocs]: https://www.reach.talkylabs.com/docs/api
|
231
|
+
[refdocs]: https://talkylabs.github.io/reach-ruby
|
232
|
+
[wiki]: https://github.com/talkylabs/reach-ruby/wiki
|
233
|
+
[bundler]: https://bundler.io
|
234
|
+
[rubygems]: https://rubygems.org
|
235
|
+
[gem]: https://rubygems.org/gems/talkylabs
|
236
|
+
[issues]: https://github.com/talkylabs/reach-ruby/issues
|
237
|
+
[faraday]: https://github.com/lostisland/faraday
|
data/Rakefile
ADDED
data/UPGRADE.md
ADDED
data/VERSIONS.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Versioning Strategy
|
2
|
+
|
3
|
+
`reach-ruby` uses a modified version of [Semantic Versioning][semver] for
|
4
|
+
all changes to the helper library. It is strongly encouraged that you pin at
|
5
|
+
least the major version and potentially the minor version to avoid pulling in
|
6
|
+
breaking changes.
|
7
|
+
|
8
|
+
Semantic Versions take the form of `MAJOR.MINOR.PATCH`
|
9
|
+
|
10
|
+
When bugs are fixed in the library in a backwards-compatible way, the `PATCH`
|
11
|
+
level will be incremented by one. When new features are added to the library
|
12
|
+
in a backwards-compatible way, the `PATCH` level will be incremented by one.
|
13
|
+
`PATCH` changes should _not_ break your code and are generally safe for upgrade.
|
14
|
+
|
15
|
+
When a new large feature set comes online or a small breaking change is
|
16
|
+
introduced, the `MINOR` version will be incremented by one and the `PATCH`
|
17
|
+
version reset to zero. `MINOR` changes _may_ require some amount of manual code
|
18
|
+
change for upgrade. These backwards-incompatible changes will generally be
|
19
|
+
limited to a small number of function signature changes.
|
20
|
+
|
21
|
+
The `MAJOR` version is used to indicate the family of technology represented by
|
22
|
+
the helper library. Breaking changes that require extensive reworking of code
|
23
|
+
will cause the `MAJOR` version to be incremented by one, and the `MINOR` and
|
24
|
+
`PATCH` versions will be reset to zero. We understands that this can be very
|
25
|
+
disruptive, so we will only introduce this type of breaking change when
|
26
|
+
absolutely necessary. New `MAJOR` versions will be communicated in advance with
|
27
|
+
`Release Candidates` and a schedule.
|
28
|
+
|
29
|
+
## Supported Versions
|
30
|
+
|
31
|
+
Only the current `MAJOR` version of `reach-ruby` is supported. New
|
32
|
+
features, functionality, bug fixes, and security updates will only be added to
|
33
|
+
the current `MAJOR` version.
|
34
|
+
|
35
|
+
[semver]: https://semver.org
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# Custom HTTP Clients for the Reach Ruby Helper Library
|
2
|
+
|
3
|
+
If you are working with the Reach Ruby Helper Library, and you need to be able to modify the HTTP requests that the library makes to the Reach servers, you’re in the right place. The most common need to alter the HTTP request is to connect and authenticate with an enterprise’s proxy server. We’ll provide sample code that you can drop right into your app to handle this use case.
|
4
|
+
|
5
|
+
Connect and authenticate with a proxy server
|
6
|
+
To connect and provide credentials to a proxy server that may be between your app and Reach, you need a way to modify the HTTP requests that the Reach helper library makes on your behalf to invoke the Reach REST API.
|
7
|
+
|
8
|
+
The Reach Ruby helper library uses the [Faraday](https://rubygems.org/gems/faraday) gem under the hood to make the HTTP requests. The following example shows a typical request, without a custom `http_client`:
|
9
|
+
|
10
|
+
```rb
|
11
|
+
@client = Reach::REST::Client.new(username, auth_token)
|
12
|
+
|
13
|
+
message = @client.messaging.messaging_items
|
14
|
+
.dispatch(
|
15
|
+
dest: "+15558675310",
|
16
|
+
body: "Hey there!",
|
17
|
+
src: "+15017122661",
|
18
|
+
)
|
19
|
+
```
|
20
|
+
|
21
|
+
Out of the box, the helper library is creating a default `Reach::Http::Client` for you, using the Reach credentials you provide. However, you can create your own `Reach::Http::Client`, and pass it to any Reach REST API resource action you want.
|
22
|
+
|
23
|
+
Here’s an example of sending an SMS message with a custom client:
|
24
|
+
|
25
|
+
```rb
|
26
|
+
require "rubygems"
|
27
|
+
require "reach-ruby"
|
28
|
+
require "dotenv/load"
|
29
|
+
|
30
|
+
# Custom HTTP Client
|
31
|
+
require_relative "MyRequestClass"
|
32
|
+
|
33
|
+
# Your username and Auth Token from the web application
|
34
|
+
username = ENV["API_USER"]
|
35
|
+
auth_token = ENV["API_KEY"]
|
36
|
+
proxy_address = ENV["PROXY_ADDRESS"]
|
37
|
+
proxy_protocol = ENV["PROXY_PROTOCOL"]
|
38
|
+
proxy_port = ENV["PROXY_PORT"]
|
39
|
+
|
40
|
+
my_request_client = MyRequestClass.new(proxy_protocol, proxy_address, proxy_port)
|
41
|
+
|
42
|
+
@client = Reach::REST::Client.new(username, auth_token, my_request_client)
|
43
|
+
|
44
|
+
message = @client.messaging.messaging_items
|
45
|
+
.dispatch(
|
46
|
+
dest: "+593978613041",
|
47
|
+
body: "RB This is the ship that made the Kesssssel Run in fourteen parsecs?",
|
48
|
+
src: "+13212855389",
|
49
|
+
)
|
50
|
+
|
51
|
+
puts "Message SID: #{message.messageId}"
|
52
|
+
```
|
53
|
+
|
54
|
+
## Create your custom ReachRestClient
|
55
|
+
|
56
|
+
When you take a closer look at the constructor for `Reach::Http::Client`, you see that this class provides it to the Reach helper library to make the necessary HTTP requests.
|
57
|
+
|
58
|
+
## Call Reach through the proxy server
|
59
|
+
|
60
|
+
Now that we understand how all the components fit together, we can create our own `http_client` that can connect through a proxy server. To make this reusable, here’s a class that you can use to create this `http_client` whenever you need one.
|
61
|
+
|
62
|
+
```rb
|
63
|
+
class MyRequestClass
|
64
|
+
attr_accessor :adapter
|
65
|
+
attr_reader :timeout, :last_response, :last_request
|
66
|
+
|
67
|
+
def initialize(proxy_prot = nil, proxy_addr = nil, proxy_port = nil, timeout: nil)
|
68
|
+
@proxy_prot = proxy_prot
|
69
|
+
@proxy_addr = proxy_addr
|
70
|
+
@proxy_port = proxy_port
|
71
|
+
@timeout = timeout
|
72
|
+
@adapter = Faraday.default_adapter
|
73
|
+
end
|
74
|
+
|
75
|
+
def _request(request)
|
76
|
+
@connection = Faraday.new(url: request.host + ":" + request.port.to_s, ssl: { verify: true }) do |f|
|
77
|
+
f.options.params_encoder = Faraday::FlatParamsEncoder
|
78
|
+
f.request :url_encoded
|
79
|
+
f.adapter @adapter
|
80
|
+
f.headers = request.headers
|
81
|
+
f.headers["ApiUser"] = request.auth[0]
|
82
|
+
f.headers["ApiKey"] = request.auth[1]
|
83
|
+
if @proxy_addr
|
84
|
+
f.proxy = "#{@proxy_prot}://#{@proxy_addr}:#{@proxy_port}"
|
85
|
+
end
|
86
|
+
f.options.open_timeout = request.timeout || @timeout
|
87
|
+
f.options.timeout = request.timeout || @timeout
|
88
|
+
end
|
89
|
+
|
90
|
+
@last_request = request
|
91
|
+
@last_response = nil
|
92
|
+
response = @connection.send(request.method.downcase.to_sym,
|
93
|
+
request.url,
|
94
|
+
request.method == "GET" ? request.params : request.data)
|
95
|
+
|
96
|
+
if response.body && !response.body.empty?
|
97
|
+
object = response.body
|
98
|
+
elsif response.status == 400
|
99
|
+
object = { message: "Bad request", code: 400 }.to_json
|
100
|
+
end
|
101
|
+
|
102
|
+
reach_response = Reach::Response.new(response.status, object, headers: response.headers)
|
103
|
+
@last_response = reach_response
|
104
|
+
|
105
|
+
reach_response
|
106
|
+
end
|
107
|
+
|
108
|
+
def request(host, port, method, url, params = {}, data = {}, headers = {}, auth = nil, timeout = nil)
|
109
|
+
request = Reach::Request.new(host, port, method, url, params, data, headers, auth, timeout)
|
110
|
+
_request(request)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
In this example, we are using some environment variables loaded at the program startup to retrieve various configuration settings:
|
116
|
+
|
117
|
+
- Your Reach Api User and Api Key
|
118
|
+
- A proxy address in the form of `http://127.0.0.1:8888`
|
119
|
+
|
120
|
+
These settings are located in a file like `.env` like so:
|
121
|
+
|
122
|
+
```env
|
123
|
+
API_USER=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
124
|
+
API_KEY= your_auth_token
|
125
|
+
|
126
|
+
HTTPS_PROXY=https://127.0.0.1:8888
|
127
|
+
HTTP_PROXY=http://127.0.0.1:8888
|
128
|
+
```
|
129
|
+
|
130
|
+
Here’s the full console program that sends a text message and shows how it all can work together. It loads the `.env` file.
|
131
|
+
|
132
|
+
```rb
|
133
|
+
require "rubygems"
|
134
|
+
require "reach-ruby"
|
135
|
+
require "dotenv/load"
|
136
|
+
|
137
|
+
# Custom HTTP Client
|
138
|
+
require_relative "MyRequestClass"
|
139
|
+
|
140
|
+
username = ENV["API_USER"]
|
141
|
+
auth_token = ENV["API_KEY"]
|
142
|
+
proxy_address = ENV["PROXY_ADDRESS"]
|
143
|
+
proxy_protocol = ENV["PROXY_PROTOCOL"]
|
144
|
+
proxy_port = ENV["PROXY_PORT"]
|
145
|
+
|
146
|
+
my_request_client = MyRequestClass.new(proxy_protocol, proxy_address, proxy_port)
|
147
|
+
|
148
|
+
@client = Reach::REST::Client.new(username, auth_token, my_request_client)
|
149
|
+
|
150
|
+
message = @client.messaging.messaging_items
|
151
|
+
.dispatch(
|
152
|
+
dest: "+593978613041",
|
153
|
+
body: "RB This is the ship that made the Kesssssel Run in fourteen parsecs?",
|
154
|
+
src: "+13212855389",
|
155
|
+
)
|
156
|
+
|
157
|
+
puts "Message SID: #{message.messageId}"
|
158
|
+
```
|
159
|
+
|
160
|
+
## What else can this technique be used for?
|
161
|
+
|
162
|
+
Now that you know how to inject your own `http_client` into the Reach API request pipeline, you could use this technique to add custom HTTP headers and authorization to the requests (perhaps as required by an upstream proxy server).
|
163
|
+
|
164
|
+
You could also implement your own `http_client` to mock the Reach API responses so your unit and integration tests can run quickly without the need to make a connection to Reach.
|
165
|
+
|
166
|
+
We can’t wait to see what you build!
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# examples version
|
2
|
+
|
3
|
+
@username = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
4
|
+
@auth_token = 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
|
5
|
+
# set up a client
|
6
|
+
@client = Reach::REST::Client.new(@username, @auth_token)
|
7
|
+
|
8
|
+
################ SMS MESSAGES ################
|
9
|
+
|
10
|
+
@client.messaging.messaging_items.list(sent_at: '2010-09-01').each do |message|
|
11
|
+
puts message.dateCreated
|
12
|
+
end
|
13
|
+
|
14
|
+
# print a particular sms message
|
15
|
+
puts @client.messaging.messaging_items('SMXXXXXXXX').fetch.body
|
16
|
+
|
17
|
+
# send an sms
|
18
|
+
@client.messaging.messaging_items.dispatch(
|
19
|
+
src: '+14159341234',
|
20
|
+
dest: '+16105557069',
|
21
|
+
body: 'Hey there!'
|
22
|
+
)
|
23
|
+
|
data/githooks/pre-commit
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
make test
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack/media_type'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
# Middleware that authenticates webhooks from Reach using the request
|
7
|
+
# validator.
|
8
|
+
#
|
9
|
+
# The middleware takes an auth token with which to set up the request
|
10
|
+
# validator and any number of paths. When a path matches the incoming request
|
11
|
+
# path, the request will be checked for authentication.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# require 'rack'
|
16
|
+
# use Rack::ReachWebhookAuthentication, ENV['REACH_TALKYLABS_API_KEY'], /\/messages/
|
17
|
+
#
|
18
|
+
# The above appends this middleware to the stack, using an auth token saved in
|
19
|
+
# the ENV and only against paths that match /\/messages/. If the request
|
20
|
+
# validates then it gets passed on to the action as normal. If the request
|
21
|
+
# doesn't validate then the middleware responds immediately with a 403 status.
|
22
|
+
|
23
|
+
class ReachWebhookAuthentication
|
24
|
+
# Rack's FORM_DATA_MEDIA_TYPES can be modified to taste, so we're slightly
|
25
|
+
# more conservative in what we consider form data.
|
26
|
+
FORM_URLENCODED_MEDIA_TYPE = Rack::MediaType.type('application/x-www-form-urlencoded')
|
27
|
+
|
28
|
+
def initialize(app, auth_token, *paths, &auth_token_lookup)
|
29
|
+
@app = app
|
30
|
+
@auth_token = auth_token
|
31
|
+
define_singleton_method(:get_auth_token, auth_token_lookup) if block_given?
|
32
|
+
@path_regex = Regexp.union(paths)
|
33
|
+
end
|
34
|
+
|
35
|
+
def call(env)
|
36
|
+
return @app.call(env) unless env['PATH_INFO'].match(@path_regex)
|
37
|
+
request = Rack::Request.new(env)
|
38
|
+
original_url = request.url
|
39
|
+
params = extract_params!(request)
|
40
|
+
auth_token = @auth_token || get_auth_token(params['ApiUser'])
|
41
|
+
validator = Reach::Security::RequestValidator.new(auth_token)
|
42
|
+
signature = env['HTTP_X_REACH_SIGNATURE'] || ''
|
43
|
+
if validator.validate(original_url, params, signature)
|
44
|
+
@app.call(env)
|
45
|
+
else
|
46
|
+
[
|
47
|
+
403,
|
48
|
+
{ 'Content-Type' => 'text/plain' },
|
49
|
+
['Reach Request Validation Failed.']
|
50
|
+
]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Extract the params from the the request that we can use to determine the
|
55
|
+
# signature. This _may_ modify the passed in request since it may read/rewind
|
56
|
+
# the body.
|
57
|
+
def extract_params!(request)
|
58
|
+
return {} unless request.post?
|
59
|
+
|
60
|
+
if request.media_type == FORM_URLENCODED_MEDIA_TYPE
|
61
|
+
request.POST
|
62
|
+
else
|
63
|
+
request.body.rewind
|
64
|
+
body = request.body.read
|
65
|
+
request.body.rewind
|
66
|
+
body
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private :extract_params!
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reach
|
4
|
+
class ReachResponse
|
5
|
+
attr_accessor :status_code, :body
|
6
|
+
|
7
|
+
# @deprecated Use 'Reach::Response' instead.
|
8
|
+
def initialize(status_code, body)
|
9
|
+
warn "'Reach::ReachResponse' has been deprecated. Use 'Reach::Response' instead."
|
10
|
+
response = Reach::Response.new(status_code, body)
|
11
|
+
@status_code = response.status_code
|
12
|
+
@body = response.body
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"[#{@status_code}] #{@body}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reach
|
4
|
+
class Request
|
5
|
+
attr_reader :host, :port, :method, :url, :params, :data, :headers, :auth, :timeout
|
6
|
+
|
7
|
+
def initialize(host, port, method, url, params = {}, data = {}, headers = {}, auth = nil, timeout = nil)
|
8
|
+
@host = host
|
9
|
+
@port = port
|
10
|
+
@url = url
|
11
|
+
@method = method
|
12
|
+
@params = params
|
13
|
+
@data = data
|
14
|
+
@headers = headers
|
15
|
+
@auth = auth
|
16
|
+
@timeout = timeout
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
auth = @auth.nil? ? '' : '(' + @auth.join(',') + ')'
|
21
|
+
|
22
|
+
params = ''
|
23
|
+
unless @params.nil? || @params.empty?
|
24
|
+
params = '?' + @params.each.map { |key, value| "#{CGI.escape(key)}=#{CGI.escape(value)}" }.join('&')
|
25
|
+
end
|
26
|
+
|
27
|
+
headers = ''
|
28
|
+
unless @headers.nil? || @headers.empty?
|
29
|
+
headers = "\n" + @headers.each.map { |key, value| "-H \"#{key}\": \"#{value}\"" }.join("\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
data = ''
|
33
|
+
unless @data.nil? || @data.empty?
|
34
|
+
data = @method.equal?('GET') ? "\n -G" : "\n"
|
35
|
+
data += @data.each.map { |key, value| "-d \"#{key}\"=\"#{value}\"" }.join("\n")
|
36
|
+
end
|
37
|
+
|
38
|
+
"#{auth} #{@method} #{@url}#{params}#{data}#{headers}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reach
|
4
|
+
class Response
|
5
|
+
attr_accessor :status_code, :body, :headers
|
6
|
+
|
7
|
+
def initialize(status_code, body, headers: nil)
|
8
|
+
@status_code = status_code
|
9
|
+
body = '{}' if !body || body.empty?
|
10
|
+
@body = JSON.parse(body)
|
11
|
+
@headers = !headers ? {} : headers.to_hash
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"[#{status_code}] #{body}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|