Skip to content

Commit de36e0f

Browse files
Update readme (#138)
Co-authored-by: Evan Sosenko <[email protected]>
1 parent 71094da commit de36e0f

File tree

7 files changed

+951
-8
lines changed

7 files changed

+951
-8
lines changed

README.md

Lines changed: 323 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,337 @@ SDK for the Seam API written in Ruby.
77

88
## Description
99

10-
TODO
10+
[Seam](https://seam.co) makes it easy to integrate IoT devices with your applications.
11+
This is an official SDK for the Seam API.
12+
Please refer to the official [Seam Docs](https://docs.seam.co/latest/) to get started.
13+
14+
Parts of this SDK are generated from always up-to-date type information
15+
provided by [@seamapi/types](https://github.com/seamapi/types/).
16+
This ensures all API methods, request shapes, and response shapes are
17+
accurate and fully typed.
18+
19+
<!-- toc -->
20+
21+
- [Installation](#installation)
22+
- [Usage](#usage)
23+
- [Examples](#examples)
24+
- [List devices](#list-devices)
25+
- [Unlock a door](#unlock-a-door)
26+
- [Authentication Method](#authentication-method)
27+
- [API Key](#api-key)
28+
- [Personal Access Token](#personal-access-token)
29+
- [Action Attempts](#action-attempts)
30+
- [Interacting with Multiple Workspaces](#interacting-with-multiple-workspaces)
31+
- [Webhooks](#webhooks)
32+
- [Advanced Usage](#advanced-usage)
33+
- [Additional Options](#additional-options)
34+
- [Setting the endpoint](#setting-the-endpoint)
35+
- [Configuring the Faraday Client](#configuring-the-faraday-client)
36+
- [Using the Faraday Client](#using-the-faraday-client)
37+
- [Overriding the Client](#overriding-the-client)
38+
- [Development and Testing](#development-and-testing)
39+
- [Quickstart](#quickstart)
40+
- [Source code](#source-code)
41+
- [Requirements](#requirements)
42+
- [Publishing](#publishing)
43+
- [Automatic](#automatic)
44+
- [Manual](#manual)
45+
- [GitHub Actions](#github-actions)
46+
- [Secrets for Optional GitHub Actions](#secrets-for-optional-github-actions)
47+
- [Contributing](#contributing)
48+
- [License](#license)
49+
- [Warranty](#warranty)
50+
51+
<!-- tocstop -->
1152

1253
## Installation
1354

14-
Add this as a dependency to your project using [Bundler] with
55+
Add this as a dependency to your project using [Bundler] with:
1556

1657
```
1758
$ bundle add seam
1859
```
1960

20-
[bundler]: https://bundler.io/
61+
[Bundler]: https://bundler.io/
62+
63+
## Usage
64+
65+
### Examples
66+
67+
> [!NOTE]
68+
> These examples assume `SEAM_API_KEY` is set in your environment.
69+
70+
#### List devices
71+
72+
```ruby
73+
require "seam"
74+
75+
seam = Seam.new
76+
devices = seam.devices.list
77+
```
78+
79+
#### Unlock a door
80+
81+
```ruby
82+
require "seam"
83+
84+
seam = Seam.new
85+
lock = seam.locks.get(name: "Front Door")
86+
seam.locks.unlock_door(device_id: lock.device_id)
87+
```
88+
89+
### Authentication Method
90+
91+
The SDK supports API key and personal access token authentication mechanisms.
92+
Authentication may be configured by passing the corresponding options directly to the `Seam` constructor, or with the more ergonomic static factory methods.
93+
94+
#### API Key
95+
96+
An API key is scoped to a single workspace and should only be used on the server.
97+
Obtain one from the Seam Console.
98+
99+
```ruby
100+
# Set the `SEAM_API_KEY` environment variable
101+
seam = Seam.new
102+
103+
# Pass as a keyword argument to the constructor
104+
seam = Seam.new(api_key: "your-api-key")
105+
106+
# Use the factory method
107+
seam = Seam.from_api_key("your-api-key")
108+
```
109+
110+
#### Personal Access Token
111+
112+
A Personal Access Token is scoped to a Seam Console user.
113+
Obtain one from the Seam Console.
114+
A workspace ID must be provided when using this method and all requests will be scoped to that workspace.
115+
116+
```ruby
117+
# Pass as an option to the constructor
118+
seam = Seam.new(
119+
personal_access_token: "your-personal-access-token",
120+
workspace_id: "your-workspace-id"
121+
)
122+
123+
# Use the factory method
124+
seam = Seam.from_personal_access_token(
125+
"your-personal-access-token",
126+
"your-workspace-id"
127+
)
128+
```
129+
130+
### Action Attempts
131+
132+
Some asynchronous operations, e.g., unlocking a door, return an
133+
[action attempt](https://docs.seam.co/latest/core-concepts/action-attempts).
134+
Seam tracks the progress of the requested operation and updates the action attempt
135+
when it succeeds or fails.
136+
137+
To make working with action attempts more convenient for applications,
138+
this library provides the `wait_for_action_attempt` option and enables it by default.
139+
140+
When the `wait_for_action_attempt` option is enabled, the SDK:
141+
142+
- Polls the action attempt up to the `timeout`
143+
at the `polling_interval` (both in seconds).
144+
- Resolves with a fresh copy of the successful action attempt.
145+
- Raises a `Seam::ActionAttemptFailedError` if the action attempt is unsuccessful.
146+
- Raises a `Seam::ActionAttemptTimeoutError` if the action attempt is still pending when the `timeout` is reached.
147+
- Both errors expose an `action_attempt` property.
148+
149+
If you already have an action attempt ID
150+
and want to wait for it to resolve, simply use:
151+
152+
```ruby
153+
seam.action_attempts.get(action_attempt_id: action_attempt_id)
154+
```
155+
156+
Or, to get the current state of an action attempt by ID without waiting:
157+
158+
```ruby
159+
seam.action_attempts.get(
160+
action_attempt_id: action_attempt_id,
161+
wait_for_action_attempt: false
162+
)
163+
```
164+
165+
To disable this behavior, set the default option for the client:
166+
167+
```ruby
168+
seam = Seam.new(
169+
api_key: "your-api-key",
170+
wait_for_action_attempt: false
171+
)
172+
173+
seam.locks.unlock_door(device_id: device_id)
174+
```
175+
176+
or the behavior may be configured per-request:
177+
178+
```ruby
179+
seam.locks.unlock_door(
180+
device_id: device_id,
181+
wait_for_action_attempt: false
182+
)
183+
```
184+
185+
The `polling_interval` and `timeout` may be configured for the client or per-request.
186+
For example:
187+
188+
```ruby
189+
require "seam"
190+
191+
seam = Seam.new("your-api-key")
192+
193+
locks = seam.locks.list
194+
195+
if locks.empty?
196+
raise "No locks in this workspace"
197+
end
198+
199+
lock = locks.first
200+
201+
begin
202+
seam.locks.unlock_door(
203+
device_id: lock.device_id,
204+
wait_for_action_attempt: {
205+
timeout: 5.0,
206+
polling_interval: 1.0
207+
}
208+
)
209+
210+
puts "Door unlocked"
211+
rescue Seam::ActionAttemptFailedError
212+
puts "Could not unlock the door"
213+
rescue Seam::ActionAttemptTimeoutError
214+
puts "Door took too long to unlock"
215+
end
216+
```
217+
218+
### Interacting with Multiple Workspaces
219+
220+
Some Seam API endpoints interact with multiple workspaces. The `Seam::Http::SeamMultiWorkspace` client is not bound to a specific workspace and may use those endpoints with a personal access token authentication method.
221+
222+
A Personal Access Token is scoped to a Seam Console user. Obtain one from the Seam Console.
223+
224+
```ruby
225+
# Pass as an option to the constructor
226+
seam = Seam::Http::SeamMultiWorkspace.new(personal_access_token: "your-personal-access-token")
227+
228+
# Use the factory method
229+
seam = Seam::Http::SeamMultiWorkspace.from_personal_access_token("your-personal-access-token")
230+
231+
# List workspaces authorized for this Personal Access Token
232+
workspaces = seam.workspaces.list
233+
```
234+
235+
### Webhooks
236+
237+
The Seam API implements webhooks using [Svix](https://www.svix.com).This SDK exports a thin wrapper `Seam::Webhook` around the svix package.
238+
Use it to parse and validate Seam webhook events.
239+
240+
> [!TIP]
241+
> This example is for [Sinatra](https://sinatrarb.com/), see the [Svix docs for more examples in specific frameworks](https://docs.svix.com/receiving/verifying-payloads/how).
242+
243+
```ruby
244+
require "sinatra"
245+
require "seam"
246+
247+
webhook = Seam::Webhook.new(ENV["SEAM_WEBHOOK_SECRET"])
248+
249+
post "/webhook" do
250+
begin
251+
headers = {
252+
"svix-id" => request.env["HTTP_SVIX_ID"],
253+
"svix-signature" => request.env["HTTP_SVIX_SIGNATURE"],
254+
"svix-timestamp" => request.env["HTTP_SVIX_TIMESTAMP"]
255+
}
256+
data = webhook.verify(request.body.read, headers)
257+
rescue Seam::WebhookVerificationError
258+
halt 400, "Bad Request"
259+
end
260+
261+
begin
262+
store_event(data)
263+
rescue
264+
halt 500, "Internal Server Error"
265+
end
266+
267+
204
268+
end
269+
270+
def store_event(data)
271+
puts data
272+
end
273+
```
274+
275+
### Advanced Usage
276+
277+
#### Additional Options
278+
279+
In addition to the various authentication options,
280+
the constructor takes some advanced options that affect behavior.
281+
282+
```ruby
283+
seam = Seam.new(
284+
api_key: "your-api-key",
285+
endpoint: "https://example.com",
286+
faraday_options: {},
287+
faraday_retry_options: {}
288+
)
289+
```
290+
291+
When using the static factory methods,
292+
these options may be passed in as keyword arguments.
293+
294+
```ruby
295+
seam = Seam.from_api_key("some-api-key",
296+
endpoint: "https://example.com",
297+
faraday_options: {},
298+
faraday_retry_options: {})
299+
```
300+
301+
#### Setting the endpoint
302+
303+
Some contexts may need to override the API endpoint,
304+
e.g., testing or proxy setups. This option corresponds to the [Faraday](https://lostisland.github.io/faraday/#/) `url` setting.
305+
306+
Either pass the `endpoint` option, or set the `SEAM_ENDPOINT` environment variable.
307+
308+
#### Configuring the Faraday Client
309+
310+
The Faraday client and retry behavior may be configured with custom initiation options
311+
via [`faraday_option`][faraday_option] and [`faraday_retry_option`][faraday_retry_option].
312+
313+
[faraday_option]: https://lostisland.github.io/faraday/#/customization/connection-options?id=connection-options
314+
[faraday_retry_option]: https://github.com/lostisland/faraday-retry
315+
316+
#### Using the Faraday Client
317+
318+
The Faraday client is exposed and may be used or configured directly:
319+
320+
```ruby
321+
require "seam"
322+
require "faraday"
323+
324+
class MyMiddleware < Faraday::Middleware
325+
def on_complete(env)
326+
puts env.response.inspect
327+
end
328+
end
329+
330+
seam = Seam.new
331+
332+
seam.client.builder.use MyMiddleware
333+
334+
devices = seam.client.get("/devices/list").body["devices"]
335+
```
336+
337+
#### Overriding the Client
338+
339+
A Faraday compatible client may be provided to create a `Seam` instance.
340+
This API is used internally and is not directly supported.
21341

22342
## Development and Testing
23343

lib/seam.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require_relative "seam/logger"
44
require_relative "seam/http"
5+
require_relative "seam/http_multi_workspace"
56
require_relative "seam/wait_for_action_attempt"
67
require_relative "seam/webhook"
78

lib/seam/http_multi_workspace.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,7 @@ def create(**kwargs)
6666
@workspaces.create(**kwargs)
6767
end
6868
end
69+
70+
private_constant :WorkspacesProxy
6971
end
7072
end

lib/seam/webhook.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
# frozen_string_literal: true
22

33
require "svix"
4+
require_relative "base_resource"
5+
require_relative "routes/resources/event"
46

57
module Seam
8+
WebhookVerificationError = Svix::WebhookVerificationError
9+
610
class Webhook
711
def initialize(secret)
812
@webhook = Svix::Webhook.new(secret)
@@ -12,7 +16,7 @@ def verify(payload, headers)
1216
normalized_headers = headers.transform_keys(&:downcase)
1317
res = @webhook.verify(payload, normalized_headers)
1418

15-
Seam::Event.load_from_response(res)
19+
Seam::Resources::SeamEvent.load_from_response(res)
1620
end
1721
end
1822
end

0 commit comments

Comments
 (0)