Skip to content

Commit

Permalink
Update readme (#138)
Browse files Browse the repository at this point in the history
Co-authored-by: Evan Sosenko <[email protected]>
  • Loading branch information
andrii-balitskyi and razor-x authored Oct 31, 2024
1 parent 71094da commit de36e0f
Show file tree
Hide file tree
Showing 7 changed files with 951 additions and 8 deletions.
326 changes: 323 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,337 @@ SDK for the Seam API written in Ruby.

## Description

TODO
[Seam](https://seam.co) makes it easy to integrate IoT devices with your applications.
This is an official SDK for the Seam API.
Please refer to the official [Seam Docs](https://docs.seam.co/latest/) to get started.

Parts of this SDK are generated from always up-to-date type information
provided by [@seamapi/types](https://github.com/seamapi/types/).
This ensures all API methods, request shapes, and response shapes are
accurate and fully typed.

<!-- toc -->

- [Installation](#installation)
- [Usage](#usage)
- [Examples](#examples)
- [List devices](#list-devices)
- [Unlock a door](#unlock-a-door)
- [Authentication Method](#authentication-method)
- [API Key](#api-key)
- [Personal Access Token](#personal-access-token)
- [Action Attempts](#action-attempts)
- [Interacting with Multiple Workspaces](#interacting-with-multiple-workspaces)
- [Webhooks](#webhooks)
- [Advanced Usage](#advanced-usage)
- [Additional Options](#additional-options)
- [Setting the endpoint](#setting-the-endpoint)
- [Configuring the Faraday Client](#configuring-the-faraday-client)
- [Using the Faraday Client](#using-the-faraday-client)
- [Overriding the Client](#overriding-the-client)
- [Development and Testing](#development-and-testing)
- [Quickstart](#quickstart)
- [Source code](#source-code)
- [Requirements](#requirements)
- [Publishing](#publishing)
- [Automatic](#automatic)
- [Manual](#manual)
- [GitHub Actions](#github-actions)
- [Secrets for Optional GitHub Actions](#secrets-for-optional-github-actions)
- [Contributing](#contributing)
- [License](#license)
- [Warranty](#warranty)

<!-- tocstop -->

## Installation

Add this as a dependency to your project using [Bundler] with
Add this as a dependency to your project using [Bundler] with:

```
$ bundle add seam
```

[bundler]: https://bundler.io/
[Bundler]: https://bundler.io/

## Usage

### Examples

> [!NOTE]
> These examples assume `SEAM_API_KEY` is set in your environment.
#### List devices

```ruby
require "seam"

seam = Seam.new
devices = seam.devices.list
```

#### Unlock a door

```ruby
require "seam"

seam = Seam.new
lock = seam.locks.get(name: "Front Door")
seam.locks.unlock_door(device_id: lock.device_id)
```

### Authentication Method

The SDK supports API key and personal access token authentication mechanisms.
Authentication may be configured by passing the corresponding options directly to the `Seam` constructor, or with the more ergonomic static factory methods.

#### API Key

An API key is scoped to a single workspace and should only be used on the server.
Obtain one from the Seam Console.

```ruby
# Set the `SEAM_API_KEY` environment variable
seam = Seam.new

# Pass as a keyword argument to the constructor
seam = Seam.new(api_key: "your-api-key")

# Use the factory method
seam = Seam.from_api_key("your-api-key")
```

#### Personal Access Token

A Personal Access Token is scoped to a Seam Console user.
Obtain one from the Seam Console.
A workspace ID must be provided when using this method and all requests will be scoped to that workspace.

```ruby
# Pass as an option to the constructor
seam = Seam.new(
personal_access_token: "your-personal-access-token",
workspace_id: "your-workspace-id"
)

# Use the factory method
seam = Seam.from_personal_access_token(
"your-personal-access-token",
"your-workspace-id"
)
```

### Action Attempts

Some asynchronous operations, e.g., unlocking a door, return an
[action attempt](https://docs.seam.co/latest/core-concepts/action-attempts).
Seam tracks the progress of the requested operation and updates the action attempt
when it succeeds or fails.

To make working with action attempts more convenient for applications,
this library provides the `wait_for_action_attempt` option and enables it by default.

When the `wait_for_action_attempt` option is enabled, the SDK:

- Polls the action attempt up to the `timeout`
at the `polling_interval` (both in seconds).
- Resolves with a fresh copy of the successful action attempt.
- Raises a `Seam::ActionAttemptFailedError` if the action attempt is unsuccessful.
- Raises a `Seam::ActionAttemptTimeoutError` if the action attempt is still pending when the `timeout` is reached.
- Both errors expose an `action_attempt` property.

If you already have an action attempt ID
and want to wait for it to resolve, simply use:

```ruby
seam.action_attempts.get(action_attempt_id: action_attempt_id)
```

Or, to get the current state of an action attempt by ID without waiting:

```ruby
seam.action_attempts.get(
action_attempt_id: action_attempt_id,
wait_for_action_attempt: false
)
```

To disable this behavior, set the default option for the client:

```ruby
seam = Seam.new(
api_key: "your-api-key",
wait_for_action_attempt: false
)

seam.locks.unlock_door(device_id: device_id)
```

or the behavior may be configured per-request:

```ruby
seam.locks.unlock_door(
device_id: device_id,
wait_for_action_attempt: false
)
```

The `polling_interval` and `timeout` may be configured for the client or per-request.
For example:

```ruby
require "seam"

seam = Seam.new("your-api-key")

locks = seam.locks.list

if locks.empty?
raise "No locks in this workspace"
end

lock = locks.first

begin
seam.locks.unlock_door(
device_id: lock.device_id,
wait_for_action_attempt: {
timeout: 5.0,
polling_interval: 1.0
}
)

puts "Door unlocked"
rescue Seam::ActionAttemptFailedError
puts "Could not unlock the door"
rescue Seam::ActionAttemptTimeoutError
puts "Door took too long to unlock"
end
```

### Interacting with Multiple Workspaces

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.

A Personal Access Token is scoped to a Seam Console user. Obtain one from the Seam Console.

```ruby
# Pass as an option to the constructor
seam = Seam::Http::SeamMultiWorkspace.new(personal_access_token: "your-personal-access-token")

# Use the factory method
seam = Seam::Http::SeamMultiWorkspace.from_personal_access_token("your-personal-access-token")

# List workspaces authorized for this Personal Access Token
workspaces = seam.workspaces.list
```

### Webhooks

The Seam API implements webhooks using [Svix](https://www.svix.com).This SDK exports a thin wrapper `Seam::Webhook` around the svix package.
Use it to parse and validate Seam webhook events.

> [!TIP]
> 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).
```ruby
require "sinatra"
require "seam"

webhook = Seam::Webhook.new(ENV["SEAM_WEBHOOK_SECRET"])

post "/webhook" do
begin
headers = {
"svix-id" => request.env["HTTP_SVIX_ID"],
"svix-signature" => request.env["HTTP_SVIX_SIGNATURE"],
"svix-timestamp" => request.env["HTTP_SVIX_TIMESTAMP"]
}
data = webhook.verify(request.body.read, headers)
rescue Seam::WebhookVerificationError
halt 400, "Bad Request"
end

begin
store_event(data)
rescue
halt 500, "Internal Server Error"
end

204
end

def store_event(data)
puts data
end
```

### Advanced Usage

#### Additional Options

In addition to the various authentication options,
the constructor takes some advanced options that affect behavior.

```ruby
seam = Seam.new(
api_key: "your-api-key",
endpoint: "https://example.com",
faraday_options: {},
faraday_retry_options: {}
)
```

When using the static factory methods,
these options may be passed in as keyword arguments.

```ruby
seam = Seam.from_api_key("some-api-key",
endpoint: "https://example.com",
faraday_options: {},
faraday_retry_options: {})
```

#### Setting the endpoint

Some contexts may need to override the API endpoint,
e.g., testing or proxy setups. This option corresponds to the [Faraday](https://lostisland.github.io/faraday/#/) `url` setting.

Either pass the `endpoint` option, or set the `SEAM_ENDPOINT` environment variable.

#### Configuring the Faraday Client

The Faraday client and retry behavior may be configured with custom initiation options
via [`faraday_option`][faraday_option] and [`faraday_retry_option`][faraday_retry_option].

[faraday_option]: https://lostisland.github.io/faraday/#/customization/connection-options?id=connection-options
[faraday_retry_option]: https://github.com/lostisland/faraday-retry

#### Using the Faraday Client

The Faraday client is exposed and may be used or configured directly:

```ruby
require "seam"
require "faraday"

class MyMiddleware < Faraday::Middleware
def on_complete(env)
puts env.response.inspect
end
end

seam = Seam.new

seam.client.builder.use MyMiddleware

devices = seam.client.get("/devices/list").body["devices"]
```

#### Overriding the Client

A Faraday compatible client may be provided to create a `Seam` instance.
This API is used internally and is not directly supported.

## Development and Testing

Expand Down
1 change: 1 addition & 0 deletions lib/seam.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require_relative "seam/logger"
require_relative "seam/http"
require_relative "seam/http_multi_workspace"
require_relative "seam/wait_for_action_attempt"
require_relative "seam/webhook"

Expand Down
2 changes: 2 additions & 0 deletions lib/seam/http_multi_workspace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,7 @@ def create(**kwargs)
@workspaces.create(**kwargs)
end
end

private_constant :WorkspacesProxy
end
end
6 changes: 5 additions & 1 deletion lib/seam/webhook.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# frozen_string_literal: true

require "svix"
require_relative "base_resource"
require_relative "routes/resources/event"

module Seam
WebhookVerificationError = Svix::WebhookVerificationError

class Webhook
def initialize(secret)
@webhook = Svix::Webhook.new(secret)
Expand All @@ -12,7 +16,7 @@ def verify(payload, headers)
normalized_headers = headers.transform_keys(&:downcase)
res = @webhook.verify(payload, normalized_headers)

Seam::Event.load_from_response(res)
Seam::Resources::SeamEvent.load_from_response(res)
end
end
end
Loading

0 comments on commit de36e0f

Please sign in to comment.