Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,43 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
flag-name: ruby-${{ matrix.ruby }}-${{ matrix.protocol }}-${{ matrix.type }}
parallel: true
# Dry run of the release pipeline: the same consistency assertions release.yml's
# pre-flight makes, plus a build of both gems — so a version-file or gemspec
# regression surfaces on every PR instead of at release time. Publishes nothing.
release-dry-run:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2
with:
persist-credentials: false
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
with:
ruby-version: '3.3'
bundler-cache: false
- name: 'Versions and pin must agree (mirrors release.yml pre-flight)'
run: |
ruby <<'RUBY'
require_relative 'core/lib/ably/version'
require_relative 'server/lib/ably/pubsub/server/version'

errors = []
errors << "core Ably::VERSION (#{Ably::VERSION}) != server Ably::PubSub::Server::VERSION (#{Ably::PubSub::Server::VERSION})" unless Ably::VERSION == Ably::PubSub::Server::VERSION

server_spec = Gem::Specification.load('server/ably-pubsub-server.gemspec')
core_dep = server_spec.dependencies.find { |d| d.name == 'ably-pubsub-core' }
errors << "server gemspec pins ably-pubsub-core '#{core_dep&.requirement}', expected '= #{Ably::VERSION}'" unless core_dep&.requirement.to_s == "= #{Ably::VERSION}"

abort errors.join("\n") unless errors.empty?
puts "Version consistency OK: #{Ably::VERSION}"
RUBY
- name: Build both gems
run: |
set -euo pipefail
(cd core && gem build ably-pubsub-core.gemspec)
(cd server && gem build ably-pubsub-server.gemspec)

finish:
needs: check
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "lib/submodules/ably-common"]
path = lib/submodules/ably-common
path = core/lib/submodules/ably-common
url = https://github.com/ably/ably-common.git
2 changes: 2 additions & 0 deletions .yardopts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
core/lib/**/*.rb
server/lib/**/*.rb
41 changes: 29 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,48 @@
# Contributing

This repository hosts two gems, released in lockstep at the same version:

- [`core/`](./core) — `ably-pubsub-core`: the shared implementation. An internal package; only Ably packages depend on it.
- [`server/`](./server) — `ably-pubsub-server`: the public server-side package. Its factory functions (`Ably::PubSub::Server.create_http_client` / `.create_realtime_client`) are the only entry points: the client classes refuse direct construction, and these factories reach them through `Ably::Internal`.

## Development

1. Fork it
2. When pulling to local, make sure to also pull the `ably-common` repo (`git submodule init && git submodule update`)
3. Create your feature branch (`git checkout -b my-new-feature`)
4. Commit your changes (`git commit -am 'Add some feature'`)
5. Ensure you have added suitable tests and the test suite is passing(`bundle exec rspec`)
5. Ensure you have added suitable tests and the test suite is passing (`bundle exec rspec`) — the root `Gemfile` wires both gems up as path dependencies, so a single `bundle install` at the root covers everything
6. Push to the branch (`git push origin my-new-feature`)
7. Create a new Pull Request

---

## Release process

This library uses [semantic versioning](http://semver.org/). For each release, the following needs to be done:
This library uses [semantic versioning](http://semver.org/). `ably-pubsub-core` and `ably-pubsub-server` always release together at the same version: the release workflow refuses to publish them independently.

For each release, the following needs to be done:

1. Create a branch for the release, named like `release/1.2.3` (where `1.2.3` is the new version number)
2. Update the version number in [version.rb](./lib/ably/version.rb) and commit the change.
1. Create a branch for the release, named like `release/2.0.1` (where `2.0.1` is the new version number)
2. Update the version number in **all three places**, which must agree (the release workflow's pre-flight enforces this):
- `Ably::VERSION` in [core/lib/ably/version.rb](./core/lib/ably/version.rb)
- `Ably::PubSub::Server::VERSION` in [server/lib/ably/pubsub/server/version.rb](./server/lib/ably/pubsub/server/version.rb)
- the exact-version `ably-pubsub-core` pin in [server/ably-pubsub-server.gemspec](./server/ably-pubsub-server.gemspec) (derived from the version constant, so it normally follows automatically)
3. Run [`github_changelog_generator`](https://github.com/github-changelog-generator/github-changelog-generator) to automate the update of the [CHANGELOG](./CHANGELOG.md). This may require some manual intervention, both in terms of how the command is run and how the change log file is modified. Your mileage may vary:
- The command you will need to run will look something like this: `github_changelog_generator -u ably -p ably-pubsub-ruby --since-tag v1.2.3 --output delta.md --token $GITHUB_TOKEN_WITH_REPO_ACCESS`. Generate token [here](https://github.com/settings/tokens/new?description=GitHub%20Changelog%20Generator%20token).
- The command you will need to run will look something like this: `github_changelog_generator -u ably -p ably-pubsub-ruby --since-tag v2.0.0 --output delta.md --token $GITHUB_TOKEN_WITH_REPO_ACCESS`. Generate token [here](https://github.com/settings/tokens/new?description=GitHub%20Changelog%20Generator%20token).
- Using the command above, `--output delta.md` writes changes made after `--since-tag` to a new file
- The contents of that new file (`delta.md`) then need to be manually inserted at the top of the `CHANGELOG.md`, changing the "Unreleased" heading and linking with the current version numbers
- Also ensure that the "Full Changelog" link points to the new version tag instead of the `HEAD`
4. Commit this change: `git add CHANGELOG.md && git commit -m "Update change log."`
5. Ideally, run `rake doc:spec` to generate a new [spec file](./SPEC.md). Then commit these changes.
6. Make a PR against `main`. Once the PR is approved, merge it into `main`.
7. Add a tag to the new `main` head commit and push to origin such as `git tag v1.0.3 && git push origin v1.0.3`.
8. Visit [https://github.com/ably/ably-pubsub-ruby/tags](https://github.com/ably/ably-pubsub-ruby/tags) and `Add release notes` for the release including links to the changelog entry.
9. Run `rake release` to publish the gem to [Rubygems](https://rubygems.org/gems/ably).
10. Release the [REST-only library `ably-ruby-rest`](https://github.com/ably/ably-ruby-rest#release-process).
11. Create the entry on the [Ably Changelog](https://changelog.ably.com/) (via [headwayapp](https://headwayapp.co/)).
7. Add a tag to the new `main` head commit and push to origin such as `git tag v2.0.1 && git push origin v2.0.1`.
8. Visit [the tags page](https://github.com/ably/ably-pubsub-ruby/tags) and `Add release notes` for the release including links to the changelog entry.
9. Run the [Release workflow](./.github/workflows/release.yml) (Actions → Release → Run workflow) with the version number. It publishes `ably-pubsub-core` and then `ably-pubsub-server` to RubyGems via trusted publishing — no local credentials involved. A failed run is safe to re-run with the same version: already-published gems are skipped.
10. Create the entry on the [Ably Changelog](https://changelog.ably.com/) (via [headwayapp](https://headwayapp.co/)).

### Trusted publishing

The workflow authenticates to RubyGems with [trusted publishing](https://guides.rubygems.org/trusted-publishing/) (GitHub OIDC): both gems have a Trusted Publisher configured on rubygems.org bound to this repository and `.github/workflows/release.yml`. There are no long-lived RubyGems API keys anywhere. If the repository is renamed, both bindings must be reconfigured on rubygems.org or publishing fails.

### The legacy `ably` gem

The `ably` gem is in its maintenance window (security and critical fixes only, released from the maintenance branch) and is **not** released from `main`. The `ably-rest` gem (from the `ably-ruby-rest` repo) is likewise in maintenance and no longer part of this release process.
7 changes: 5 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in ably.gemspec
gemspec
# This repo hosts two gems, released in lockstep at the same version (PDR-091b):
# - core/ ably-pubsub-core: the shared implementation (internal package)
# - server/ ably-pubsub-server: the public server-side package (factory entry points)
gemspec path: 'core'
gemspec path: 'server'
5 changes: 5 additions & 0 deletions INTRO.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Ably `Ruby` Client Library SDK API Reference

> **Using the `ably` gem?** This is the API reference for the `ably-pubsub-server` gem, which replaces it.
> The API reference for the maintenance-only `ably` gem remains available at
> [sdk.ably.com/builds/ably/ably-ruby/main/docs](https://sdk.ably.com/builds/ably/ably-ruby/main/docs/)
> until its end of life.

The `Ruby` Client Library SDK supports a realtime and a REST interface.

The realtime interface enables a client to maintain a persistent connection to Ably and publish, subscribe and be present on channels.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ To use the Ably Realtime SDK in Ruby, the `EventMachine` reactor loop must be ru
Wrap your code inside a `EventMachine.run` block:

```ruby
require 'ably'
require 'ably/pubsub/server'

EventMachine.run do
client = Ably::Realtime.new(key: 'your-api-key')
client = Ably::PubSub::Server.create_realtime_client(key: 'your-api-key')

client.connection.connect do
puts "Connected with connection ID: #{client.connection.id}"
Expand All @@ -87,7 +87,7 @@ The following code connects to Ably's realtime messaging service, subscribes to

```ruby
# Initialize Ably Realtime client
realtime_client = Ably::Realtime.new(key: 'your-ably-api-key', client_id: 'me')
realtime_client = Ably::PubSub::Server.create_realtime_client(key: 'your-ably-api-key', client_id: 'me')

# Wait for connection to be established
realtime_client.connection.on(:connected) do
Expand Down
6 changes: 3 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'rubygems'
require 'bundler/setup'
require 'bundler/gem_tasks'

require 'json'

require 'yard'
Expand Down Expand Up @@ -32,8 +32,8 @@ begin

desc 'Generate error code constants from ably-common: https://github.com/ably/ably-common/issues/32'
task :generate_error_codes do
errors_json_path = File.join(File.dirname(__FILE__), 'lib/submodules/ably-common/protocol/errors.json')
module_path = File.join(File.dirname(__FILE__), 'lib/ably/modules/exception_codes.rb')
errors_json_path = File.join(File.dirname(__FILE__), 'core/lib/submodules/ably-common/protocol/errors.json')
module_path = File.join(File.dirname(__FILE__), 'core/lib/ably/modules/exception_codes.rb')
max_length = 0

errors = JSON.parse(File.read(errors_json_path)).each_with_object({}) do |(key, val), hash|
Expand Down
178 changes: 178 additions & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,183 @@
# Upgrade / Migration Guide

## Version 1.x (`ably` gem) to 2.0.0 (`ably-pubsub-server` gem)

### Gemfile

```ruby
# 1.x
gem 'ably'

# 2.0
gem 'ably-pubsub-server'
```

### Require

```ruby
# 1.x
require 'ably'

# 2.0
require 'ably/pubsub/server'
```

### HTTP client

```ruby
# 1.x
client = Ably::Rest::Client.new(key: ENV['ABLY_API_KEY'])
client = Ably::Rest::Client.new('key.id:secret')
client = Ably::Rest::Client.new(token: 'token')
client = Ably::Rest.new(key: ENV['ABLY_API_KEY'])

# 2.0
client = Ably::PubSub::Server.create_http_client(key: ENV['ABLY_API_KEY'])
client = Ably::PubSub::Server.create_http_client('key.id:secret')
client = Ably::PubSub::Server.create_http_client(token: 'token')
```

### Realtime client

```ruby
# 1.x
client = Ably::Realtime::Client.new(key: ENV['ABLY_API_KEY'])
client = Ably::Realtime::Client.new('key.id:secret')
client = Ably::Realtime.new(key: ENV['ABLY_API_KEY'])

# 2.0
client = Ably::PubSub::Server.create_realtime_client(key: ENV['ABLY_API_KEY'])
client = Ably::PubSub::Server.create_realtime_client('key.id:secret')
```

Both factories accept an options `Hash`, an API key `String`, or a token `String`.

`Ably::PubSub::Http::Client.new` and `Ably::PubSub::Realtime::Client.new` raise
`Ably::Exceptions::DirectConstructionNotSupported`.

### Namespaces

| 1.x | 2.0 |
| --- | --- |
| `Ably::Rest::*` | `Ably::PubSub::Http::*` |
| `Ably::Realtime::*` | `Ably::PubSub::Realtime::*` |

```ruby
# 1.x
client.is_a?(Ably::Rest::Client)
Ably::Rest::Client::MAX_MESSAGE_SIZE
Ably::Realtime::Channel::STATE.Attached
Ably::Realtime::Connection::STATE.Connected

# 2.0
client.is_a?(Ably::PubSub::Http::Client)
Ably::PubSub::Http::Client::MAX_MESSAGE_SIZE
Ably::PubSub::Realtime::Channel::STATE.Attached
Ably::PubSub::Realtime::Connection::STATE.Connected
```

`Ably::Rest` and `Ably::Realtime` raise `NameError` in 2.0.

### Connection options: `endpoint`

The `:endpoint` option is now the only way to choose where a client connects. The
`:environment`, `:rest_host`, `:realtime_host` (and its alias `:ws_host`) and
`:fallback_hosts_use_default` options have been removed and are ignored, so a client that
still passes them connects to production (`main.realtime.ably.net`).

| 1.x | 2.0 |
| --- | --- |
| no host options | no change; traffic moves to `main.realtime.ably.net` |
| `environment: 'sandbox'` | `endpoint: 'nonprod:sandbox'` |
| `environment: 'acme'` (dedicated cluster) | `endpoint: 'acme'` |
| `rest_host:` / `realtime_host: 'localhost'` | `endpoint: 'localhost'` |
| `rest_host` and `realtime_host` set to the same custom host | `endpoint: '<that host>'` |
| `rest_host` and `realtime_host` set to different hosts | not supported; use one host that serves both, or contact Ably |
| `fallback_hosts_use_default: true` | delete it |
| custom `fallback_hosts` | unchanged |

```ruby
# 1.x
client = Ably::Rest::Client.new(key: ENV['ABLY_API_KEY'], environment: 'sandbox')

# 2.0
client = Ably::PubSub::Server.create_http_client(key: ENV['ABLY_API_KEY'], endpoint: 'nonprod:sandbox')
```

REST requests and the realtime connection now use the same primary domain:

| `endpoint` | Primary domain | Default fallback hosts |
| --- | --- | --- |
| unset | `main.realtime.ably.net` | `main.[a-e].fallback.ably-realtime.com` |
| `'acme'` | `acme.realtime.ably.net` | `acme.[a-e].fallback.ably-realtime.com` |
| `'nonprod:sandbox'` | `sandbox.realtime.ably-nonprod.net` | `sandbox.[a-e].fallback.ably-realtime-nonprod.com` |
| a hostname, such as `'localhost'`, `'127.0.0.1'` or `'ably.example.com'` | the hostname | none |

A custom `port` or `tls_port` no longer disables the default fallback hosts; pass
`fallback_hosts: []` to disable them.

Firewall allowlists and proxies must allow `*.realtime.ably.net` and
`*.fallback.ably-realtime.com` in place of `rest.ably.io`, `realtime.ably.io` and
`*.ably-realtime.com`.

Related client attributes:

| 1.x | 2.0 |
| --- | --- |
| `client.endpoint` (a `URI`) | `client.uri`; `client.endpoint` now returns the `:endpoint` option |
| `client.environment` | `client.endpoint` |
| `client.custom_host`, `realtime_client.custom_realtime_host` | `client.primary_domain` |
| `Ably::PubSub::Http::Client::DOMAIN`, `Ably::PubSub::Realtime::Client::DOMAIN` | `client.primary_domain` |
| `Ably::CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES` | removed |

### Releasing realtime channels

In 1.x, `Channels#release` detached a realtime channel before removing it. In 2.0, a realtime channel can only be released
when it is in the `initialized`, `detached` or `failed` state. Releasing it in any other state raises
`Ably::Exceptions::InvalidState` with code `90011`, and the channel is left in place. Detach the channel and wait for the
detach to complete before releasing it:

```ruby
# 1.x
client.channels.release('example')

# 2.0
client.channels.get('example').detach do
client.channels.release('example')
end
```

`Ably::PubSub::Http::Channels#release` is unchanged.

### Unchanged

Everything after construction:

```ruby
client = Ably::PubSub::Server.create_http_client(key: ENV['ABLY_API_KEY'])

channel = client.channels.get('example')
channel.publish 'event', 'payload'
channel.history
channel.presence.get

client.auth.request_token
client.stats
client.time
```

```ruby
client = Ably::PubSub::Server.create_realtime_client(key: ENV['ABLY_API_KEY'])

client.connection.on(:connected) { }

channel = client.channels.get('example')
channel.attach
channel.subscribe { |message| }
channel.publish 'event', 'payload'
channel.presence.enter
```

## Version 1.1.8 to 1.2.0

### Notable Changes
Expand Down
20 changes: 20 additions & 0 deletions core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ably-pubsub-core

Internal implementation package for Ably's own Pub/Sub Ruby packages.

**This gem is not intended for direct external use.** It is published only so that
Ably's public packages can depend on it, and it constructs nothing on its own:
`Ably::PubSub::Http::Client.new` and `Ably::PubSub::Realtime::Client.new` raise
`Ably::Exceptions::DirectConstructionNotSupported`. The package a client is created from
is what declares its side to the platform, and a directly constructed client declares
none. Ably's per-side packages construct clients through `Ably::Internal`, which is not
public API.

Use [`ably-pubsub-server`](../server) instead, which exposes the supported entry points:

```ruby
client = Ably::PubSub::Server.create_http_client(key) # stateless HTTP client
client = Ably::PubSub::Server.create_realtime_client(key) # stateful realtime client
```

`ably-pubsub-core` and `ably-pubsub-server` are released in lockstep at the same version.
Loading
Loading