diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index 8a492dc6e..922cfdfd6 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -36,13 +36,13 @@ jobs:
go-version: ${{ matrix.go-version }}
- name: Download Packages
- run: go get -t -v ./ably/...
+ run: go get -t -v ./...
- name: Format
run: if [ "$(gofmt -l . | wc -l)" -gt 0 ]; then exit 1; fi
- name: Vet
- run: go vet ./ably/... ./scripts/...
+ run: go vet ./...
- name: Ensure generated code is up-to-date
run: scripts/ci-generate.sh
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index db2f31dd5..69138cc5e 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -45,7 +45,7 @@ jobs:
BASE_URL: ${{ steps.sdk-upload-prempt.outputs.url-base }}
run: >
gopages
- -source-link "https://github.com/ably/ably-go/blob/${GITHUB_SHA}/ably/{{slice .Path 5}}{{if .Line}}#L{{.Line}}{{end}}"
+ -source-link "https://github.com/ably/ably-go/blob/${GITHUB_SHA}/{{.Path}}{{if .Line}}#L{{.Line}}{{end}}"
-brand-description "Go client library for Ably realtime messaging service."
-brand-title "Ably Go SDK"
-base "${BASE_URL}../godoc"
diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
index 9cd693763..6b0343fab 100644
--- a/.github/workflows/integration-test.yml
+++ b/.github/workflows/integration-test.yml
@@ -35,7 +35,7 @@ jobs:
go-version: ${{ matrix.go-version }}
- name: Download Packages
- run: go get -t -v ./ably/...
+ run: go get -t -v ./...
- name: Install go-junit-report
run: go install github.com/jstemmer/go-junit-report@latest
diff --git a/README.md b/README.md
index f27eaae4f..ca1c9279d 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@

-[](https://pkg.go.dev/github.com/ably/ably-go/ably)
+[](https://pkg.go.dev/github.com/ably/ably-pubsub-go/server/pubsub)
[](https://github.com/ably/ably-go/blob/main/LICENSE)
---
@@ -40,9 +40,29 @@ Ably aims to support a wide range of platforms. If you experience any compatibil
To get started with your project, install the package:
```bash
-~ $ go get -u github.com/ably/ably-go/ably
+~ $ go get -u github.com/ably/ably-pubsub-go/server/pubsub # or .../device/pubsub
```
+> [!NOTE]
+> The module is being renamed from `github.com/ably/ably-go` to
+> `github.com/ably/ably-pubsub-go` for v2. Until the repository move lands,
+> this path does not resolve and `go get` will fail; build against a local
+> checkout with a `replace` directive in the meantime.
+
+The SDK has two entry points, and which one you import depends on where your
+code runs:
+
+| Import path | Use it for |
+| --- | --- |
+| `github.com/ably/ably-pubsub-go/server/pubsub` | Trusted environments that authenticate with an API key. Connections are exempt from monthly-active-user counting. Offers both an HTTP client and a realtime client. |
+| `github.com/ably/ably-pubsub-go/device/pubsub` | Applications on end-user devices, identified by a `clientId` and counted on accounts with monthly-active-user billing. Offers a realtime client. |
+
+Both packages are named `pubsub`, after the last element of their import path,
+so only the import path differs between them and the call site reads the same
+either way. Each entry point exposes the whole API it needs — channels,
+messages, presence, options, errors — so an application imports one of them
+and nothing else.
+
---
## Usage
@@ -50,14 +70,20 @@ To get started with your project, install the package:
The following code connects to Ably's realtime messaging service, subscribes to a channel to receive messages, and publishes a test message to that same channel:
```go
-// Initialize Ably Realtime client
-client, err := ably.NewRealtime(
- ably.WithKey("your-ably-api-key"),
- ably.WithClientID("me"),
+import "github.com/ably/ably-pubsub-go/server/pubsub"
+
+// Initialize an Ably realtime client
+client, err := pubsub.NewRealtimeClient(
+ pubsub.WithKey("your-ably-api-key"),
+ pubsub.WithClientID("me"),
+)
+if err != nil {
+ return err
+}
// Wait for connection to be established
-ch := make(chan ably.ConnectionStateChange, 1)
-client.Connection.On(ably.ConnectionEventConnected, func(change ably.ConnectionStateChange) {
+ch := make(chan pubsub.ConnectionStateChange, 1)
+client.Connection.On(pubsub.ConnectionEventConnected, func(change pubsub.ConnectionStateChange) {
ch <- change
})
<-ch
@@ -67,16 +93,29 @@ fmt.Println("Connected to Ably")
channel := client.Channels.Get("test-channel")
// Subscribe to all messages published to this channel
-channel.SubscribeAll(context.Background(), func(msg *ably.Message) {
+channel.SubscribeAll(context.Background(), func(msg *pubsub.Message) {
fmt.Printf("Received message: %s\n", msg.Data)
})
// Publish a test message to the channel
channel.Publish(context.Background(), "test-event", "hello world")
+```
-}
+On an end-user device, import `github.com/ably/ably-pubsub-go/device/pubsub`
+instead, and authenticate with tokens issued by your own server. Never embed an
+API key in an application that runs on an end-user device:
+
+```go
+import "github.com/ably/ably-pubsub-go/device/pubsub"
+
+client, err := pubsub.NewClient(
+ pubsub.WithAuthURL("https://your-server.example.com/ably/token"),
+ pubsub.WithClientID("user-123"),
+)
```
+The rest of the code above is the same.
+
---
## Proxy
@@ -98,7 +137,7 @@ The `proxy.example.com` is the domain or IP address of your proxy server and `80
Include the protocol (`http` or `https`) in the proxy URL. If your proxy requires authentication, you can include the username and password in the URL, for example: `http://username:password@proxy.example.com:8080`.
-After setting the environment variables, the `ably-go` SDK will route its traffic through the specified proxy for both Rest and Realtime clients.
+After setting the environment variables, the `ably-go` SDK will route its traffic through the specified proxy for both HTTP and realtime clients.
For more details on environment variable configurations in Go, see [ Go documentation on http.ProxyFromEnvironment](https://golang.org/pkg/net/http/#ProxyFromEnvironment).
@@ -109,10 +148,10 @@ For more details on environment variable configurations in Go, see [ Go document
Set up proxy via custom http client details.
-For Rest client, you can also set proxy by providing custom http client option `ably.WithHTTPClient`:
+For the HTTP client, you can also set a proxy by providing the custom http client option `pubsub.WithHTTPClient`:
```go
-ably.WithHTTPClient(&http.Client{
+pubsub.WithHTTPClient(&http.Client{
Transport: &http.Transport{
Proxy: proxy // custom proxy implementation
},
diff --git a/UPDATING.md b/UPDATING.md
index 979b59860..c5f47bdc5 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -1,5 +1,253 @@
# Upgrade / Migration Guide
+## Version 1.4.x to 2.0
+
+### The module is now `github.com/ably/ably-pubsub-go`
+
+v2 ships under a new module path. Add the entry point your application uses
+(see the next section) and drop the old requirement:
+
+```bash
+go get github.com/ably/ably-pubsub-go/server/pubsub # or .../device/pubsub
+go mod edit -droprequire github.com/ably/ably-go
+```
+
+`go mod edit -droprequire` does not rewrite your imports. Replace each import of
+`github.com/ably/ably-go/ably` with one of the two packages below, and change the
+`ably.` qualifiers to `pubsub.`.
+
+### The `ably` package is replaced by two entry points
+
+`github.com/ably/ably-go/ably` no longer exists. The implementation is internal
+to the module, and an application imports one of two packages instead,
+depending on where it runs:
+
+| Import path | Use it for |
+| --- | --- |
+| `github.com/ably/ably-pubsub-go/server/pubsub` | Trusted environments that authenticate with an API key. Connections are exempt from monthly-active-user counting. Offers `HTTPClient` and `RealtimeClient`. |
+| `github.com/ably/ably-pubsub-go/device/pubsub` | Applications on end-user devices, identified by a `clientId` and counted on accounts with monthly-active-user billing. Offers `Client`. |
+
+Both packages are named `pubsub`, so only the import path differs between them.
+Each one re-exports the whole API it needs — channels, messages, presence,
+options, errors — so an application imports one of them and nothing else. The
+re-exports are type aliases, so the types are the same across both packages:
+the `Message` of one is the `Message` of the other.
+
+```go
+// Before
+import "github.com/ably/ably-go/ably"
+
+client, err := ably.NewRealtime(ably.WithKey(key))
+channel := client.Channels.Get("some-channel")
+channel.SubscribeAll(ctx, func(msg *ably.Message) { ... })
+
+// After, on a server
+import "github.com/ably/ably-pubsub-go/server/pubsub"
+
+client, err := pubsub.NewRealtimeClient(pubsub.WithKey(key))
+channel := client.Channels.Get("some-channel")
+channel.SubscribeAll(ctx, func(msg *pubsub.Message) { ... })
+
+// After, on a device
+import "github.com/ably/ably-pubsub-go/device/pubsub"
+
+client, err := pubsub.NewClient(
+ pubsub.WithAuthURL("https://your-server.example.com/ably/token"),
+ pubsub.WithClientID("user-123"),
+)
+```
+
+Never embed an API key in an application that runs on an end-user device:
+authenticate it with tokens issued by your own server, as above.
+
+Differences to be aware of:
+
+- The `device` entry point omits the part of the API that only an HTTP client
+ can reach:
+ - `HTTPClient` and `NewHTTPClient`, `HTTPChannels`, `HTTPChannel`,
+ `HTTPPresence`, `HTTPRequest`, `HTTPPaginatedItems` and
+ `HTTPPaginatedResponse`;
+ - the `Request` options (`RequestOption`, `RequestWith…`);
+ - the presence `Get` and presence history options (`GetPresence…`,
+ `PresenceHistory…`), `PresenceRequest` and the presence paginated results;
+ - the channel status types (`ChannelDetails`, `ChannelStatus`,
+ `ChannelOccupancy`, `ChannelMetrics`);
+ - the HTTP-only publish options (`PublishWithParams`,
+ `PublishWithConnectionKey` and related).
+
+ A device `Client` still has `Stats` and `Time`, and the HTTP options that
+ apply to the requests it makes (`WithHTTPClient`, `WithHTTPRequestTimeout`,
+ `WithIdempotentHTTPPublishing`).
+- The LiveObjects names of the old `ably/objects` package are re-exported with
+ an `Objects` prefix, so that `objects.Message` and `ably.Message` can live in
+ one package: `objects.Plugin` is now `pubsub.ObjectsPlugin`, `objects.Map` is
+ `pubsub.ObjectsMap`, and so on. The constants also drop their underscore:
+ `objects.Operation_MapSet` is now `pubsub.ObjectsOperationMapSet`, and
+ `objects.Map_LWW` is `pubsub.ObjectsMapLWW`.
+- `ably.WithDial` is not re-exported. Its signature names an unexported type,
+ so no caller outside the implementation package could ever have supplied a
+ usable argument to it.
+
+The re-exports (`device/pubsub/api.go`, `server/pubsub/api.go`) are written by
+hand. `server/pubsub/reexport_test.go` fails when `internal/ably` exports a
+name that an entry point neither re-exports nor lists as deliberately left out.
+
+### The `REST` prefix is now `HTTP`
+
+The `REST` prefix has been removed from the public API. Every exported identifier that
+named the stateless, HTTP-based client now uses an `HTTP` prefix instead, so that the
+Go type names describe the transport rather than the Ably REST API they talk to.
+
+These are **breaking changes**: the old names have been removed outright rather than kept
+as deprecated aliases. The new names are exported from
+`github.com/ably/ably-pubsub-go/server/pubsub`.
+
+#### Renamed types
+
+| Before | After |
+| --- | --- |
+| `ably.REST` | `pubsub.HTTPClient` |
+| `ably.RESTChannels` | `pubsub.HTTPChannels` |
+| `ably.RESTChannel` | `pubsub.HTTPChannel` |
+| `ably.RESTPresence` | `pubsub.HTTPPresence` |
+| `ably.RESTRequest` | `pubsub.HTTPRequest` |
+| `ably.RESTPaginatedItems` | `pubsub.HTTPPaginatedItems` |
+
+#### Renamed functions
+
+| Before | After |
+| --- | --- |
+| `ably.NewREST` | `pubsub.NewHTTPClient` |
+| `ably.WithIdempotentRESTPublishing` | `pubsub.WithIdempotentHTTPPublishing` |
+
+#### Example
+
+```go
+// Before
+import "github.com/ably/ably-go/ably"
+
+client, err := ably.NewREST(ably.WithKey(key))
+var channel *ably.RESTChannel = client.Channels.Get("some-channel")
+
+// After
+import "github.com/ably/ably-pubsub-go/server/pubsub"
+
+client, err := pubsub.NewHTTPClient(pubsub.WithKey(key))
+var channel *pubsub.HTTPChannel = client.Channels.Get("some-channel")
+```
+
+### `Channels.Release` no longer detaches the channel
+
+`Channels.Release` no longer takes a `context.Context`, and it no longer
+detaches the channel for you (RTS4c-e). A channel can be released only in the
+`INITIALIZED`, `DETACHED` or `FAILED` state. In any other state, `Release`
+returns an `ErrorInfo` with code `90011` (status code 400) and leaves the
+channel as it is. Detach the channel and wait for that to finish first:
+
+```go
+// Before
+err := client.Channels.Release(ctx, "some-channel")
+
+// After
+if err := client.Channels.Get("some-channel").Detach(ctx); err != nil {
+ return err
+}
+err := client.Channels.Release("some-channel")
+```
+
+### Requests identify as `ably-pubsub-go`
+
+The `Ably-Agent` header now starts with `ably-pubsub-go/` instead of
+`ably-go/`, and also carries `ably-pubsub-server` or
+`ably-pubsub-device`, depending on the entry point the client was created
+from. Update anything that matches on `ably-go/`, such as proxy rules or log
+queries.
+
+`WithAgents`, which is meant for Ably-authored SDKs built on this one, now adds
+its entries to the agents already configured instead of replacing them. A
+repeated product name overrides the earlier version, the `ably-pubsub-server`
+or `ably-pubsub-device` entry cannot be removed, and agents appear in
+alphabetical order.
+
+### Smaller changes
+
+- Log messages from the HTTP client are prefixed `HTTPClient:` instead of
+ `RestClient:`.
+- The examples in `examples/rest` moved to `examples/http`.
+
+### Host options are replaced by `WithEndpoint`
+
+`ably.WithEnvironment`, `ably.WithRESTHost`, `ably.WithRealtimeHost` and
+`ably.WithFallbackHostsUseDefault` have been removed. `WithEndpoint` selects the host for both HTTP
+requests and the realtime connection, so code that still uses the old options fails to compile
+instead of silently connecting to a different host.
+
+| Before | After |
+| --- | --- |
+| `ably.WithEnvironment("sandbox")` | `pubsub.WithEndpoint("nonprod:sandbox")` |
+| `ably.WithEnvironment("acme")` (a routing policy name) | `pubsub.WithEndpoint("acme")` |
+| `ably.WithRESTHost(h)` and `ably.WithRealtimeHost(h)` set to the same host `h` | `pubsub.WithEndpoint(h)` |
+| `ably.WithRESTHost("localhost:8080")` (TLS, the default) | `pubsub.WithEndpoint("localhost")` and `pubsub.WithTLSPort(8080)` |
+| `ably.WithRESTHost("localhost:8080")` with `ably.WithTLS(false)` | `pubsub.WithEndpoint("localhost")`, `pubsub.WithTLS(false)` and `pubsub.WithPort(8080)` |
+| `ably.WithRESTHost(a)` and `ably.WithRealtimeHost(b)` with different hosts | Not supported. Use one host that serves both, or contact Ably. |
+| `ably.WithFallbackHostsUseDefault(true)` | Remove it. Default fallback hosts apply automatically, unless the endpoint is a hostname (next row). |
+| `ably.WithRESTHost(h)` with `ably.WithFallbackHostsUseDefault(true)` | `pubsub.WithEndpoint(h)` and `pubsub.WithFallbackHosts(...)` listing the default fallback hosts `main.a.fallback.ably-realtime.com` … `main.e.fallback.ably-realtime.com`. A hostname endpoint has no default fallback hosts, so they must now be passed explicitly. |
+| `ably.WithFallbackHosts(hosts)` | `pubsub.WithFallbackHosts(hosts)`, unchanged |
+
+How the endpoint is resolved:
+
+- Unset: the primary host is `main.realtime.ably.net`, with the default fallback hosts
+ `main.[a-e].fallback.ably-realtime.com`.
+- A routing policy name such as `acme`: `acme.realtime.ably.net`, with fallback hosts
+ `acme.[a-e].fallback.ably-realtime.com`.
+- `nonprod:` plus a name, such as `nonprod:sandbox`: `sandbox.realtime.ably-nonprod.net`, with
+ fallback hosts `sandbox.[a-e].fallback.ably-realtime-nonprod.com`.
+- A hostname, meaning a value that contains `.` or `::`, or is `localhost`: used as given, with no
+ default fallback hosts. Do not include a port in the value; use `WithTLSPort`, or `WithPort` when
+ TLS is disabled with `WithTLS(false)`.
+
+An explicit `WithFallbackHosts` always replaces the default fallback hosts. A custom `WithPort` or
+`WithTLSPort` no longer disables the default fallback hosts.
+
+```go
+// Before
+client, err := ably.NewRealtime(
+ ably.WithKey(key),
+ ably.WithEnvironment("sandbox"),
+)
+
+// After
+client, err := pubsub.NewRealtimeClient(
+ pubsub.WithKey(key),
+ pubsub.WithEndpoint("nonprod:sandbox"),
+)
+```
+
+If you used any of the removed options, check your firewall or proxy rules before upgrading. The
+client now connects to:
+
+- production endpoints: `.realtime.ably.net` and `.[a-e].fallback.ably-realtime.com`,
+ so allow `*.realtime.ably.net` and `*.fallback.ably-realtime.com`;
+- non-production endpoints (`nonprod:`, such as `nonprod:sandbox`):
+ `.realtime.ably-nonprod.net` and `.[a-e].fallback.ably-realtime-nonprod.com`, so allow
+ `*.realtime.ably-nonprod.net` and `*.fallback.ably-realtime-nonprod.com`.
+
+The default hosts are unchanged for clients that set no host option.
+
+### Deprecated API removed
+
+Deprecated symbols that had a replacement have been removed.
+
+| Removed | Use instead |
+| --- | --- |
+| `ably.WithEnvironment` | `pubsub.WithEndpoint` |
+| `ably.WithRESTHost` | `pubsub.WithEndpoint` |
+| `ably.WithRealtimeHost` | `pubsub.WithEndpoint` |
+| `ably.WithFallbackHostsUseDefault` | Remove it. Default fallback hosts apply automatically, except with a hostname endpoint, where they must be passed with `pubsub.WithFallbackHosts`. |
+| `ably.Connection.RecoveryKey()` | `pubsub.Connection.CreateRecoveryKey()` |
+| `ably.RESTChannel.PublishMultipleWithOptions` | `pubsub.HTTPChannel.PublishMultiple` |
+| `ably.PublishMultipleWithParams` | `pubsub.PublishWithParams` |
+
## Version 1.1.5 to 1.2.0
We have made many **breaking changes** in the version 1.2 release of this SDK.
diff --git a/ably/generate.go b/ably/generate.go
deleted file mode 100644
index 74a8b69b1..000000000
--- a/ably/generate.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package ably
-
-//go:generate go run ../scripts/errors/main.go -json ../common/protocol/errors.json -o errors.go
diff --git a/device/pubsub/api.go b/device/pubsub/api.go
new file mode 100644
index 000000000..83ed719ee
--- /dev/null
+++ b/device/pubsub/api.go
@@ -0,0 +1,1353 @@
+package pubsub
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably/objects"
+)
+
+// Types.
+
+// AblyVCDiffDecoder is a production-ready implementation of the VCDiffDecoder interface
+// that uses the official ably/vcdiff-go library for delta decoding.
+// Implements VCDiffDecoder interface for decoding VCDiff-encoded delta messages (PC3).
+type AblyVCDiffDecoder = ably.AblyVCDiffDecoder
+
+// Auth creates Ably [TokenRequest] objects and obtains Ably Tokens from Ably to
+// subsequently issue to less trusted clients.
+type Auth = ably.Auth
+
+// AuthOption configures authentication/authorization for a [Client]
+// instance or operation.
+type AuthOption = ably.AuthOption
+
+// ChannelEvent describes the events emitted by a [RealtimeChannel] object.
+// An event is either an UPDATE or a [ChannelState].
+type ChannelEvent = ably.ChannelEvent
+
+// ChannelEventEmitter emits the [ChannelStateChange] events of a [RealtimeChannel]; register
+// handlers with its On, Once and OnAll methods.
+type ChannelEventEmitter = ably.ChannelEventEmitter
+
+// ChannelMode Describes the possible flags used to configure client capabilities, using [ChannelOption].
+type ChannelMode = ably.ChannelMode
+
+// ChannelOption configures a channel.
+type ChannelOption = ably.ChannelOption
+
+// ChannelProperties holds properties of a realtime channel, such as its attach serial (CP2).
+type ChannelProperties = ably.ChannelProperties
+
+// ChannelState describes the possible states of a [RealtimeChannel] object.
+type ChannelState = ably.ChannelState
+
+// ChannelStateChange contains state change information emitted by [RealtimeChannel] objects.
+// A ChannelStateChange is the data associated with a ChannelEvent.
+type ChannelStateChange = ably.ChannelStateChange
+
+// CipherAlgorithm is a supported algorithm for channel encryption.
+type CipherAlgorithm = ably.CipherAlgorithm
+
+// CipherMode is a supported cipher mode for channel encryption.
+type CipherMode = ably.CipherMode
+
+// CipherParams sets the properties to configure encryption for a [RealtimeChannel] object (TZ1).
+type CipherParams = ably.CipherParams
+
+// ClientOption configures a [Client] instance.
+//
+// See: https://www.ably.io/documentation/realtime/usage#client-options
+type ClientOption = ably.ClientOption
+
+// Connection represents a single connection Realtime instantiates for communication with Ably servers.
+// It also enables the management of a connection to Ably.
+type Connection = ably.Connection
+
+// ConnectionEvent describes the events emitted by a [Connection] object.
+// An event is either an [ConnectionEventUpdate] or [ConnectionState} change event.
+type ConnectionEvent = ably.ConnectionEvent
+
+type ConnectionEventEmitter = ably.ConnectionEventEmitter
+
+// ConnectionState describes the realtime [Connection] object states.
+type ConnectionState = ably.ConnectionState
+
+// ConnectionStateChange Contains [ConnectionState]change information emitted by the [Connection] object.
+// A ConnectionStateChange is the data associated with a ConnectionEvent.
+type ConnectionStateChange = ably.ConnectionStateChange
+
+// DecodingContext provides context needed for decoding messages, including delta support.
+type DecodingContext = ably.DecodingContext
+
+// DeltaExtras describes a message whose payload is a "vcdiff"-encoded delta generated with respect to a base message (DE1, DE2).
+type DeltaExtras = ably.DeltaExtras
+
+// DeriveOptions allows options to be used in creating a derived channel
+type DeriveOptions = ably.DeriveOptions
+
+type Direction = ably.Direction
+
+// ErrorCode is the type for predefined Ably error codes.
+type ErrorCode = ably.ErrorCode
+
+// ErrorInfo is a generic Ably error object that contains an Ably-specific status code, and a generic status code.
+// Errors returned from the Ably server are compatible with the ErrorInfo structure and should result in errors
+// that inherit from ErrorInfo. It may contain underlying error value which caused the failure.
+type ErrorInfo = ably.ErrorInfo
+
+// A HistoryOption configures a call to HTTPChannel.History or RealtimeChannel.History.
+type HistoryOption = ably.HistoryOption
+
+// HistoryRequest represents a request prepared by the HTTPChannel.History or
+// RealtimeChannel.History method, ready to be performed by its Pages or Items methods.
+type HistoryRequest = ably.HistoryRequest
+
+type LogLevel = ably.LogLevel
+
+// Logger is an interface for ably loggers.
+type Logger = ably.Logger
+
+// Message contains an individual message that is sent to, or received from, Ably.
+type Message = ably.Message
+
+// MessageAction represents the type of message operation (TM5).
+type MessageAction = ably.MessageAction
+
+// MessageVersion contains version information for a message (TM2s).
+// When received from the server, Serial and Timestamp are server-populated.
+// When sending an update/delete/append, ClientID, Description, and Metadata
+// are user-provided via UpdateOption functions (mapped from MOP2a/MOP2b/MOP2c).
+type MessageVersion = ably.MessageVersion
+
+type MessagesPaginatedItems = ably.MessagesPaginatedItems
+
+// A MessagesPaginatedResult is an iterator for the result of a History request.
+//
+// It is paginated: see [PaginatedResult] for how to iterate over the pages.
+type MessagesPaginatedResult = ably.MessagesPaginatedResult
+
+type PaginateParams = ably.PaginateParams
+
+// PaginatedResult is a generic iterator for PaginatedResult pagination.
+// Items decoding is delegated to type-specific wrappers.
+//
+// Call Next to fetch each page and read it with Items; once Next returns false,
+// check Err to tell an error from the end of the results.
+type PaginatedResult = ably.PaginatedResult
+
+type PeriodUnit = ably.PeriodUnit
+
+// PresenceAction describes the possible actions members in the presence set can emit (TP2).
+type PresenceAction = ably.PresenceAction
+
+// A PresenceGetOption is an optional parameter for RealtimePresence.GetWithOptions.
+type PresenceGetOption = ably.PresenceGetOption
+
+type PresenceMessage = ably.PresenceMessage
+
+// PublishResult contains the result of a publish operation with serial tracking.
+// The spec (PBR2a) defines PublishResult with a serials array, but per RSL1n1/RTL6j1,
+// SDKs may implement alternatives where adding a response value would be a breaking
+// API change. This SDK returns one PublishResult per message for ergonomics.
+type PublishResult = ably.PublishResult
+
+// PushStats provides details the stats on push notifications.
+type PushStats = ably.PushStats
+
+// RealtimeChannel represents a single named message/presence channel.
+// It enables messages to be published and subscribed to. Also enables historic messages to be retrieved and
+// provides access to the [RealtimePresence] object of a channel.
+type RealtimeChannel = ably.RealtimeChannel
+
+// RealtimeChannels is a goroutine-safe container for realtime channels that allows for creating,
+// deleting and iterating over existing channels.
+// Creates and destroys [RealtimeChannel] objects.
+type RealtimeChannels = ably.RealtimeChannels
+
+// RealtimeExperimentalObjects exposes a method to publish LiveObject messages on a Realtime channel.
+// It requires the LiveObjects plugin to be configured; see [ObjectsPlugin].
+//
+// NOTE: this option is experimental, the LiveObjects plugin API may change in a
+// backwards incompatible way between minor/patch versions. Once the API has been finalised,
+// a new non-experimental option will be added, and this one will be removed.
+//
+// [LiveObjects]: https://ably.com/docs/liveobjects
+type RealtimeExperimentalObjects = ably.RealtimeExperimentalObjects
+
+// RealtimePresence represents a single presence map of a particular channel.
+// It allows entering, leaving and updating presence state for the current client or on behalf of other client.
+// It enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel.
+type RealtimePresence = ably.RealtimePresence
+
+// RecoveryKeyContext contains the properties required to recover existing connection.
+type RecoveryKeyContext = ably.RecoveryKeyContext
+
+type ScopeParams = ably.ScopeParams
+
+// Stats contains application statistics for a specified time interval and time period.
+type Stats = ably.Stats
+
+// StatsConnectionTypes contains a breakdown of summary stats data for different (TLS vs non-TLS) connection types.
+type StatsConnectionTypes = ably.StatsConnectionTypes
+
+// StatsMessageCount contains the aggregate counts for messages and data transferred.
+type StatsMessageCount = ably.StatsMessageCount
+
+type StatsMessageDirections = ably.StatsMessageDirections
+
+type StatsMessageTraffic = ably.StatsMessageTraffic
+
+// StatsMessageTypes contains a breakdown of summary stats data for different (channel vs presence) message types.
+type StatsMessageTypes = ably.StatsMessageTypes
+
+// A StatsOption configures a call to HTTPClient.Stats or Realtime.Stats.
+type StatsOption = ably.StatsOption
+
+type StatsPaginatedItems = ably.StatsPaginatedItems
+
+// A StatsPaginatedResult is an iterator for the result of a Stats request.
+//
+// It is paginated: see [PaginatedResult] for how to iterate over the pages.
+type StatsPaginatedResult = ably.StatsPaginatedResult
+
+type StatsPushNotificationFailures = ably.StatsPushNotificationFailures
+
+type StatsPushNotifications = ably.StatsPushNotifications
+
+type StatsPushTransportTypeCounter = ably.StatsPushTransportTypeCounter
+
+type StatsRates = ably.StatsRates
+
+type StatsReactorRates = ably.StatsReactorRates
+
+// StatsRequest represents a request prepared by the HTTPClient.Stats or
+// Realtime.Stats method, ready to be performed by its Pages or Items methods.
+type StatsRequest = ably.StatsRequest
+
+type StatsRequestCount = ably.StatsRequestCount
+
+type StatsResourceCount = ably.StatsResourceCount
+
+type StatsXchgMessages = ably.StatsXchgMessages
+
+// TokenDetails contains an Ably Token and its associated metadata.
+type TokenDetails = ably.TokenDetails
+
+// TokenParams contains token params to be sent to ably to get auth token
+type TokenParams = ably.TokenParams
+
+// TokenRequest contains tokenparams with extra details, sent to ably for getting auth token
+type TokenRequest = ably.TokenRequest
+
+// TokenString is the string representation of an authentication token.
+type TokenString = ably.TokenString
+
+// Tokener is or can be used to get a [TokenDetails].
+type Tokener = ably.Tokener
+
+// UpdateDeleteResult contains the result of an update, delete, or append operation (UDR1).
+type UpdateDeleteResult = ably.UpdateDeleteResult
+
+// UpdateOption is a functional option for message update operations.
+type UpdateOption = ably.UpdateOption
+
+// VCDiffDecoder provides an interface for decoding vcdiff-encoded message payloads (VD1, VD2).
+// This interface must be implemented by plugins that provide vcdiff delta decoding functionality.
+type VCDiffDecoder = ably.VCDiffDecoder
+
+// LiveObjects types. The Objects prefix keeps them apart from the
+// same-named types of the main API once both live in one package.
+
+type ObjectsCounter = objects.Counter
+
+type ObjectsCounterCreate = objects.CounterCreate
+
+type ObjectsCounterCreateWithObjectId = objects.CounterCreateWithObjectId
+
+type ObjectsCounterInc = objects.CounterInc
+
+type ObjectsCounterOp = objects.CounterOp
+
+type ObjectsData = objects.Data
+
+type ObjectsMap = objects.Map
+
+type ObjectsMapCreate = objects.MapCreate
+
+type ObjectsMapCreateWithObjectId = objects.MapCreateWithObjectId
+
+type ObjectsMapEntry = objects.MapEntry
+
+type ObjectsMapOp = objects.MapOp
+
+type ObjectsMapRemove = objects.MapRemove
+
+type ObjectsMapSemantics = objects.MapSemantics
+
+type ObjectsMapSet = objects.MapSet
+
+type ObjectsMessage = objects.Message
+
+type ObjectsObjectDelete = objects.ObjectDelete
+
+type ObjectsOperation = objects.Operation
+
+type ObjectsOperationAction = objects.OperationAction
+
+// Plugin is used to add LiveObject functionality to a realtime client:
+//
+// client, err := NewRealtime(
+// WithKey(...),
+// WithExperimentalObjectsPlugin(myObjectsPlugin),
+// )
+//
+// The plugin is used to prepare object messages to be published in the
+// realtime channel's publishObjects method, and is used to handle incoming
+// object and object sync messages.
+type ObjectsPlugin = objects.Plugin
+
+type ObjectsState = objects.State
+
+// LiveObjects constants. The values of the objects package's enums, with the
+// same Objects prefix as their types and the underscore dropped.
+const (
+ // ObjectsOperationMapCreate creates a new map object.
+ ObjectsOperationMapCreate = objects.Operation_MapCreate
+ // ObjectsOperationMapSet sets a key in a map object.
+ ObjectsOperationMapSet = objects.Operation_MapSet
+ // ObjectsOperationMapRemove removes a key from a map object.
+ ObjectsOperationMapRemove = objects.Operation_MapRemove
+ // ObjectsOperationCounterCreate creates a new counter object.
+ ObjectsOperationCounterCreate = objects.Operation_CounterCreate
+ // ObjectsOperationCounterInc increments a counter object.
+ ObjectsOperationCounterInc = objects.Operation_CounterInc
+ // ObjectsOperationDelete deletes an object.
+ ObjectsOperationDelete = objects.Operation_Delete
+ // ObjectsMapLWW is the last-write-wins conflict-resolution semantics of a map object.
+ ObjectsMapLWW = objects.Map_LWW
+)
+
+// Constants and variables.
+
+// Crypto contains the properties required to configure the encryption of [Message] payloads.
+var Crypto = ably.Crypto
+
+const ErrNotSet = ably.ErrNotSet
+
+const ErrBadRequest = ably.ErrBadRequest
+
+const ErrInvalidCredential = ably.ErrInvalidCredential
+
+const ErrInvalidClientID = ably.ErrInvalidClientID
+
+const ErrDeltaDecodingFailed = ably.ErrDeltaDecodingFailed
+
+const ErrMissingPlugin = ably.ErrMissingPlugin
+
+const ErrUnauthorized = ably.ErrUnauthorized
+
+const ErrInvalidCredentials = ably.ErrInvalidCredentials
+
+const ErrIncompatibleCredentials = ably.ErrIncompatibleCredentials
+
+const ErrInvalidUseOfBasicAuthOverNonTLSTransport = ably.ErrInvalidUseOfBasicAuthOverNonTLSTransport
+
+const ErrTokenErrorUnspecified = ably.ErrTokenErrorUnspecified
+
+const ErrErrorFromClientTokenCallback = ably.ErrErrorFromClientTokenCallback
+
+const ErrForbidden = ably.ErrForbidden
+
+const ErrNotFound = ably.ErrNotFound
+
+const ErrMethodNotAllowed = ably.ErrMethodNotAllowed
+
+const ErrInternalError = ably.ErrInternalError
+
+const ErrInternalChannelError = ably.ErrInternalChannelError
+
+const ErrInternalConnectionError = ably.ErrInternalConnectionError
+
+const ErrTimeoutError = ably.ErrTimeoutError
+
+const ErrConnectionFailed = ably.ErrConnectionFailed
+
+const ErrConnectionSuspended = ably.ErrConnectionSuspended
+
+const ErrConnectionClosed = ably.ErrConnectionClosed
+
+const ErrDisconnected = ably.ErrDisconnected
+
+const ErrProtocolError = ably.ErrProtocolError
+
+const ErrChannelOperationFailed = ably.ErrChannelOperationFailed
+
+const ErrChannelOperationFailedInvalidChannelState = ably.ErrChannelOperationFailedInvalidChannelState
+
+const PeriodMinute = ably.PeriodMinute
+
+const PeriodHour = ably.PeriodHour
+
+const PeriodDay = ably.PeriodDay
+
+const PeriodMonth = ably.PeriodMonth
+
+const LogNone = ably.LogNone
+
+const LogError = ably.LogError
+
+const LogWarning = ably.LogWarning
+
+const LogInfo = ably.LogInfo
+
+const LogVerbose = ably.LogVerbose
+
+const LogDebug = ably.LogDebug
+
+const Port = ably.Port
+
+const TLSPort = ably.TLSPort
+
+const Backwards = ably.Backwards
+
+const Forwards = ably.Forwards
+
+// ChannelModePresence allows the attached channel to enter Presence.
+const ChannelModePresence = ably.ChannelModePresence
+
+// ChannelModePublish allows for messages to be published on the attached channel.
+const ChannelModePublish = ably.ChannelModePublish
+
+// ChannelModeSubscribe allows the attached channel to subscribe to messages.
+const ChannelModeSubscribe = ably.ChannelModeSubscribe
+
+// ChannelModePresenceSubscribe allows the attached channel to subscribe to Presence updates.
+const ChannelModePresenceSubscribe = ably.ChannelModePresenceSubscribe
+
+// ChannelModeObjectSubscribe allows the attached channel to subscribe to Objects updates.
+const ChannelModeObjectSubscribe = ably.ChannelModeObjectSubscribe
+
+// ChannelModeObjectPublish allows for objects messages to be published on the attached channel.
+const ChannelModeObjectPublish = ably.ChannelModeObjectPublish
+
+const CipherAES = ably.CipherAES
+
+const CipherCBC = ably.CipherCBC
+
+// AblyAgentHeaderName is the name of the Ably-Agent header (RSC7d), exported for
+// use by test helpers that issue their own requests to Ably.
+const AblyAgentHeaderName = ably.AblyAgentHeaderName
+
+const MessageActionUnknown = ably.MessageActionUnknown
+
+const MessageActionCreate = ably.MessageActionCreate
+
+const MessageActionUpdate = ably.MessageActionUpdate
+
+const MessageActionDelete = ably.MessageActionDelete
+
+const MessageActionMeta = ably.MessageActionMeta
+
+const MessageActionMessageSummary = ably.MessageActionMessageSummary
+
+const MessageActionAppend = ably.MessageActionAppend
+
+// PresenceActionAbsent specifies a member is not present in the channel.
+const PresenceActionAbsent = ably.PresenceActionAbsent
+
+// PresenceActionPresent denotes when subscribing to presence events on a channel that already has members present,
+// this event is emitted for every member already present on the channel before the subscribe listener was registered.
+const PresenceActionPresent = ably.PresenceActionPresent
+
+// PresenceActionEnter denotes that new member has entered the channel.
+const PresenceActionEnter = ably.PresenceActionEnter
+
+// PresenceActionLeave is a member who was present has now left the channel. This may be a result of an explicit
+// request to leave or implicitly when detaching from the channel. Alternatively, if a member's connection is
+// abruptly disconnected and they do not resume their connection within a minute, Ably treats this as a
+// leave event as the client is no longer present.
+const PresenceActionLeave = ably.PresenceActionLeave
+
+// PresenceActionUpdate is a already present member has updated their member data. Being notified of member data
+// updates can be very useful, for example, it can be used to update the status of a user when they are
+// typing a message.
+const PresenceActionUpdate = ably.PresenceActionUpdate
+
+// StatGranularityMinute specifies interval unit over which statistics are gathered as minutes.
+const StatGranularityMinute = ably.StatGranularityMinute
+
+// StatGranularityHour specifies interval unit over which statistics are gathered as hours.
+const StatGranularityHour = ably.StatGranularityHour
+
+// StatGranularityDay specifies interval unit over which statistics are gathered as days.
+const StatGranularityDay = ably.StatGranularityDay
+
+// StatGranularityMonth specifies interval unit over which statistics are gathered as months.
+const StatGranularityMonth = ably.StatGranularityMonth
+
+// ConnectionStateInitialized - A connection with this state has been initialized
+// but no connection has yet been attempted.
+var ConnectionStateInitialized = ably.ConnectionStateInitialized
+
+// ConnectionStateConnecting - A connection attempt has been initiated.
+// The connecting state is entered as soon as the library has completed initialization,
+// and is reentered each time connection is re-attempted following disconnection.
+var ConnectionStateConnecting = ably.ConnectionStateConnecting
+
+// ConnectionStateConnected - A connection exists and is active.
+var ConnectionStateConnected = ably.ConnectionStateConnected
+
+// ConnectionStateDisconnected - A temporary failure condition. No current connection exists
+// because there is no network connectivity or no host is available.
+// The disconnected state is entered if an established connection is dropped,
+// or if a connection attempt was unsuccessful. In the disconnected state the library will periodically attempt
+// to open a new connection (approximately every 15 seconds), anticipating that the connection will
+// be re-established soon and thus connection and channel continuity will be possible.
+// In this state, developers can continue to publish messages as they are automatically placed in a local queue,
+// to be sent as soon as a connection is reestablished. Messages published by other clients
+// while this client is disconnected will be delivered to it upon reconnection, so long as
+// the connection was resumed within 2 minutes. After 2 minutes have elapsed, recovery is no longer possible
+// and the connection will move to the SUSPENDED state.
+var ConnectionStateDisconnected = ably.ConnectionStateDisconnected
+
+// ConnectionStateSuspended - A long term failure condition. No current connection exists
+// because there is no network connectivity or no host is available. The suspended state is entered
+// after a failed connection attempt if there has then been no connection for a period of two minutes.
+// In the suspended state, the library will periodically attempt to open a new connection every 30 seconds.
+// Developers are unable to publish messages in this state. A new connection attempt can also be triggered
+// by an explicit call to Connection.Connect. Once the connection has been re-established,
+// channels will be automatically re-attached. The client has been disconnected for too long for them
+// to resume from where they left off, so if it wants to catch up on messages published by other clients
+// while it was disconnected, it needs to use the History API.
+var ConnectionStateSuspended = ably.ConnectionStateSuspended
+
+// ConnectionStateClosing is when an explicit request by the developer to close the connection has been sent to
+// the Ably service. If a reply is not received from Ably within a short period of time,
+// the connection is forcibly terminated and the connection state becomes CLOSED.
+var ConnectionStateClosing = ably.ConnectionStateClosing
+
+// ConnectionStateClosed - The connection has been explicitly closed by the client.
+// In the closed state, no reconnection attempts are made automatically by the library,
+// and clients may not publish messages. No connection state is preserved by the service or by the library.
+// A new connection attempt can be triggered by an explicit call to Connection.Connect,
+// which results in a new connection.
+var ConnectionStateClosed = ably.ConnectionStateClosed
+
+// ConnectionStateFailed - This state is entered if the client library encounters a failure condition
+// that it cannot recover from. This may be a fatal connection error received from the Ably service,
+// for example an attempt to connect with an incorrect API key, or a local terminal error,
+// for example the token in use has expired and the library does not have any way to renew it.
+// In the failed state, no reconnection attempts are made automatically by the library, and
+// clients may not publish messages. A new connection attempt can be triggered by an explicit call
+// to Connection.Connect.
+var ConnectionStateFailed = ably.ConnectionStateFailed
+
+// ConnectionEventInitialized - A connection with this state has been initialized
+// but no connection has yet been attempted.
+var ConnectionEventInitialized = ably.ConnectionEventInitialized
+
+// ConnectionEventConnecting - A connection attempt has been initiated.
+// The connecting state is entered as soon as the library has completed initialization,
+// and is reentered each time connection is re-attempted following disconnection.
+var ConnectionEventConnecting = ably.ConnectionEventConnecting
+
+// ConnectionEventConnected - A connection exists and is active.
+var ConnectionEventConnected = ably.ConnectionEventConnected
+
+// ConnectionEventDisconnected - A temporary failure condition. No current connection exists
+// because there is no network connectivity or no host is available.
+// The disconnected state is entered if an established connection is dropped,
+// or if a connection attempt was unsuccessful. In the disconnected state the library will periodically attempt
+// to open a new connection (approximately every 15 seconds), anticipating that the connection will
+// be re-established soon and thus connection and channel continuity will be possible.
+// In this state, developers can continue to publish messages as they are automatically placed in a local queue,
+// to be sent as soon as a connection is reestablished. Messages published by other clients
+// while this client is disconnected will be delivered to it upon reconnection, so long as
+// the connection was resumed within 2 minutes. After 2 minutes have elapsed, recovery is no longer possible
+// and the connection will move to the SUSPENDED state.
+var ConnectionEventDisconnected = ably.ConnectionEventDisconnected
+
+// ConnectionEventSuspended - A long term failure condition. No current connection exists
+// because there is no network connectivity or no host is available. The suspended state is entered
+// after a failed connection attempt if there has then been no connection for a period of two minutes.
+// In the suspended state, the library will periodically attempt to open a new connection every 30 seconds.
+// Developers are unable to publish messages in this state. A new connection attempt can also be triggered
+// by an explicit call to Connection.Connect. Once the connection has been re-established,
+// channels will be automatically re-attached. The client has been disconnected for too long for them
+// to resume from where they left off, so if it wants to catch up on messages published by other clients
+// while it was disconnected, it needs to use the History API.
+var ConnectionEventSuspended = ably.ConnectionEventSuspended
+
+// ConnectionEventClosing is when an explicit request by the developer to close the connection has been sent to
+// the Ably service. If a reply is not received from Ably within a short period of time,
+// the connection is forcibly terminated and the connection state becomes CLOSED.
+var ConnectionEventClosing = ably.ConnectionEventClosing
+
+// ConnectionEventClosed - The connection has been explicitly closed by the client.
+// In the closed state, no reconnection attempts are made automatically by the library,
+// and clients may not publish messages. No connection state is preserved by the service or by the library.
+// A new connection attempt can be triggered by an explicit call to Connection.Connect,
+// which results in a new connection.
+var ConnectionEventClosed = ably.ConnectionEventClosed
+
+// ConnectionEventFailed - This state is entered if the client library encounters a failure condition
+// that it cannot recover from. This may be a fatal connection error received from the Ably service,
+// for example an attempt to connect with an incorrect API key, or a local terminal error,
+// for example the token in use has expired and the library does not have any way to renew it.
+// In the failed state, no reconnection attempts are made automatically by the library, and
+// clients may not publish messages. A new connection attempt can be triggered by an explicit call
+// to Connection.Connect.
+var ConnectionEventFailed = ably.ConnectionEventFailed
+
+// ConnectionEventUpdate is an event for changes to connection conditions for which the
+// [ConnectionState] does not change (RTN4h).
+var ConnectionEventUpdate = ably.ConnectionEventUpdate
+
+// ChannelStateInitialized is when the channel has been initialized but no attach has yet been attempted.
+var ChannelStateInitialized = ably.ChannelStateInitialized
+
+// ChannelStateAttaching is when the attach has been initiated by sending a request to Ably.
+// This is a transient state, followed either by a transition to ATTACHED, SUSPENDED, or FAILED.
+var ChannelStateAttaching = ably.ChannelStateAttaching
+
+// ChannelStateAttached is when the attach has succeeded.
+// In the ATTACHED state a client may publish and subscribe to messages, or be present on the channel.
+var ChannelStateAttached = ably.ChannelStateAttached
+
+// ChannelStateDetaching is when a detach has been initiated on an ATTACHED channel by sending a request to Ably.
+// This is a transient state, followed either by a transition to DETACHED or FAILED.
+var ChannelStateDetaching = ably.ChannelStateDetaching
+
+// ChannelStateDetached os when the channel, having previously been ATTACHED, has been detached by the user.
+var ChannelStateDetached = ably.ChannelStateDetached
+
+// ChannelStateSuspended is when the channel, having previously been ATTACHED, has lost continuity,
+// usually due to the client being disconnected from Ably for longer than two minutes.
+// It will automatically attempt to reattach as soon as connectivity is restored.
+var ChannelStateSuspended = ably.ChannelStateSuspended
+
+// ChannelStateFailed is an indefinite failure condition.
+// This state is entered if a channel error has been received from the Ably service,
+// such as an attempt to attach without the necessary access rights.
+var ChannelStateFailed = ably.ChannelStateFailed
+
+// ChannelEventInitialized is when the channel has been initialized but no attach has yet been attempted.
+var ChannelEventInitialized = ably.ChannelEventInitialized
+
+// ChannelEventAttaching is when the attach has been initiated by sending a request to Ably.
+// This is a transient state, followed either by a transition to ATTACHED, SUSPENDED, or FAILED.
+var ChannelEventAttaching = ably.ChannelEventAttaching
+
+// ChannelEventAttached is when the attach has succeeded.
+// In the ATTACHED state a client may publish and subscribe to messages, or be present on the channel.
+var ChannelEventAttached = ably.ChannelEventAttached
+
+// ChannelEventDetaching is when a detach has been initiated on an ATTACHED channel by sending a request to Ably.
+// This is a transient state, followed either by a transition to DETACHED or FAILED.
+var ChannelEventDetaching = ably.ChannelEventDetaching
+
+// ChannelEventDetached os when the channel, having previously been ATTACHED, has been detached by the user.
+var ChannelEventDetached = ably.ChannelEventDetached
+
+// ChannelEventSuspended is when the channel, having previously been ATTACHED, has lost continuity,
+// usually due to the client being disconnected from Ably for longer than two minutes.
+// It will automatically attempt to reattach as soon as connectivity is restored.
+var ChannelEventSuspended = ably.ChannelEventSuspended
+
+// ChannelEventFailed is an indefinite failure condition.
+// This state is entered if a channel error has been received from the Ably service,
+// such as an attempt to attach without the necessary access rights.
+var ChannelEventFailed = ably.ChannelEventFailed
+
+// ChannelEventUpdate is an event for changes to channel conditions that do not result
+// in a change in [ChannelState] (RTL2g).
+var ChannelEventUpdate = ably.ChannelEventUpdate
+
+// Functions.
+
+// AgentIdentifier returns the value the library sends in the Ably-Agent header
+// (RSC7d) for the given additional agents. It is exported so that test helpers
+// can identify their own requests to Ably with the same agent string the client
+// uses, without duplicating the library version.
+func AgentIdentifier(agents map[string]string) string {
+ return ably.AgentIdentifier(agents)
+}
+
+// AuthWithCallback is used for setting AuthCallback function using [AuthOption].
+//
+// AuthCallback function is called when a new token is required.
+// The role of the callback is to obtain a fresh token, one of
+//
+// 1. An Ably Token string (https://ably.com/docs/core-features/authentication#token-process)
+// 2. A signed [TokenRequest] (https://ably.com/docs/core-features/authentication#token-request-process)
+// 3. An [TokenDetails] (https://ably.com/docs/core-features/authentication#token-process)
+// 4. [An Ably JWT].
+//
+// See the [authentication doc] for more details and associated API calls (RSA4a, RSA4, TO3j5, AO2b).
+//
+// [authentication doc]: https://ably.com/docs/core-features/authentication
+// [An Ably JWT]: https://ably.com/docs/core-features/authentication#ably-jwt-process
+func AuthWithCallback(authCallback func(a0 context.Context, a1 TokenParams) (Tokener, error)) AuthOption {
+ return ably.AuthWithCallback(authCallback)
+}
+
+// AuthWithDefaultTokenParams is used for setting DefaultTokenParams token using [AuthOption].
+// DefaultTokenParams when provided, it overrides the client library defaults when issuing new Ably Tokens
+// for multiple Ably [TokenRequest] (TO3j11).
+func AuthWithDefaultTokenParams(params TokenParams) AuthOption {
+ return ably.AuthWithDefaultTokenParams(params)
+}
+
+// AuthWithHeaders is used for setting AuthHeaders using [AuthOption].
+// AuthHeaders are key-value pair HTTP request headers to be added to any request made to the AuthURL.
+// Useful when an application requires these to be added to validate the request or implement the response.
+// If the authHeaders object contains an authorization key, then withCredentials is set on the XHR request.
+// (RSA8c3, TO3j8, AO2e).
+func AuthWithHeaders(headers http.Header) AuthOption {
+ return ably.AuthWithHeaders(headers)
+}
+
+// AuthWithKey is used for setting root/non-root apikey using [AuthOption].
+// Key is a full API key string, as obtained from the Ably dashboard.
+// Use this option if you wish to use Basic authentication, or wish to be able to issue Ably Tokens
+// without needing to defer to a separate entity to sign multiple [TokenRequest].
+// Read more about [Basic authentication] (RSA11, RSA14, TO3j1, AO2a).
+//
+// [Basic authentication]: https://ably.com/docs/core-features/authentication#basic-authentication
+func AuthWithKey(key string) AuthOption {
+ return ably.AuthWithKey(key)
+}
+
+// AuthWithMethod is used for setting AuthMethod using [AuthOption]
+// AuthMethod specifies HTTP verb to use for any request made to the AuthURL, either GET or POST for
+// getting token information [TokenRequest] or [TokenDetails].
+// The default value is GET (RSA8c, TO3j7, AO2d).
+func AuthWithMethod(method string) AuthOption {
+ return ably.AuthWithMethod(method)
+}
+
+// AuthWithParams is used for setting AuthParams using [AuthOption].
+// AuthParams are key-value pair HTTP query params to be added to any request made to the AuthURL.
+// When the authMethod is GET, query params are added to the URL, whereas when authMethod is POST,
+// the params are sent as URL encoded form data. Useful when an application requires these to be added to
+// validate the request or implement the response (RSA8c3, RSA8c1, TO3j9, AO2f).
+func AuthWithParams(params url.Values) AuthOption {
+ return ably.AuthWithParams(params)
+}
+
+// AuthWithQueryTime is used for setting UseQueryTime token using [AuthOption].
+// UseQueryTime when set to true, the library queries the Ably servers for the current time when
+// issuing multiple [TokenRequest] instead of relying on a locally-available time of day.
+// Knowing the time accurately is needed to create valid signed Ably [TokenRequest],
+// so this option is useful for library instances on auth servers where for some reason
+// the server clock cannot be kept synchronized through normal means,
+// such as an NTP daemon. The server is queried for the current time once per client library instance
+// (which stores the offset from the local clock), so if using this option you should avoid instancing
+// a new version of the library for each request.
+// The default is false (RSA9d, TO3j10, AO2a).
+func AuthWithQueryTime(queryTime bool) AuthOption {
+ return ably.AuthWithQueryTime(queryTime)
+}
+
+// AuthWithToken is used for setting authenticated token using [AuthOption].
+// This can either be a token string (obtained from the token property of a [TokenDetails] component of
+// an Ably [TokenRequest] response, or a JSON Web Token satisfying the Ably requirements for JWTs).
+//
+// This option is mostly useful for testing: since tokens are short-lived,
+// in production you almost always want to use an authentication method that enables the client library
+// to renew the token automatically when the previous one expires, such as AuthURL or authCallback.
+// Read more about [Token authentication] (RSA4a, RSA4, TO3j2).
+//
+// [Token authentication]: https://ably.com/docs/core-features/authentication#token-authentication
+func AuthWithToken(token string) AuthOption {
+ return ably.AuthWithToken(token)
+}
+
+// AuthWithTokenDetails is used for setting authenticated [TokenDetails] object using [AuthOption].
+// Only TokenDetails.Token can be set via token string (obtained from the token property of a [TokenDetails]
+// component of an Ably [TokenRequest] response, or a JSON Web Token satisfying the Ably requirements for JWTs).
+// This option is mostly useful for testing: since tokens are short-lived,
+// in production you almost always want to use an authentication method that enables the client library
+// to renew the token automatically when the previous one expires, such as AuthURL or authCallback.
+// Read more about [Token authentication] (RSA4a, RSA4, TO3j3).
+//
+// [Token authentication]: https://ably.com/docs/core-features/authentication#token-authentication
+func AuthWithTokenDetails(details *TokenDetails) AuthOption {
+ return ably.AuthWithTokenDetails(details)
+}
+
+// AuthWithURL is used for setting AuthURL using [AuthOption].
+// AuthURL is a url that library will use to obtain
+// 1. An Ably Token string (https://ably.com/docs/core-features/authentication#token-process)
+// 2. A signed [TokenRequest] (https://ably.com/docs/core-features/authentication#token-request-process)
+// 3. An [TokenDetails] (https://ably.com/docs/core-features/authentication#token-process)
+// 4. [An Ably JWT].
+//
+// See the [authentication doc] for more details and associated API calls (RSA4a, RSA4, RSA8c, TO3j6, AO2c).
+//
+// This enables a client to obtain token requests from another entity,
+// so tokens can be renewed without the client requiring access to keys.
+//
+// If AuthURL is non-empty and AuthCallback is nil, the Ably library
+// builds a req (*http.Request) which then is issued against the given AuthURL
+// in order to obtain authentication token. The response is expected to
+// carry a single token string in the payload when Content-Type header
+// is "text/plain" or JSON-encoded *TokenDetails when the header
+// is "application/json".
+//
+// The req is built with the following values:
+//
+// GET requests:
+//
+// - req.URL.RawQuery is encoded from *TokenParams and AuthParams
+// - req.Header is set to AuthHeaders
+//
+// POST requests:
+//
+// - req.Header is set to AuthHeaders
+// - Content-Type is set to "application/x-www-form-urlencoded" and
+// the payload is encoded from *TokenParams and AuthParams
+//
+// [authentication doc]: https://ably.com/docs/core-features/authentication
+// [An Ably JWT]: https://ably.com/docs/core-features/authentication#ably-jwt-process
+func AuthWithURL(url string) AuthOption {
+ return ably.AuthWithURL(url)
+}
+
+// AuthWithUseTokenAuth is used for setting UseTokenAuth using [AuthOption].
+// UseTokenAuth when set to true, forces token authentication to be used by the library.
+// If a clientId is not specified in the [ClientOption] or [TokenParams],
+// then the Ably Token issued is anonymous (RSA4, RSA14, TO3j4).
+func AuthWithUseTokenAuth(use bool) AuthOption {
+ return ably.AuthWithUseTokenAuth(use)
+}
+
+// ChannelWithCipher requests encryption for this channel when not null, and specifies encryption-related parameters
+// (such as algorithm, chaining mode, key length and key). See [an example] (RSL5a, TB2b).
+//
+// [an example]: https://ably.com/docs/realtime/encryption#getting-started
+func ChannelWithCipher(params CipherParams) ChannelOption {
+ return ably.ChannelWithCipher(params)
+}
+
+// ChannelWithCipherKey is a constructor that takes private key as a argument.
+// It is used to encrypt and decrypt payloads (TB3)
+func ChannelWithCipherKey(key []byte) ChannelOption {
+ return ably.ChannelWithCipherKey(key)
+}
+
+// ChannelWithModes set an array of [ChannelMode] to a channel (TB2d).
+func ChannelWithModes(modes ...ChannelMode) ChannelOption {
+ return ably.ChannelWithModes(modes...)
+}
+
+// ChannelWithParams sets channel parameters that configure the behavior of the channel (TB2c).
+func ChannelWithParams(key string, value string) ChannelOption {
+ return ably.ChannelWithParams(key, value)
+}
+
+// ChannelWithVCDiff sets channel parameters for VCDIFF delta compression.
+// This is a convenience method that internally sets "delta" to "vcdiff" parameters.
+func ChannelWithVCDiff() ChannelOption {
+ return ably.ChannelWithVCDiff()
+}
+
+func DecodeRecoveryKey(recoveryKey string) (*RecoveryKeyContext, error) {
+ return ably.DecodeRecoveryKey(recoveryKey)
+}
+
+// DefaultCipherParams returns CipherParams with fields set to default values.
+// This generates random secret key and iv values
+func DefaultCipherParams() (*CipherParams, error) {
+ return ably.DefaultCipherParams()
+}
+
+// HistoryWithDirection sets the order for which messages are returned in.
+// Valid values are backwards which orders messages from most recent to oldest,
+// or forwards which orders messages from oldest to most recent.
+// The default is backwards (RSL2b2).
+func HistoryWithDirection(d Direction) HistoryOption {
+ return ably.HistoryWithDirection(d)
+}
+
+// HistoryWithEnd sets time until messages are retrieved, specified as milliseconds since the Unix epoch (RSL2b1).
+func HistoryWithEnd(t time.Time) HistoryOption {
+ return ably.HistoryWithEnd(t)
+}
+
+// HistoryWithLimit sets an upper limit on the number of messages returned.
+// The default is 100, and the maximum is 1000 (RSL2b3).
+func HistoryWithLimit(limit int) HistoryOption {
+ return ably.HistoryWithLimit(limit)
+}
+
+// HistoryWithStart sets time from which messages are retrieved, specified as milliseconds since the Unix epoch.
+// RSL2b1
+func HistoryWithStart(t time.Time) HistoryOption {
+ return ably.HistoryWithStart(t)
+}
+
+// NewVCDiffPlugin creates a new AblyVCDiffDecoder plugin instance.
+// Used publicly to set the VCDiff plugin in the client options.
+//
+// Example:
+//
+// client, err := NewHTTPClient(WithVCDiffPlugin(NewVCDiffPlugin()))
+func NewVCDiffPlugin() VCDiffDecoder {
+ return ably.NewVCDiffPlugin()
+}
+
+// PresenceGetWithWaitForSync sets whether to wait for a full presence set synchronization between Ably
+// and the clients on the channel to complete before returning the results.
+// Synchronization begins as soon as the channel is [ChannelStateAttached].
+// When set to true the results will be returned as soon as the sync is complete.
+// When set to false the current list of members will be returned without the sync completing.
+// The default is true (RTP11c1).
+func PresenceGetWithWaitForSync(wait bool) PresenceGetOption {
+ return ably.PresenceGetWithWaitForSync(wait)
+}
+
+// StatsWithDirection sets the order for which stats are returned in. Valid values are backwards which
+// orders stats from most recent to oldest, or forwards which orders stats from oldest to most recent.
+// The default is backwards (RSC6b2).
+func StatsWithDirection(d Direction) StatsOption {
+ return ably.StatsWithDirection(d)
+}
+
+// StatsWithEnd sets the time until stats are retrieved, specified as milliseconds since the Unix epoch (RSC6b1).
+func StatsWithEnd(t time.Time) StatsOption {
+ return ably.StatsWithEnd(t)
+}
+
+// StatsWithLimit sets an upper limit on the number of stats returned.
+// The default is 100, and the maximum is 1000 (RSC6b3).
+func StatsWithLimit(limit int) StatsOption {
+ return ably.StatsWithLimit(limit)
+}
+
+// StatsWithStart sets the time from which stats are retrieved, specified as milliseconds since the Unix epoch (RSC6b1).
+func StatsWithStart(t time.Time) StatsOption {
+ return ably.StatsWithStart(t)
+}
+
+// StatsWithUnit sets minute, hour, day or month. Based on the unit selected, the given start or end times are
+// rounded down to the start of the relevant interval depending on the unit granularity of the query (RSC6b4).
+func StatsWithUnit(d PeriodUnit) StatsOption {
+ return ably.StatsWithUnit(d)
+}
+
+// UpdateWithClientID sets the client ID for the update operation.
+func UpdateWithClientID(clientID string) UpdateOption {
+ return ably.UpdateWithClientID(clientID)
+}
+
+// UpdateWithDescription sets a description for the update operation.
+func UpdateWithDescription(description string) UpdateOption {
+ return ably.UpdateWithDescription(description)
+}
+
+// UpdateWithMetadata sets metadata for the update operation.
+func UpdateWithMetadata(metadata map[string]string) UpdateOption {
+ return ably.UpdateWithMetadata(metadata)
+}
+
+// UpdateWithParams sets operation params. Over HTTP, these are included as URL query
+// parameters (RSL15a, RSL15f). Over a realtime connection, these are set as protocolMessage.Params (RTL32e).
+func UpdateWithParams(params map[string]string) UpdateOption {
+ return ably.UpdateWithParams(params)
+}
+
+// WithAgents is used to add product/version key-value pairs to include in the
+// agent library identifiers. This must only be used by Ably-authored SDKs.
+// Successive calls merge their entries into any agents already configured
+// rather than replacing them.
+func WithAgents(agents map[string]string) ClientOption {
+ return ably.WithAgents(agents)
+}
+
+// WithAuthCallback is used for setting authCallback function using [ClientOption].
+// AuthCallback function is called when a new token is required.
+// The role of the callback is to obtain a fresh token, one of
+//
+// 1. An Ably Token string (https://ably.com/docs/core-features/authentication#token-process)
+// 2. A signed [TokenRequest] (https://ably.com/docs/core-features/authentication#token-request-process)
+// 3. An [TokenDetails] (https://ably.com/docs/core-features/authentication#token-process)
+// 4. [An Ably JWT].
+//
+// See the [authentication doc] for more details and associated API calls (RSA4a, RSA4, TO3j5, AO2b).
+//
+// [authentication doc]: https://ably.com/docs/core-features/authentication
+// [An Ably JWT]: https://ably.com/docs/core-features/authentication#ably-jwt-process
+func WithAuthCallback(authCallback func(a0 context.Context, a1 TokenParams) (Tokener, error)) ClientOption {
+ return ably.WithAuthCallback(authCallback)
+}
+
+// WithAuthHeaders is used for setting AuthHeaders using [ClientOption].
+// AuthHeaders are key-value pair HTTP request headers to be added to any request made to the AuthURL.
+// Useful when an application requires these to be added to validate the request or implement the response.
+// If the authHeaders object contains an authorization key, then withCredentials is set on the XHR request.
+// (RSA8c3, TO3j8, AO2e).
+func WithAuthHeaders(headers http.Header) ClientOption {
+ return ably.WithAuthHeaders(headers)
+}
+
+// WithAuthMethod is used for setting AuthMethod using [ClientOption].
+// AuthMethod specifies HTTP verb to use for any request made to the AuthURL, either GET or POST for
+// getting token information [TokenRequest] or [TokenDetails].
+// The default value is GET (RSA8c, TO3j7, AO2d)
+func WithAuthMethod(method string) ClientOption {
+ return ably.WithAuthMethod(method)
+}
+
+// WithAuthParams is used for setting AuthParams using [ClientOption].
+// AuthParams are key-value pair HTTP query params to be added to any request made to the AuthURL.
+// When the authMethod is GET, query params are added to the URL, whereas when authMethod is POST,
+// the params are sent as URL encoded form data. Useful when an application requires these to be added to
+// validate the request or implement the response (RSA8c3, RSA8c1, TO3j9, AO2f).
+func WithAuthParams(params url.Values) ClientOption {
+ return ably.WithAuthParams(params)
+}
+
+// WithAuthURL is used for setting AuthURL using [ClientOption].
+// AuthURL is a url that library will use to obtain
+// 1. An Ably Token string (https://ably.com/docs/core-features/authentication#token-process)
+// 2. A signed [TokenRequest] (https://ably.com/docs/core-features/authentication#token-request-process)
+// 3. An [TokenDetails] (https://ably.com/docs/core-features/authentication#token-process)
+// 4. [An Ably JWT].
+//
+// See the [authentication doc] for more details and associated API calls (RSA4a, RSA4, RSA8c, TO3j6, AO2c).
+//
+// This enables a client to obtain token requests from another entity,
+// so tokens can be renewed without the client requiring access to keys.
+//
+// If AuthURL is non-empty and AuthCallback is nil, the Ably library
+// builds a req (*http.Request) which then is issued against the given AuthURL
+// in order to obtain authentication token. The response is expected to
+// carry a single token string in the payload when Content-Type header
+// is "text/plain" or JSON-encoded *TokenDetails when the header
+// is "application/json".
+//
+// The req is built with the following values:
+//
+// GET requests:
+//
+// - req.URL.RawQuery is encoded from *TokenParams and AuthParams
+// - req.Header is set to AuthHeaders
+//
+// POST requests:
+//
+// - req.Header is set to AuthHeaders
+// - Content-Type is set to "application/x-www-form-urlencoded" and
+// the payload is encoded from *TokenParams and AuthParams
+//
+// [authentication doc]: https://ably.com/docs/core-features/authentication
+// [An Ably JWT]: https://ably.com/docs/core-features/authentication#ably-jwt-process
+func WithAuthURL(url string) ClientOption {
+ return ably.WithAuthURL(url)
+}
+
+// WithAutoConnect is used for setting NoConnect using [ClientOption].
+// NoConnect when set to false, the client connects to Ably as soon as it is instantiated.
+// You can set this to true and explicitly connect to Ably using the [Connection]#connect()
+// The default is false (RTC1b, TO3e).
+func WithAutoConnect(autoConnect bool) ClientOption {
+ return ably.WithAutoConnect(autoConnect)
+}
+
+// WithChannelRetryTimeout is used for setting ChannelRetryTimeout using [ClientOption].
+// ChannelRetryTimeout when a channel becomes [ChannelStateSuspended} following a server initiated
+// [ChannelStateDetached], after this delay, if the channel is still [ChannelStateSuspended]
+// and the connection is in [ConnectionStateConnected], the client library will attempt to re-attach
+// the channel automatically.
+// The default is 15 seconds (RTL13b, TO3l7).
+func WithChannelRetryTimeout(d time.Duration) ClientOption {
+ return ably.WithChannelRetryTimeout(d)
+}
+
+// WithClientID is used for setting ClientID using [ClientOption].
+// ClientID is used for identifying this client when publishing messages or for presence purposes.
+// The clientId can be any non-empty string, except it cannot contain a *.
+// This option is primarily intended to be used in situations where the library is instantiated with a key.
+// Note that a clientId may also be implicit in a token used to instantiate the library.
+// An error will be raised if a clientId specified here conflicts with the clientId implicit in the token.
+// (RSC17, RSA4, RSA15, TO3a).
+func WithClientID(clientID string) ClientOption {
+ return ably.WithClientID(clientID)
+}
+
+// WithDefaultTokenParams is used for setting DefaultTokenParams token using [ClientOption].
+// DefaultTokenParams when provided, it overrides the client library defaults when issuing new Ably Tokens
+// for multiple Ably [TokenRequest] (TO3j11).
+func WithDefaultTokenParams(params TokenParams) ClientOption {
+ return ably.WithDefaultTokenParams(params)
+}
+
+// WithDisconnectedRetryTimeout is used for setting DisconnectedRetryTimeout using [ClientOption].
+// DisconnectedRetryTimeout when the connection enters the [ConnectionStateDisconnected] state, after this
+// timeout, if the state is still [ConnectionStateDisconnected], the client library will attempt
+// to reconnect automatically.
+// The default is 15 seconds (TO3l1).
+func WithDisconnectedRetryTimeout(d time.Duration) ClientOption {
+ return ably.WithDisconnectedRetryTimeout(d)
+}
+
+// WithEchoMessages is used for setting NoEcho using [ClientOption].
+// NoEcho if set to true, prevents messages originating from this connection being echoed back
+// on the same connection.
+// The default is false (RTC1a, TO3h).
+func WithEchoMessages(echo bool) ClientOption {
+ return ably.WithEchoMessages(echo)
+}
+
+// WithEndpoint sets the endpoint the client connects to (REC1). It takes one of:
+//
+// - a routing policy name, such as "main", which resolves to [name].realtime.ably.net;
+// - a non-production routing policy name, such as "nonprod:sandbox", which resolves to
+// [name].realtime.ably-nonprod.net;
+// - a hostname, such as "localhost" or "ably.example.com", used as given. A value is
+// treated as a hostname if it contains "." or "::" or is "localhost". A hostname
+// endpoint has no default fallback hosts.
+//
+// Unset, the endpoint is "main". To use a port other than the default, set it with
+// [WithPort] or [WithTLSPort]: "localhost:8080" is not a hostname and does not resolve.
+// See [Platform Customization] for more information.
+//
+// [Platform Customization]: https://ably.com/docs/platform-customization
+func WithEndpoint(endpoint string) ClientOption {
+ return ably.WithEndpoint(endpoint)
+}
+
+// WithExperimentalObjectsPlugin configures the client to use the given plugin to implement
+// [LiveObjects] functionality.
+//
+// NOTE: this option is experimental, the LiveObjects plugin API may change in a
+// backwards incompatible way between minor/patch versions. Once the API has been finalised,
+// a new non-experimental option will be added, and this one will be removed.
+//
+// [LiveObjects]: https://ably.com/docs/liveobjects
+func WithExperimentalObjectsPlugin(plugin ObjectsPlugin) ClientOption {
+ return ably.WithExperimentalObjectsPlugin(plugin)
+}
+
+// WithFallbackHosts is used for setting FallbackHosts using [ClientOption].
+// FallbackHosts is an array of fallback hosts to be used in the case of an error necessitating
+// the use of an alternative host. If you have been provided a set of custom fallback hosts by Ably,
+// please specify them here (RSC15b, RSC15a, TO3k6).
+func WithFallbackHosts(hosts []string) ClientOption {
+ return ably.WithFallbackHosts(hosts)
+}
+
+// WithFallbackRetryTimeout is used for setting FallbackRetryTimeout using [ClientOption].
+// FallbackRetryTimeout is the max time in milliseconds before HTTP requests are retried against the default endpoint.
+// The default is 600 seconds (TO3l10).
+func WithFallbackRetryTimeout(fallbackRetryTimeout time.Duration) ClientOption {
+ return ably.WithFallbackRetryTimeout(fallbackRetryTimeout)
+}
+
+// WithHTTPClient is used for setting HTTPClient using [ClientOption].
+// It specifies the client used for the HTTP requests a [Client] makes, such as for history and token requests.
+// When set to nil, a client configured with default settings is used.
+func WithHTTPClient(client *http.Client) ClientOption {
+ return ably.WithHTTPClient(client)
+}
+
+// WithHTTPMaxRetryCount is used for setting HTTPMaxRetryCount using [ClientOption].
+// HTTPMaxRetryCount denotes the maximum number of fallback hosts to use as a fallback when an HTTP request
+// to the primary host is unreachable or indicates that it is unserviceable. The default value is 3 (TO3l5).
+func WithHTTPMaxRetryCount(count int) ClientOption {
+ return ably.WithHTTPMaxRetryCount(count)
+}
+
+// WithHTTPOpenTimeout is used for setting HTTPOpenTimeout using [ClientOption].
+// HTTPOpenTimeout is timeout for opening a connection to Ably to initiate an HTTP request.
+// The default is 4 seconds (TO3l3).
+func WithHTTPOpenTimeout(d time.Duration) ClientOption {
+ return ably.WithHTTPOpenTimeout(d)
+}
+
+// WithHTTPRequestTimeout is used for setting HTTPRequestTimeout using [ClientOption].
+// HTTPRequestTimeout is a timeout for a client performing a complete HTTP request to Ably, including the connection phase.
+// Will only be used if no custom HTTPClient is set.
+// The default is 10 seconds (TO3l4).
+func WithHTTPRequestTimeout(timeout time.Duration) ClientOption {
+ return ably.WithHTTPRequestTimeout(timeout)
+}
+
+// WithIdempotentHTTPPublishing is used for setting IdempotentHTTPPublishing using [ClientOption].
+// IdempotentHTTPPublishing when set to true, enables idempotent publishing by assigning a
+// unique message ID client-side, allowing the Ably servers to discard automatic publish retries
+// following a failure such as a network fault.
+// The default is true (RSL1k1, RTL6a1, TO3n).
+func WithIdempotentHTTPPublishing(idempotent bool) ClientOption {
+ return ably.WithIdempotentHTTPPublishing(idempotent)
+}
+
+// WithInsecureAllowBasicAuthWithoutTLS permits an API key to be used even if the connection
+// will not use TLS, something which would otherwise not be permitted for security reasons.
+func WithInsecureAllowBasicAuthWithoutTLS() ClientOption {
+ return ably.WithInsecureAllowBasicAuthWithoutTLS()
+}
+
+// WithKey is used for setting root/non-root apikey using [ClientOption].
+// Key is a full API key string, as obtained from the Ably dashboard.
+// Use this option if you wish to use Basic authentication, or wish to be able to issue Ably Tokens
+// without needing to defer to a separate entity to sign multiple [TokenRequest].
+// Read more about [Basic authentication] (RSC1, RSA11, RSA14, TO3j1, AO2a).
+//
+// [Basic authentication]: https://ably.com/docs/core-features/authentication#basic-authentication
+func WithKey(key string) ClientOption {
+ return ably.WithKey(key)
+}
+
+// WithLogHandler is used for setting LogHandler using [ClientOption].
+// LogHandler controls the log output of the library. This is a function to handle each line of log output.
+// platform specific (TO3c)
+func WithLogHandler(handler Logger) ClientOption {
+ return ably.WithLogHandler(handler)
+}
+
+// WithLogLevel is used for setting LogLevel using [ClientOption].
+// LogLevel controls the verbosity of the logs output from the library.
+// Levels include verbose, debug, info, warn and error.
+// platform specific (TO3b)
+func WithLogLevel(level LogLevel) ClientOption {
+ return ably.WithLogLevel(level)
+}
+
+// WithPort is used for setting custom Port using [ClientOption].
+// Port is used for non-TLS connections and requests
+func WithPort(port int) ClientOption {
+ return ably.WithPort(port)
+}
+
+// WithQueryTime is used for setting UseQueryTime token using [ClientOption].
+// UseQueryTime when set to true, the library queries the Ably servers for the current time when
+// issuing multiple [TokenRequest] instead of relying on a locally-available time of day.
+// Knowing the time accurately is needed to create valid signed Ably [TokenRequest],
+// so this option is useful for library instances on auth servers where for some reason
+// the server clock cannot be kept synchronized through normal means,
+// such as an NTP daemon. The server is queried for the current time once per client library instance
+// (which stores the offset from the local clock), so if using this option you should avoid instancing
+// a new version of the library for each request.
+// The default is false (RSA9d, TO3j10, AO2a).
+func WithQueryTime(queryTime bool) ClientOption {
+ return ably.WithQueryTime(queryTime)
+}
+
+// WithQueueMessages is used for setting NoQueueing using [ClientOption].
+// NoQueueing if set to true, this disables the default behavior whereby the library queues messages on a
+// connection in the disconnected or connecting states. The default behavior enables applications to
+// submit messages immediately upon instantiating the library without having to wait for the connection
+// to be established. Applications may use this option to disable queueing if they wish to have
+// application-level control over the queueing.
+// The default is false (RTP16b, TO3g).
+func WithQueueMessages(queue bool) ClientOption {
+ return ably.WithQueueMessages(queue)
+}
+
+// WithRealtimeRequestTimeout is used for setting RealtimeRequestTimeout using [ClientOption].
+// RealtimeRequestTimeout is the timeout for the wait of acknowledgement for operations performed via a
+// realtime connection, before the client library considers a request failed and triggers a failure condition.
+// Operations include establishing a connection with Ably, or sending a HEARTBEAT, CONNECT, ATTACH, DETACH or
+// CLOSE request. It is the equivalent of httpRequestTimeout but for realtime operations, rather than HTTP requests.
+// The default is 10 seconds (TO3l11).
+func WithRealtimeRequestTimeout(d time.Duration) ClientOption {
+ return ably.WithRealtimeRequestTimeout(d)
+}
+
+// WithRecover is used for setting Recover using [ClientOption].
+// Recover enables a connection to inherit the state of a previous connection that may have existed
+// under a different instance of the Realtime library. This might typically be used by clients of the browser
+// library to ensure connection state can be preserved when the user refreshes the page.
+// A recovery key string can be explicitly provided, or alternatively if a callback function is provided,
+// the client library will automatically persist the recovery key between page reloads and call the callback
+// when the connection is recoverable. The callback is then responsible for confirming whether the connection
+// should be recovered or not. See connection state recovery for further information (RTC1c, TO3i).
+func WithRecover(key string) ClientOption {
+ return ably.WithRecover(key)
+}
+
+// WithSuspendedRetryTimeout is used for setting SuspendedRetryTimeout using [ClientOption].
+// SuspendedRetryTimeout is the timeout when the connection enters the [ConnectionStateSuspended] state,
+// after this timeout, if the state is still [ConnectionStateSuspended], the client library attempts
+// to reconnect automatically.
+// The default is 30 seconds (RTN14d, TO3l2).
+func WithSuspendedRetryTimeout(d time.Duration) ClientOption {
+ return ably.WithSuspendedRetryTimeout(d)
+}
+
+// WithTLS is used for setting NoTLS using [ClientOption].
+// NoTLS when set to true, the client will use an insecure connection.
+// The default is false, meaning a TLS connection will be used to connect to Ably (RSC18, TO3d).
+func WithTLS(tls bool) ClientOption {
+ return ably.WithTLS(tls)
+}
+
+// WithTLSPort is used for setting TLSPort using [ClientOption].
+// TLSPort enables a non-default Ably port to be specified.
+// This is used for TLS connections and requests and restricted to development environments only.
+// The default value is 80 (TO3k4)>
+func WithTLSPort(port int) ClientOption {
+ return ably.WithTLSPort(port)
+}
+
+// WithToken is used for setting authenticated token using [ClientOption].
+// This can either be a token string (obtained from the token property of a [TokenDetails] component of
+// an Ably [TokenRequest] response, or a JSON Web Token satisfying the Ably requirements for JWTs).
+//
+// This option is mostly useful for testing: since tokens are short-lived,
+// in production you almost always want to use an authentication method that enables the client library
+// to renew the token automatically when the previous one expires, such as AuthURL or authCallback.
+// Read more about [Token authentication] (RSA4a, RSA4, TO3j2).
+//
+// [Token authentication]: https://ably.com/docs/core-features/authentication#token-authentication
+func WithToken(token string) ClientOption {
+ return ably.WithToken(token)
+}
+
+// WithTokenDetails is used for setting authenticated [TokenDetails] object using [ClientOption].
+// Only TokenDetails.Token can be set via token string (obtained from the token property of a [TokenDetails]
+// component of an Ably [TokenRequest] response, or a JSON Web Token satisfying the Ably requirements for JWTs).
+// This option is mostly useful for testing: since tokens are short-lived,
+// in production you almost always want to use an authentication method that enables the client library
+// to renew the token automatically when the previous one expires, such as AuthURL or authCallback.
+// Read more about [Token authentication] (RSA4a, RSA4, TO3j3).
+//
+// [Token authentication]: https://ably.com/docs/core-features/authentication#token-authentication
+func WithTokenDetails(details *TokenDetails) ClientOption {
+ return ably.WithTokenDetails(details)
+}
+
+// WithTransportParams is used for setting TransportParams using [ClientOption].
+// TransportParams is a set of key-value pairs that can be used to pass in arbitrary connection parameters,
+// such as heartbeatInterval or remainPresentFor (RTC1f).
+func WithTransportParams(params url.Values) ClientOption {
+ return ably.WithTransportParams(params)
+}
+
+// WithUseBinaryProtocol is used for setting NoBinaryProtocol using [ClientOption].
+// NoBinaryProtocol when set to true, JSON text encoding is used.
+// When false, the more efficient MsgPack binary encoding is used.
+// The default is true (TO3f).
+func WithUseBinaryProtocol(use bool) ClientOption {
+ return ably.WithUseBinaryProtocol(use)
+}
+
+// WithUseTokenAuth is used for setting UseTokenAuth using [ClientOption].
+// UseTokenAuth when set to true, forces token authentication to be used by the library.
+// If a clientId is not specified in the [ClientOption] or [TokenParams],
+// then the Ably Token issued is anonymous (RSA4, RSA14, TO3j4).
+func WithUseTokenAuth(use bool) ClientOption {
+ return ably.WithUseTokenAuth(use)
+}
+
+// WithVCDiffPlugin configures the client to use the given plugin to decode vcdiff-encoded
+// delta messages (PC3). The plugin must implement the VCDiffDecoder interface.
+// This is required for channels that have delta encoding enabled with params {"delta": "vcdiff"}.
+func WithVCDiffPlugin(plugin VCDiffDecoder) ClientOption {
+ return ably.WithVCDiffPlugin(plugin)
+}
diff --git a/device/pubsub/api_test.go b/device/pubsub/api_test.go
new file mode 100644
index 000000000..be142eaf5
--- /dev/null
+++ b/device/pubsub/api_test.go
@@ -0,0 +1,54 @@
+package pubsub_test
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/ably/ably-pubsub-go/device/pubsub"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// TestReExportedAPIIsSelfContained builds a realistic device client and
+// touches the re-exported API around it, so that a symbol dropped from the
+// re-exported surface fails here rather than in a user's build.
+func TestReExportedAPIIsSelfContained(t *testing.T) {
+ client, err := pubsub.NewClient(
+ pubsub.WithKey("fake:key"),
+ pubsub.WithClientID("me"),
+ pubsub.WithAutoConnect(false),
+ pubsub.WithEchoMessages(false),
+ pubsub.WithLogLevel(pubsub.LogError),
+ pubsub.WithRealtimeRequestTimeout(time.Second),
+ pubsub.WithTokenDetails(&pubsub.TokenDetails{Token: "token"}),
+ )
+ require.NoError(t, err)
+ t.Cleanup(func() { client.Close() })
+
+ channel := client.Channels.Get("some-channel",
+ pubsub.ChannelWithParams("rewind", "1"),
+ pubsub.ChannelWithModes(pubsub.ChannelModeSubscribe),
+ )
+ assert.Equal(t, pubsub.ChannelStateInitialized, channel.State())
+
+ var (
+ _ pubsub.ConnectionState = client.Connection.State()
+ _ *pubsub.RealtimeChannels = client.Channels
+ _ *pubsub.RealtimePresence = channel.Presence
+ _ pubsub.HistoryRequest = channel.History(pubsub.HistoryWithLimit(10), pubsub.HistoryWithDirection(pubsub.Backwards))
+ _ pubsub.StatsRequest = client.Stats(pubsub.StatsWithUnit(pubsub.PeriodHour))
+ _ func(context.Context) error = channel.Attach
+ )
+
+ unsubscribe := client.Connection.OnAll(func(pubsub.ConnectionStateChange) {})
+ unsubscribe()
+
+ msg := &pubsub.Message{Name: "event", Data: "payload"}
+ assert.Equal(t, "event", msg.Name)
+
+ _, err = pubsub.NewClient(pubsub.WithKey("malformed"))
+ var info *pubsub.ErrorInfo
+ require.ErrorAs(t, err, &info)
+ assert.Equal(t, pubsub.ErrInvalidCredential, info.Code)
+}
diff --git a/device/pubsub/device.go b/device/pubsub/device.go
new file mode 100644
index 000000000..55497b490
--- /dev/null
+++ b/device/pubsub/device.go
@@ -0,0 +1,31 @@
+// Package pubsub provides the Ably Pub/Sub client for devices: applications
+// running on end-user devices, whose connections are identified by a
+// clientId and counted on accounts with monthly-active-user billing.
+//
+// It is imported from github.com/ably/ably-pubsub-go/device/pubsub. Both entry
+// points are named pubsub, after the product, so that they read the same at
+// the call site and the device segment of the import path alone says which side
+// the code runs on.
+//
+// The rest of the API this package exposes — channels, messages, presence,
+// options, errors — is re-exported from the implementation in internal/ably
+// and lives in api.go. The parts of it that only a server's HTTP client
+// can reach are not re-exported here.
+package pubsub
+
+import "github.com/ably/ably-pubsub-go/internal/ably"
+
+// agentName declares the side in the Ably-Agent header (RSC7d) so that
+// traffic from clients constructed by this package is classified as
+// device-side.
+const agentName = "ably-pubsub-device"
+
+// Client is a device Pub/Sub client.
+type Client = ably.Realtime
+
+// NewClient constructs a device Pub/Sub client: a realtime connection to
+// Ably with channels, presence and history.
+func NewClient(opts ...ClientOption) (*Client, error) {
+ opts = append(opts[:len(opts):len(opts)], ably.WithAgents(map[string]string{agentName: ""}))
+ return ably.NewRealtime(opts...)
+}
diff --git a/device/pubsub/device_test.go b/device/pubsub/device_test.go
new file mode 100644
index 000000000..aeea1107c
--- /dev/null
+++ b/device/pubsub/device_test.go
@@ -0,0 +1,43 @@
+package pubsub_test
+
+import (
+ "context"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+
+ "github.com/ably/ably-pubsub-go/device/pubsub"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNewClient_DeclaresDeviceAgent(t *testing.T) {
+ var agent string
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ agent = r.Header.Get(ably.AblyAgentHeaderName)
+ w.WriteHeader(http.StatusInternalServerError)
+ }))
+ defer ts.Close()
+ u, err := url.Parse(ts.URL)
+ require.NoError(t, err)
+
+ // The realtime client's HTTP requests carry the same agents as its
+ // connection, so assert the header via Time without connecting.
+ client, err := pubsub.NewClient(
+ ably.WithEndpoint(u.Host),
+ ably.WithTLS(false),
+ ably.WithUseTokenAuth(true),
+ ably.WithAutoConnect(false),
+ )
+ require.NoError(t, err)
+
+ client.Time(context.Background())
+ assert.Equal(t, ably.AgentIdentifier(map[string]string{"ably-pubsub-device": ""}), agent)
+ // Literal, not derived: the family and side identifiers are what Ably's
+ // traffic classification keys on, so a change to either must fail here.
+ assert.True(t, strings.HasPrefix(agent, "ably-pubsub-go/"), "wrong SDK family: %q", agent)
+ assert.True(t, strings.HasSuffix(agent, " ably-pubsub-device"), "missing side agent: %q", agent)
+}
diff --git a/examples/README.md b/examples/README.md
index 5b4a9fe32..154ecddf1 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -13,22 +13,22 @@
`go run pub-sub/main.go`
-2. REST
+2. HTTP
-- Go to rest dir `cd rest`
+- Go to http dir `cd http`
- Set the `ABLY_KEY` environment variable to your [Ably API key](https://faqs.ably.com/setting-up-and-managing-api-keys)
-- Rest channel publish
+- HTTP channel publish
`go run publish/main.go`
-- Rest channel presence
+- HTTP channel presence
`go run presence/main.go`
-- Rest channel message history
+- HTTP channel message history
`go run history/main.go`
-- Rest application stats
+- HTTP application stats
`go run stats/main.go`
diff --git a/examples/rest/history/main.go b/examples/http/history/main.go
similarity index 72%
rename from examples/rest/history/main.go
rename to examples/http/history/main.go
index 3734dcd33..ba2013c3e 100644
--- a/examples/rest/history/main.go
+++ b/examples/http/history/main.go
@@ -6,25 +6,25 @@ import (
"os"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/examples"
+ "github.com/ably/ably-pubsub-go/examples"
+ "github.com/ably/ably-pubsub-go/server/pubsub"
)
func main() {
// Connect to Ably using the API key and ClientID
- client, err := ably.NewREST(
- ably.WithKey(os.Getenv(examples.AblyKey)),
- ably.WithClientID(examples.UserName))
+ client, err := pubsub.NewHTTPClient(
+ pubsub.WithKey(os.Getenv(examples.AblyKey)),
+ pubsub.WithClientID(examples.UserName))
if err != nil {
panic(err)
}
- checkRestChannelMessageHistory(client)
- checkRestChannelPresenceHistory(client)
+ checkHTTPChannelMessageHistory(client)
+ checkHTTPChannelPresenceHistory(client)
}
-func checkRestChannelMessageHistory(client *ably.REST) {
+func checkHTTPChannelMessageHistory(client *pubsub.HTTPClient) {
channel := client.Channels.Get(examples.ChannelName)
realtimeClient := examples.InitRealtimeClient()
examples.RealtimePublish(realtimeClient, "Hey there!")
@@ -36,7 +36,7 @@ func checkRestChannelMessageHistory(client *ably.REST) {
realtimeClient.Close()
}
-func checkRestChannelPresenceHistory(client *ably.REST) {
+func checkHTTPChannelPresenceHistory(client *pubsub.HTTPClient) {
channel := client.Channels.Get(examples.ChannelName)
realtimeClient := examples.InitRealtimeClient()
examples.RealtimeEnterPresence(realtimeClient)
@@ -47,7 +47,7 @@ func checkRestChannelPresenceHistory(client *ably.REST) {
realtimeClient.Close()
}
-func printChannelMessageHistory(channel *ably.RESTChannel) {
+func printChannelMessageHistory(channel *pubsub.HTTPChannel) {
pages, err := channel.History().Pages(context.Background())
if err != nil {
panic(err)
@@ -64,7 +64,7 @@ func printChannelMessageHistory(channel *ably.RESTChannel) {
}
}
-func printChannelPresenceHistory(channel *ably.RESTChannel) {
+func printChannelPresenceHistory(channel *pubsub.HTTPChannel) {
pages, err := channel.Presence.History().Pages(context.Background())
if err != nil {
panic(err)
diff --git a/examples/rest/presence/main.go b/examples/http/presence/main.go
similarity index 71%
rename from examples/rest/presence/main.go
rename to examples/http/presence/main.go
index 74bf537b8..a1cf5c3bd 100644
--- a/examples/rest/presence/main.go
+++ b/examples/http/presence/main.go
@@ -6,15 +6,15 @@ import (
"os"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/examples"
+ "github.com/ably/ably-pubsub-go/examples"
+ "github.com/ably/ably-pubsub-go/server/pubsub"
)
func main() {
// Connect to Ably using the API key and ClientID
- client, err := ably.NewREST(
- ably.WithKey(os.Getenv(examples.AblyKey)),
- ably.WithClientID(examples.UserName))
+ client, err := pubsub.NewHTTPClient(
+ pubsub.WithKey(os.Getenv(examples.AblyKey)),
+ pubsub.WithClientID(examples.UserName))
if err != nil {
panic(err)
@@ -23,7 +23,7 @@ func main() {
checkPresence(client)
}
-func checkPresence(client *ably.REST) {
+func checkPresence(client *pubsub.HTTPClient) {
channel := client.Channels.Get(examples.ChannelName)
realtimeClient := examples.InitRealtimeClient()
examples.RealtimeEnterPresence(realtimeClient)
@@ -35,7 +35,7 @@ func checkPresence(client *ably.REST) {
realtimeClient.Close()
}
-func printPresenceMessages(channel *ably.RESTChannel) {
+func printPresenceMessages(channel *pubsub.HTTPChannel) {
pages, err := channel.Presence.Get().Pages(context.Background())
if err != nil {
diff --git a/examples/rest/publish/main.go b/examples/http/publish/main.go
similarity index 59%
rename from examples/rest/publish/main.go
rename to examples/http/publish/main.go
index 2765399b7..53b40c5b9 100644
--- a/examples/rest/publish/main.go
+++ b/examples/http/publish/main.go
@@ -6,48 +6,48 @@ import (
"os"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/examples"
+ "github.com/ably/ably-pubsub-go/examples"
+ "github.com/ably/ably-pubsub-go/server/pubsub"
)
func main() {
// Connect to Ably using the API key and ClientID
- client, err := ably.NewREST(
- ably.WithKey(os.Getenv(examples.AblyKey)),
- ably.WithClientID(examples.UserName))
+ client, err := pubsub.NewHTTPClient(
+ pubsub.WithKey(os.Getenv(examples.AblyKey)),
+ pubsub.WithClientID(examples.UserName))
if err != nil {
panic(err)
}
- checkRestPublish(client)
- checkRestBulkPublish(client)
+ checkHTTPPublish(client)
+ checkHTTPBulkPublish(client)
}
-func checkRestPublish(client *ably.REST) {
+func checkHTTPPublish(client *pubsub.HTTPClient) {
channel := client.Channels.Get(examples.ChannelName)
realtimeClient := examples.InitRealtimeClient()
unsubscribe := examples.RealtimeSubscribeToEvent(realtimeClient)
- restPublish(channel, "Hey there")
+ httpPublish(channel, "Hey there")
time.Sleep(time.Second)
unsubscribe()
realtimeClient.Close()
}
-func checkRestBulkPublish(client *ably.REST) {
+func checkHTTPBulkPublish(client *pubsub.HTTPClient) {
channel := client.Channels.Get(examples.ChannelName)
realtimeClient := examples.InitRealtimeClient()
unsubscribe := examples.RealtimeSubscribeToEvent(realtimeClient)
- restPublishBatch(channel, "Hey there", "How are you?")
+ httpPublishBatch(channel, "Hey there", "How are you?")
time.Sleep(time.Second)
unsubscribe()
realtimeClient.Close()
}
-func restPublish(channel *ably.RESTChannel, message string) {
+func httpPublish(channel *pubsub.HTTPChannel, message string) {
err := channel.Publish(context.Background(), examples.EventName, message)
if err != nil {
@@ -56,9 +56,9 @@ func restPublish(channel *ably.RESTChannel, message string) {
}
}
-func restPublishBatch(channel *ably.RESTChannel, message1 string, message2 string) {
+func httpPublishBatch(channel *pubsub.HTTPChannel, message1 string, message2 string) {
- err := channel.PublishMultiple(context.Background(), []*ably.Message{
+ err := channel.PublishMultiple(context.Background(), []*pubsub.Message{
{Name: examples.EventName, Data: message1},
{Name: examples.EventName, Data: message2},
})
diff --git a/examples/rest/stats/main.go b/examples/http/stats/main.go
similarity index 62%
rename from examples/rest/stats/main.go
rename to examples/http/stats/main.go
index c727d1022..93300d73a 100644
--- a/examples/rest/stats/main.go
+++ b/examples/http/stats/main.go
@@ -5,15 +5,15 @@ import (
"fmt"
"os"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/examples"
+ "github.com/ably/ably-pubsub-go/examples"
+ "github.com/ably/ably-pubsub-go/server/pubsub"
)
func main() {
// Connect to Ably using the API key and ClientID
- client, err := ably.NewREST(
- ably.WithKey(os.Getenv(examples.AblyKey)),
- ably.WithClientID(examples.UserName))
+ client, err := pubsub.NewHTTPClient(
+ pubsub.WithKey(os.Getenv(examples.AblyKey)),
+ pubsub.WithClientID(examples.UserName))
if err != nil {
panic(err)
}
@@ -21,7 +21,7 @@ func main() {
printApplicationStats(client)
}
-func printApplicationStats(client *ably.REST) {
+func printApplicationStats(client *pubsub.HTTPClient) {
pages, err := client.Stats().Pages(context.Background())
if err != nil {
panic(err)
diff --git a/examples/rest/status/main.go b/examples/http/status/main.go
similarity index 62%
rename from examples/rest/status/main.go
rename to examples/http/status/main.go
index e82c01238..7fc0e3566 100644
--- a/examples/rest/status/main.go
+++ b/examples/http/status/main.go
@@ -5,15 +5,15 @@ import (
"fmt"
"os"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/examples"
+ "github.com/ably/ably-pubsub-go/examples"
+ "github.com/ably/ably-pubsub-go/server/pubsub"
)
func main() {
// Connect to Ably using the API key and ClientID
- client, err := ably.NewREST(
- ably.WithKey(os.Getenv(examples.AblyKey)),
- ably.WithClientID(examples.UserName))
+ client, err := pubsub.NewHTTPClient(
+ pubsub.WithKey(os.Getenv(examples.AblyKey)),
+ pubsub.WithClientID(examples.UserName))
if err != nil {
panic(err)
}
diff --git a/examples/realtime/presence/main.go b/examples/realtime/presence/main.go
index 322ec8fa1..b87302320 100644
--- a/examples/realtime/presence/main.go
+++ b/examples/realtime/presence/main.go
@@ -6,15 +6,15 @@ import (
"os"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/examples"
+ "github.com/ably/ably-pubsub-go/examples"
+ "github.com/ably/ably-pubsub-go/server/pubsub"
)
func main() {
// Connect to Ably using the API key and ClientID specified
- client, err := ably.NewRealtime(
- ably.WithKey(os.Getenv(examples.AblyKey)),
- ably.WithClientID(examples.UserName))
+ client, err := pubsub.NewRealtimeClient(
+ pubsub.WithKey(os.Getenv(examples.AblyKey)),
+ pubsub.WithClientID(examples.UserName))
if err != nil {
panic(err)
}
@@ -24,7 +24,7 @@ func main() {
checkPresenceEnterAndLeave(client)
}
-func checkPresenceEnter(client *ably.Realtime) {
+func checkPresenceEnter(client *pubsub.RealtimeClient) {
channel := client.Channels.Get(examples.ChannelName)
unsubscribe := subscribePresenceEnter(channel)
enterPresence(channel)
@@ -32,7 +32,7 @@ func checkPresenceEnter(client *ably.Realtime) {
unsubscribe()
}
-func checkPresenceLeave(client *ably.Realtime) {
+func checkPresenceLeave(client *pubsub.RealtimeClient) {
channel := client.Channels.Get(examples.ChannelName)
unsubscribe := subscribePresenceLeave(channel)
leavePresence(channel)
@@ -40,7 +40,7 @@ func checkPresenceLeave(client *ably.Realtime) {
unsubscribe()
}
-func checkPresenceEnterAndLeave(client *ably.Realtime) {
+func checkPresenceEnterAndLeave(client *pubsub.RealtimeClient) {
channel := client.Channels.Get(examples.ChannelName)
unsubscribe := subscribeAllPresence(channel)
enterPresence(channel)
@@ -50,7 +50,7 @@ func checkPresenceEnterAndLeave(client *ably.Realtime) {
unsubscribe()
}
-func enterPresence(channel *ably.RealtimeChannel) {
+func enterPresence(channel *pubsub.RealtimeChannel) {
pErr := channel.Presence.Enter(context.Background(), examples.UserName+" entered the channel")
if pErr != nil {
err := fmt.Errorf("error with enter presence on the channel %w", pErr)
@@ -58,7 +58,7 @@ func enterPresence(channel *ably.RealtimeChannel) {
}
}
-func enterOnBehalfOf(clientId string, channel *ably.RealtimeChannel) {
+func enterOnBehalfOf(clientId string, channel *pubsub.RealtimeChannel) {
pErr := channel.Presence.EnterClient(context.Background(), clientId, examples.UserName+" entered the channel on behalf of "+clientId)
if pErr != nil {
err := fmt.Errorf("error with enter presence on behalf of other client on the channel %w", pErr)
@@ -66,7 +66,7 @@ func enterOnBehalfOf(clientId string, channel *ably.RealtimeChannel) {
}
}
-func updatePresence(channel *ably.RealtimeChannel) {
+func updatePresence(channel *pubsub.RealtimeChannel) {
pErr := channel.Presence.Update(context.Background(), examples.UserName+" entered the channel")
if pErr != nil {
err := fmt.Errorf("error with update presence on the channel %w", pErr)
@@ -74,7 +74,7 @@ func updatePresence(channel *ably.RealtimeChannel) {
}
}
-func leavePresence(channel *ably.RealtimeChannel) {
+func leavePresence(channel *pubsub.RealtimeChannel) {
pErr := channel.Presence.Leave(context.Background(), examples.UserName+" entered the channel")
if pErr != nil {
err := fmt.Errorf("error with leave presence on the channel %w", pErr)
@@ -82,12 +82,12 @@ func leavePresence(channel *ably.RealtimeChannel) {
}
}
-func subscribeAllPresence(channel *ably.RealtimeChannel) func() {
+func subscribeAllPresence(channel *pubsub.RealtimeChannel) func() {
// Subscribe to presence events (people entering and leaving) on the channel
- unsubscribeAll, pErr := channel.Presence.SubscribeAll(context.Background(), func(msg *ably.PresenceMessage) {
- if msg.Action == ably.PresenceActionEnter {
+ unsubscribeAll, pErr := channel.Presence.SubscribeAll(context.Background(), func(msg *pubsub.PresenceMessage) {
+ if msg.Action == pubsub.PresenceActionEnter {
fmt.Printf("%v has entered the chat\n", msg.ClientID)
- } else if msg.Action == ably.PresenceActionLeave {
+ } else if msg.Action == pubsub.PresenceActionLeave {
fmt.Printf("%v has left the chat\n", msg.ClientID)
}
})
@@ -99,10 +99,10 @@ func subscribeAllPresence(channel *ably.RealtimeChannel) func() {
return unsubscribeAll
}
-func subscribePresenceEnter(channel *ably.RealtimeChannel) func() {
+func subscribePresenceEnter(channel *pubsub.RealtimeChannel) func() {
// Subscribe to presence events entering the channel
- unsubscribe, pErr := channel.Presence.Subscribe(context.Background(), ably.PresenceActionEnter, func(msg *ably.PresenceMessage) {
- if msg.Action == ably.PresenceActionEnter {
+ unsubscribe, pErr := channel.Presence.Subscribe(context.Background(), pubsub.PresenceActionEnter, func(msg *pubsub.PresenceMessage) {
+ if msg.Action == pubsub.PresenceActionEnter {
fmt.Printf("%v has entered the chat\n", msg.ClientID)
} else {
panic("Not supposed to get presence related to actions other than presence enter")
@@ -116,10 +116,10 @@ func subscribePresenceEnter(channel *ably.RealtimeChannel) func() {
return unsubscribe
}
-func subscribePresenceLeave(channel *ably.RealtimeChannel) func() {
+func subscribePresenceLeave(channel *pubsub.RealtimeChannel) func() {
// Subscribe to presence events leaving the channel
- unsubscribe, pErr := channel.Presence.Subscribe(context.Background(), ably.PresenceActionLeave, func(msg *ably.PresenceMessage) {
- if msg.Action == ably.PresenceActionLeave {
+ unsubscribe, pErr := channel.Presence.Subscribe(context.Background(), pubsub.PresenceActionLeave, func(msg *pubsub.PresenceMessage) {
+ if msg.Action == pubsub.PresenceActionLeave {
fmt.Printf("%v has left the chat\n", msg.ClientID)
} else {
panic("Not supposed to get presence related actions other than presence leave")
@@ -132,7 +132,7 @@ func subscribePresenceLeave(channel *ably.RealtimeChannel) func() {
return unsubscribe
}
-func printAllClientsOnChannel(channel *ably.RealtimeChannel) {
+func printAllClientsOnChannel(channel *pubsub.RealtimeChannel) {
clients, err := channel.Presence.Get(context.Background())
if err != nil {
panic(err)
diff --git a/examples/realtime/pub-sub/main.go b/examples/realtime/pub-sub/main.go
index df66fdaea..1bd553ae0 100644
--- a/examples/realtime/pub-sub/main.go
+++ b/examples/realtime/pub-sub/main.go
@@ -6,15 +6,15 @@ import (
"os"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/examples"
+ "github.com/ably/ably-pubsub-go/examples"
+ "github.com/ably/ably-pubsub-go/server/pubsub"
)
func main() {
// Connect to Ably using the API key and ClientID specified
- client, err := ably.NewRealtime(
- ably.WithKey(os.Getenv(examples.AblyKey)),
- ably.WithClientID(examples.UserName))
+ client, err := pubsub.NewRealtimeClient(
+ pubsub.WithKey(os.Getenv(examples.AblyKey)),
+ pubsub.WithClientID(examples.UserName))
if err != nil {
panic(err)
}
@@ -23,7 +23,7 @@ func main() {
checkSubscribeToEvent(client)
}
-func checkSubscribeAll(client *ably.Realtime) {
+func checkSubscribeAll(client *pubsub.RealtimeClient) {
channel := client.Channels.Get(examples.ChannelName)
@@ -36,7 +36,7 @@ func checkSubscribeAll(client *ably.Realtime) {
unsubscribeAll()
}
-func checkSubscribeToEvent(client *ably.Realtime) {
+func checkSubscribeToEvent(client *pubsub.RealtimeClient) {
// Connect to the Ably Channel with name 'chat'
channel := client.Channels.Get(examples.ChannelName)
@@ -50,9 +50,9 @@ func checkSubscribeToEvent(client *ably.Realtime) {
unsubscribe()
}
-func subscribeToEvent(channel *ably.RealtimeChannel) func() {
+func subscribeToEvent(channel *pubsub.RealtimeChannel) func() {
// Subscribe to messages sent on the channel with given eventName
- unsubscribe, err := channel.Subscribe(context.Background(), examples.EventName, func(msg *ably.Message) {
+ unsubscribe, err := channel.Subscribe(context.Background(), examples.EventName, func(msg *pubsub.Message) {
fmt.Printf("Received message from %v: '%v'\n", msg.ClientID, msg.Data)
})
if err != nil {
@@ -62,9 +62,9 @@ func subscribeToEvent(channel *ably.RealtimeChannel) func() {
return unsubscribe
}
-func subscribeAll(channel *ably.RealtimeChannel) func() {
+func subscribeAll(channel *pubsub.RealtimeChannel) func() {
// Subscribe to all messages sent on the channel
- unsubscribeAll, err := channel.SubscribeAll(context.Background(), func(msg *ably.Message) {
+ unsubscribeAll, err := channel.SubscribeAll(context.Background(), func(msg *pubsub.Message) {
fmt.Printf("Received message from %v: '%v'\n", msg.ClientID, msg.Data)
})
if err != nil {
@@ -74,7 +74,7 @@ func subscribeAll(channel *ably.RealtimeChannel) func() {
return unsubscribeAll
}
-func publish(channel *ably.RealtimeChannel, message string) {
+func publish(channel *pubsub.RealtimeChannel, message string) {
// Publish the message to Ably Channel
err := channel.Publish(context.Background(), examples.EventName, message)
if err != nil {
diff --git a/examples/utils.go b/examples/utils.go
index 82c16b829..23b08ce9d 100644
--- a/examples/utils.go
+++ b/examples/utils.go
@@ -6,25 +6,25 @@ import (
"fmt"
"os"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/server/pubsub"
)
-func InitRealtimeClient() *ably.Realtime {
- client, err := ably.NewRealtime(
- ably.WithKey(os.Getenv(AblyKey)),
- // ably.WithEchoMessages(true), // Uncomment to stop messages you send from being sent back
- ably.WithClientID(UserName))
+func InitRealtimeClient() *pubsub.RealtimeClient {
+ client, err := pubsub.NewRealtimeClient(
+ pubsub.WithKey(os.Getenv(AblyKey)),
+ // pubsub.WithEchoMessages(true), // Uncomment to stop messages you send from being sent back
+ pubsub.WithClientID(UserName))
if err != nil {
panic(err)
}
return client
}
-func RealtimeSubscribeToEvent(client *ably.Realtime) func() {
+func RealtimeSubscribeToEvent(client *pubsub.RealtimeClient) func() {
channel := client.Channels.Get(ChannelName)
// Subscribe to messages sent on the channel
- unsubscribe, err := channel.Subscribe(context.Background(), EventName, func(msg *ably.Message) {
+ unsubscribe, err := channel.Subscribe(context.Background(), EventName, func(msg *pubsub.Message) {
fmt.Printf("Received message from %v: '%v'\n", msg.ClientID, msg.Data)
})
if err != nil {
@@ -34,7 +34,7 @@ func RealtimeSubscribeToEvent(client *ably.Realtime) func() {
return unsubscribe
}
-func RealtimeEnterPresence(client *ably.Realtime) {
+func RealtimeEnterPresence(client *pubsub.RealtimeClient) {
channel := client.Channels.Get(ChannelName)
pErr := channel.Presence.Enter(context.Background(), UserName+" entered the channel")
if pErr != nil {
@@ -43,7 +43,7 @@ func RealtimeEnterPresence(client *ably.Realtime) {
}
}
-func RealtimeLeavePresence(client *ably.Realtime) {
+func RealtimeLeavePresence(client *pubsub.RealtimeClient) {
channel := client.Channels.Get(ChannelName)
pErr := channel.Presence.Leave(context.Background(), UserName+" entered the channel")
if pErr != nil {
@@ -52,7 +52,7 @@ func RealtimeLeavePresence(client *ably.Realtime) {
}
}
-func RealtimePublish(client *ably.Realtime, message string) {
+func RealtimePublish(client *pubsub.RealtimeClient, message string) {
channel := client.Channels.Get(ChannelName)
// Publish the message typed in to the Ably Channel
err := channel.Publish(context.Background(), EventName, message)
diff --git a/go.mod b/go.mod
index 0ee934377..3eb8788d6 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/ably/ably-go
+module github.com/ably/ably-pubsub-go
go 1.18
diff --git a/ably/ably.go b/internal/ably/ably.go
similarity index 100%
rename from ably/ably.go
rename to internal/ably/ably.go
diff --git a/ably/ably_test.go b/internal/ably/ably_test.go
similarity index 98%
rename from ably/ably_test.go
rename to internal/ably/ably_test.go
index 73d9dcb48..ec02770b8 100644
--- a/ably/ably_test.go
+++ b/internal/ably/ably_test.go
@@ -12,9 +12,9 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/ably/internal/ablyutil"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
)
type Result interface {
diff --git a/ably/auth.go b/internal/ably/auth.go
similarity index 99%
rename from ably/auth.go
rename to internal/ably/auth.go
index 56d1a3092..5f46404e0 100644
--- a/ably/auth.go
+++ b/internal/ably/auth.go
@@ -63,7 +63,7 @@ type Auth struct {
mtx sync.Mutex
method int
- client *REST
+ client *HTTPClient
// params to use with token renewal
params *TokenParams
@@ -89,7 +89,7 @@ type Auth struct {
serverTimeHandler func() (time.Time, error)
}
-func newAuth(client *REST) (*Auth, error) {
+func newAuth(client *HTTPClient) (*Auth, error) {
a := &Auth{
client: client,
onExplicitAuthorize: func(context.Context, *TokenDetails) error { return nil },
diff --git a/ably/auth_integration_test.go b/internal/ably/auth_integration_test.go
similarity index 96%
rename from ably/auth_integration_test.go
rename to internal/ably/auth_integration_test.go
index 7dd4e1c41..3aca92024 100644
--- a/ably/auth_integration_test.go
+++ b/internal/ably/auth_integration_test.go
@@ -17,8 +17,8 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -42,7 +42,7 @@ func TestAuth_BasicAuth(t *testing.T) {
rec, extraOpt := recorder()
defer rec.Stop()
opts := []ably.ClientOption{ably.WithQueryTime(true)}
- app, client := ablytest.NewREST(append(opts, extraOpt...)...)
+ app, client := ablytest.NewHTTPClient(append(opts, extraOpt...)...)
_, err := client.Time(context.Background())
assert.NoError(t, err,
@@ -66,7 +66,7 @@ func TestAuth_BasicAuth(t *testing.T) {
"Expected rec.Request(1).URL.Scheme to be https, got %s", rec.Request(1).URL.Scheme)
// Can't use basic auth over HTTP.
- _, err := ably.NewREST(app.Options(ably.WithTLS(false))...)
+ _, err := ably.NewHTTPClient(app.Options(ably.WithTLS(false))...)
assert.Error(t, err)
assert.Equal(t, ably.ErrorCode(40103), ably.UnwrapErrorCode(err),
"want code=40103; got %d", ably.UnwrapErrorCode(err))
@@ -110,7 +110,7 @@ func TestAuth_TokenAuth(t *testing.T) {
ably.WithUseTokenAuth(true),
ably.WithQueryTime(true),
}
- _, client := ablytest.NewREST(append(opts, extraOpt...)...)
+ _, client := ablytest.NewHTTPClient(append(opts, extraOpt...)...)
beforeAuth := time.Now().Add(-time.Second)
_, err := client.Time(context.Background())
@@ -157,7 +157,7 @@ func TestAuth_TokenAuth_Renew(t *testing.T) {
rec, extraOpt := recorder()
defer rec.Stop()
opts := []ably.ClientOption{ably.WithUseTokenAuth(true)}
- app, client := ablytest.NewREST(append(opts, extraOpt...)...)
+ app, client := ablytest.NewHTTPClient(append(opts, extraOpt...)...)
params := &ably.TokenParams{
TTL: time.Second.Milliseconds(),
@@ -197,9 +197,9 @@ func TestAuth_TokenAuth_Renew(t *testing.T) {
rec.Reset()
opts = app.Options(opts...)
opts = append(opts, ably.WithKey(""), ably.WithTokenDetails(tok))
- client, err = ably.NewREST(opts...)
+ client, err = ably.NewHTTPClient(opts...)
require.NoError(t, err,
- "NewREST()=%v", err)
+ "NewHTTPClient()=%v", err)
_, err = client.Stats().Pages(context.Background())
assert.Error(t, err)
// Ensure no requests were made to Ably servers.
@@ -214,7 +214,7 @@ func TestAuth_RequestToken(t *testing.T) {
ably.WithAuthParams(url.Values{"param_1": []string{"this", "should", "get", "overwritten"}}),
}
defer rec.Stop()
- app, client := ablytest.NewREST(append(opts, extraOpt...)...)
+ app, client := ablytest.NewHTTPClient(append(opts, extraOpt...)...)
server := ablytest.MustAuthReverseProxy(app.Options(append(opts, extraOpt...)...)...)
defer safeclose(t, server)
@@ -333,9 +333,9 @@ func TestAuth_RequestToken(t *testing.T) {
optsURL := append(app.Options(opts...),
ably.WithToken(tokURL.Token),
)
- c, err := ably.NewREST(optsURL...)
+ c, err := ably.NewHTTPClient(optsURL...)
require.NoError(t, err,
- "NewREST()=%v", err)
+ "NewHTTPClient()=%v", err)
_, err = c.Stats().Pages(context.Background())
assert.NoError(t, err,
"c.Stats()=%v (method=%s)", err, method)
@@ -382,7 +382,7 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) {
assert.True(t, strings.HasPrefix(jwt, "ey"))
rec, optn := ablytest.NewHttpRecorder()
- rest, err := ably.NewREST(app.Options(
+ rest, err := ably.NewHTTPClient(app.Options(
ably.WithToken(jwt),
optn[0],
)...)
@@ -404,7 +404,7 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) {
app := ablytest.MustSandbox()
rec, optn := ablytest.NewHttpRecorder()
- rest, err := ably.NewREST(app.Options(
+ rest, err := ably.NewHTTPClient(app.Options(
ably.WithAuthURL(ablytest.CREATE_JWT_URL),
ably.WithAuthParams(app.GetJwtAuthParams(30*time.Second, false)),
optn[0],
@@ -447,7 +447,7 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) {
})
rec, optn := ablytest.NewHttpRecorder()
- rest, err := ably.NewREST(app.Options(
+ rest, err := ably.NewHTTPClient(app.Options(
authCallback,
optn[0],
)...)
@@ -471,7 +471,7 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) {
app := ablytest.MustSandbox()
rec, optn := ablytest.NewHttpRecorder()
- rest, err := ably.NewREST(app.Options(
+ rest, err := ably.NewHTTPClient(app.Options(
ably.WithAuthURL(ablytest.CREATE_JWT_URL),
ably.WithAuthParams(app.GetJwtAuthParams(30*time.Second, true)),
optn[0],
@@ -493,7 +493,7 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) {
func TestAuth_ReuseClientID(t *testing.T) {
opts := []ably.ClientOption{ably.WithUseTokenAuth(true)}
- _, client := ablytest.NewREST(opts...)
+ _, client := ablytest.NewHTTPClient(opts...)
params := &ably.TokenParams{
ClientID: "reuse-me",
@@ -528,7 +528,7 @@ func TestAuth_RequestToken_PublishClientID(t *testing.T) {
}
for i, cas := range cases {
- rclient, err := ably.NewREST(app.Options()...)
+ rclient, err := ably.NewHTTPClient(app.Options()...)
require.NoError(t, err)
params := &ably.TokenParams{
ClientID: cas.authAs,
@@ -678,7 +678,7 @@ func TestAuth_ClientID(t *testing.T) {
}
func TestAuth_CreateTokenRequest(t *testing.T) {
- app, client := ablytest.NewREST()
+ app, client := ablytest.NewHTTPClient()
opts := []ably.AuthOption{
ably.AuthWithQueryTime(true),
@@ -831,7 +831,7 @@ func TestAuth_IgnoreTimestamp_QueryTime(t *testing.T) {
}
for _, test := range tests {
- app, client := ablytest.NewREST(append(test.opt, extraOpt...)...)
+ app, client := ablytest.NewHTTPClient(append(test.opt, extraOpt...)...)
prevTokenParams := client.Auth.Params()
prevAuthOptions := client.Auth.AuthOptions()
prevUseQueryTime := prevAuthOptions.UseQueryTime
@@ -863,6 +863,6 @@ func TestAuth_RSA7c(t *testing.T) {
app := ablytest.MustSandbox()
opts := app.Options()
opts = append(opts, ably.WithClientID("*"))
- _, err := ably.NewREST(opts...)
+ _, err := ably.NewHTTPClient(opts...)
assert.Error(t, err)
}
diff --git a/ably/auth_internal_test.go b/internal/ably/auth_internal_test.go
similarity index 97%
rename from ably/auth_internal_test.go
rename to internal/ably/auth_internal_test.go
index de16aec49..9c8c73113 100644
--- a/ably/auth_internal_test.go
+++ b/internal/ably/auth_internal_test.go
@@ -67,12 +67,12 @@ func TestAddHeaders(t *testing.T) {
func TestNewAuth(t *testing.T) {
tests := map[string]struct {
- client *REST
+ client *HTTPClient
expectedMethod int
expectedErr error
}{
"Use authBasic for a client with a valid key and no token": {
- client: &REST{
+ client: &HTTPClient{
opts: &clientOptions{authOptions: authOptions{
Key: "abc:def",
}},
@@ -81,7 +81,7 @@ func TestNewAuth(t *testing.T) {
expectedErr: nil,
},
"Use authToken for a client with a valid key and a token": {
- client: &REST{
+ client: &HTTPClient{
opts: &clientOptions{authOptions: authOptions{
Key: "abc:def",
Token: "123",
@@ -91,7 +91,7 @@ func TestNewAuth(t *testing.T) {
expectedErr: nil,
},
"Can handle a client with an invalid key": {
- client: &REST{
+ client: &HTTPClient{
opts: &clientOptions{authOptions: authOptions{
Key: "abcdef",
}},
@@ -99,7 +99,7 @@ func TestNewAuth(t *testing.T) {
expectedErr: newError(ErrInvalidCredential, errInvalidKey),
},
"Can handle an invalid auth URL": {
- client: &REST{
+ client: &HTTPClient{
opts: &clientOptions{authOptions: authOptions{
Key: "abc:def",
AuthURL: ":",
@@ -219,7 +219,7 @@ func TestCreateTokenRequest(t *testing.T) {
"Can create a token request": {
auth: &Auth{
clientID: "aClientID",
- client: &REST{
+ client: &HTTPClient{
opts: &clientOptions{authOptions: authOptions{
Key: "abc:def",
}},
@@ -258,7 +258,7 @@ func TestRequestToken(t *testing.T) {
}{
"Can request successfully request token when a token is found in auth options": {
auth: &Auth{
- client: &REST{
+ client: &HTTPClient{
log: logger{l: &stdLogger{mocklogger}},
},
},
@@ -269,7 +269,7 @@ func TestRequestToken(t *testing.T) {
"Can handle an error when making a http request to request token ": {
auth: &Auth{
clientID: "aClientID",
- client: &REST{
+ client: &HTTPClient{
hostCache: &hostCache{},
log: logger{l: &stdLogger{mocklogger}},
opts: &clientOptions{authOptions: authOptions{
diff --git a/ably/auth_test.go b/internal/ably/auth_test.go
similarity index 93%
rename from ably/auth_test.go
rename to internal/ably/auth_test.go
index 7b1e8081a..dc5104d44 100644
--- a/ably/auth_test.go
+++ b/internal/ably/auth_test.go
@@ -9,7 +9,7 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
"github.com/stretchr/testify/assert"
)
@@ -20,7 +20,7 @@ func TestAuth_TimestampRSA10k(t *testing.T) {
t.Run("must use local time when UseQueryTime is false", func(t *testing.T) {
- rest, _ := ably.NewREST(
+ rest, _ := ably.NewHTTPClient(
ably.WithKey("fake:key"),
ably.WithNow(func() time.Time {
return now
@@ -36,7 +36,7 @@ func TestAuth_TimestampRSA10k(t *testing.T) {
})
t.Run("must use server time when UseQueryTime is true", func(t *testing.T) {
- rest, _ := ably.NewREST(
+ rest, _ := ably.NewHTTPClient(
ably.WithKey("fake:key"),
ably.WithNow(func() time.Time {
return now
@@ -54,7 +54,7 @@ func TestAuth_TimestampRSA10k(t *testing.T) {
t.Run("must use server time offset ", func(t *testing.T) {
now := now
- rest, _ := ably.NewREST(
+ rest, _ := ably.NewHTTPClient(
ably.WithKey("fake:key"),
ably.WithNow(func() time.Time {
return now
diff --git a/ably/crypto.go b/internal/ably/crypto.go
similarity index 100%
rename from ably/crypto.go
rename to internal/ably/crypto.go
diff --git a/ably/crypto_spec_test.go b/internal/ably/crypto_spec_test.go
similarity index 98%
rename from ably/crypto_spec_test.go
rename to internal/ably/crypto_spec_test.go
index 222bdb2b7..360f82e6e 100644
--- a/ably/crypto_spec_test.go
+++ b/internal/ably/crypto_spec_test.go
@@ -7,7 +7,7 @@ import (
"crypto/aes"
"testing"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/doc.go b/internal/ably/doc.go
similarity index 92%
rename from ably/doc.go
rename to internal/ably/doc.go
index 53d8a593c..91d7472d7 100644
--- a/ably/doc.go
+++ b/internal/ably/doc.go
@@ -1,4 +1,9 @@
-// Package ably
+// Package ably is the implementation of the Ably Go Client Library SDK.
+//
+// It is internal: applications reach this API through one of the SDK's entry
+// points, [github.com/ably/ably-pubsub-go/server/pubsub] or
+// [github.com/ably/ably-pubsub-go/device/pubsub], each of which re-exports the
+// part of it that its users need.
//
// # Ably Go Client Library SDK API Reference
//
diff --git a/ably/doc_test.go b/internal/ably/doc_test.go
similarity index 94%
rename from ably/doc_test.go
rename to internal/ably/doc_test.go
index 6437163a7..08b3f25af 100644
--- a/ably/doc_test.go
+++ b/internal/ably/doc_test.go
@@ -4,7 +4,7 @@ import (
"fmt"
"context"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
)
func Example_paginatedResults() {
diff --git a/ably/error.go b/internal/ably/error.go
similarity index 100%
rename from ably/error.go
rename to internal/ably/error.go
diff --git a/ably/error_internal_test.go b/internal/ably/error_internal_test.go
similarity index 100%
rename from ably/error_internal_test.go
rename to internal/ably/error_internal_test.go
diff --git a/ably/error_names.go b/internal/ably/error_names.go
similarity index 96%
rename from ably/error_names.go
rename to internal/ably/error_names.go
index ffa11b14a..db1aece33 100644
--- a/ably/error_names.go
+++ b/internal/ably/error_names.go
@@ -31,4 +31,5 @@ const (
ErrProtocolError ErrorCode = 80013
ErrChannelOperationFailed ErrorCode = 90000
ErrChannelOperationFailedInvalidChannelState ErrorCode = 90001
+ ErrChannelReleaseInvalidState ErrorCode = 90011
)
diff --git a/ably/error_test.go b/internal/ably/error_test.go
similarity index 96%
rename from ably/error_test.go
rename to internal/ably/error_test.go
index fe7ea9e0b..5e69a70a9 100644
--- a/ably/error_test.go
+++ b/internal/ably/error_test.go
@@ -15,15 +15,15 @@ import (
"strings"
"testing"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
"github.com/stretchr/testify/assert"
)
func TestErrorResponseWithInvalidKey(t *testing.T) {
opts := []ably.ClientOption{ably.WithKey(":")}
- _, e := ably.NewREST(opts...)
+ _, e := ably.NewHTTPClient(opts...)
assert.Error(t, e,
- "NewREST(): expected err != nil")
+ "NewHTTPClient(): expected err != nil")
err, ok := e.(*ably.ErrorInfo)
assert.True(t, ok,
"want e be *ably.Error; was %T", e)
@@ -58,7 +58,7 @@ func TestIssue127ErrorResponse(t *testing.T) {
}
port, _ := strconv.ParseInt(endpointURL.Port(), 10, 0)
opts = append(opts, ably.WithPort(int(port)))
- client, err := ably.NewREST(opts...)
+ client, err := ably.NewHTTPClient(opts...)
assert.NoError(t, err)
_, err = client.Time(context.Background())
@@ -138,7 +138,7 @@ func TestIssue_154(t *testing.T) {
}
port, _ := strconv.ParseInt(endpointURL.Port(), 10, 0)
opts = append(opts, ably.WithPort(int(port)))
- client, e := ably.NewREST(opts...)
+ client, e := ably.NewHTTPClient(opts...)
assert.NoError(t, e)
_, err = client.Time(context.Background())
diff --git a/ably/errors.go b/internal/ably/errors.go
similarity index 100%
rename from ably/errors.go
rename to internal/ably/errors.go
diff --git a/ably/event_emitter.go b/internal/ably/event_emitter.go
similarity index 100%
rename from ably/event_emitter.go
rename to internal/ably/event_emitter.go
diff --git a/ably/event_emitter_integration_test.go b/internal/ably/event_emitter_integration_test.go
similarity index 94%
rename from ably/event_emitter_integration_test.go
rename to internal/ably/event_emitter_integration_test.go
index e76c831c6..1b9cf74c4 100644
--- a/ably/event_emitter_integration_test.go
+++ b/internal/ably/event_emitter_integration_test.go
@@ -6,8 +6,8 @@ package ably_test
import (
"testing"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/event_emitter_spec_integration_test.go b/internal/ably/event_emitter_spec_integration_test.go
similarity index 98%
rename from ably/event_emitter_spec_integration_test.go
rename to internal/ably/event_emitter_spec_integration_test.go
index f279473e9..d2f50f0f3 100644
--- a/ably/event_emitter_spec_integration_test.go
+++ b/internal/ably/event_emitter_spec_integration_test.go
@@ -8,8 +8,8 @@ import (
"math/rand"
"testing"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/example_message_updates_test.go b/internal/ably/example_message_updates_test.go
similarity index 91%
rename from ably/example_message_updates_test.go
rename to internal/ably/example_message_updates_test.go
index d70320b1e..50d8f2d81 100644
--- a/ably/example_message_updates_test.go
+++ b/internal/ably/example_message_updates_test.go
@@ -4,12 +4,12 @@ import (
"context"
"fmt"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
)
// Example demonstrating how to publish a message and get its serial
-func ExampleRESTChannel_PublishWithResult() {
- client, err := ably.NewREST(ably.WithKey("xxx:xxx"))
+func ExampleHTTPChannel_PublishWithResult() {
+ client, err := ably.NewHTTPClient(ably.WithKey("xxx:xxx"))
if err != nil {
panic(err)
}
@@ -30,8 +30,8 @@ func ExampleRESTChannel_PublishWithResult() {
}
// Example demonstrating how to update a message
-func ExampleRESTChannel_UpdateMessage() {
- client, err := ably.NewREST(ably.WithKey("xxx:xxx"))
+func ExampleHTTPChannel_UpdateMessage() {
+ client, err := ably.NewHTTPClient(ably.WithKey("xxx:xxx"))
if err != nil {
panic(err)
}
diff --git a/ably/export_test.go b/internal/ably/export_test.go
similarity index 94%
rename from ably/export_test.go
rename to internal/ably/export_test.go
index 020fa4809..0d55efe00 100644
--- a/ably/export_test.go
+++ b/internal/ably/export_test.go
@@ -16,8 +16,8 @@ func GetEndpointFallbackHosts(endpoint string) []string {
return getEndpointFallbackHosts(endpoint)
}
-func (opts *clientOptions) GetRestHost() string {
- return opts.getRestHost()
+func (opts *clientOptions) GetHTTPHost() string {
+ return opts.getHTTPHost()
}
func (opts *clientOptions) GetRealtimeHost() string {
@@ -40,11 +40,11 @@ func (opts *clientOptions) GetFallbackHosts() ([]string, error) {
return opts.getFallbackHosts()
}
-func (opts *clientOptions) RestURL() string {
+func (opts *clientOptions) HTTPURL() string {
return opts.restURL()
}
-func (c *REST) Post(ctx context.Context, path string, in, out interface{}) (*http.Response, error) {
+func (c *HTTPClient) Post(ctx context.Context, path string, in, out interface{}) (*http.Response, error) {
return c.post(ctx, path, in, out)
}
@@ -85,7 +85,7 @@ func (a *Auth) AuthOptions() *authOptions {
return &a.opts().authOptions
}
-func (c *REST) Timestamp(query bool) (time.Time, error) {
+func (c *HTTPClient) Timestamp(query bool) (time.Time, error) {
return c.Auth.timestamp(context.Background(), query)
}
@@ -93,11 +93,11 @@ func (a *Auth) SetServerTimeFunc(st func() (time.Time, error)) {
a.serverTimeHandler = st
}
-func (c *REST) GetCachedFallbackHost() string {
+func (c *HTTPClient) GetCachedFallbackHost() string {
return c.hostCache.get()
}
-func (c *REST) ActiveRealtimeHost() string {
+func (c *HTTPClient) ActiveRealtimeHost() string {
return c.activeRealtimeHost
}
@@ -232,7 +232,7 @@ func (c *Connection) SetKey(key string) {
c.key = key
}
-func (r *Realtime) Rest() *REST {
+func (r *Realtime) HTTPClient() *HTTPClient {
return r.rest
}
diff --git a/internal/ably/generate.go b/internal/ably/generate.go
new file mode 100644
index 000000000..8b74c8a59
--- /dev/null
+++ b/internal/ably/generate.go
@@ -0,0 +1,3 @@
+package ably
+
+//go:generate go run ../../scripts/errors/main.go -json ../../common/protocol/errors.json -o errors.go
diff --git a/ably/rest_channel.go b/internal/ably/http_channel.go
similarity index 87%
rename from ably/rest_channel.go
rename to internal/ably/http_channel.go
index b58ef2a3c..381de2ca4 100644
--- a/ably/rest_channel.go
+++ b/internal/ably/http_channel.go
@@ -9,31 +9,31 @@ import (
"strconv"
"time"
- "github.com/ably/ably-go/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
"github.com/ugorji/go/codec"
)
-// RESTChannel is the interface for REST API operations on a channel.
+// HTTPChannel is the interface for REST API operations on a channel.
// It enables messages to be published and historic messages to be retrieved for a channel.
-type RESTChannel struct {
+type HTTPChannel struct {
// Name is the channel name.
Name string
- // Presence is a [ably.RESTPresence] object (RSL3).
- Presence *RESTPresence
+ // Presence is a [ably.HTTPPresence] object (RSL3).
+ Presence *HTTPPresence
- client *REST
+ client *HTTPClient
baseURL string
options *protoChannelOptions
}
-func newRESTChannel(name string, client *REST) *RESTChannel {
- c := &RESTChannel{
+func newHTTPChannel(name string, client *HTTPClient) *HTTPChannel {
+ c := &HTTPChannel{
Name: name,
client: client,
baseURL: "/channels/" + url.PathEscape(name),
}
- c.Presence = &RESTPresence{
+ c.Presence = &HTTPPresence{
client: client,
channel: c,
}
@@ -41,18 +41,18 @@ func newRESTChannel(name string, client *REST) *RESTChannel {
}
// pathName is channel's name path escaped
-func (c *RESTChannel) pathName() string {
+func (c *HTTPChannel) pathName() string {
return url.PathEscape(c.Name)
}
// Publish publishes a single message to the channel with the given event name and payload. Returns error
// if there is a problem performing message publish (RSL1).
-func (c *RESTChannel) Publish(ctx context.Context, name string, data interface{}, options ...PublishMultipleOption) error {
+func (c *HTTPChannel) Publish(ctx context.Context, name string, data interface{}, options ...PublishMultipleOption) error {
return c.PublishMultiple(ctx, []*Message{{Name: name, Data: data}}, options...)
}
// PublishMultipleOption is an optional parameter for
-// RESTChannel.Publish and RESTChannel.PublishMultiple.
+// HTTPChannel.Publish and HTTPChannel.PublishMultiple.
//
// TODO: This started out as just an option for PublishMultiple, but has since
//
@@ -95,17 +95,9 @@ func PublishWithParams(params map[string]string) PublishMultipleOption {
}
}
-// Deprecated: Use PublishWithParams instead.
-// PublishMultipleWithParams is the same as PublishWithParams.
-//
-// TODO: Remove this in the next major version bump to 2.x.x.
-func PublishMultipleWithParams(params map[string]string) PublishMultipleOption {
- return PublishWithParams(params)
-}
-
// publishMultiple is the internal implementation for publishing multiple messages.
// If out is non-nil, the response body will be decoded into it.
-func (c *RESTChannel) publishMultiple(ctx context.Context, messages []*Message, out interface{}, options ...PublishMultipleOption) error {
+func (c *HTTPChannel) publishMultiple(ctx context.Context, messages []*Message, out interface{}, options ...PublishMultipleOption) error {
var publishOpts publishMultipleOptions
for _, o := range options {
o(&publishOpts)
@@ -118,7 +110,7 @@ func (c *RESTChannel) publishMultiple(ctx context.Context, messages []*Message,
return fmt.Errorf("encoding data for message #%d: %w", i, err)
}
}
- useIdempotent := c.client.opts.idempotentRESTPublishing()
+ useIdempotent := c.client.opts.idempotentHTTPPublishing()
if useIdempotent {
switch len(messages) {
case 1:
@@ -172,23 +164,14 @@ func (c *RESTChannel) publishMultiple(ctx context.Context, messages []*Message,
}
// PublishMultiple publishes multiple messages in a batch. Returns error if there is a problem publishing message (RSL1).
-func (c *RESTChannel) PublishMultiple(ctx context.Context, messages []*Message, options ...PublishMultipleOption) error {
+func (c *HTTPChannel) PublishMultiple(ctx context.Context, messages []*Message, options ...PublishMultipleOption) error {
return c.publishMultiple(ctx, messages, nil, options...)
}
-// PublishMultipleWithOptions is the same as PublishMultiple.
-//
-// Deprecated: Use PublishMultiple instead.
-//
-// TODO: Remove this in the next major version bump to 2.x.x.
-func (c *RESTChannel) PublishMultipleWithOptions(ctx context.Context, messages []*Message, options ...PublishMultipleOption) error {
- return c.PublishMultiple(ctx, messages, options...)
-}
-
// PublishWithResult publishes a single message to the channel with the given event name and payload,
// and returns the serial assigned by the server (RSL1n, RSL1n1 alternative).
// Returns error if there is a problem performing message publish.
-func (c *RESTChannel) PublishWithResult(ctx context.Context, name string, data interface{}, options ...PublishMultipleOption) (*PublishResult, error) {
+func (c *HTTPChannel) PublishWithResult(ctx context.Context, name string, data interface{}, options ...PublishMultipleOption) (*PublishResult, error) {
results, err := c.PublishMultipleWithResult(ctx, []*Message{{Name: name, Data: data}}, options...)
if err != nil {
return nil, err
@@ -202,7 +185,7 @@ func (c *RESTChannel) PublishWithResult(ctx context.Context, name string, data i
// PublishMultipleWithResult publishes multiple messages in a batch and returns the serials
// assigned by the server (RSL1n, RSL1n1 alternative).
// Returns error if there is a problem publishing messages.
-func (c *RESTChannel) PublishMultipleWithResult(ctx context.Context, messages []*Message, options ...PublishMultipleOption) ([]PublishResult, error) {
+func (c *HTTPChannel) PublishMultipleWithResult(ctx context.Context, messages []*Message, options ...PublishMultipleOption) ([]PublishResult, error) {
var response publishResponse
if err := c.publishMultiple(ctx, messages, &response, options...); err != nil {
return nil, err
@@ -227,7 +210,7 @@ type updateDeleteResult struct {
// performMessageOperation is a shared helper for UpdateMessage, DeleteMessage, and AppendMessage.
// It validates the message serial, applies update options, sets the action, encodes data, and sends the request.
// Uses PATCH /channels/{name}/messages/{serial} per RSL15b with a single Message body (not an array).
-func (c *RESTChannel) performMessageOperation(ctx context.Context, msg *Message, action MessageAction, options ...UpdateOption) (*UpdateDeleteResult, error) {
+func (c *HTTPChannel) performMessageOperation(ctx context.Context, msg *Message, action MessageAction, options ...UpdateOption) (*UpdateDeleteResult, error) {
if err := validateMessageSerial(msg); err != nil {
return nil, err
}
@@ -277,21 +260,21 @@ func (c *RESTChannel) performMessageOperation(ctx context.Context, msg *Message,
}
// UpdateMessage updates a previously published message.
-func (c *RESTChannel) UpdateMessage(ctx context.Context, msg *Message, options ...UpdateOption) (*UpdateDeleteResult, error) {
+func (c *HTTPChannel) UpdateMessage(ctx context.Context, msg *Message, options ...UpdateOption) (*UpdateDeleteResult, error) {
return c.performMessageOperation(ctx, msg, MessageActionUpdate, options...)
}
// DeleteMessage deletes a previously published message.
-func (c *RESTChannel) DeleteMessage(ctx context.Context, msg *Message, options ...UpdateOption) (*UpdateDeleteResult, error) {
+func (c *HTTPChannel) DeleteMessage(ctx context.Context, msg *Message, options ...UpdateOption) (*UpdateDeleteResult, error) {
return c.performMessageOperation(ctx, msg, MessageActionDelete, options...)
}
// AppendMessage appends to a previously published message.
-func (c *RESTChannel) AppendMessage(ctx context.Context, msg *Message, options ...UpdateOption) (*UpdateDeleteResult, error) {
+func (c *HTTPChannel) AppendMessage(ctx context.Context, msg *Message, options ...UpdateOption) (*UpdateDeleteResult, error) {
return c.performMessageOperation(ctx, msg, MessageActionAppend, options...)
}
-// ChannelDetails contains the details of a [ably.RESTChannel] or [ably.RealtimeChannel] object
+// ChannelDetails contains the details of a [ably.HTTPChannel] or [ably.RealtimeChannel] object
// such as its ID and [ably.ChannelStatus].
type ChannelDetails struct {
// ChannelId is the identifier of the channel (CHD2a).
@@ -300,7 +283,7 @@ type ChannelDetails struct {
Status ChannelStatus `json:"status" codec:"status"`
}
-// ChannelStatus contains the status of a [ably.RESTChannel] or [ably.RealtimeChannel] object such as whether
+// ChannelStatus contains the status of a [ably.HTTPChannel] or [ably.RealtimeChannel] object such as whether
// it is active and its [ably.ChannelOccupancy].
type ChannelStatus struct {
// IsActive if set to true, the channel is active, otherwise inactive (CHS2a).
@@ -310,13 +293,13 @@ type ChannelStatus struct {
Occupancy ChannelOccupancy `json:"occupancy" codec:"occupancy"`
}
-// ChannelOccupancy contains the metrics of a [ably.RESTChannel] or [ably.RealtimeChannel] object.
+// ChannelOccupancy contains the metrics of a [ably.HTTPChannel] or [ably.RealtimeChannel] object.
type ChannelOccupancy struct {
// Metrics is a [ably.ChannelMetrics] object (CHO2a).
Metrics ChannelMetrics `json:"metrics" codec:"metrics"`
}
-// ChannelMetrics contains the metrics associated with a [ably.RESTChannel] or [ably.RealtimeChannel],
+// ChannelMetrics contains the metrics associated with a [ably.HTTPChannel] or [ably.RealtimeChannel],
// such as the number of publishers, subscribers and connections it has.
type ChannelMetrics struct {
// Connections is the number of realtime connections attached to the channel (CHM2a).
@@ -350,7 +333,7 @@ type ChannelMetrics struct {
// Status returns ChannelDetails representing information for a channel which includes status and occupancy metrics.
// (RSL8)
-func (c *RESTChannel) Status(ctx context.Context) (*ChannelDetails, error) {
+func (c *HTTPChannel) Status(ctx context.Context) (*ChannelDetails, error) {
var channelDetails ChannelDetails
req := &request{
Method: "GET",
@@ -368,7 +351,7 @@ func (c *RESTChannel) Status(ctx context.Context) (*ChannelDetails, error) {
// History gives the channel's message history (RSL2a)
//
// See package-level documentation => [ably] Pagination for details about history pagination.
-func (c *RESTChannel) History(o ...HistoryOption) HistoryRequest {
+func (c *HTTPChannel) History(o ...HistoryOption) HistoryRequest {
params := (&historyOptions{}).apply(o...)
return HistoryRequest{
r: c.client.newPaginatedRequest("/channels/"+c.Name+"/history", "/channels/"+c.pathName()+"/history", params),
@@ -376,7 +359,7 @@ func (c *RESTChannel) History(o ...HistoryOption) HistoryRequest {
}
}
-// A HistoryOption configures a call to RESTChannel.History or RealtimeChannel.History.
+// A HistoryOption configures a call to HTTPChannel.History or RealtimeChannel.History.
type HistoryOption func(*historyOptions)
// HistoryWithStart sets time from which messages are retrieved, specified as milliseconds since the Unix epoch.
@@ -424,11 +407,11 @@ func (o *historyOptions) apply(opts ...HistoryOption) url.Values {
return o.params
}
-// HistoryRequest represents a request prepared by the RESTChannel.History or
+// HistoryRequest represents a request prepared by the HTTPChannel.History or
// RealtimeChannel.History method, ready to be performed by its Pages or Items methods.
type HistoryRequest struct {
r paginatedRequest
- channel *RESTChannel
+ channel *HTTPChannel
// err is a validation error set at construction time. If non-nil, Pages and
// Items return it immediately without making any requests.
err error
@@ -504,22 +487,22 @@ func (r HistoryRequest) Items(ctx context.Context) (*MessagesPaginatedItems, err
// fullMessagesDecoder wraps a destination slice of messages in a decoder value
// that decodes both the message itself from the transport-level encoding and
// the data field within from its message-specific encoding.
-func (c *RESTChannel) fullMessagesDecoder(dst *[]*Message) interface{} {
+func (c *HTTPChannel) fullMessagesDecoder(dst *[]*Message) interface{} {
return &fullMessagesDecoder{dst: dst, c: c}
}
-func (c *RESTChannel) fullMessageDecoder(dst *Message) interface{} {
+func (c *HTTPChannel) fullMessageDecoder(dst *Message) interface{} {
return &fullMessageDecoder{dst: dst, c: c}
}
type fullMessagesDecoder struct {
dst *[]*Message
- c *RESTChannel
+ c *HTTPChannel
}
type fullMessageDecoder struct {
dst *Message
- c *RESTChannel
+ c *HTTPChannel
}
func (t *fullMessagesDecoder) UnmarshalJSON(b []byte) error {
@@ -616,7 +599,7 @@ func (p *MessagesPaginatedItems) Item() *Message {
}
// GetMessage retrieves a message by its serial.
-func (c *RESTChannel) GetMessage(ctx context.Context, serial string) (*Message, error) {
+func (c *HTTPChannel) GetMessage(ctx context.Context, serial string) (*Message, error) {
if serial == "" {
return nil, newError(40003, errors.New("serial is required to retrieve a message"))
}
@@ -635,7 +618,7 @@ func (c *RESTChannel) GetMessage(ctx context.Context, serial string) (*Message,
// GetMessageVersions retrieves the version history of a message by its serial.
// Returns a HistoryRequest that can be used to paginate through message versions.
-func (c *RESTChannel) GetMessageVersions(serial string, params url.Values) HistoryRequest {
+func (c *HTTPChannel) GetMessageVersions(serial string, params url.Values) HistoryRequest {
if serial == "" {
return HistoryRequest{err: newError(40003, errors.New("serial is required to retrieve message versions"))}
}
@@ -647,6 +630,6 @@ func (c *RESTChannel) GetMessageVersions(serial string, params url.Values) Histo
}
}
-func (c *RESTChannel) log() logger {
+func (c *HTTPChannel) log() logger {
return c.client.log
}
diff --git a/ably/rest_channel_integration_test.go b/internal/ably/http_channel_integration_test.go
similarity index 94%
rename from ably/rest_channel_integration_test.go
rename to internal/ably/http_channel_integration_test.go
index 964d94a1e..a46c976c3 100644
--- a/ably/rest_channel_integration_test.go
+++ b/internal/ably/http_channel_integration_test.go
@@ -17,19 +17,19 @@ import (
"strings"
"testing"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/ably/internal/ablyutil"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
-func TestRESTChannel(t *testing.T) {
+func TestHTTPChannel(t *testing.T) {
app, err := ablytest.NewSandbox()
require.NoError(t, err)
options := app.Options()
- client, err := ably.NewREST(options...)
+ client, err := ably.NewHTTPClient(options...)
require.NoError(t, err)
t.Run("Publish", func(t *testing.T) {
channel := client.Channels.Get("test_publish_channel")
@@ -84,15 +84,15 @@ func TestRESTChannel(t *testing.T) {
})
t.Run("PublishMultiple", func(t *testing.T) {
- encodingRESTChannel := client.Channels.Get("this?is#an?encoding#channel")
+ encodingHTTPChannel := client.Channels.Get("this?is#an?encoding#channel")
messages := []*ably.Message{
{Name: "send", Data: "test data 1"},
{Name: "send", Data: "test data 2"},
}
- err := encodingRESTChannel.PublishMultiple(context.Background(), messages)
+ err := encodingHTTPChannel.PublishMultiple(context.Background(), messages)
assert.NoError(t, err)
var history []*ably.Message
- err = ablytest.AllPages(&history, encodingRESTChannel.History(ably.HistoryWithLimit(2)))
+ err = ablytest.AllPages(&history, encodingHTTPChannel.History(ably.HistoryWithLimit(2)))
assert.NoError(t, err)
assert.Equal(t, 2, len(history),
"expected 2 messages got %d", len(history))
@@ -144,8 +144,8 @@ func TestRESTChannel(t *testing.T) {
func TestIdempotentPublishing(t *testing.T) {
app, err := ablytest.NewSandbox()
require.NoError(t, err)
- options := app.Options(ably.WithIdempotentRESTPublishing(true))
- client, err := ably.NewREST(options...)
+ options := app.Options(ably.WithIdempotentHTTPPublishing(true))
+ client, err := ably.NewHTTPClient(options...)
require.NoError(t, err)
randomStr, err := ablyutil.BaseID()
assert.NoError(t, err)
@@ -312,7 +312,7 @@ func TestIdempotent_retry(t *testing.T) {
nopts := []ably.ClientOption{
ably.WithTLS(false),
ably.WithFallbackHosts(fallbackHosts),
- ably.WithIdempotentRESTPublishing(true),
+ ably.WithIdempotentHTTPPublishing(true),
ably.WithUseTokenAuth(true),
}
@@ -322,7 +322,7 @@ func TestIdempotent_retry(t *testing.T) {
// app's endpoint/port. Against a per-test local child that address is not
// derivable from the endpoint name, so a bare nopts URL would send those
// requests to the wrong server (which 404s the app id).
- defaultURL, _ := url.Parse(ably.ApplyOptionsWithDefaults(app.Options(nopts...)...).RestURL())
+ defaultURL, _ := url.Parse(ably.ApplyOptionsWithDefaults(app.Options(nopts...)...).HTTPURL())
proxy := func(r *http.Request) (*url.URL, error) {
if !strings.HasPrefix(r.URL.Path, "/channels/") {
// this is to handle token requests
@@ -347,7 +347,7 @@ func TestIdempotent_retry(t *testing.T) {
},
}))
- client, err := ably.NewREST(app.Options(nopts...)...)
+ client, err := ably.NewHTTPClient(app.Options(nopts...)...)
require.NoError(t, err)
t.Run("two REST publish retries result in only one message being published'", func(t *testing.T) {
diff --git a/ably/rest_channel_spec_integration_test.go b/internal/ably/http_channel_spec_integration_test.go
similarity index 94%
rename from ably/rest_channel_spec_integration_test.go
rename to internal/ably/http_channel_spec_integration_test.go
index 46eab7366..1fb2795c6 100644
--- a/ably/rest_channel_spec_integration_test.go
+++ b/internal/ably/http_channel_spec_integration_test.go
@@ -9,8 +9,8 @@ import (
"reflect"
"testing"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -22,7 +22,7 @@ func TestRSL1f1(t *testing.T) {
opts := app.Options()
// RSL1f
opts = append(opts, ably.WithUseTokenAuth(false))
- client, err := ably.NewREST(opts...)
+ client, err := ably.NewHTTPClient(opts...)
require.NoError(t, err)
channel := client.Channels.Get("RSL1f")
var msgs []*ably.Message
@@ -53,7 +53,7 @@ func TestRSL1g(t *testing.T) {
ably.WithUseTokenAuth(true),
)
opts = append(opts, ably.WithClientID("some_client_id"))
- client, err := ably.NewREST(opts...)
+ client, err := ably.NewHTTPClient(opts...)
require.NoError(t, err)
t.Run("RSL1g1b", func(t *testing.T) {
channel := client.Channels.Get("RSL1g1b")
@@ -109,7 +109,7 @@ func TestHistory_RSL2_RSL2b3(t *testing.T) {
for _, limit := range []int{2, 3, 20} {
t.Run(fmt.Sprintf("limit=%d", limit), func(t *testing.T) {
- _, rest := ablytest.NewREST()
+ _, rest := ablytest.NewHTTPClient()
channel := rest.Channels.Get(ablytest.ChannelName("persisted:test"))
fixtures := historyFixtures()
@@ -142,7 +142,7 @@ func TestHistory_Direction_RSL2b2(t *testing.T) {
} {
c := c
t.Run(fmt.Sprintf("direction=%v", c.direction), func(t *testing.T) {
- _, rest := ablytest.NewREST()
+ _, rest := ablytest.NewHTTPClient()
channel := rest.Channels.Get(ablytest.ChannelName("persisted:test"))
fixtures := historyFixtures()
@@ -161,7 +161,7 @@ func TestHistory_Direction_RSL2b2(t *testing.T) {
func TestGetChannelLifecycleStatus_RSL8(t *testing.T) {
ctx := context.Background()
- _, rest := ablytest.NewREST()
+ _, rest := ablytest.NewHTTPClient()
t.Run("Test Channel Status after Publish", func(t *testing.T) {
channel := rest.Channels.Get("lifecycle:test")
diff --git a/ably/rest_client.go b/internal/ably/http_client.go
similarity index 85%
rename from ably/rest_client.go
rename to internal/ably/http_client.go
index 0727708c8..85ffe9d1b 100644
--- a/ably/rest_client.go
+++ b/internal/ably/http_client.go
@@ -19,7 +19,7 @@ import (
"sync"
"time"
- "github.com/ably/ably-go/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
"github.com/ugorji/go/codec"
)
@@ -54,12 +54,12 @@ func (r *raw) CodecDecodeSelf(decoder *codec.Decoder) {
*r = []byte(raw)
}
-// RESTChannels provides an API for managing collection of RESTChannel.
+// HTTPChannels provides an API for managing collection of HTTPChannel.
// This is safe for concurrent use.
-type RESTChannels struct {
- chans map[string]*RESTChannel
+type HTTPChannels struct {
+ chans map[string]*HTTPChannel
mu sync.RWMutex
- client *REST
+ client *HTTPClient
}
// Iterate returns a list of created channels.
@@ -67,9 +67,9 @@ type RESTChannels struct {
// It is safe to call Iterate from multiple goroutines, however there's no guarantee
// the returned list would not list a channel that was already released from
// different goroutine.
-func (c *RESTChannels) Iterate() []*RESTChannel { // RSN2, RTS2
+func (c *HTTPChannels) Iterate() []*HTTPChannel { // RSN2, RTS2
c.mu.Lock()
- chans := make([]*RESTChannel, 0, len(c.chans))
+ chans := make([]*HTTPChannel, 0, len(c.chans))
for _, restChannel := range c.chans {
chans = append(chans, restChannel)
}
@@ -78,7 +78,7 @@ func (c *RESTChannels) Iterate() []*RESTChannel { // RSN2, RTS2
}
// Exists returns true if the channel by the given name exists.
-func (c *RESTChannels) Exists(name string) bool { // RSN2, RTS2
+func (c *HTTPChannels) Exists(name string) bool { // RSN2, RTS2
c.mu.RLock()
_, ok := c.chans[name]
c.mu.RUnlock()
@@ -90,7 +90,7 @@ func (c *RESTChannels) Exists(name string) bool { // RSN2, RTS2
// You can optionally pass ChannelOptions, if the channel exists it will
// updated with the options and when it doesn't a new channel will be created
// with the given options.
-func (c *RESTChannels) Get(name string, options ...ChannelOption) *RESTChannel {
+func (c *HTTPChannels) Get(name string, options ...ChannelOption) *HTTPChannel {
var o channelOptions
for _, set := range options {
set(&o)
@@ -98,7 +98,7 @@ func (c *RESTChannels) Get(name string, options ...ChannelOption) *RESTChannel {
return c.get(name, (*protoChannelOptions)(&o))
}
-func (c *RESTChannels) get(name string, opts *protoChannelOptions) *RESTChannel {
+func (c *HTTPChannels) get(name string, opts *protoChannelOptions) *HTTPChannel {
c.mu.RLock()
v, ok := c.chans[name]
c.mu.RUnlock()
@@ -108,7 +108,7 @@ func (c *RESTChannels) get(name string, opts *protoChannelOptions) *RESTChannel
}
return v
}
- v = newRESTChannel(name, c.client)
+ v = newHTTPChannel(name, c.client)
v.options = opts
c.mu.Lock()
c.chans[name] = v
@@ -117,19 +117,19 @@ func (c *RESTChannels) get(name string, opts *protoChannelOptions) *RESTChannel
}
// Release deletes the channel from the chans.
-func (c *RESTChannels) Release(name string) {
+func (c *HTTPChannels) Release(name string) {
c.mu.Lock()
delete(c.chans, name)
c.mu.Unlock()
}
-// REST is rest client that offers a simple stateless API to interact directly with Ably's REST API.
-type REST struct {
+// HTTPClient is a stateless client that offers a simple API to interact directly with Ably's REST API.
+type HTTPClient struct {
// Auth is a [ably.Auth] object (RSC5).
Auth *Auth
- //Channels is a [ably.RESTChannels] object (RSN1).
- Channels *RESTChannels
+ //Channels is a [ably.HTTPChannels] object (RSN1).
+ Channels *HTTPChannels
opts *clientOptions
hostCache *hostCache
@@ -137,10 +137,10 @@ type REST struct {
log logger
}
-// NewREST construct a RestClient object using an [ably.ClientOption] object to configure
+// NewHTTPClient constructs an HTTPClient object using an [ably.ClientOption] object to configure
// the client connection to Ably (RSC1).
-func NewREST(options ...ClientOption) (*REST, error) {
- c := &REST{
+func NewHTTPClient(options ...ClientOption) (*HTTPClient, error) {
+ c := &HTTPClient{
opts: applyOptionsWithDefaults(options...),
}
if err := c.opts.validate(); err != nil {
@@ -152,8 +152,8 @@ func NewREST(options ...ClientOption) (*REST, error) {
return nil, err
}
c.Auth = auth
- c.Channels = &RESTChannels{
- chans: make(map[string]*RESTChannel),
+ c.Channels = &HTTPChannels{
+ chans: make(map[string]*HTTPChannel),
client: c,
}
c.hostCache = &hostCache{
@@ -166,7 +166,7 @@ func NewREST(options ...ClientOption) (*REST, error) {
// Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably
// multiple [ably.TokenRequest] with a more accurate timestamp should use the ClientOptions.UseQueryTime
// property instead of this method (RSC16).
-func (c *REST) Time(ctx context.Context) (time.Time, error) {
+func (c *HTTPClient) Time(ctx context.Context) (time.Time, error) {
var times []int64
r := &request{
Method: "GET",
@@ -192,7 +192,7 @@ func (c *REST) Time(ctx context.Context) (time.Time, error) {
// Note: Stats requests use protocol version 2 to maintain compatibility with the existing
// nested Stats structure. Migrating to the flattened protocol v3+ stats format is planned
// for ably-go v2 as it requires breaking API changes.
-func (c *REST) Stats(o ...StatsOption) StatsRequest {
+func (c *HTTPClient) Stats(o ...StatsOption) StatsRequest {
params := (&statsOptions{}).apply(o...)
// Use protocol v2 for stats to maintain compatibility with existing Stats structure.
@@ -210,11 +210,11 @@ func (c *REST) Stats(o ...StatsOption) StatsRequest {
return StatsRequest{r: req}
}
-func (c *REST) setActiveRealtimeHost(realtimeHost string) {
+func (c *HTTPClient) setActiveRealtimeHost(realtimeHost string) {
c.activeRealtimeHost = realtimeHost
}
-// A StatsOption configures a call to REST.Stats or Realtime.Stats.
+// A StatsOption configures a call to HTTPClient.Stats or Realtime.Stats.
type StatsOption func(*statsOptions)
// StatsWithStart sets the time from which stats are retrieved, specified as milliseconds since the Unix epoch (RSC6b1).
@@ -277,7 +277,7 @@ func (o *statsOptions) apply(opts ...StatsOption) url.Values {
return o.params
}
-// StatsRequest represents a request prepared by the REST.Stats or
+// StatsRequest represents a request prepared by the HTTPClient.Stats or
// Realtime.Stats method, ready to be performed by its Pages or Items methods.
type StatsRequest struct {
r paginatedRequest
@@ -387,11 +387,11 @@ type request struct {
// developers who wish to use REST API functionality that is either not documented or is not yet included in the
// public API, without having to directly handle features such as authentication, paging, fallback hosts, MsgPack
// and JSON support (RSC19).
-func (c *REST) Request(method string, path string, o ...RequestOption) RESTRequest {
+func (c *HTTPClient) Request(method string, path string, o ...RequestOption) HTTPRequest {
method = strings.ToUpper(method)
var opts requestOptions
opts.apply(o...)
- return RESTRequest{r: paginatedRequest{
+ return HTTPRequest{r: paginatedRequest{
path: path,
params: opts.params,
query: func(ctx context.Context, path string) (*http.Response, error) {
@@ -430,7 +430,7 @@ type requestOptions struct {
body interface{}
}
-// RequestOption configures a call to REST.Request.
+// RequestOption configures a call to HTTPClient.Request.
type RequestOption func(*requestOptions)
// RequestWithParams sets the parameters to include in the URL query of the request.
@@ -463,9 +463,9 @@ func (o *requestOptions) apply(opts ...RequestOption) {
}
}
-// RESTRequest represents a request prepared by the REST.Request method, ready
+// HTTPRequest represents a request prepared by the HTTPClient.Request method, ready
// to be performed by its Pages or Items methods.
-type RESTRequest struct {
+type HTTPRequest struct {
// r is an [ably.HTTPPaginatedResponse] object returned by the HTTP request, containing an empty or
// JSON-encodable object.
r paginatedRequest
@@ -474,7 +474,7 @@ type RESTRequest struct {
// Pages returns an iterator for whole pages of results.
//
// See package-level documentation => [ably] Pagination for more details.
-func (r RESTRequest) Pages(ctx context.Context) (*HTTPPaginatedResponse, error) {
+func (r HTTPRequest) Pages(ctx context.Context) (*HTTPPaginatedResponse, error) {
var res HTTPPaginatedResponse
return &res, res.load(ctx, r.r)
}
@@ -576,8 +576,8 @@ func (p *HTTPPaginatedResponse) Items(dst interface{}) error {
// paginated iterator.
//
// See package-level documentation => [ably] Pagination for more details.
-func (r RESTRequest) Items(ctx context.Context) (*RESTPaginatedItems, error) {
- var res RESTPaginatedItems
+func (r HTTPRequest) Items(ctx context.Context) (*HTTPPaginatedItems, error) {
+ var res HTTPPaginatedItems
var err error
res.next, err = res.loadItems(ctx, r.r, func() (interface{}, func() int) {
res.items = nil
@@ -586,7 +586,7 @@ func (r RESTRequest) Items(ctx context.Context) (*RESTPaginatedItems, error) {
return &res, err
}
-type RESTPaginatedItems struct {
+type HTTPPaginatedItems struct {
PaginatedResult
items []raw
item raw
@@ -596,7 +596,7 @@ type RESTPaginatedItems struct {
// Next retrieves the next result.
//
// See the "Paginated results" section in the package-level documentation.
-func (p *RESTPaginatedItems) Next(ctx context.Context) bool {
+func (p *HTTPPaginatedItems) Next(ctx context.Context) bool {
i, ok := p.next(ctx)
if !ok {
return false
@@ -608,21 +608,21 @@ func (p *RESTPaginatedItems) Next(ctx context.Context) bool {
// IsLast returns true if the page is last page.
//
// See package-level documentation => [ably] Pagination for more details.
-func (p *RESTPaginatedItems) IsLast(ctx context.Context) bool {
+func (p *HTTPPaginatedItems) IsLast(ctx context.Context) bool {
return !p.HasNext(ctx)
}
// HasNext returns true is there are more pages available.
//
// See package-level documentation => [ably] Pagination for more details.
-func (p *RESTPaginatedItems) HasNext(ctx context.Context) bool {
+func (p *HTTPPaginatedItems) HasNext(ctx context.Context) bool {
return p.nextLink != ""
}
// Item unmarshal the current result into the provided variable.
//
// See the "Paginated results" section in the package-level documentation.
-func (p *RESTPaginatedItems) Item(dst interface{}) error {
+func (p *HTTPPaginatedItems) Item(dst interface{}) error {
typ, _, err := mime.ParseMediaType(p.PaginatedResult.res.Header.Get("Content-Type"))
if err != nil {
return err
@@ -630,7 +630,7 @@ func (p *RESTPaginatedItems) Item(dst interface{}) error {
return decode(typ, bytes.NewReader(p.item), dst)
}
-func (c *REST) get(ctx context.Context, path string, out interface{}) (*http.Response, error) {
+func (c *HTTPClient) get(ctx context.Context, path string, out interface{}) (*http.Response, error) {
r := &request{
Method: "GET",
Path: path,
@@ -640,7 +640,7 @@ func (c *REST) get(ctx context.Context, path string, out interface{}) (*http.Res
}
// getWithHeader is like get but allows specifying custom HTTP headers.
-func (c *REST) getWithHeader(ctx context.Context, path string, out interface{}, header http.Header) (*http.Response, error) {
+func (c *HTTPClient) getWithHeader(ctx context.Context, path string, out interface{}, header http.Header) (*http.Response, error) {
r := &request{
Method: "GET",
Path: path,
@@ -650,7 +650,7 @@ func (c *REST) getWithHeader(ctx context.Context, path string, out interface{},
return c.do(ctx, r)
}
-func (c *REST) post(ctx context.Context, path string, in, out interface{}) (*http.Response, error) {
+func (c *HTTPClient) post(ctx context.Context, path string, in, out interface{}) (*http.Response, error) {
r := &request{
Method: "POST",
Path: path,
@@ -660,7 +660,7 @@ func (c *REST) post(ctx context.Context, path string, in, out interface{}) (*htt
return c.do(ctx, r)
}
-func (c *REST) patch(ctx context.Context, path string, in, out interface{}) (*http.Response, error) {
+func (c *HTTPClient) patch(ctx context.Context, path string, in, out interface{}) (*http.Response, error) {
r := &request{
Method: "PATCH",
Path: path,
@@ -670,39 +670,39 @@ func (c *REST) patch(ctx context.Context, path string, in, out interface{}) (*ht
return c.do(ctx, r)
}
-func (c *REST) do(ctx context.Context, r *request) (*http.Response, error) {
+func (c *HTTPClient) do(ctx context.Context, r *request) (*http.Response, error) {
return c.doWithHandle(ctx, r, c.handleResponse)
}
-func (c *REST) doWithHandle(ctx context.Context, r *request, handle func(*http.Response, interface{}) (*http.Response, error)) (*http.Response, error) {
+func (c *HTTPClient) doWithHandle(ctx context.Context, r *request, handle func(*http.Response, interface{}) (*http.Response, error)) (*http.Response, error) {
req, err := c.newHTTPRequest(ctx, r)
if err != nil {
return nil, err
}
if h := c.hostCache.get(); h != "" {
req.URL.Host = h // RSC15f
- c.log.Verbosef("RestClient: setting cached URL.Host=%q", h)
+ c.log.Verbosef("HTTPClient: setting cached URL.Host=%q", h)
} else if !empty(c.activeRealtimeHost) { // RTN17e
req.URL.Host = c.activeRealtimeHost
- c.log.Verbosef("RestClient: setting activeRealtimeHost URL.Host=%q", c.activeRealtimeHost)
+ c.log.Verbosef("HTTPClient: setting activeRealtimeHost URL.Host=%q", c.activeRealtimeHost)
}
if c.opts.Trace != nil {
req = req.WithContext(httptrace.WithClientTrace(req.Context(), c.opts.Trace))
- c.log.Verbose("RestClient: enabling httptrace")
+ c.log.Verbose("HTTPClient: enabling httptrace")
}
resp, err := c.opts.httpclient().Do(req)
serverResp := resp
if err == nil {
resp, err = handle(resp, r.Out)
} else {
- c.log.Error("RestClient: failed sending a request ", err)
+ c.log.Error("HTTPClient: failed sending a request ", err)
}
if err != nil {
- c.log.Error("RestClient: error handling response: ", err)
+ c.log.Error("HTTPClient: error handling response: ", err)
if canFallBack(err, serverResp) {
fallbacks, _ := c.opts.getFallbackHosts()
- c.log.Infof("RestClient: trying to fallback with hosts=%v", fallbacks)
+ c.log.Infof("HTTPClient: trying to fallback with hosts=%v", fallbacks)
if len(fallbacks) > 0 {
left := fallbacks
iteration := 0
@@ -710,11 +710,11 @@ func (c *REST) doWithHandle(ctx context.Context, r *request, handle func(*http.R
if maxLimit == 0 {
maxLimit = defaultOptions.HTTPMaxRetryCount
}
- c.log.Infof("RestClient: maximum fallback retry limit=%d", maxLimit)
+ c.log.Infof("HTTPClient: maximum fallback retry limit=%d", maxLimit)
for {
if len(left) == 0 {
- c.log.Errorf("RestClient: exhausted fallback hosts", err)
+ c.log.Errorf("HTTPClient: exhausted fallback hosts", err)
return nil, err
}
var h string
@@ -734,7 +734,7 @@ func (c *REST) doWithHandle(ctx context.Context, r *request, handle func(*http.R
if err != nil {
return nil, err
}
- c.log.Infof("RestClient: chose fallback host=%q ", h)
+ c.log.Infof("HTTPClient: chose fallback host=%q ", h)
req.URL.Host = h
req.Host = ""
req.Header.Set(hostHeader, h)
@@ -743,10 +743,10 @@ func (c *REST) doWithHandle(ctx context.Context, r *request, handle func(*http.R
if err == nil {
resp, err = handle(resp, r.Out)
} else {
- c.log.Error("RestClient: failed sending a request to a fallback host", err)
+ c.log.Error("HTTPClient: failed sending a request to a fallback host", err)
}
if err != nil {
- c.log.Error("RestClient: error handling response: ", err)
+ c.log.Error("HTTPClient: error handling response: ", err)
if iteration == maxLimit-1 {
return nil, err
}
@@ -801,7 +801,7 @@ func isCloudFrontError(res *http.Response) bool {
// newHTTPRequest creates a new http.Request that can be sent to ably endpoints.
// This makes sure necessary headers are set.
-func (c *REST) newHTTPRequest(ctx context.Context, r *request) (*http.Request, error) {
+func (c *HTTPClient) newHTTPRequest(ctx context.Context, r *request) (*http.Request, error) {
var body io.Reader
var protocol = c.opts.protocol()
if r.In != nil {
@@ -842,18 +842,18 @@ func (c *REST) newHTTPRequest(ctx context.Context, r *request) (*http.Request, e
return req, nil
}
-func (c *REST) handleResponse(resp *http.Response, out interface{}) (*http.Response, error) {
- c.log.Info("RestClient:checking valid http response")
+func (c *HTTPClient) handleResponse(resp *http.Response, out interface{}) (*http.Response, error) {
+ c.log.Info("HTTPClient:checking valid http response")
if err := checkValidHTTPResponse(resp); err != nil {
- c.log.Error("RestClient: failed to check valid http response ", err)
+ c.log.Error("HTTPClient: failed to check valid http response ", err)
return nil, err
}
if out == nil {
return resp, nil
}
- c.log.Info("RestClient: decoding response")
+ c.log.Info("HTTPClient: decoding response")
if err := decodeResp(resp, out); err != nil {
- c.log.Error("RestClient: failed to decode response ", err)
+ c.log.Error("HTTPClient: failed to decode response ", err)
return nil, err
}
return resp, nil
@@ -909,7 +909,7 @@ func decodeResp(resp *http.Response, out interface{}) error {
}
// hostCache caches a successful fallback host for 10 minutes.
-// Only used by REST client while making requests RSC15f
+// Only used by [ably.HTTPClient] while making requests RSC15f
type hostCache struct {
duration time.Duration
diff --git a/ably/rest_client_integration_test.go b/internal/ably/http_client_integration_test.go
similarity index 93%
rename from ably/rest_client_integration_test.go
rename to internal/ably/http_client_integration_test.go
index 62d048fd2..1c002e310 100644
--- a/ably/rest_client_integration_test.go
+++ b/internal/ably/http_client_integration_test.go
@@ -22,9 +22,9 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/ably/internal/ablyutil"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -38,7 +38,7 @@ func newHTTPClientMock(srv *httptest.Server) *http.Client {
}
}
-func TestRestClient(t *testing.T) {
+func TestHTTPClient(t *testing.T) {
app, err := ablytest.NewSandbox()
assert.NoError(t, err)
t.Run("encoding messages", func(t *testing.T) {
@@ -63,7 +63,7 @@ func TestRestClient(t *testing.T) {
ably.WithHTTPClient(newHTTPClientMock(server)),
}
- client, err := ably.NewREST(app.Options(options...)...)
+ client, err := ably.NewHTTPClient(app.Options(options...)...)
require.NoError(t, err)
err = client.Channels.Get(ablytest.UniqueChannelName(t, "test")).Publish(context.Background(), "ping", "pong")
assert.NoError(t, err)
@@ -90,7 +90,7 @@ func TestRestClient(t *testing.T) {
ably.WithHTTPClient(newHTTPClientMock(server)),
}
- client, err := ably.NewREST(app.Options(options...)...)
+ client, err := ably.NewHTTPClient(app.Options(options...)...)
require.NoError(t, err)
err = client.Channels.Get(ablytest.UniqueChannelName(t, "test")).Publish(context.Background(), "ping", "pong")
assert.NoError(t, err)
@@ -107,7 +107,7 @@ func TestRestClient(t *testing.T) {
})
t.Run("Time", func(t *testing.T) {
- client, err := ably.NewREST(app.Options()...)
+ client, err := ably.NewHTTPClient(app.Options()...)
require.NoError(t, err)
ti, err := client.Time(context.Background())
require.NoError(t, err)
@@ -121,7 +121,7 @@ func TestRestClient(t *testing.T) {
})
t.Run("Stats", func(t *testing.T) {
- client, err := ably.NewREST(app.Options()...)
+ client, err := ably.NewHTTPClient(app.Options()...)
assert.NoError(t, err)
lastInterval := time.Now().Add(-365 * 24 * time.Hour)
var stats []*ably.Stats
@@ -221,7 +221,7 @@ func TestRSC7(t *testing.T) {
return nil, errors.New("fake round tripper")
})
- c, err := ably.NewREST(
+ c, err := ably.NewHTTPClient(
ably.WithKey("fake:key"),
ably.WithHTTPClient(client))
assert.NoError(t, err)
@@ -238,7 +238,7 @@ func TestRSC7(t *testing.T) {
})
}
-func TestRest_RSC7_AblyAgent(t *testing.T) {
+func TestHTTP_RSC7_AblyAgent(t *testing.T) {
t.Run("RSC7d2 : Should set ablyAgent header with correct identifiers", func(t *testing.T) {
var agentHeaderValue string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -255,7 +255,7 @@ func TestRest_RSC7_AblyAgent(t *testing.T) {
ably.WithUseTokenAuth(true),
}
- client, err := ably.NewREST(opts...)
+ client, err := ably.NewHTTPClient(opts...)
require.NoError(t, err)
expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier()
@@ -282,7 +282,7 @@ func TestRest_RSC7_AblyAgent(t *testing.T) {
}),
}
- client, err := ably.NewREST(opts...)
+ client, err := ably.NewHTTPClient(opts...)
require.NoError(t, err)
expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " foo/1.2.3"
@@ -309,7 +309,7 @@ func TestRest_RSC7_AblyAgent(t *testing.T) {
}),
}
- client, err := ably.NewREST(opts...)
+ client, err := ably.NewHTTPClient(opts...)
require.NoError(t, err)
expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " bar"
@@ -318,7 +318,7 @@ func TestRest_RSC7_AblyAgent(t *testing.T) {
})
}
-func TestRest_RSC15_HostFallback(t *testing.T) {
+func TestHTTP_RSC15_HostFallback(t *testing.T) {
app, err := ablytest.NewSandbox()
assert.NoError(t, err)
@@ -331,7 +331,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()
- client, err := ably.NewREST(app.Options(append(options, ably.WithHTTPClient(newHTTPClientMock(server)))...)...)
+ client, err := ably.NewHTTPClient(app.Options(append(options, ably.WithHTTPClient(newHTTPClientMock(server)))...)...)
require.NoError(t, err)
err = client.Channels.Get(ablytest.UniqueChannelName(t, "test")).Publish(context.Background(), "ping", "pong")
assert.Error(t, err, "expected an error")
@@ -375,7 +375,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) {
Proxy: func(*http.Request) (*url.URL, error) { return url.Parse(server.URL) },
},
}
- client, err := ably.NewREST(app.Options(append(options, ably.WithHTTPClient(httpClientMock))...)...)
+ client, err := ably.NewHTTPClient(app.Options(append(options, ably.WithHTTPClient(httpClientMock))...)...)
require.NoError(t, err)
err = client.Channels.Get(ablytest.UniqueChannelName(t, "test")).Publish(context.Background(), "ping", "pong")
<-allHostsTried
@@ -409,7 +409,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) {
ably.WithTLS(false),
ably.WithUseTokenAuth(true),
}
- client, err := ably.NewREST(app.Options(options...)...)
+ client, err := ably.NewHTTPClient(app.Options(options...)...)
require.NoError(t, err)
tm, err := client.Time(context.Background())
assert.Nil(t, err)
@@ -434,7 +434,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) {
assert.Contains(t, host, "example.com",
"expected host to contain \"example.com\" got %s", host)
})
- t.Run("must occur when fallbackHostsUseDefault is true", func(t *testing.T) {
+ t.Run("must occur when fallbackHosts is set to the default fallback hosts", func(t *testing.T) {
options := []ably.ClientOption{
ably.WithTLS(false),
@@ -478,7 +478,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) {
assert.Equal(t, 4, retryCount,
"expected 4 http calls got %d", retryCount)
firstHostCalled := hosts[0]
- restURL, _ := url.Parse(ably.ApplyOptionsWithDefaults(options...).RestURL())
+ restURL, _ := url.Parse(ably.ApplyOptionsWithDefaults(options...).HTTPURL())
assert.True(t, strings.HasPrefix(firstHostCalled, restURL.Hostname()),
"expected primary host got %s", firstHostCalled)
})
@@ -496,7 +496,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) {
})
}
-func TestRest_rememberHostFallback(t *testing.T) {
+func TestHTTP_rememberHostFallback(t *testing.T) {
app, err := ablytest.NewSandbox()
assert.NoError(t, err)
@@ -523,7 +523,7 @@ func TestRest_rememberHostFallback(t *testing.T) {
// host/port — for a per-test local child that isn't derivable from the
// endpoint name, and a bare nopts URL would 404 the app id.
serverURL, _ := url.Parse(server.URL)
- defaultURL, _ := url.Parse(ably.ApplyOptionsWithDefaults(app.Options(nopts...)...).RestURL())
+ defaultURL, _ := url.Parse(ably.ApplyOptionsWithDefaults(app.Options(nopts...)...).HTTPURL())
proxy := func(r *http.Request) (*url.URL, error) {
if r.URL.Hostname() == "fallback2" {
@@ -541,7 +541,7 @@ func TestRest_rememberHostFallback(t *testing.T) {
},
}))
- client, err := ably.NewREST(app.Options(nopts...)...)
+ client, err := ably.NewHTTPClient(app.Options(nopts...)...)
require.NoError(t, err)
channel := client.Channels.Get("remember_fallback_host")
err = channel.Publish(context.Background(), "ping", "pong")
@@ -561,11 +561,11 @@ func TestRest_rememberHostFallback(t *testing.T) {
"expected 0 retries got %d retries", retryCount)
})
}
-func TestRESTChannels_RSN1(t *testing.T) {
+func TestHTTPChannels_RSN1(t *testing.T) {
app, err := ablytest.NewSandbox()
require.NoError(t, err)
- client, err := ably.NewREST(app.Options()...)
+ client, err := ably.NewHTTPClient(app.Options()...)
require.NoError(t, err)
assert.NotNil(t, client.Channels,
"expected Channels to be initialized")
@@ -617,7 +617,7 @@ func TestFixConnLeak_ISSUE89(t *testing.T) {
}
opts := app.Options(ably.WithHTTPClient(httpClient))
- client, err := ably.NewREST(opts...)
+ client, err := ably.NewHTTPClient(opts...)
require.NoError(t, err)
channel := client.Channels.Get("issue89")
for i := 0; i < 10; i++ {
@@ -648,7 +648,7 @@ func TestStatsPagination_RSC6a_RSCb3(t *testing.T) {
for _, limit := range []int{2, 3, 20} {
t.Run(fmt.Sprintf("limit=%d", limit), func(t *testing.T) {
- app, rest := ablytest.NewREST()
+ app, rest := ablytest.NewHTTPClient()
fixtures := statsFixtures()
postStats(app, fixtures)
@@ -673,7 +673,7 @@ func TestStats_StartEnd_RSC6b1(t *testing.T) {
ctx := context.Background()
- app, rest := ablytest.NewREST()
+ app, rest := ablytest.NewHTTPClient()
fixtures := statsFixtures()
postStats(app, fixtures)
@@ -697,7 +697,7 @@ func TestStats_StartEnd_RSC6b1(t *testing.T) {
func TestStats_Direction_RSC6b2(t *testing.T) {
ctx := context.Background()
- app, rest := ablytest.NewREST()
+ app, rest := ablytest.NewHTTPClient()
for _, c := range []struct {
direction ably.Direction
@@ -743,7 +743,7 @@ func TestStats_Unit_RSC6b4(t *testing.T) {
ctx := context.Background()
- app, rest := ablytest.NewREST()
+ app, rest := ablytest.NewHTTPClient()
fixtures := statsFixtures()
postStats(app, fixtures)
diff --git a/ably/rest_client_test.go b/internal/ably/http_client_test.go
similarity index 90%
rename from ably/rest_client_test.go
rename to internal/ably/http_client_test.go
index ffe47d5a9..02de7e8c3 100644
--- a/ably/rest_client_test.go
+++ b/internal/ably/http_client_test.go
@@ -8,15 +8,15 @@ import (
"fmt"
"time"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
)
// When publishing a message to a channel, data can be either a single string or
// a struct of type Message. This example shows the different ways to publish a message.
-func ExampleRESTChannel_Publish() {
+func ExampleHTTPChannel_Publish() {
- // Create a new REST client.
- client, err := ably.NewREST(
+ // Create a new HTTP client.
+ client, err := ably.NewHTTPClient(
ably.WithKey("ABLY_PRIVATE_KEY"),
ably.WithClientID("Client A"),
)
diff --git a/ably/http_paginated_response_integration_test.go b/internal/ably/http_paginated_response_integration_test.go
similarity index 95%
rename from ably/http_paginated_response_integration_test.go
rename to internal/ably/http_paginated_response_integration_test.go
index 4bbfdd0a1..17cdae0e6 100644
--- a/ably/http_paginated_response_integration_test.go
+++ b/internal/ably/http_paginated_response_integration_test.go
@@ -10,8 +10,8 @@ import (
"sort"
"testing"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -23,7 +23,7 @@ func TestHTTPPaginatedFallback(t *testing.T) {
opts := app.Options(ably.WithUseBinaryProtocol(false),
ably.WithEndpoint("ably.invalid"),
ably.WithFallbackHosts(nil))
- client, err := ably.NewREST(opts...)
+ client, err := ably.NewHTTPClient(opts...)
require.NoError(t, err)
t.Run("request_time", func(t *testing.T) {
_, err := client.Request("get", "/time").Pages(context.Background())
@@ -34,7 +34,7 @@ func TestHTTPPaginatedFallback(t *testing.T) {
func TestHTTPPaginatedResponse(t *testing.T) {
app, err := ablytest.NewSandbox()
require.NoError(t, err)
- client, err := ably.NewREST(app.Options()...)
+ client, err := ably.NewHTTPClient(app.Options()...)
require.NoError(t, err)
t.Run("request_time", func(t *testing.T) {
res, err := client.Request("get", "/time").Pages(context.Background())
diff --git a/ably/rest_presence.go b/internal/ably/http_presence.go
similarity index 93%
rename from ably/rest_presence.go
rename to internal/ably/http_presence.go
index 9fb4728d1..2f78a9bb9 100644
--- a/ably/rest_presence.go
+++ b/internal/ably/http_presence.go
@@ -10,10 +10,10 @@ import (
"github.com/ugorji/go/codec"
)
-// RESTPresence enables the retrieval of the current and historic presence set for a channel.
-type RESTPresence struct {
- client *REST
- channel *RESTChannel
+// HTTPPresence enables the retrieval of the current and historic presence set for a channel.
+type HTTPPresence struct {
+ client *HTTPClient
+ channel *HTTPChannel
}
// Get retrieves the current members present on the channel and the metadata for each member, such as
@@ -21,7 +21,7 @@ type RESTPresence struct {
// containing an array of [ably.PresenceMessage]objects (RSPa).
//
// See package-level documentation => [ably] Pagination for more details.
-func (c *RESTPresence) Get(o ...GetPresenceOption) PresenceRequest {
+func (c *HTTPPresence) Get(o ...GetPresenceOption) PresenceRequest {
params := (&getPresenceOptions{}).apply(o...)
return PresenceRequest{
r: c.client.newPaginatedRequest("/channels/"+c.channel.Name+"/presence", "/channels/"+c.channel.pathName()+"/presence", params),
@@ -29,7 +29,7 @@ func (c *RESTPresence) Get(o ...GetPresenceOption) PresenceRequest {
}
}
-// GetPresenceOption configures a call to RESTPresence.Get or RealtimePresence.Get.
+// GetPresenceOption configures a call to HTTPPresence.Get or RealtimePresence.Get.
type GetPresenceOption func(*getPresenceOptions)
// GetPresenceWithLimit sets an upper limit on the number of messages returned.
@@ -67,7 +67,7 @@ func (o *getPresenceOptions) apply(opts ...GetPresenceOption) url.Values {
return o.params
}
-func (p *RESTPresence) log() logger {
+func (p *HTTPPresence) log() logger {
return p.client.log
}
@@ -77,7 +77,7 @@ func (p *RESTPresence) log() logger {
// to two minutes in the past (RSP4a).
//
// See package-level documentation => [ably] Pagination for details about history pagination.
-func (c *RESTPresence) History(o ...PresenceHistoryOption) PresenceRequest {
+func (c *HTTPPresence) History(o ...PresenceHistoryOption) PresenceRequest {
params := (&presenceHistoryOptions{}).apply(o...)
return PresenceRequest{
r: c.client.newPaginatedRequest("/channels/"+c.channel.Name+"/presence/history", "/channels/"+c.channel.pathName()+"/presence/history", params),
@@ -85,7 +85,7 @@ func (c *RESTPresence) History(o ...PresenceHistoryOption) PresenceRequest {
}
}
-// PresenceHistoryOption configures a call to RESTChannel.History or RealtimeChannel.History.
+// PresenceHistoryOption configures a call to HTTPChannel.History or RealtimeChannel.History.
type PresenceHistoryOption func(*presenceHistoryOptions)
// PresenceHistoryWithStart sets the time from which messages are retrieved, specified as milliseconds
@@ -134,11 +134,11 @@ func (o *presenceHistoryOptions) apply(opts ...PresenceHistoryOption) url.Values
return o.params
}
-// PresenceRequest represents a request prepared by the RESTPresence.History or
+// PresenceRequest represents a request prepared by the HTTPPresence.History or
// RealtimePresence.History method, ready to be performed by its Pages or Items methods.
type PresenceRequest struct {
r paginatedRequest
- channel *RESTChannel
+ channel *HTTPChannel
}
// Pages returns an iterator for whole pages of presence messages.
@@ -202,13 +202,13 @@ func (r PresenceRequest) Items(ctx context.Context) (*PresencePaginatedItems, er
// fullPresenceDecoder wraps a destination slice of messages in a decoder value
// that decodes both the message itself from the transport-level encoding and
// the data field within from its message-specific encoding.
-func (c *RESTChannel) fullPresenceDecoder(dst *[]*PresenceMessage) interface{} {
+func (c *HTTPChannel) fullPresenceDecoder(dst *[]*PresenceMessage) interface{} {
return &fullPresenceDecoder{dst: dst, c: c}
}
type fullPresenceDecoder struct {
dst *[]*PresenceMessage
- c *RESTChannel
+ c *HTTPChannel
}
func (t *fullPresenceDecoder) UnmarshalJSON(b []byte) error {
diff --git a/ably/rest_presence_spec_integration_test.go b/internal/ably/http_presence_spec_integration_test.go
similarity index 96%
rename from ably/rest_presence_spec_integration_test.go
rename to internal/ably/http_presence_spec_integration_test.go
index 3b1b38707..34f6da254 100644
--- a/ably/rest_presence_spec_integration_test.go
+++ b/internal/ably/http_presence_spec_integration_test.go
@@ -13,8 +13,8 @@ import (
"strings"
"testing"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
)
@@ -23,7 +23,7 @@ func TestPresenceHistory_RSP4_RSP4b3(t *testing.T) {
for _, limit := range []int{2, 3, 20} {
t.Run(fmt.Sprintf("limit=%d", limit), func(t *testing.T) {
- app, rest := ablytest.NewREST()
+ app, rest := ablytest.NewHTTPClient()
channelName := ablytest.ChannelName("persisted:test")
channel := rest.Channels.Get(channelName)
@@ -64,7 +64,7 @@ func TestPresenceHistory_Direction_RSP4b2(t *testing.T) {
c := c
t.Run(fmt.Sprintf("direction=%v", c.direction), func(t *testing.T) {
- app, rest := ablytest.NewREST()
+ app, rest := ablytest.NewHTTPClient()
channelName := ablytest.ChannelName("persisted:test")
channel := rest.Channels.Get(channelName)
@@ -96,7 +96,7 @@ func TestPresenceGet_RSP3_RSP3a1(t *testing.T) {
for _, limit := range []int{2, 3, 20} {
t.Run(fmt.Sprintf("limit=%d", limit), func(t *testing.T) {
- _, rest := ablytest.NewREST()
+ _, rest := ablytest.NewHTTPClient()
channel := presenceFixturesChannel(rest)
expected := persistedPresenceFixtures()
@@ -126,7 +126,7 @@ func TestPresenceGet_ClientID_RSP3a2(t *testing.T) {
clientID := clientID
t.Run(fmt.Sprintf("clientID=%v", clientID), func(t *testing.T) {
- _, rest := ablytest.NewREST()
+ _, rest := ablytest.NewHTTPClient()
channel := presenceFixturesChannel(rest)
expected := persistedPresenceFixtures(func(p ablytest.Presence) bool {
@@ -152,7 +152,7 @@ func TestPresenceGet_ClientID_RSP3a2(t *testing.T) {
}
func TestPresenceGet_ConnectionID_RSP3a3(t *testing.T) {
- app, rest := ablytest.NewREST()
+ app, rest := ablytest.NewHTTPClient()
expectedByConnID := map[string]ably.Message{}
@@ -264,7 +264,7 @@ const presenceFixturesChannelName = "persisted:presence_fixtures"
// presenceFixturesChannel returns the presence-fixtures channel configured with
// the cipher needed to decode the client_encoded fixture member.
-func presenceFixturesChannel(rest *ably.REST) *ably.RESTChannel {
+func presenceFixturesChannel(rest *ably.HTTPClient) *ably.HTTPChannel {
return rest.Channels.Get(
presenceFixturesChannelName,
ably.ChannelWithCipher(ablytest.PresenceFixturesCipher()),
diff --git a/ably/internal/ablyutil/idempotent.go b/internal/ably/internal/ablyutil/idempotent.go
similarity index 100%
rename from ably/internal/ablyutil/idempotent.go
rename to internal/ably/internal/ablyutil/idempotent.go
diff --git a/ably/internal/ablyutil/merge.go b/internal/ably/internal/ablyutil/merge.go
similarity index 100%
rename from ably/internal/ablyutil/merge.go
rename to internal/ably/internal/ablyutil/merge.go
diff --git a/ably/internal/ablyutil/msgpack.go b/internal/ably/internal/ablyutil/msgpack.go
similarity index 100%
rename from ably/internal/ablyutil/msgpack.go
rename to internal/ably/internal/ablyutil/msgpack.go
diff --git a/ably/internal/ablyutil/msgpack_test.go b/internal/ably/internal/ablyutil/msgpack_test.go
similarity index 100%
rename from ably/internal/ablyutil/msgpack_test.go
rename to internal/ably/internal/ablyutil/msgpack_test.go
diff --git a/ably/internal/ablyutil/regex.go b/internal/ably/internal/ablyutil/regex.go
similarity index 100%
rename from ably/internal/ablyutil/regex.go
rename to internal/ably/internal/ablyutil/regex.go
diff --git a/ably/internal/ablyutil/regex_test.go b/internal/ably/internal/ablyutil/regex_test.go
similarity index 100%
rename from ably/internal/ablyutil/regex_test.go
rename to internal/ably/internal/ablyutil/regex_test.go
diff --git a/ably/internal/ablyutil/strings.go b/internal/ably/internal/ablyutil/strings.go
similarity index 100%
rename from ably/internal/ablyutil/strings.go
rename to internal/ably/internal/ablyutil/strings.go
diff --git a/ably/internal/ablyutil/strings_test.go b/internal/ably/internal/ablyutil/strings_test.go
similarity index 97%
rename from ably/internal/ablyutil/strings_test.go
rename to internal/ably/internal/ablyutil/strings_test.go
index 0ae8f7b59..c1c76cdc1 100644
--- a/ably/internal/ablyutil/strings_test.go
+++ b/internal/ably/internal/ablyutil/strings_test.go
@@ -3,7 +3,7 @@ package ablyutil_test
import (
"testing"
- "github.com/ably/ably-go/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/internal/ablyutil/time.go b/internal/ably/internal/ablyutil/time.go
similarity index 100%
rename from ably/internal/ablyutil/time.go
rename to internal/ably/internal/ablyutil/time.go
diff --git a/ably/logger.go b/internal/ably/logger.go
similarity index 100%
rename from ably/logger.go
rename to internal/ably/logger.go
diff --git a/ably/logger_test.go b/internal/ably/logger_test.go
similarity index 95%
rename from ably/logger_test.go
rename to internal/ably/logger_test.go
index 71ffa7d93..9ce204cfc 100644
--- a/ably/logger_test.go
+++ b/internal/ably/logger_test.go
@@ -6,7 +6,7 @@ package ably_test
import (
"testing"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/main_integration_test.go b/internal/ably/main_integration_test.go
similarity index 91%
rename from ably/main_integration_test.go
rename to internal/ably/main_integration_test.go
index 2bae8d05f..a44b5f94f 100644
--- a/ably/main_integration_test.go
+++ b/internal/ably/main_integration_test.go
@@ -8,7 +8,7 @@ import (
"os"
"testing"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
)
// TestMain tears down the shared sandbox app once after all tests in this
diff --git a/ably/message_updates_integration_test.go b/internal/ably/message_updates_integration_test.go
similarity index 98%
rename from ably/message_updates_integration_test.go
rename to internal/ably/message_updates_integration_test.go
index b4e745c19..12d3dc46b 100644
--- a/ably/message_updates_integration_test.go
+++ b/internal/ably/message_updates_integration_test.go
@@ -8,18 +8,18 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
-func TestRESTChannel_MessageUpdates(t *testing.T) {
+func TestHTTPChannel_MessageUpdates(t *testing.T) {
app, err := ablytest.NewSandbox()
require.NoError(t, err)
- client, err := ably.NewREST(app.Options()...)
+ client, err := ably.NewHTTPClient(app.Options()...)
require.NoError(t, err)
ctx := context.Background()
diff --git a/ably/objects/liveobjects.go b/internal/ably/objects/liveobjects.go
similarity index 100%
rename from ably/objects/liveobjects.go
rename to internal/ably/objects/liveobjects.go
diff --git a/ably/options.go b/internal/ably/options.go
similarity index 89%
rename from ably/options.go
rename to internal/ably/options.go
index 70af05470..e162d3ace 100644
--- a/ably/options.go
+++ b/internal/ably/options.go
@@ -3,7 +3,6 @@ package ably
import (
"bytes"
"context"
- "errors"
"fmt"
"io"
"log"
@@ -16,8 +15,8 @@ import (
"strings"
"time"
- "github.com/ably/ably-go/ably/internal/ablyutil"
- "github.com/ably/ably-go/ably/objects"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ably/objects"
)
const (
@@ -39,11 +38,9 @@ const (
var defaultOptions = clientOptions{
Endpoint: defaultEndpoint,
- RESTHost: defaultPrimaryHost,
FallbackHosts: getEndpointFallbackHosts(defaultEndpoint), // REC2c1
HTTPMaxRetryCount: 3,
HTTPRequestTimeout: 10 * time.Second,
- RealtimeHost: defaultPrimaryHost,
TimeoutDisconnect: 30 * time.Second,
ConnectionStateTTL: 120 * time.Second,
RealtimeRequestTimeout: 10 * time.Second, // DF1b
@@ -52,7 +49,7 @@ var defaultOptions = clientOptions{
HTTPOpenTimeout: 4 * time.Second, //TO3l3
ChannelRetryTimeout: 15 * time.Second, // TO3l7
FallbackRetryTimeout: 10 * time.Minute,
- IdempotentRESTPublishing: true, // TO3n
+ IdempotentHTTPPublishing: true, // TO3n
Port: Port,
TLSPort: TLSPort,
Now: time.Now,
@@ -257,7 +254,7 @@ func (opts *authOptions) KeySecret() string {
return ""
}
-// clientOptions passes additional client-specific properties to the [ably.NewREST] or to the [ably.NewRealtime].
+// clientOptions passes additional client-specific properties to the [ably.NewHTTPClient] or to the [ably.NewRealtime].
// Properties set using [ably.clientOptions] are used instead of the [ably.defaultOptions] values.
type clientOptions struct {
// authOptions Embedded an [ably.authOptions] object (TO3j).
@@ -266,29 +263,11 @@ type clientOptions struct {
// Endpoint specifies either a routing policy name or fully qualified domain name to connect to Ably.
Endpoint string
- // Deprecated: this property is deprecated and will be removed in a future version.
- // If the restHost option is specified the primary domain is the value of the restHost option REC1d1).
- RESTHost string
-
- // Deprecated: this property is deprecated and will be removed in a future version.
- // Enables default fallback hosts to be used (TO3k7).
- FallbackHostsUseDefault bool
-
// FallbackHosts is an array of fallback hosts to be used in the case of an error necessitating
// the use of an alternative host. If you have been provided a set of custom fallback hosts by Ably,
// please specify them here (RSC15b, RSC15a, TO3k6).
FallbackHosts []string
- // Deprecated: this property is deprecated and will be removed in a future version.
- // If the realtimeHost option is specified the primary domain is the value of the realtimeHost option (REC1d2).
- RealtimeHost string
-
- // Deprecated: this property is deprecated and will be removed in a future version.
- // If the deprecated environment option is specified then it defines a production routing policy name [name] (REC1c):
- // If any one of the deprecated options restHost, realtimeHost are also specified then the options as a set are invalid (REC1c1).
- // Otherwise, the primary domain is [name].realtime.ably.net (REC1c2).
- Environment string
-
// Port is used for non-TLS connections and requests
Port int
@@ -358,11 +337,11 @@ type clientOptions struct {
// The default is true (TO3f).
NoBinaryProtocol bool
- // IdempotentRESTPublishing when set to true, enables idempotent publishing by assigning a
+ // IdempotentHTTPPublishing when set to true, enables idempotent publishing by assigning a
// unique message ID client-side, allowing the Ably servers to discard automatic publish retries
// following a failure such as a network fault.
// The default is true (RSL1k1, RTL6a1, TO3n).
- IdempotentRESTPublishing bool
+ IdempotentHTTPPublishing bool
// Deprecated: use RealtimeRequestTimeout instead.
// TimeoutConnect is a timeout for the wait of acknowledgement for operations performed via a realtime connection,
@@ -409,7 +388,7 @@ type clientOptions struct {
// If Dial is nil, the default websocket connection is used.
Dial func(protocol string, u *url.URL, timeout time.Duration) (conn, error)
- // HTTPClient specifies the client used for HTTP communication by REST.
+ // HTTPClient specifies the client used for HTTP communication by [ably.HTTPClient].
// When set to nil, a client configured with default settings is used.
HTTPClient *http.Client
@@ -453,14 +432,6 @@ type clientOptions struct {
}
func (opts *clientOptions) validate() error {
- // REC1b1
- if !empty(opts.Endpoint) && (!empty(opts.Environment) || !empty(opts.RealtimeHost) || !empty(opts.RESTHost) || opts.FallbackHostsUseDefault) {
- err := errors.New("invalid client option: cannot use endpoint with any of deprecated options environment, realtimeHost, restHost or FallbackHostsUseDefault")
- logger := opts.LogHandler
- logger.Printf(LogError, "Invalid client options : %v", err.Error())
- return err
- }
-
_, err := opts.getFallbackHosts()
if err != nil {
logger := opts.LogHandler
@@ -470,11 +441,6 @@ func (opts *clientOptions) validate() error {
return nil
}
-func (opts *clientOptions) isProductionEnvironment() bool {
- env := opts.Environment
- return empty(env) || strings.EqualFold(env, "production")
-}
-
func (opts *clientOptions) activePort() (port int, isDefault bool) {
if opts.NoTLS {
port = opts.Port
@@ -496,35 +462,14 @@ func (opts *clientOptions) activePort() (port int, isDefault bool) {
return
}
-func (opts *clientOptions) getRestHost() string {
- if !empty(opts.Endpoint) {
- return opts.getHostnameFromEndpoint()
- }
- if !empty(opts.RESTHost) {
- return opts.RESTHost
- }
- if !opts.isProductionEnvironment() {
- return getPrimaryHost(opts.Environment)
- }
- return defaultOptions.RESTHost
+// getHTTPHost returns the primary domain for HTTP requests (REC1).
+func (opts *clientOptions) getHTTPHost() string {
+ return opts.getHostnameFromEndpoint()
}
+// getRealtimeHost returns the primary domain for realtime connections (REC1).
func (opts *clientOptions) getRealtimeHost() string {
- if !empty(opts.Endpoint) {
- return opts.getHostnameFromEndpoint()
- }
- if !empty(opts.RealtimeHost) {
- return opts.RealtimeHost
- }
- if !empty(opts.RESTHost) {
- logger := opts.LogHandler
- logger.Printf(LogWarning, "restHost is set to %s but realtimeHost is not set so setting realtimeHost to %s too. If this is not what you want, please set realtimeHost explicitly.", opts.RESTHost, opts.RealtimeHost)
- return opts.RESTHost
- }
- if !opts.isProductionEnvironment() {
- return getPrimaryHost(opts.Environment)
- }
- return defaultOptions.RealtimeHost
+ return opts.getHostnameFromEndpoint()
}
// REC1b2: isEndpointHostname returns true if the given endpoint is a hostname, which may
@@ -550,7 +495,7 @@ func empty(s string) bool {
}
func (opts *clientOptions) restURL() (restUrl string) {
- baseUrl := opts.getRestHost()
+ baseUrl := opts.getHTTPHost()
_, _, err := net.SplitHostPort(baseUrl)
if err != nil { // set port if not set in baseUrl
port, _ := opts.activePort()
@@ -575,39 +520,18 @@ func (opts *clientOptions) realtimeURL(realtimeHost string) (realtimeUrl string)
return "wss://" + baseUrl
}
+// getFallbackHosts returns the fallback domains (REC2).
func (opts *clientOptions) getFallbackHosts() ([]string, error) {
- if !empty(opts.Endpoint) {
- if opts.FallbackHosts == nil {
- if isEndpointHostname(opts.Endpoint) { // REC2c2
- return opts.FallbackHosts, nil
- }
- return getEndpointFallbackHosts(opts.Endpoint), nil
- }
- return opts.FallbackHosts, nil //REC2a2
+ if opts.FallbackHosts != nil { // REC2a2
+ return opts.FallbackHosts, nil
}
-
- logger := opts.LogHandler
- _, isDefaultPort := opts.activePort()
- if opts.FallbackHostsUseDefault {
- if opts.FallbackHosts != nil {
- return nil, errors.New("fallbackHosts and fallbackHostsUseDefault cannot both be set")
- }
- if !isDefaultPort {
- return nil, errors.New("fallbackHostsUseDefault cannot be set when port or tlsPort are set")
- }
- if !empty(opts.Environment) {
- logger.Printf(LogWarning, "Deprecated fallbackHostsUseDefault : There is no longer a need to set this when the environment option is also set since the library can generate the correct fallback hosts using the environment option.")
- }
- logger.Printf(LogWarning, "Deprecated fallbackHostsUseDefault : using default fallbackhosts")
+ if empty(opts.Endpoint) { // REC2c1
return defaultOptions.FallbackHosts, nil
}
- if opts.FallbackHosts == nil && empty(opts.RESTHost) && empty(opts.RealtimeHost) && isDefaultPort {
- if opts.isProductionEnvironment() {
- return defaultOptions.FallbackHosts, nil
- }
- return getEndpointFallbackHosts(opts.Environment), nil // REC2c5
+ if isEndpointHostname(opts.Endpoint) { // REC2c2
+ return nil, nil
}
- return opts.FallbackHosts, nil
+ return getEndpointFallbackHosts(opts.Endpoint), nil // REC2c3, REC2c4
}
func (opts *clientOptions) timeoutConnect() time.Duration {
@@ -681,8 +605,8 @@ func (opts *clientOptions) protocol() string {
return protocolMsgPack
}
-func (opts *clientOptions) idempotentRESTPublishing() bool {
- return opts.IdempotentRESTPublishing
+func (opts *clientOptions) idempotentHTTPPublishing() bool {
+ return opts.IdempotentHTTPPublishing
}
// RTN17c
@@ -746,12 +670,12 @@ func (p *PaginateParams) EncodeValues(out *url.Values) error {
return nil
}
-// ClientOption configures a [ably.REST] or [ably.Realtime] instance.
+// ClientOption configures a [ably.HTTPClient] or [ably.Realtime] instance.
//
// See: https://www.ably.io/documentation/realtime/usage#client-options
type ClientOption func(*clientOptions)
-// AuthOption configures authentication/authorization for a [ably.REST] or [ably.Realtime]
+// AuthOption configures authentication/authorization for a [ably.HTTPClient] or [ably.Realtime]
// instance or operation.
type AuthOption func(*authOptions)
@@ -1150,26 +1074,24 @@ func WithEchoMessages(echo bool) ClientOption {
}
}
-// WithEndpoint sets a custom endpoint for connecting to the Ably service (see
-// [Platform Customization] for more information).
+// WithEndpoint sets the endpoint used for both HTTP requests and the realtime
+// connection (REC1). It is one of:
//
-// [Platform Customization]: https://ably.com/docs/platform-customization
-func WithEndpoint(env string) ClientOption {
- return func(os *clientOptions) {
- os.Endpoint = env
- }
-}
-
-// WithEnvironment sets a custom endpoint for connecting to the Ably service
-// (see [Platform Customization] for more information).
+// - a routing policy name, such as "acme", which connects to
+// acme.realtime.ably.net;
+// - "nonprod:" followed by a routing policy name, such as "nonprod:sandbox",
+// which connects to sandbox.realtime.ably-nonprod.net;
+// - a hostname (it contains "." or "::", or is "localhost"), which is used as
+// given and has no default fallback hosts.
//
-// Deprecated: this option is deprecated and will be removed in a future
-// version.
+// If unset, the client connects to main.realtime.ably.net. Default fallback
+// hosts follow the endpoint (REC2); use [WithFallbackHosts] to replace them.
+// See [Platform Customization] for more information.
//
// [Platform Customization]: https://ably.com/docs/platform-customization
-func WithEnvironment(env string) ClientOption {
+func WithEndpoint(endpoint string) ClientOption {
return func(os *clientOptions) {
- os.Environment = env
+ os.Endpoint = endpoint
}
}
@@ -1194,9 +1116,18 @@ func WithLogLevel(level LogLevel) ClientOption {
// WithAgents is used to add product/version key-value pairs to include in the
// agent library identifiers. This must only be used by Ably-authored SDKs.
+// Successive calls merge their entries into any agents already configured
+// rather than replacing them.
func WithAgents(agents map[string]string) ClientOption {
return func(os *clientOptions) {
- os.Agents = agents
+ merged := make(map[string]string, len(os.Agents)+len(agents))
+ for product, version := range os.Agents {
+ merged[product] = version
+ }
+ for product, version := range agents {
+ merged[product] = version
+ }
+ os.Agents = merged
}
}
@@ -1221,18 +1152,6 @@ func WithQueueMessages(queue bool) ClientOption {
}
}
-// WithRESTHost is used for setting RESTHost using [ably.ClientOption].
-// RESTHost enables a non-default Ably host to be specified. For development environments only.
-// The default value is rest.ably.io (RSC12, TO3k2).
-//
-// Deprecated: this option is deprecated and will be removed in a future
-// version.
-func WithRESTHost(host string) ClientOption {
- return func(os *clientOptions) {
- os.RESTHost = host
- }
-}
-
// WithHTTPRequestTimeout is used for setting HTTPRequestTimeout using [ably.ClientOption].
// HTTPRequestTimeout is a timeout for a client performing a complete HTTP request to Ably, including the connection phase.
// Will only be used if no custom HTTPClient is set.
@@ -1243,18 +1162,6 @@ func WithHTTPRequestTimeout(timeout time.Duration) ClientOption {
}
}
-// WithRealtimeHost is used for setting RealtimeHost using [ably.ClientOption].
-// RealtimeHost enables a non-default Ably host to be specified for realtime connections.
-// For development environments only. The default value is realtime.ably.io (RTC1d, TO3k3).
-//
-// Deprecated: this option is deprecated and will be removed in a future
-// version.
-func WithRealtimeHost(host string) ClientOption {
- return func(os *clientOptions) {
- os.RealtimeHost = host
- }
-}
-
// WithFallbackHosts is used for setting FallbackHosts using [ably.ClientOption].
// FallbackHosts is an array of fallback hosts to be used in the case of an error necessitating
// the use of an alternative host. If you have been provided a set of custom fallback hosts by Ably,
@@ -1381,19 +1288,19 @@ func WithHTTPMaxRetryCount(count int) ClientOption {
}
}
-// WithIdempotentRESTPublishing is used for setting IdempotentRESTPublishing using [ably.ClientOption].
-// IdempotentRESTPublishing when set to true, enables idempotent publishing by assigning a
+// WithIdempotentHTTPPublishing is used for setting IdempotentHTTPPublishing using [ably.ClientOption].
+// IdempotentHTTPPublishing when set to true, enables idempotent publishing by assigning a
// unique message ID client-side, allowing the Ably servers to discard automatic publish retries
// following a failure such as a network fault.
// The default is true (RSL1k1, RTL6a1, TO3n).
-func WithIdempotentRESTPublishing(idempotent bool) ClientOption {
+func WithIdempotentHTTPPublishing(idempotent bool) ClientOption {
return func(os *clientOptions) {
- os.IdempotentRESTPublishing = idempotent
+ os.IdempotentHTTPPublishing = idempotent
}
}
// WithHTTPClient is used for setting HTTPClient using [ably.ClientOption].
-// HTTPClient specifies the client used for HTTP communication by REST.
+// HTTPClient specifies the client used for HTTP communication by [ably.HTTPClient].
// When set to nil, a client configured with default settings is used.
func WithHTTPClient(client *http.Client) ClientOption {
return func(os *clientOptions) {
@@ -1401,16 +1308,6 @@ func WithHTTPClient(client *http.Client) ClientOption {
}
}
-// Deprecated: This function is deprecated and will be removed in a future versions.
-// WithFallbackHostsUseDefault is used for setting FallbackHostsUseDefault using [ably.ClientOption].
-// Deprecated: this property is deprecated and will be removed in a future version.
-// Enables default fallback hosts to be used (TO3k7).
-func WithFallbackHostsUseDefault(fallbackHostsUseDefault bool) ClientOption {
- return func(os *clientOptions) {
- os.FallbackHostsUseDefault = fallbackHostsUseDefault
- }
-}
-
// WithDial is used for setting Dial using [ably.ClientOption].
// Dial specifies the dial function for creating message connections used by Realtime.
// If Dial is nil, the default websocket connection is used.
@@ -1455,8 +1352,6 @@ func applyOptionsWithDefaults(opts ...ClientOption) *clientOptions {
to := defaultOptions
// No need to set hosts by default
to.Endpoint = ""
- to.RESTHost = ""
- to.RealtimeHost = ""
to.FallbackHosts = nil
for _, set := range opts {
diff --git a/ably/options_test.go b/internal/ably/options_test.go
similarity index 58%
rename from ably/options_test.go
rename to internal/ably/options_test.go
index 11bb75951..075872689 100644
--- a/ably/options_test.go
+++ b/internal/ably/options_test.go
@@ -8,7 +8,7 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
"github.com/stretchr/testify/assert"
)
@@ -126,100 +126,9 @@ func TestHosts_REC1(t *testing.T) {
assert.Equal(t, []string{"fallback.foo.example.com"}, fallbackHosts)
})
- t.Run("legacy support", func(t *testing.T) {
- t.Run("REC1c with production environment", func(t *testing.T) {
- clientOptions := ably.NewClientOptions(ably.WithEnvironment("production"))
- assert.Equal(t, "main.realtime.ably.net", clientOptions.GetHostnameFromEndpoint())
- assert.False(t, clientOptions.NoTLS)
- port, isDefaultPort := clientOptions.ActivePort()
- assert.Equal(t, 443, port)
- assert.True(t, isDefaultPort)
- fallbackHosts, _ := clientOptions.GetFallbackHosts()
- assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts)
- })
-
- t.Run("REC1c with custom environment", func(t *testing.T) {
- clientOptions := ably.NewClientOptions(ably.WithEnvironment("acme"))
- assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetRestHost())
- assert.False(t, clientOptions.NoTLS)
- port, isDefaultPort := clientOptions.ActivePort()
- assert.Equal(t, 443, port)
- assert.True(t, isDefaultPort)
- fallbackHosts, _ := clientOptions.GetFallbackHosts()
- assert.Equal(t, ably.GetEndpointFallbackHosts("acme"), fallbackHosts)
- })
-
- t.Run("REC1c REC2a1 with custom environment and fallbackHostUseDefault", func(t *testing.T) {
- clientOptions := ably.NewClientOptions(ably.WithEnvironment("acme"), ably.WithFallbackHostsUseDefault(true))
- assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetRestHost())
- assert.False(t, clientOptions.NoTLS)
- port, isDefaultPort := clientOptions.ActivePort()
- assert.Equal(t, 443, port)
- assert.True(t, isDefaultPort)
- fallbackHosts, _ := clientOptions.GetFallbackHosts()
- assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts)
- })
-
- t.Run("REC1d1 with custom restHost", func(t *testing.T) {
- clientOptions := ably.NewClientOptions(ably.WithRESTHost("test.org"))
- assert.Equal(t, "test.org", clientOptions.GetRestHost())
- assert.False(t, clientOptions.NoTLS)
- port, isDefaultPort := clientOptions.ActivePort()
- assert.Equal(t, 443, port)
- assert.True(t, isDefaultPort)
- fallbackHosts, _ := clientOptions.GetFallbackHosts()
- assert.Nil(t, fallbackHosts)
- })
-
- t.Run("REC1d2 with custom realtimeHost", func(t *testing.T) {
- clientOptions := ably.NewClientOptions(ably.WithRealtimeHost("ws.test.org"))
- assert.Equal(t, "ws.test.org", clientOptions.GetRealtimeHost())
- assert.False(t, clientOptions.NoTLS)
- port, isDefaultPort := clientOptions.ActivePort()
- assert.Equal(t, 443, port)
- assert.True(t, isDefaultPort)
- fallbackHosts, _ := clientOptions.GetFallbackHosts()
- assert.Nil(t, fallbackHosts)
- })
-
- t.Run("REC1d with custom restHost and realtimeHost", func(t *testing.T) {
- clientOptions := ably.NewClientOptions(ably.WithRealtimeHost("ws.test.org"), ably.WithRESTHost("test.org"))
- assert.Equal(t, "test.org", clientOptions.GetRestHost())
- assert.False(t, clientOptions.NoTLS)
- port, isDefaultPort := clientOptions.ActivePort()
- assert.Equal(t, 443, port)
- assert.True(t, isDefaultPort)
- fallbackHosts, _ := clientOptions.GetFallbackHosts()
- assert.Nil(t, fallbackHosts)
- })
-
- t.Run("REC1d REC2b with custom restHost and realtimeHost and fallbackHostsUseDefault", func(t *testing.T) {
- clientOptions := ably.NewClientOptions(
- ably.WithRealtimeHost("ws.test.org"),
- ably.WithRESTHost("test.org"),
- ably.WithFallbackHostsUseDefault(true))
- assert.Equal(t, "test.org", clientOptions.GetRestHost())
- assert.False(t, clientOptions.NoTLS)
- port, isDefaultPort := clientOptions.ActivePort()
- assert.Equal(t, 443, port)
- assert.True(t, isDefaultPort)
- fallbackHosts, _ := clientOptions.GetFallbackHosts()
- assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts)
- })
- })
-
- t.Run("If endpoint option is used with deprecated fallbackHostUseDefault, throw error", func(t *testing.T) {
- clientOptions := ably.NewClientOptions(
- ably.WithFallbackHostsUseDefault(true),
- ably.WithEndpoint("custom"))
- err := clientOptions.Validate()
- assert.Equal(t, err.Error(),
- "invalid client option: cannot use endpoint with any of deprecated options environment, realtimeHost, restHost or FallbackHostsUseDefault")
- })
-
t.Run("REC2a with fallbackHosts", func(t *testing.T) {
clientOptions := ably.NewClientOptions(ably.WithFallbackHosts([]string{"a.example.com", "b.example.com"}))
- assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRestHost())
+ assert.Equal(t, "main.realtime.ably.net", clientOptions.GetHTTPHost())
assert.False(t, clientOptions.NoTLS)
port, isDefaultPort := clientOptions.ActivePort()
assert.Equal(t, 443, port)
@@ -227,75 +136,58 @@ func TestHosts_REC1(t *testing.T) {
fallbackHosts, _ := clientOptions.GetFallbackHosts()
assert.Equal(t, []string{"a.example.com", "b.example.com"}, fallbackHosts)
})
+}
- t.Run("REC2a1 with fallbackHosts and fallbackHostsUseDefault", func(t *testing.T) {
- clientOptions := ably.NewClientOptions(
- ably.WithFallbackHosts([]string{"a.example.com", "b.example.com"}),
- ably.WithFallbackHostsUseDefault(true))
- _, err := clientOptions.GetFallbackHosts()
- assert.Equal(t, err.Error(),
- "fallbackHosts and fallbackHostsUseDefault cannot both be set")
- })
-
- t.Run("REC2a1 with fallbackHostsUseDefault And custom port", func(t *testing.T) {
- clientOptions := ably.NewClientOptions(ably.WithTLSPort(8081), ably.WithFallbackHostsUseDefault(true))
- _, isDefaultPort := clientOptions.ActivePort()
- assert.False(t, isDefaultPort)
- _, err := clientOptions.GetFallbackHosts()
- assert.Equal(t, err.Error(),
- "fallbackHostsUseDefault cannot be set when port or tlsPort are set")
-
- clientOptions = ably.NewClientOptions(
- ably.WithTLS(false),
- ably.WithPort(8080),
- ably.WithFallbackHostsUseDefault(true))
-
- _, isDefaultPort = clientOptions.ActivePort()
- assert.False(t, isDefaultPort)
- _, err = clientOptions.GetFallbackHosts()
- assert.Equal(t, err.Error(),
- "fallbackHostsUseDefault cannot be set when port or tlsPort are set")
- })
+func TestEndpointResolution_REC1_REC2(t *testing.T) {
+ defaultFallbacks := ably.DefaultFallbackHosts()
+ custom := []string{"a.example.com", "b.example.com"}
+
+ tests := []struct {
+ name string
+ opts []ably.ClientOption
+ host string
+ fallbackHosts []string
+ }{
+ {"unset endpoint", nil, "main.realtime.ably.net", defaultFallbacks},
+ {"routing policy name", []ably.ClientOption{ably.WithEndpoint("acme")}, "acme.realtime.ably.net", ably.GetEndpointFallbackHosts("acme")},
+ {"nonprod routing policy name", []ably.ClientOption{ably.WithEndpoint("nonprod:sandbox")}, "sandbox.realtime.ably-nonprod.net", ably.GetEndpointFallbackHosts("nonprod:sandbox")},
+ {"hostname", []ably.ClientOption{ably.WithEndpoint("foo.example.com")}, "foo.example.com", nil},
+ {"localhost", []ably.ClientOption{ably.WithEndpoint("localhost")}, "localhost", nil},
+ {"ipv6 hostname", []ably.ClientOption{ably.WithEndpoint("::1")}, "::1", nil},
+ {"explicit fallbackHosts replace default fallbacks", []ably.ClientOption{ably.WithFallbackHosts(custom)}, "main.realtime.ably.net", custom},
+ {"explicit fallbackHosts replace routing policy fallbacks", []ably.ClientOption{ably.WithEndpoint("acme"), ably.WithFallbackHosts(custom)}, "acme.realtime.ably.net", custom},
+ {"explicit fallbackHosts with hostname endpoint", []ably.ClientOption{ably.WithEndpoint("foo.example.com"), ably.WithFallbackHosts(custom)}, "foo.example.com", custom},
+ {"custom port keeps default fallbacks", []ably.ClientOption{ably.WithTLS(false), ably.WithPort(8080)}, "main.realtime.ably.net", defaultFallbacks},
+ {"custom TLS port keeps default fallbacks", []ably.ClientOption{ably.WithTLSPort(8081)}, "main.realtime.ably.net", defaultFallbacks},
+ {"custom port keeps routing policy fallbacks", []ably.ClientOption{ably.WithEndpoint("acme"), ably.WithTLSPort(8081)}, "acme.realtime.ably.net", ably.GetEndpointFallbackHosts("acme")},
+ {"custom port with hostname endpoint has no fallbacks", []ably.ClientOption{ably.WithEndpoint("localhost"), ably.WithTLS(false), ably.WithPort(8080)}, "localhost", nil},
+ }
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ clientOptions := ably.NewClientOptions(tc.opts...)
+ assert.NoError(t, clientOptions.Validate())
+ assert.Equal(t, tc.host, clientOptions.GetHTTPHost())
+ assert.Equal(t, tc.host, clientOptions.GetRealtimeHost())
+ fallbackHosts, err := clientOptions.GetFallbackHosts()
+ assert.NoError(t, err)
+ assert.Equal(t, tc.fallbackHosts, fallbackHosts)
+ })
+ }
}
func TestClientOptions(t *testing.T) {
t.Run("must return error on invalid key", func(t *testing.T) {
- _, err := ably.NewREST([]ably.ClientOption{ably.WithKey("invalid")}...)
+ _, err := ably.NewHTTPClient([]ably.ClientOption{ably.WithKey("invalid")}...)
assert.Error(t, err,
"expected an error")
})
t.Run("must return error on empty options", func(t *testing.T) {
- _, err := ably.NewREST()
+ _, err := ably.NewHTTPClient()
assert.Error(t, err,
"expected an error")
})
t.Run("must return error on nil value options", func(t *testing.T) {
- _, err := ably.NewREST(nil)
- assert.Error(t, err,
- "expected an error")
- })
- t.Run("must return error on invalid combinations", func(t *testing.T) {
- _, err := ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithEnvironment("acme"), ably.WithRealtimeHost("foo.example.com"), ably.WithRESTHost("foo.example.com")}...)
- assert.Error(t, err,
- "expected an error")
-
- _, err = ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithEnvironment("acme")}...)
- assert.Error(t, err,
- "expected an error")
-
- _, err = ably.NewREST([]ably.ClientOption{ably.WithEnvironment("acme"), ably.WithRealtimeHost("foo.example.com")}...)
- assert.Error(t, err,
- "expected an error")
-
- _, err = ably.NewREST([]ably.ClientOption{ably.WithEnvironment("acme"), ably.WithRESTHost("foo.example.com")}...)
- assert.Error(t, err,
- "expected an error")
-
- _, err = ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithRealtimeHost("foo.example.com")}...)
- assert.Error(t, err,
- "expected an error")
-
- _, err = ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithRESTHost("foo.example.com")}...)
+ _, err := ably.NewHTTPClient(nil)
assert.Error(t, err,
"expected an error")
})
@@ -333,7 +225,7 @@ func TestScopeParams(t *testing.T) {
func TestOption_NoTLS(t *testing.T) {
t.Run("does not allow basic auth with no TLS", func(t *testing.T) {
- _, err := ably.NewREST(
+ _, err := ably.NewHTTPClient(
ably.WithKey("xxxxxx.yyyyyy:zzzzzz"),
ably.WithTLS(false),
)
@@ -344,7 +236,7 @@ func TestOption_NoTLS(t *testing.T) {
})
t.Run("allows basic auth with no TLS when InsecureAllowBasicAuthWithoutTLS is set", func(t *testing.T) {
- _, err := ably.NewREST(
+ _, err := ably.NewHTTPClient(
ably.WithKey("xxxxxx.yyyyyy:zzzzzz"),
ably.WithTLS(false),
ably.WithInsecureAllowBasicAuthWithoutTLS(),
diff --git a/ably/os_version_other.go b/internal/ably/os_version_other.go
similarity index 100%
rename from ably/os_version_other.go
rename to internal/ably/os_version_other.go
diff --git a/ably/os_version_unix.go b/internal/ably/os_version_unix.go
similarity index 100%
rename from ably/os_version_unix.go
rename to internal/ably/os_version_unix.go
diff --git a/ably/os_version_windows.go b/internal/ably/os_version_windows.go
similarity index 100%
rename from ably/os_version_windows.go
rename to internal/ably/os_version_windows.go
diff --git a/ably/paginated_result.go b/internal/ably/paginated_result.go
similarity index 97%
rename from ably/paginated_result.go
rename to internal/ably/paginated_result.go
index 373665eff..9e7b22c98 100644
--- a/ably/paginated_result.go
+++ b/internal/ably/paginated_result.go
@@ -24,7 +24,7 @@ type paginatedRequest struct {
query queryFunc
}
-func (r *REST) newPaginatedRequest(path, rawPath string, params url.Values) paginatedRequest {
+func (r *HTTPClient) newPaginatedRequest(path, rawPath string, params url.Values) paginatedRequest {
return paginatedRequest{
path: path,
rawPath: rawPath,
diff --git a/ably/proto_action.go b/internal/ably/proto_action.go
similarity index 100%
rename from ably/proto_action.go
rename to internal/ably/proto_action.go
diff --git a/ably/proto_channel_options.go b/internal/ably/proto_channel_options.go
similarity index 97%
rename from ably/proto_channel_options.go
rename to internal/ably/proto_channel_options.go
index 98893bb78..726c73fcc 100644
--- a/ably/proto_channel_options.go
+++ b/internal/ably/proto_channel_options.go
@@ -62,7 +62,7 @@ func channelModeFromFlag(flags protoFlag) []ChannelMode {
return modes
}
-// protoChannelOptions defines additional properties to a [ably.RESTChannel] or [ably.RealtimeChannel] object,
+// protoChannelOptions defines additional properties to a [ably.HTTPChannel] or [ably.RealtimeChannel] object,
// such as encryption, [ably.ChannelMode] and channel parameters.
// It defines options provided for creating a new channel.
type protoChannelOptions struct {
diff --git a/ably/proto_channel_options_test.go b/internal/ably/proto_channel_options_test.go
similarity index 97%
rename from ably/proto_channel_options_test.go
rename to internal/ably/proto_channel_options_test.go
index d04bfb101..5ea23c73d 100644
--- a/ably/proto_channel_options_test.go
+++ b/internal/ably/proto_channel_options_test.go
@@ -7,7 +7,7 @@ import (
"reflect"
"testing"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/proto_channel_propeties.go b/internal/ably/proto_channel_propeties.go
similarity index 100%
rename from ably/proto_channel_propeties.go
rename to internal/ably/proto_channel_propeties.go
diff --git a/ably/proto_crypto.go b/internal/ably/proto_crypto.go
similarity index 99%
rename from ably/proto_crypto.go
rename to internal/ably/proto_crypto.go
index ffd719fd8..bc8fd13a1 100644
--- a/ably/proto_crypto.go
+++ b/internal/ably/proto_crypto.go
@@ -56,7 +56,7 @@ const (
defaultCipherMode = CipherCBC
)
-// CipherParams sets the properties to configure encryption for a [ably.RESTChannel] or
+// CipherParams sets the properties to configure encryption for a [ably.HTTPChannel] or
// [ably.RealtimeChannel] object (TZ1).
type CipherParams struct {
// Algorithm is the algorithm to use for encryption. Only AES is supported and is the default value (TZ2a).
diff --git a/ably/proto_error.go b/internal/ably/proto_error.go
similarity index 100%
rename from ably/proto_error.go
rename to internal/ably/proto_error.go
diff --git a/ably/proto_error_test.go b/internal/ably/proto_error_test.go
similarity index 100%
rename from ably/proto_error_test.go
rename to internal/ably/proto_error_test.go
diff --git a/ably/proto_http.go b/internal/ably/proto_http.go
similarity index 81%
rename from ably/proto_http.go
rename to internal/ably/proto_http.go
index d6d667813..28fff08bf 100644
--- a/ably/proto_http.go
+++ b/internal/ably/proto_http.go
@@ -3,6 +3,7 @@ package ably
import (
"fmt"
"runtime"
+ "sort"
"strings"
)
@@ -23,8 +24,8 @@ const (
ablyProtocolVersion = "5" // CSV2
ablyClientIDHeader = "X-Ably-ClientId"
hostHeader = "Host"
- ablyAgentHeader = "Ably-Agent" // RSC7d
- ablySDKIdentifier = "ably-go/" + clientLibraryVersion // RSC7d1
+ ablyAgentHeader = "Ably-Agent" // RSC7d
+ ablySDKIdentifier = "ably-pubsub-go/" + clientLibraryVersion // RSC7d1
)
// AblyAgentHeaderName is the name of the Ably-Agent header (RSC7d), exported for
@@ -54,7 +55,15 @@ func ablyAgentIdentifier(agents map[string]string) string {
identifiers = append(identifiers, osIdentifier)
}
- for product, version := range agents {
+ // Sort the products so the header is the same on every request; map
+ // iteration order is random.
+ products := make([]string, 0, len(agents))
+ for product := range agents {
+ products = append(products, product)
+ }
+ sort.Strings(products)
+ for _, product := range products {
+ version := agents[product]
if empty(version) {
identifiers = append(identifiers, product)
} else {
diff --git a/ably/proto_message.go b/internal/ably/proto_message.go
similarity index 100%
rename from ably/proto_message.go
rename to internal/ably/proto_message.go
diff --git a/ably/proto_message_decoding_test.go b/internal/ably/proto_message_decoding_test.go
similarity index 95%
rename from ably/proto_message_decoding_test.go
rename to internal/ably/proto_message_decoding_test.go
index f96d5a487..92d684020 100644
--- a/ably/proto_message_decoding_test.go
+++ b/internal/ably/proto_message_decoding_test.go
@@ -9,7 +9,7 @@ import (
"strings"
"testing"
- "github.com/ably/ably-go/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -28,7 +28,7 @@ func loadFixtures() ([]fixture, error) {
}
// We can't embed the fixtures as go:embed forbids embedding of resources above the current directory.
- text, err := os.ReadFile("../common/test-resources/messages-encoding.json")
+ text, err := os.ReadFile("../../common/test-resources/messages-encoding.json")
if err != nil {
return nil, err
}
@@ -98,7 +98,7 @@ func loadMsgpackFixtures() ([]MsgpackTestFixture, error) {
var o []MsgpackTestFixture
// We can't embed the fixtures as go:embed forbids embedding of resources above the current directory.
- text, err := os.ReadFile("../common/test-resources/msgpack_test_fixtures.json")
+ text, err := os.ReadFile("../../common/test-resources/msgpack_test_fixtures.json")
if err != nil {
return nil, err
}
diff --git a/ably/proto_message_delta_test.go b/internal/ably/proto_message_delta_test.go
similarity index 100%
rename from ably/proto_message_delta_test.go
rename to internal/ably/proto_message_delta_test.go
diff --git a/ably/proto_message_doc.txt b/internal/ably/proto_message_doc.txt
similarity index 100%
rename from ably/proto_message_doc.txt
rename to internal/ably/proto_message_doc.txt
diff --git a/ably/proto_message_integration_test.go b/internal/ably/proto_message_integration_test.go
similarity index 96%
rename from ably/proto_message_integration_test.go
rename to internal/ably/proto_message_integration_test.go
index ec0ead238..46bf6ffd0 100644
--- a/ably/proto_message_integration_test.go
+++ b/internal/ably/proto_message_integration_test.go
@@ -7,8 +7,8 @@ import (
"encoding/json"
"testing"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
diff --git a/ably/proto_message_operations_test.go b/internal/ably/proto_message_operations_test.go
similarity index 100%
rename from ably/proto_message_operations_test.go
rename to internal/ably/proto_message_operations_test.go
diff --git a/ably/proto_message_test.go b/internal/ably/proto_message_test.go
similarity index 98%
rename from ably/proto_message_test.go
rename to internal/ably/proto_message_test.go
index b6ce4de4b..9acb981aa 100644
--- a/ably/proto_message_test.go
+++ b/internal/ably/proto_message_test.go
@@ -8,7 +8,7 @@ import (
"encoding/json"
"testing"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/proto_presence_message.go b/internal/ably/proto_presence_message.go
similarity index 100%
rename from ably/proto_presence_message.go
rename to internal/ably/proto_presence_message.go
diff --git a/ably/proto_presence_message_test.go b/internal/ably/proto_presence_message_test.go
similarity index 99%
rename from ably/proto_presence_message_test.go
rename to internal/ably/proto_presence_message_test.go
index 97f032a12..67fef8f03 100644
--- a/ably/proto_presence_message_test.go
+++ b/internal/ably/proto_presence_message_test.go
@@ -10,9 +10,9 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/ably/internal/ablyutil"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/proto_protocol_message.go b/internal/ably/proto_protocol_message.go
similarity index 99%
rename from ably/proto_protocol_message.go
rename to internal/ably/proto_protocol_message.go
index cff9370cd..4b117cfec 100644
--- a/ably/proto_protocol_message.go
+++ b/internal/ably/proto_protocol_message.go
@@ -4,7 +4,7 @@ import (
"fmt"
"time"
- "github.com/ably/ably-go/ably/objects"
+ "github.com/ably/ably-pubsub-go/internal/ably/objects"
)
// TR3
diff --git a/ably/proto_protocol_message_test.go b/internal/ably/proto_protocol_message_test.go
similarity index 97%
rename from ably/proto_protocol_message_test.go
rename to internal/ably/proto_protocol_message_test.go
index 29fbded1a..656d157dc 100644
--- a/ably/proto_protocol_message_test.go
+++ b/internal/ably/proto_protocol_message_test.go
@@ -8,8 +8,8 @@ import (
"strconv"
"testing"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/proto_stat.go b/internal/ably/proto_stat.go
similarity index 100%
rename from ably/proto_stat.go
rename to internal/ably/proto_stat.go
diff --git a/ably/proto_types.go b/internal/ably/proto_types.go
similarity index 100%
rename from ably/proto_types.go
rename to internal/ably/proto_types.go
diff --git a/ably/proto_types_test.go b/internal/ably/proto_types_test.go
similarity index 90%
rename from ably/proto_types_test.go
rename to internal/ably/proto_types_test.go
index c8fc799f8..71f7ec625 100644
--- a/ably/proto_types_test.go
+++ b/internal/ably/proto_types_test.go
@@ -8,8 +8,8 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/realtime_channel.go b/internal/ably/realtime_channel.go
similarity index 96%
rename from ably/realtime_channel.go
rename to internal/ably/realtime_channel.go
index 79d6cc401..94a49c2a0 100644
--- a/ably/realtime_channel.go
+++ b/internal/ably/realtime_channel.go
@@ -9,7 +9,7 @@ import (
"sort"
"sync"
- "github.com/ably/ably-go/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
)
var (
@@ -34,7 +34,7 @@ func (ch chanSlice) Sort() { sort.Sort(ch) }
// RealtimeChannels is a goroutine-safe container for realtime channels that allows for creating,
// deleting and iterating over existing channels.
-// Creates and destroys [ably.RESTChannel] and [ably.RealtimeChannel] objects.
+// Creates and destroys [ably.HTTPChannel] and [ably.RealtimeChannel] objects.
type RealtimeChannels struct {
mtx sync.Mutex
client *Realtime
@@ -206,50 +206,30 @@ func (c *RealtimeChannels) Exists(name string) bool {
// Release releases a [ably.RealtimeChannel] object with given channel name, frees all
// resources associated, e.g. Removes any listeners associated with the channel (RSN4, RTS4).
//
-// A realtime channel should only be released when it is in the INITIALIZED, DETACHED, or FAILED state.
-// Releasing a realtime channel in any other state is deprecated and will return an error in the next major
-// version. For now, such a channel is detached before it is released (RTS4b).
-func (ch *RealtimeChannels) Release(ctx context.Context, name string) error {
+// A realtime channel can only be released when it is in the INITIALIZED, DETACHED, or FAILED state.
+// For a channel in any other state, Release returns an [ably.ErrorInfo] with code 90011 and leaves the
+// channel as it is. Call [ably.RealtimeChannel.Detach] and wait for it to return before releasing it.
+func (ch *RealtimeChannels) Release(name string) error {
ch.mtx.Lock()
+ defer ch.mtx.Unlock()
c, ok := ch.chans[name]
- ch.mtx.Unlock()
if !ok {
return nil // RTS4c
}
- // A concurrent Attach may move the channel out of a releasable state while it's being detached,
- // in which case detach it again rather than release an attached channel.
- for warned := false; ; warned = true {
- released, state := ch.releaseIfReleasable(name, c)
- if released {
- return nil
- }
- if !warned {
- // RTS4b
- ch.client.log().Warnf("Calling Channels.Release() on a channel in the %s state is deprecated, and will return an error in the next major version. Call RealtimeChannel.Detach() and wait for it to return before calling Channels.Release(name).", state)
- }
- if err := c.Detach(ctx); err != nil {
- return err
- }
- }
-}
-
-// releaseIfReleasable removes c from the channels map if its state allows it to be released
-// (RTS4d), returning whether it was removed (or had already been) and the state it was in.
-// The channel lock is held until the removal, so that a concurrent Attach can't move it
-// out of a releasable state in between.
-func (ch *RealtimeChannels) releaseIfReleasable(name string, c *RealtimeChannel) (bool, ChannelState) {
- ch.mtx.Lock()
- defer ch.mtx.Unlock()
+ // Hold the channel lock until the channel is removed, so that a concurrent Attach can't move it
+ // out of a releasable state in between.
c.mtx.Lock()
defer c.mtx.Unlock()
- if ch.chans[name] != c {
- return true, c.state
- }
- if !c.state.canRelease() {
- return false, c.state
+ if state := c.state; !state.canRelease() {
+ // RTS4e
+ return &ErrorInfo{
+ Code: ErrChannelReleaseInvalidState,
+ StatusCode: 400,
+ err: fmt.Errorf("can only release a channel in a state where there is no possibility of further updates from the server being received (INITIALIZED, DETACHED, or FAILED); the current state is %s", state),
+ }
}
- delete(ch.chans, name)
- return true, c.state
+ delete(ch.chans, name) // RTS4d
+ return nil
}
func (ch *RealtimeChannels) broadcastConnStateChange(change ConnectionStateChange) {
diff --git a/ably/realtime_channel_delta_integration_test.go b/internal/ably/realtime_channel_delta_integration_test.go
similarity index 98%
rename from ably/realtime_channel_delta_integration_test.go
rename to internal/ably/realtime_channel_delta_integration_test.go
index f5364c5a6..bc9e6b4f4 100644
--- a/ably/realtime_channel_delta_integration_test.go
+++ b/internal/ably/realtime_channel_delta_integration_test.go
@@ -10,8 +10,8 @@ import (
"sync/atomic"
"testing"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/realtime_channel_integration_test.go b/internal/ably/realtime_channel_integration_test.go
similarity index 93%
rename from ably/realtime_channel_integration_test.go
rename to internal/ably/realtime_channel_integration_test.go
index 29d8040e9..37b5d421f 100644
--- a/ably/realtime_channel_integration_test.go
+++ b/internal/ably/realtime_channel_integration_test.go
@@ -12,9 +12,9 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/ably/internal/ablyutil"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -114,7 +114,7 @@ func TestRealtimeChannel_SubscriptionFilters(t *testing.T) {
app, err := ablytest.NewSandbox()
require.NoError(t, err)
options := app.Options()
- restClient, err := ably.NewREST(options...)
+ restClient, err := ably.NewHTTPClient(options...)
assert.NoError(t, err)
realtimeClient := app.NewRealtime(ably.WithEchoMessages(false))
@@ -346,7 +346,7 @@ func TestRealtimeChannel_ShouldReturnErrorIfReadLimitExceeded(t *testing.T) {
assert.Equal(t, "failed to read: read limited at 1025 bytes", errorInfo.Unwrap().Error())
}
-// Test that RealtimeChannels.Release detaches an attached channel and releases it (deprecated, RTS4b).
+// Test that RealtimeChannels.Release refuses to release an attached channel, and releases it once detached.
func TestRealtimeChannels_Release(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -376,17 +376,25 @@ func TestRealtimeChannels_Release(t *testing.T) {
ablytest.Soon.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf)
assert.Equal(t, ably.ChannelStateAttached, channelStatechange.Current)
- t.Log("releasing test channel")
- err = client.Channels.Release(ctx, ablytest.UniqueChannelName(t, "test"))
- assert.NoError(t, err)
-
- t.Log("checking test channel is detached")
+ t.Log("releasing attached test channel")
+ err = client.Channels.Release(channel.Name)
+ var errInfo *ably.ErrorInfo
+ require.ErrorAs(t, err, &errInfo)
+ assert.Equal(t, ably.ErrChannelReleaseInvalidState, errInfo.Code)
+ assert.Equal(t, 400, errInfo.StatusCode)
+ assert.Equal(t, ably.ChannelStateAttached, channel.State())
+ assert.True(t, client.Channels.Exists(channel.Name))
+
+ t.Log("detaching test channel")
+ err = channel.Detach(ctx)
+ require.NoError(t, err)
ablytest.Soon.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf)
assert.Equal(t, ably.ChannelStateDetaching, channelStatechange.Current)
ablytest.Soon.Recv(t, &channelStatechange, channelStateChanges, t.Fatalf)
assert.Equal(t, ably.ChannelStateDetached, channelStatechange.Current)
- t.Log("checking test channel is released")
- exists := client.Channels.Exists(ablytest.UniqueChannelName(t, "test"))
- assert.False(t, exists)
+ t.Log("releasing detached test channel")
+ err = client.Channels.Release(channel.Name)
+ assert.NoError(t, err)
+ assert.False(t, client.Channels.Exists(channel.Name))
}
diff --git a/ably/realtime_channel_internal_test.go b/internal/ably/realtime_channel_internal_test.go
similarity index 73%
rename from ably/realtime_channel_internal_test.go
rename to internal/ably/realtime_channel_internal_test.go
index a5b168c68..1e1bc595a 100644
--- a/ably/realtime_channel_internal_test.go
+++ b/internal/ably/realtime_channel_internal_test.go
@@ -4,10 +4,7 @@
package ably
import (
- "context"
"fmt"
- "strings"
- "sync"
"testing"
"github.com/stretchr/testify/assert"
@@ -148,7 +145,7 @@ func TestChannelGet(t *testing.T) {
mock: &RealtimeChannels{
chans: map[string]*RealtimeChannel{},
client: &Realtime{
- rest: &REST{
+ rest: &HTTPClient{
log: logger{l: &stdLogger{mocklogger}},
opts: NewClientOptions(),
},
@@ -169,68 +166,43 @@ func TestChannelGet(t *testing.T) {
}
}
-type recordingLogger struct {
- mtx sync.Mutex
- logs []string
-}
-
-func (l *recordingLogger) Printf(level LogLevel, format string, v ...interface{}) {
- l.mtx.Lock()
- defer l.mtx.Unlock()
- l.logs = append(l.logs, fmt.Sprintf("[%s] %s", level, fmt.Sprintf(format, v...)))
-}
-
-func (l *recordingLogger) warnings() []string {
- l.mtx.Lock()
- defer l.mtx.Unlock()
- var warnings []string
- for _, log := range l.logs {
- if strings.HasPrefix(log, "[WARN] ") {
- warnings = append(warnings, log)
- }
- }
- return warnings
-}
-
func TestChannelsRelease(t *testing.T) {
- newClient := func(t *testing.T) (*Realtime, *recordingLogger) {
- logger := &recordingLogger{}
- client, err := NewRealtime(WithKey("abc:def"), WithAutoConnect(false), WithLogHandler(logger), WithLogLevel(LogWarning))
+ newClient := func(t *testing.T) *Realtime {
+ client, err := NewRealtime(WithKey("abc:def"), WithAutoConnect(false))
require.NoError(t, err)
- return client, logger
+ return client
}
t.Run("RTS4c: releasing a non-existent channel is a no-op", func(t *testing.T) {
- client, logger := newClient(t)
- err := client.Channels.Release(context.Background(), "nonexistent")
- assert.NoError(t, err)
- assert.Empty(t, logger.warnings())
+ client := newClient(t)
+ assert.NoError(t, client.Channels.Release("nonexistent"))
})
for _, state := range []ChannelState{ChannelStateInitialized, ChannelStateDetached, ChannelStateFailed} {
- t.Run(fmt.Sprintf("RTS4d: releasing a %s channel removes it without a warning", state), func(t *testing.T) {
- client, logger := newClient(t)
+ t.Run(fmt.Sprintf("RTS4d: releasing a %s channel removes it", state), func(t *testing.T) {
+ client := newClient(t)
channel := client.Channels.Get("test")
channel.state = state
- err := client.Channels.Release(context.Background(), "test")
- assert.NoError(t, err)
+ assert.NoError(t, client.Channels.Release("test"))
assert.False(t, client.Channels.Exists("test"))
assert.Equal(t, state, channel.State())
- assert.Empty(t, logger.warnings())
+ assert.NotSame(t, channel, client.Channels.Get("test"))
})
}
- t.Run("RTS4b: releasing a channel that isn't detached logs a deprecation warning", func(t *testing.T) {
- client, logger := newClient(t)
- channel := client.Channels.Get("test")
- // A SUSPENDED channel detaches locally, without sending a DETACH to the server (RTL5j).
- channel.state = ChannelStateSuspended
- err := client.Channels.Release(context.Background(), "test")
- assert.NoError(t, err)
- assert.False(t, client.Channels.Exists("test"))
- assert.Equal(t, ChannelStateDetached, channel.State())
- warnings := logger.warnings()
- require.Len(t, warnings, 1)
- assert.Contains(t, warnings[0], "Calling Channels.Release() on a channel in the SUSPENDED state is deprecated")
- })
+ for _, state := range []ChannelState{ChannelStateAttaching, ChannelStateAttached, ChannelStateDetaching, ChannelStateSuspended} {
+ t.Run(fmt.Sprintf("RTS4e: releasing a %s channel fails and leaves it in place", state), func(t *testing.T) {
+ client := newClient(t)
+ channel := client.Channels.Get("test")
+ channel.state = state
+ err := client.Channels.Release("test")
+ var errInfo *ErrorInfo
+ require.ErrorAs(t, err, &errInfo)
+ assert.Equal(t, ErrChannelReleaseInvalidState, errInfo.Code)
+ assert.Equal(t, 400, errInfo.StatusCode)
+ assert.Contains(t, errInfo.Message(), "the current state is "+state.String())
+ assert.Equal(t, state, channel.State())
+ assert.Same(t, channel, client.Channels.Get("test"))
+ })
+ }
}
diff --git a/ably/realtime_channel_spec_integration_test.go b/internal/ably/realtime_channel_spec_integration_test.go
similarity index 99%
rename from ably/realtime_channel_spec_integration_test.go
rename to internal/ably/realtime_channel_spec_integration_test.go
index d0d129eed..cc6a9c825 100644
--- a/ably/realtime_channel_spec_integration_test.go
+++ b/internal/ably/realtime_channel_spec_integration_test.go
@@ -13,8 +13,8 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -581,7 +581,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) {
app, err := ablytest.NewSandbox()
require.NoError(t, err)
- rest, _ := ably.NewREST(app.Options()...)
+ rest, _ := ably.NewHTTPClient(app.Options()...)
var params ably.TokenParams
params.Capability = `{"foo":["subscribe"]}`
token, _ := rest.Auth.RequestToken(context.Background(), ¶ms)
diff --git a/ably/realtime_channel_test.go b/internal/ably/realtime_channel_test.go
similarity index 95%
rename from ably/realtime_channel_test.go
rename to internal/ably/realtime_channel_test.go
index a3f7646a2..146b129f1 100644
--- a/ably/realtime_channel_test.go
+++ b/internal/ably/realtime_channel_test.go
@@ -8,7 +8,7 @@ import (
"fmt"
"time"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
)
// When publishing a message to a channel, data can be either a single string or
diff --git a/ably/realtime_client.go b/internal/ably/realtime_client.go
similarity index 96%
rename from ably/realtime_client.go
rename to internal/ably/realtime_client.go
index 56de739a9..0fd750f3f 100644
--- a/ably/realtime_client.go
+++ b/internal/ably/realtime_client.go
@@ -6,7 +6,7 @@ import (
"time"
)
-// Realtime is an ably realtime client that extends the functionality of the [ably.REST] and provides
+// Realtime is an ably realtime client that extends the functionality of the [ably.HTTPClient] and provides
// additional realtime-specific features.
type Realtime struct {
// An [ably.Auth] object (RTC4).
@@ -15,13 +15,13 @@ type Realtime struct {
Channels *RealtimeChannels
// A [ably.Connection] object (RTC2).
Connection *Connection
- rest *REST
+ rest *HTTPClient
}
// NewRealtime constructs a new [ably.Realtime] client object using an Ably [ably.ClientOption] object (RSC1)
func NewRealtime(options ...ClientOption) (*Realtime, error) {
c := &Realtime{}
- rest, err := NewREST(options...) //options validated in NewREST
+ rest, err := NewHTTPClient(options...) //options validated in NewHTTPClient
if err != nil {
return nil, err
}
diff --git a/ably/realtime_client_integration_test.go b/internal/ably/realtime_client_integration_test.go
similarity index 76%
rename from ably/realtime_client_integration_test.go
rename to internal/ably/realtime_client_integration_test.go
index 5fff5801b..44149c89a 100644
--- a/ably/realtime_client_integration_test.go
+++ b/internal/ably/realtime_client_integration_test.go
@@ -15,9 +15,9 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/ably/internal/ablyutil"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -51,28 +51,6 @@ func TestRealtime_RealtimeHost(t *testing.T) {
assert.Equal(t, host, h, "expected %q got %q", host, h)
}
})
-
- t.Run("REC1d2 with legacy realtimeHost option", func(t *testing.T) {
- for _, host := range hosts {
- dial := make(chan string, 1)
- client, err := ably.NewRealtime(
- ably.WithKey("xxx:xxx"),
- ably.WithRealtimeHost(host),
- ably.WithAutoConnect(false),
- ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) {
- dial <- u.Host
- return MessagePipe(nil, nil)(protocol, u, timeout)
- }),
- )
- assert.NoError(t, err)
- client.Connect()
- var recordedHost string
- ablytest.Instantly.Recv(t, &recordedHost, dial, t.Fatalf)
- h, _, err := net.SplitHostPort(recordedHost)
- assert.NoError(t, err)
- assert.Equal(t, host, h, "expected %q got %q", host, h)
- }
- })
}
func TestRealtime_RSC7_AblyAgent(t *testing.T) {
@@ -157,88 +135,6 @@ func TestRealtime_RSC7_AblyAgent(t *testing.T) {
assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue)
})
})
-
- t.Run("using legacy options", func(t *testing.T) {
- t.Run("RSC7d3 : Should set ablyAgent header with correct identifiers", func(t *testing.T) {
- var agentHeaderValue string
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- agentHeaderValue = r.Header.Get(ably.AblyAgentHeader)
- w.WriteHeader(http.StatusInternalServerError)
- }))
- defer server.Close()
- serverURL, err := url.Parse(server.URL)
- require.NoError(t, err)
-
- client, err := ably.NewRealtime(
- ably.WithTLS(false),
- ably.WithToken("fake:token"),
- ably.WithUseTokenAuth(true),
- ably.WithRealtimeHost(serverURL.Host))
- assert.NoError(t, err)
- defer client.Close()
-
- expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier()
- ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil)
-
- assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue)
- })
-
- t.Run("RSC7d6 : Should set ablyAgent header with custom agents", func(t *testing.T) {
- var agentHeaderValue string
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- agentHeaderValue = r.Header.Get(ably.AblyAgentHeader)
- w.WriteHeader(http.StatusInternalServerError)
- }))
- defer server.Close()
- serverURL, err := url.Parse(server.URL)
- require.NoError(t, err)
-
- client, err := ably.NewRealtime(
- ably.WithTLS(false),
- ably.WithToken("fake:token"),
- ably.WithUseTokenAuth(true),
- ably.WithRealtimeHost(serverURL.Host),
- ably.WithAgents(map[string]string{
- "foo": "1.2.3",
- }),
- )
- assert.NoError(t, err)
- defer client.Close()
-
- expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " foo/1.2.3"
- ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil)
-
- assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue)
- })
-
- t.Run("RSC7d6 : Should set ablyAgent header with custom agents missing version", func(t *testing.T) {
- var agentHeaderValue string
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- agentHeaderValue = r.Header.Get(ably.AblyAgentHeader)
- w.WriteHeader(http.StatusInternalServerError)
- }))
- defer server.Close()
- serverURL, err := url.Parse(server.URL)
- require.NoError(t, err)
-
- client, err := ably.NewRealtime(
- ably.WithTLS(false),
- ably.WithToken("fake:token"),
- ably.WithUseTokenAuth(true),
- ably.WithRealtimeHost(serverURL.Host),
- ably.WithAgents(map[string]string{
- "bar": "",
- }),
- )
- assert.NoError(t, err)
- defer client.Close()
-
- expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " bar"
- ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil)
-
- assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue)
- })
- })
}
func TestRealtime_RTN17_HostFallback(t *testing.T) {
@@ -291,14 +187,6 @@ func TestRealtime_RTN17_HostFallback(t *testing.T) {
assert.Equal(t, expectedHost, visitedHosts[0])
})
- t.Run("does not apply when legacy custom realtimeHost is used", func(t *testing.T) {
- visitedHosts := initClientWithConnError(getTimeoutErr(), ably.WithRealtimeHost("custom-realtime.ably.io"))
- expectedHost := "custom-realtime.ably.io"
-
- require.Equal(t, 1, len(visitedHosts))
- assert.Equal(t, expectedHost, visitedHosts[0])
- })
-
t.Run("apply when fallbacks are provided", func(t *testing.T) {
fallbacks := []string{"fallback0", "fallback1", "fallback2"}
visitedHosts := initClientWithConnError(getTimeoutErr(), ably.WithFallbackHosts(fallbacks))
@@ -309,18 +197,12 @@ func TestRealtime_RTN17_HostFallback(t *testing.T) {
assert.ElementsMatch(t, fallbacks, visitedHosts[1:])
})
- t.Run("apply when fallbackHostUseDefault is true, even if legacy realtimeHost is set", func(t *testing.T) {
- visitedHosts := initClientWithConnError(
- getTimeoutErr(),
- ably.WithFallbackHostsUseDefault(true),
- ably.WithRealtimeHost("custom-ably.realtime.com"))
-
- expectedPrimaryHost := "custom-ably.realtime.com"
- expectedFallbackHosts := ably.DefaultFallbackHosts()
+ t.Run("apply when a custom port is set", func(t *testing.T) {
+ visitedHosts := initClientWithConnError(getTimeoutErr(), ably.WithTLSPort(8081))
assert.Equal(t, 6, len(visitedHosts))
- assert.Equal(t, expectedPrimaryHost, visitedHosts[0])
- assert.ElementsMatch(t, expectedFallbackHosts, visitedHosts[1:])
+ assert.Equal(t, "main.realtime.ably.net", visitedHosts[0])
+ assert.ElementsMatch(t, ably.DefaultFallbackHosts(), visitedHosts[1:])
})
})
@@ -419,7 +301,7 @@ func TestRealtime_RTN17_Integration_HostFallback_Internal_Server_Error(t *testin
assert.NoError(t, err)
assert.Equal(t, 2, connAttempts)
- assert.Equal(t, fallbackHost, realtime.Rest().ActiveRealtimeHost())
+ assert.Equal(t, fallbackHost, realtime.HTTPClient().ActiveRealtimeHost())
}
func TestRealtime_RTN17_Integration_HostFallback_Timeout(t *testing.T) {
@@ -470,7 +352,7 @@ func TestRealtime_RTN17_Integration_HostFallback_Timeout(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, 2, connAttempts)
- assert.Equal(t, fallbackHost, realtime.Rest().ActiveRealtimeHost())
+ assert.Equal(t, fallbackHost, realtime.HTTPClient().ActiveRealtimeHost())
}
func checkUnique(ch chan string, typ string, n int) error {
diff --git a/ably/realtime_client_internal_test.go b/internal/ably/realtime_client_internal_test.go
similarity index 100%
rename from ably/realtime_client_internal_test.go
rename to internal/ably/realtime_client_internal_test.go
diff --git a/ably/realtime_conn.go b/internal/ably/realtime_conn.go
similarity index 99%
rename from ably/realtime_conn.go
rename to internal/ably/realtime_conn.go
index bfb550455..c8f793a65 100644
--- a/ably/realtime_conn.go
+++ b/internal/ably/realtime_conn.go
@@ -9,7 +9,7 @@ import (
"sync"
"time"
- "github.com/ably/ably-go/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
)
var (
@@ -513,12 +513,6 @@ func (c *Connection) ErrorReason() *ErrorInfo {
return c.errorReason
}
-// Deprecated: this property is deprecated, use CreateRecoveryKey method instead.
-func (c *Connection) RecoveryKey() string {
- c.log().Warn("RecoveryKey is deprecated, use CreateRecoveryKey method instead")
- return c.CreateRecoveryKey()
-}
-
// CreateRecoveryKey is an attribute composed of the connectionKey, messageSerial and channelSerials (RTN16g, RTN16g1, RTN16h).
func (c *Connection) CreateRecoveryKey() string {
c.mtx.Lock()
diff --git a/ably/realtime_conn_integration_test.go b/internal/ably/realtime_conn_integration_test.go
similarity index 99%
rename from ably/realtime_conn_integration_test.go
rename to internal/ably/realtime_conn_integration_test.go
index 384f166de..a691a19b9 100644
--- a/ably/realtime_conn_integration_test.go
+++ b/internal/ably/realtime_conn_integration_test.go
@@ -11,8 +11,8 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
diff --git a/ably/realtime_conn_spec_integration_test.go b/internal/ably/realtime_conn_spec_integration_test.go
similarity index 99%
rename from ably/realtime_conn_spec_integration_test.go
rename to internal/ably/realtime_conn_spec_integration_test.go
index 91b360a72..cd20a2ea4 100644
--- a/ably/realtime_conn_spec_integration_test.go
+++ b/internal/ably/realtime_conn_spec_integration_test.go
@@ -20,8 +20,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
)
func Test_RTN2_WebsocketQueryParams(t *testing.T) {
@@ -750,7 +750,7 @@ func TestRealtimeConn_RTN15a_ReconnectOnEOF(t *testing.T) {
// Publish a message to the channel through REST. If connection recovery
// succeeds, we should then receive it without reattaching.
- rest, err := ably.NewREST(app.Options()...)
+ rest, err := ably.NewHTTPClient(app.Options()...)
require.NoError(t, err)
err = rest.Channels.Get(ablytest.UniqueChannelName(t, "channel")).Publish(context.Background(), "name", "data")
assert.NoError(t, err)
@@ -884,7 +884,7 @@ func TestRealtimeConn_RTN15b(t *testing.T) {
// Publish a message to the channel through REST. If connection recovery
// succeeds, we should then receive it without reattaching.
- rest, err := ably.NewREST(app.Options()...)
+ rest, err := ably.NewHTTPClient(app.Options()...)
require.NoError(t, err)
goOn := <-gotDial
err = rest.Channels.Get(ablytest.UniqueChannelName(t, "channel")).Publish(context.Background(), "name", "data")
@@ -985,7 +985,7 @@ func TestRealtimeConn_RTN15c6(t *testing.T) {
assert.Equal(t, ably.ConnectionStateDisconnected, connState.Current,
"expected transition to %v, got %v", ably.ConnectionStateDisconnected, connState.Current)
- rest, err := ably.NewREST(app.Options()...)
+ rest, err := ably.NewHTTPClient(app.Options()...)
require.NoError(t, err)
err = rest.Channels.Get(ablytest.UniqueChannelName(t, "channel")).Publish(context.Background(), "name", "data")
assert.NoError(t, err)
@@ -1097,7 +1097,7 @@ func TestRealtimeConn_RTN15c7_attached(t *testing.T) {
assert.Equal(t, ably.ConnectionStateDisconnected, connState.Current,
"expected transition to %v, got %v", ably.ConnectionStateDisconnected, connState.Current)
- rest, err := ably.NewREST(app.Options()...)
+ rest, err := ably.NewHTTPClient(app.Options()...)
require.NoError(t, err)
err = rest.Channels.Get(ablytest.UniqueChannelName(t, "channel")).Publish(context.Background(), "name", "data")
assert.NoError(t, err)
@@ -1165,7 +1165,7 @@ func TestRealtimeConn_RTN15d_MessageRecovery(t *testing.T) {
// REST. If we then successfully recover connection state, the channel will
// still be attached and the messages will arrive.
- rest, err := ably.NewREST(app.Options()...)
+ rest, err := ably.NewHTTPClient(app.Options()...)
require.NoError(t, err)
for i := 0; i < 3; i++ {
err := rest.Channels.Get(ablytest.UniqueChannelName(t, "test")).Publish(context.Background(), "test", fmt.Sprintf("msg %d", i))
@@ -1719,7 +1719,7 @@ func TestRealtimeConn_RTN15h3_Success(t *testing.T) {
func TestRealtimeConn_RTN22a_RTN15h2_Integration_ServerInitiatedAuth(t *testing.T) {
t.Parallel()
- app, restClient := ablytest.NewREST()
+ app, restClient := ablytest.NewHTTPClient()
recorder := NewMessageRecorder()
authCallbackTokens := []string{}
@@ -1782,7 +1782,7 @@ func TestRealtimeConn_RTN22a_RTN15h2_Integration_ServerInitiatedAuth(t *testing.
}
func TestRealtimeConn_RTN22_RTC8_Integration_ServerInitiatedAuth(t *testing.T) {
- app, restClient := ablytest.NewREST()
+ app, restClient := ablytest.NewHTTPClient()
recorder := NewMessageRecorder()
authCallbackTokens := []string{}
@@ -1941,9 +1941,6 @@ func TestRealtimeConn_RTN16(t *testing.T) {
decodedRecoveryKey, err := ably.DecodeRecoveryKey(recoveryKey) // RTN16g1
assert.Nil(t, err)
- deprecatedRecoveryKey := c.Connection.RecoveryKey()
- assert.Equal(t, deprecatedRecoveryKey, recoveryKey) //RTN16m
-
client := app.NewRealtime(
ably.WithRecover(recoveryKey),
)
@@ -1967,8 +1964,8 @@ func TestRealtimeConn_RTN16(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "", client.Connection.Key(),
"expected key to be empty got %q instead", client.Connection.Key())
- assert.Equal(t, "", client.Connection.RecoveryKey(),
- "expected recovery key to be empty got %q instead", client.Connection.RecoveryKey())
+ assert.Equal(t, "", client.Connection.CreateRecoveryKey(),
+ "expected recovery key to be empty got %q instead", client.Connection.CreateRecoveryKey())
assert.Equal(t, "", client.Connection.ID(),
"expected id to be empty got %q instead", client.Connection.ID())
}
@@ -2893,8 +2890,8 @@ func TestRealtimeConn_RTC8a_ExplicitAuthorizeWhileConnected(t *testing.T) {
}, t.Fatalf)
}
- // We'll use this REST to get real, working tokens.
- app, rest := ablytest.NewREST()
+ // We'll use this HTTP client to get real, working tokens.
+ app, rest := ablytest.NewHTTPClient()
getToken := func(params ably.TokenParams) ably.Tokener {
t.Helper()
token, err := rest.Auth.RequestToken(context.Background(), ¶ms)
diff --git a/ably/realtime_experimental_objects.go b/internal/ably/realtime_experimental_objects.go
similarity index 97%
rename from ably/realtime_experimental_objects.go
rename to internal/ably/realtime_experimental_objects.go
index 98ab0bdd3..94b0df9cd 100644
--- a/ably/realtime_experimental_objects.go
+++ b/internal/ably/realtime_experimental_objects.go
@@ -4,7 +4,7 @@ import (
"context"
"errors"
- "github.com/ably/ably-go/ably/objects"
+ "github.com/ably/ably-pubsub-go/internal/ably/objects"
)
// RealtimeExperimentalObjects exposes a method to publish LiveObject messages on a Realtime channel.
diff --git a/ably/realtime_experimental_objects_test.go b/internal/ably/realtime_experimental_objects_test.go
similarity index 99%
rename from ably/realtime_experimental_objects_test.go
rename to internal/ably/realtime_experimental_objects_test.go
index 8f3eaa4d5..005cda51e 100644
--- a/ably/realtime_experimental_objects_test.go
+++ b/internal/ably/realtime_experimental_objects_test.go
@@ -5,7 +5,7 @@ import (
"errors"
"testing"
- "github.com/ably/ably-go/ably/objects"
+ "github.com/ably/ably-pubsub-go/internal/ably/objects"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/realtime_presence.go b/internal/ably/realtime_presence.go
similarity index 100%
rename from ably/realtime_presence.go
rename to internal/ably/realtime_presence.go
diff --git a/ably/realtime_presence_integration_test.go b/internal/ably/realtime_presence_integration_test.go
similarity index 98%
rename from ably/realtime_presence_integration_test.go
rename to internal/ably/realtime_presence_integration_test.go
index c9e226968..367a8c986 100644
--- a/ably/realtime_presence_integration_test.go
+++ b/internal/ably/realtime_presence_integration_test.go
@@ -10,8 +10,8 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/realtime_presence_internal_test.go b/internal/ably/realtime_presence_internal_test.go
similarity index 99%
rename from ably/realtime_presence_internal_test.go
rename to internal/ably/realtime_presence_internal_test.go
index 3b4494348..087a5f39f 100644
--- a/ably/realtime_presence_internal_test.go
+++ b/internal/ably/realtime_presence_internal_test.go
@@ -22,7 +22,7 @@ var (
func mockChannelWithState(channelState *ChannelState, connectionState *ConnectionState) *RealtimeChannel {
mockChannel := RealtimeChannel{
client: &Realtime{
- rest: &REST{
+ rest: &HTTPClient{
log: logger{l: &stdLogger{mocklogger}},
},
Connection: &Connection{},
diff --git a/ably/recovery_context.go b/internal/ably/recovery_context.go
similarity index 100%
rename from ably/recovery_context.go
rename to internal/ably/recovery_context.go
diff --git a/ably/recovery_context_test.go b/internal/ably/recovery_context_test.go
similarity index 97%
rename from ably/recovery_context_test.go
rename to internal/ably/recovery_context_test.go
index 6c7416c96..d501ad726 100644
--- a/ably/recovery_context_test.go
+++ b/internal/ably/recovery_context_test.go
@@ -6,7 +6,7 @@ package ably_test
import (
"testing"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
"github.com/stretchr/testify/assert"
)
diff --git a/ably/state.go b/internal/ably/state.go
similarity index 99%
rename from ably/state.go
rename to internal/ably/state.go
index 6953e7cfd..9652165b1 100644
--- a/ably/state.go
+++ b/internal/ably/state.go
@@ -541,7 +541,7 @@ type ConnectionStateChange struct {
func (ConnectionStateChange) isEmitterData() {}
-// ChannelState describes the possible states of a [ably.RESTChannel] or [ably.RealtimeChannel] object.
+// ChannelState describes the possible states of a [ably.HTTPChannel] or [ably.RealtimeChannel] object.
type ChannelState struct {
name string
}
@@ -581,7 +581,7 @@ func (e ChannelState) String() string {
}
// canRelease reports whether a channel in this state can no longer receive updates from
-// the server, so can be released without detaching it first (RTS4d).
+// the server, so can be released (RTS4d).
func (e ChannelState) canRelease() bool {
switch e {
case ChannelStateInitialized, ChannelStateDetached, ChannelStateFailed:
@@ -590,7 +590,7 @@ func (e ChannelState) canRelease() bool {
return false
}
-// ChannelEvent describes the events emitted by a [ably.RESTChannel] or [ably.RealtimeChannel] object.
+// ChannelEvent describes the events emitted by a [ably.HTTPChannel] or [ably.RealtimeChannel] object.
// An event is either an UPDATE or a [ably.ChannelState].
type ChannelEvent struct {
name string
@@ -637,7 +637,7 @@ func (e ChannelEvent) String() string {
return e.name
}
-// ChannelStateChange contains state change information emitted by [ably.RESTChannel] and [ably.RealtimeChannel] objects.
+// ChannelStateChange contains state change information emitted by [ably.HTTPChannel] and [ably.RealtimeChannel] objects.
// A ChannelStateChange is the data associated with a ChannelEvent.
type ChannelStateChange struct {
diff --git a/ably/state_test.go b/internal/ably/state_test.go
similarity index 100%
rename from ably/state_test.go
rename to internal/ably/state_test.go
diff --git a/ably/token.go b/internal/ably/token.go
similarity index 100%
rename from ably/token.go
rename to internal/ably/token.go
diff --git a/ably/transitioner_integration_test.go b/internal/ably/transitioner_integration_test.go
similarity index 99%
rename from ably/transitioner_integration_test.go
rename to internal/ably/transitioner_integration_test.go
index 95bada4c4..634194759 100644
--- a/ably/transitioner_integration_test.go
+++ b/internal/ably/transitioner_integration_test.go
@@ -11,8 +11,8 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
)
type ConnTransitioner struct {
diff --git a/ably/vcdiff_plugin.go b/internal/ably/vcdiff_plugin.go
similarity index 92%
rename from ably/vcdiff_plugin.go
rename to internal/ably/vcdiff_plugin.go
index ec58bc06c..f7bddc488 100644
--- a/ably/vcdiff_plugin.go
+++ b/internal/ably/vcdiff_plugin.go
@@ -24,7 +24,7 @@ func (a AblyVCDiffDecoder) Decode(delta []byte, base []byte) ([]byte, error) {
//
// Example:
//
-// client, err := ably.NewREST(ably.WithVCDiffPlugin(ably.NewVCDiffPlugin()))
+// client, err := ably.NewHTTPClient(ably.WithVCDiffPlugin(ably.NewVCDiffPlugin()))
func NewVCDiffPlugin() VCDiffDecoder {
return AblyVCDiffDecoder{}
}
diff --git a/ably/vcdiff_plugin_test.go b/internal/ably/vcdiff_plugin_test.go
similarity index 100%
rename from ably/vcdiff_plugin_test.go
rename to internal/ably/vcdiff_plugin_test.go
diff --git a/ably/websocket.go b/internal/ably/websocket.go
similarity index 98%
rename from ably/websocket.go
rename to internal/ably/websocket.go
index 48e6fe5ac..99d237bba 100644
--- a/ably/websocket.go
+++ b/internal/ably/websocket.go
@@ -9,7 +9,7 @@ import (
"strings"
"time"
- "github.com/ably/ably-go/ably/internal/ablyutil"
+ "github.com/ably/ably-pubsub-go/internal/ably/internal/ablyutil"
"github.com/coder/websocket"
)
diff --git a/ably/websocket_internal_test.go b/internal/ably/websocket_internal_test.go
similarity index 100%
rename from ably/websocket_internal_test.go
rename to internal/ably/websocket_internal_test.go
diff --git a/internal/ablytest/ablytest.go b/internal/ablytest/ablytest.go
index b13732476..1093ae258 100644
--- a/internal/ablytest/ablytest.go
+++ b/internal/ablytest/ablytest.go
@@ -22,7 +22,7 @@ import (
"testing"
"time"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
)
var Timeout = 30 * time.Second
diff --git a/internal/ablytest/cryptodata.go b/internal/ablytest/cryptodata.go
index 90fcd1bc4..b019b93d1 100644
--- a/internal/ablytest/cryptodata.go
+++ b/internal/ablytest/cryptodata.go
@@ -22,7 +22,7 @@ type CryptoData struct {
func LoadCryptoData(rel string) (*CryptoData, []byte, []byte, error) {
data := &CryptoData{}
- f, err := os.Open(filepath.Join("..", "common", filepath.FromSlash(rel)))
+ f, err := os.Open(filepath.Join("..", "..", "common", filepath.FromSlash(rel)))
if err != nil {
return nil, nil, nil, errors.New("missing common subrepo - ensure git submodules are initialized")
}
diff --git a/internal/ablytest/fmt_test.go b/internal/ablytest/fmt_test.go
index 6f0789d0d..107f09a46 100644
--- a/internal/ablytest/fmt_test.go
+++ b/internal/ablytest/fmt_test.go
@@ -6,7 +6,7 @@ package ablytest_test
import (
"fmt"
- "github.com/ably/ably-go/internal/ablytest"
+ "github.com/ably/ably-pubsub-go/internal/ablytest"
)
func ExampleFmtFunc_Wrap() {
diff --git a/internal/ablytest/logger.go b/internal/ablytest/logger.go
index 81549a276..70b89666f 100644
--- a/internal/ablytest/logger.go
+++ b/internal/ablytest/logger.go
@@ -3,7 +3,7 @@ package ablytest
import (
"fmt"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
)
type LogMessage struct {
diff --git a/internal/ablytest/pagination.go b/internal/ablytest/pagination.go
index 9812265d6..7c04d26bd 100644
--- a/internal/ablytest/pagination.go
+++ b/internal/ablytest/pagination.go
@@ -5,7 +5,7 @@ import (
"fmt"
"reflect"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
)
// AllPages appends all items from all pages resulting from a paginated
diff --git a/internal/ablytest/proxies.go b/internal/ablytest/proxies.go
index fbb2e07ce..dc59854a7 100644
--- a/internal/ablytest/proxies.go
+++ b/internal/ablytest/proxies.go
@@ -10,7 +10,7 @@ import (
"net/url"
"strconv"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
)
var hopHeaders = map[string]struct{}{
@@ -78,7 +78,7 @@ type AuthReverseProxy struct {
// NewAuthReverseProxy creates new auth reverse proxy. The given opts
// are used to create a Auth client, used to reverse proxying token requests.
func NewAuthReverseProxy(opts ...ably.ClientOption) (*AuthReverseProxy, error) {
- client, err := ably.NewREST(append(opts,
+ client, err := ably.NewHTTPClient(append(opts,
ably.WithUseTokenAuth(true),
)...)
if err != nil {
diff --git a/internal/ablytest/recorders.go b/internal/ablytest/recorders.go
index af50443d7..c1303b193 100644
--- a/internal/ablytest/recorders.go
+++ b/internal/ablytest/recorders.go
@@ -8,7 +8,7 @@ import (
"sync"
"sync/atomic"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
)
// RoundTripRecorder is a http.Transport wrapper which records
diff --git a/internal/ablytest/resultgroup.go b/internal/ablytest/resultgroup.go
index eff796312..2dfe5bb76 100644
--- a/internal/ablytest/resultgroup.go
+++ b/internal/ablytest/resultgroup.go
@@ -7,7 +7,7 @@ import (
"sync"
"time"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
)
type Result interface {
diff --git a/internal/ablytest/sandbox.go b/internal/ablytest/sandbox.go
index 6c19d8701..b9698a7fb 100644
--- a/internal/ablytest/sandbox.go
+++ b/internal/ablytest/sandbox.go
@@ -22,7 +22,7 @@ import (
"syscall"
"time"
- "github.com/ably/ably-go/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably"
)
// Key is a single API key as returned in the /apps response.
@@ -155,9 +155,9 @@ func NewRealtime(opts ...ably.ClientOption) (*Sandbox, *ably.Realtime) {
return app, client
}
-func NewREST(opts ...ably.ClientOption) (*Sandbox, *ably.REST) {
+func NewHTTPClient(opts ...ably.ClientOption) (*Sandbox, *ably.HTTPClient) {
app := MustSandbox()
- client, err := ably.NewREST(app.Options(opts...)...)
+ client, err := ably.NewHTTPClient(app.Options(opts...)...)
if err != nil {
panic(err)
}
@@ -210,7 +210,7 @@ func provisionSandbox(endpoint string) (*Sandbox, error) {
app := &Sandbox{
Config: &Config{},
Endpoint: endpoint,
- client: NewHTTPClient(),
+ client: newHTTPClient(),
local: LocalSandboxURL != "",
}
@@ -331,7 +331,7 @@ func (app *Sandbox) Options(opts ...ably.ClientOption) []ably.ClientOption {
type transportHijacker interface {
Hijack(http.RoundTripper) http.RoundTripper
}
- appHTTPClient := NewHTTPClient()
+ appHTTPClient := newHTTPClient()
appOpts := []ably.ClientOption{
ably.WithKey(app.Key()),
ably.WithEndpoint(app.Endpoint),
@@ -440,7 +440,7 @@ func (app *Sandbox) CreateJwt(expiresIn time.Duration, invalid bool) (string, er
return string(resBody), nil
}
-func NewHTTPClient() *http.Client {
+func newHTTPClient() *http.Client {
const timeout = time.Minute
return &http.Client{
Timeout: timeout,
diff --git a/server/pubsub/api.go b/server/pubsub/api.go
new file mode 100644
index 000000000..18fe88cc9
--- /dev/null
+++ b/server/pubsub/api.go
@@ -0,0 +1,1491 @@
+package pubsub
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/internal/ably/objects"
+)
+
+// Types.
+
+// AblyVCDiffDecoder is a production-ready implementation of the VCDiffDecoder interface
+// that uses the official ably/vcdiff-go library for delta decoding.
+// Implements VCDiffDecoder interface for decoding VCDiff-encoded delta messages (PC3).
+type AblyVCDiffDecoder = ably.AblyVCDiffDecoder
+
+// Auth creates Ably [TokenRequest] objects and obtains Ably Tokens from Ably to
+// subsequently issue to less trusted clients.
+type Auth = ably.Auth
+
+// AuthOption configures authentication/authorization for a [HTTPClient] or [RealtimeClient]
+// instance or operation.
+type AuthOption = ably.AuthOption
+
+// ChannelDetails contains the details of a [HTTPChannel] or [RealtimeChannel] object
+// such as its ID and [ChannelStatus].
+type ChannelDetails = ably.ChannelDetails
+
+// ChannelEvent describes the events emitted by a [HTTPChannel] or [RealtimeChannel] object.
+// An event is either an UPDATE or a [ChannelState].
+type ChannelEvent = ably.ChannelEvent
+
+// ChannelEventEmitter emits the [ChannelStateChange] events of a [RealtimeChannel]; register
+// handlers with its On, Once and OnAll methods.
+type ChannelEventEmitter = ably.ChannelEventEmitter
+
+// ChannelMetrics contains the metrics associated with a [HTTPChannel] or [RealtimeChannel],
+// such as the number of publishers, subscribers and connections it has.
+type ChannelMetrics = ably.ChannelMetrics
+
+// ChannelMode Describes the possible flags used to configure client capabilities, using [ChannelOption].
+type ChannelMode = ably.ChannelMode
+
+// ChannelOccupancy contains the metrics of a [HTTPChannel] or [RealtimeChannel] object.
+type ChannelOccupancy = ably.ChannelOccupancy
+
+// ChannelOption configures a channel.
+type ChannelOption = ably.ChannelOption
+
+// ChannelProperties holds properties of a realtime channel, such as its attach serial (CP2).
+type ChannelProperties = ably.ChannelProperties
+
+// ChannelState describes the possible states of a [HTTPChannel] or [RealtimeChannel] object.
+type ChannelState = ably.ChannelState
+
+// ChannelStateChange contains state change information emitted by [HTTPChannel] and [RealtimeChannel] objects.
+// A ChannelStateChange is the data associated with a ChannelEvent.
+type ChannelStateChange = ably.ChannelStateChange
+
+// ChannelStatus contains the status of a [HTTPChannel] or [RealtimeChannel] object such as whether
+// it is active and its [ChannelOccupancy].
+type ChannelStatus = ably.ChannelStatus
+
+// CipherAlgorithm is a supported algorithm for channel encryption.
+type CipherAlgorithm = ably.CipherAlgorithm
+
+// CipherMode is a supported cipher mode for channel encryption.
+type CipherMode = ably.CipherMode
+
+// CipherParams sets the properties to configure encryption for a [HTTPChannel] or
+// [RealtimeChannel] object (TZ1).
+type CipherParams = ably.CipherParams
+
+// ClientOption configures a [HTTPClient] or [RealtimeClient] instance.
+//
+// See: https://www.ably.io/documentation/realtime/usage#client-options
+type ClientOption = ably.ClientOption
+
+// Connection represents a single connection Realtime instantiates for communication with Ably servers.
+// It also enables the management of a connection to Ably.
+type Connection = ably.Connection
+
+// ConnectionEvent describes the events emitted by a [Connection] object.
+// An event is either an [ConnectionEventUpdate] or [ConnectionState} change event.
+type ConnectionEvent = ably.ConnectionEvent
+
+type ConnectionEventEmitter = ably.ConnectionEventEmitter
+
+// ConnectionState describes the realtime [Connection] object states.
+type ConnectionState = ably.ConnectionState
+
+// ConnectionStateChange Contains [ConnectionState]change information emitted by the [Connection] object.
+// A ConnectionStateChange is the data associated with a ConnectionEvent.
+type ConnectionStateChange = ably.ConnectionStateChange
+
+// DecodingContext provides context needed for decoding messages, including delta support.
+type DecodingContext = ably.DecodingContext
+
+// DeltaExtras describes a message whose payload is a "vcdiff"-encoded delta generated with respect to a base message (DE1, DE2).
+type DeltaExtras = ably.DeltaExtras
+
+// DeriveOptions allows options to be used in creating a derived channel
+type DeriveOptions = ably.DeriveOptions
+
+type Direction = ably.Direction
+
+// ErrorCode is the type for predefined Ably error codes.
+type ErrorCode = ably.ErrorCode
+
+// ErrorInfo is a generic Ably error object that contains an Ably-specific status code, and a generic status code.
+// Errors returned from the Ably server are compatible with the ErrorInfo structure and should result in errors
+// that inherit from ErrorInfo. It may contain underlying error value which caused the failure.
+type ErrorInfo = ably.ErrorInfo
+
+// GetPresenceOption configures a call to HTTPPresence.Get or RealtimePresence.Get.
+type GetPresenceOption = ably.GetPresenceOption
+
+// HTTPChannel performs operations on a channel over HTTP, using the Ably REST API.
+// It enables messages to be published and historic messages to be retrieved for a channel.
+type HTTPChannel = ably.HTTPChannel
+
+// HTTPChannels provides an API for managing collection of HTTPChannel.
+// This is safe for concurrent use.
+type HTTPChannels = ably.HTTPChannels
+
+type HTTPPaginatedItems = ably.HTTPPaginatedItems
+
+// HTTPPaginatedResponse is a superset of [PaginatedResult] which represents a page of results plus metadata
+// indicating the relative queries available to it. HttpPaginatedResponse additionally carries information
+// about the response to an HTTP request.
+// A HTTPPaginatedResponse is an iterator for the response of a request made with [HTTPClient.Request].
+type HTTPPaginatedResponse = ably.HTTPPaginatedResponse
+
+// HTTPPresence enables the retrieval of the current and historic presence set for a channel.
+type HTTPPresence = ably.HTTPPresence
+
+// HTTPRequest represents a request prepared by the HTTPClient.Request method, ready
+// to be performed by its Pages or Items methods.
+type HTTPRequest = ably.HTTPRequest
+
+// A HistoryOption configures a call to HTTPChannel.History or RealtimeChannel.History.
+type HistoryOption = ably.HistoryOption
+
+// HistoryRequest represents a request prepared by the HTTPChannel.History or
+// RealtimeChannel.History method, ready to be performed by its Pages or Items methods.
+type HistoryRequest = ably.HistoryRequest
+
+type LogLevel = ably.LogLevel
+
+// Logger is an interface for ably loggers.
+type Logger = ably.Logger
+
+// Message contains an individual message that is sent to, or received from, Ably.
+type Message = ably.Message
+
+// MessageAction represents the type of message operation (TM5).
+type MessageAction = ably.MessageAction
+
+// MessageVersion contains version information for a message (TM2s).
+// When received from the server, Serial and Timestamp are server-populated.
+// When sending an update/delete/append, ClientID, Description, and Metadata
+// are user-provided via UpdateOption functions (mapped from MOP2a/MOP2b/MOP2c).
+type MessageVersion = ably.MessageVersion
+
+type MessagesPaginatedItems = ably.MessagesPaginatedItems
+
+// A MessagesPaginatedResult is an iterator for the result of a History request.
+//
+// It is paginated: see [PaginatedResult] for how to iterate over the pages.
+type MessagesPaginatedResult = ably.MessagesPaginatedResult
+
+type PaginateParams = ably.PaginateParams
+
+// PaginatedResult is a generic iterator for PaginatedResult pagination.
+// Items decoding is delegated to type-specific wrappers.
+//
+// Call Next to fetch each page and read it with Items; once Next returns false,
+// check Err to tell an error from the end of the results.
+type PaginatedResult = ably.PaginatedResult
+
+type PeriodUnit = ably.PeriodUnit
+
+// PresenceAction describes the possible actions members in the presence set can emit (TP2).
+type PresenceAction = ably.PresenceAction
+
+// A PresenceGetOption is an optional parameter for RealtimePresence.GetWithOptions.
+type PresenceGetOption = ably.PresenceGetOption
+
+// PresenceHistoryOption configures a call to HTTPChannel.History or RealtimeChannel.History.
+type PresenceHistoryOption = ably.PresenceHistoryOption
+
+type PresenceMessage = ably.PresenceMessage
+
+type PresencePaginatedItems = ably.PresencePaginatedItems
+
+// A PresencePaginatedResult is an iterator for the result of a PresenceHistory request.
+//
+// It is paginated: see [PaginatedResult] for how to iterate over the pages.
+type PresencePaginatedResult = ably.PresencePaginatedResult
+
+// PresenceRequest represents a request prepared by the HTTPPresence.History or
+// RealtimePresence.History method, ready to be performed by its Pages or Items methods.
+type PresenceRequest = ably.PresenceRequest
+
+// PublishMultipleOption is an optional parameter for
+// HTTPChannel.Publish and HTTPChannel.PublishMultiple.
+//
+// TODO: This started out as just an option for PublishMultiple, but has since
+//
+// been added as an option for Publish too, so it should be renamed to
+// PublishOption when we perform the next major version bump to 2.x.x.
+type PublishMultipleOption = ably.PublishMultipleOption
+
+// PublishResult contains the result of a publish operation with serial tracking.
+// The spec (PBR2a) defines PublishResult with a serials array, but per RSL1n1/RTL6j1,
+// SDKs may implement alternatives where adding a response value would be a breaking
+// API change. This SDK returns one PublishResult per message for ergonomics.
+type PublishResult = ably.PublishResult
+
+// PushStats provides details the stats on push notifications.
+type PushStats = ably.PushStats
+
+// RealtimeChannel represents a single named message/presence channel.
+// It enables messages to be published and subscribed to. Also enables historic messages to be retrieved and
+// provides access to the [RealtimePresence] object of a channel.
+type RealtimeChannel = ably.RealtimeChannel
+
+// RealtimeChannels is a goroutine-safe container for realtime channels that allows for creating,
+// deleting and iterating over existing channels.
+// Creates and destroys [RealtimeChannel] objects.
+type RealtimeChannels = ably.RealtimeChannels
+
+// RealtimeExperimentalObjects exposes a method to publish LiveObject messages on a Realtime channel.
+// It requires the LiveObjects plugin to be configured; see [ObjectsPlugin].
+//
+// NOTE: this option is experimental, the LiveObjects plugin API may change in a
+// backwards incompatible way between minor/patch versions. Once the API has been finalised,
+// a new non-experimental option will be added, and this one will be removed.
+//
+// [LiveObjects]: https://ably.com/docs/liveobjects
+type RealtimeExperimentalObjects = ably.RealtimeExperimentalObjects
+
+// RealtimePresence represents a single presence map of a particular channel.
+// It allows entering, leaving and updating presence state for the current client or on behalf of other client.
+// It enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel.
+type RealtimePresence = ably.RealtimePresence
+
+// RecoveryKeyContext contains the properties required to recover existing connection.
+type RecoveryKeyContext = ably.RecoveryKeyContext
+
+// RequestOption configures a call to HTTPClient.Request.
+type RequestOption = ably.RequestOption
+
+type ScopeParams = ably.ScopeParams
+
+// Stats contains application statistics for a specified time interval and time period.
+type Stats = ably.Stats
+
+// StatsConnectionTypes contains a breakdown of summary stats data for different (TLS vs non-TLS) connection types.
+type StatsConnectionTypes = ably.StatsConnectionTypes
+
+// StatsMessageCount contains the aggregate counts for messages and data transferred.
+type StatsMessageCount = ably.StatsMessageCount
+
+type StatsMessageDirections = ably.StatsMessageDirections
+
+type StatsMessageTraffic = ably.StatsMessageTraffic
+
+// StatsMessageTypes contains a breakdown of summary stats data for different (channel vs presence) message types.
+type StatsMessageTypes = ably.StatsMessageTypes
+
+// A StatsOption configures a call to HTTPClient.Stats or Realtime.Stats.
+type StatsOption = ably.StatsOption
+
+type StatsPaginatedItems = ably.StatsPaginatedItems
+
+// A StatsPaginatedResult is an iterator for the result of a Stats request.
+//
+// It is paginated: see [PaginatedResult] for how to iterate over the pages.
+type StatsPaginatedResult = ably.StatsPaginatedResult
+
+type StatsPushNotificationFailures = ably.StatsPushNotificationFailures
+
+type StatsPushNotifications = ably.StatsPushNotifications
+
+type StatsPushTransportTypeCounter = ably.StatsPushTransportTypeCounter
+
+type StatsRates = ably.StatsRates
+
+type StatsReactorRates = ably.StatsReactorRates
+
+// StatsRequest represents a request prepared by the HTTPClient.Stats or
+// Realtime.Stats method, ready to be performed by its Pages or Items methods.
+type StatsRequest = ably.StatsRequest
+
+type StatsRequestCount = ably.StatsRequestCount
+
+type StatsResourceCount = ably.StatsResourceCount
+
+type StatsXchgMessages = ably.StatsXchgMessages
+
+// TokenDetails contains an Ably Token and its associated metadata.
+type TokenDetails = ably.TokenDetails
+
+// TokenParams contains token params to be sent to ably to get auth token
+type TokenParams = ably.TokenParams
+
+// TokenRequest contains tokenparams with extra details, sent to ably for getting auth token
+type TokenRequest = ably.TokenRequest
+
+// TokenString is the string representation of an authentication token.
+type TokenString = ably.TokenString
+
+// Tokener is or can be used to get a [TokenDetails].
+type Tokener = ably.Tokener
+
+// UpdateDeleteResult contains the result of an update, delete, or append operation (UDR1).
+type UpdateDeleteResult = ably.UpdateDeleteResult
+
+// UpdateOption is a functional option for message update operations.
+type UpdateOption = ably.UpdateOption
+
+// VCDiffDecoder provides an interface for decoding vcdiff-encoded message payloads (VD1, VD2).
+// This interface must be implemented by plugins that provide vcdiff delta decoding functionality.
+type VCDiffDecoder = ably.VCDiffDecoder
+
+// LiveObjects types. The Objects prefix keeps them apart from the
+// same-named types of the main API once both live in one package.
+
+type ObjectsCounter = objects.Counter
+
+type ObjectsCounterCreate = objects.CounterCreate
+
+type ObjectsCounterCreateWithObjectId = objects.CounterCreateWithObjectId
+
+type ObjectsCounterInc = objects.CounterInc
+
+type ObjectsCounterOp = objects.CounterOp
+
+type ObjectsData = objects.Data
+
+type ObjectsMap = objects.Map
+
+type ObjectsMapCreate = objects.MapCreate
+
+type ObjectsMapCreateWithObjectId = objects.MapCreateWithObjectId
+
+type ObjectsMapEntry = objects.MapEntry
+
+type ObjectsMapOp = objects.MapOp
+
+type ObjectsMapRemove = objects.MapRemove
+
+type ObjectsMapSemantics = objects.MapSemantics
+
+type ObjectsMapSet = objects.MapSet
+
+type ObjectsMessage = objects.Message
+
+type ObjectsObjectDelete = objects.ObjectDelete
+
+type ObjectsOperation = objects.Operation
+
+type ObjectsOperationAction = objects.OperationAction
+
+// Plugin is used to add LiveObject functionality to a realtime client:
+//
+// client, err := NewRealtime(
+// WithKey(...),
+// WithExperimentalObjectsPlugin(myObjectsPlugin),
+// )
+//
+// The plugin is used to prepare object messages to be published in the
+// realtime channel's publishObjects method, and is used to handle incoming
+// object and object sync messages.
+type ObjectsPlugin = objects.Plugin
+
+type ObjectsState = objects.State
+
+// LiveObjects constants. The values of the objects package's enums, with the
+// same Objects prefix as their types and the underscore dropped.
+const (
+ // ObjectsOperationMapCreate creates a new map object.
+ ObjectsOperationMapCreate = objects.Operation_MapCreate
+ // ObjectsOperationMapSet sets a key in a map object.
+ ObjectsOperationMapSet = objects.Operation_MapSet
+ // ObjectsOperationMapRemove removes a key from a map object.
+ ObjectsOperationMapRemove = objects.Operation_MapRemove
+ // ObjectsOperationCounterCreate creates a new counter object.
+ ObjectsOperationCounterCreate = objects.Operation_CounterCreate
+ // ObjectsOperationCounterInc increments a counter object.
+ ObjectsOperationCounterInc = objects.Operation_CounterInc
+ // ObjectsOperationDelete deletes an object.
+ ObjectsOperationDelete = objects.Operation_Delete
+ // ObjectsMapLWW is the last-write-wins conflict-resolution semantics of a map object.
+ ObjectsMapLWW = objects.Map_LWW
+)
+
+// Constants and variables.
+
+// Crypto contains the properties required to configure the encryption of [Message] payloads.
+var Crypto = ably.Crypto
+
+const ErrNotSet = ably.ErrNotSet
+
+const ErrBadRequest = ably.ErrBadRequest
+
+const ErrInvalidCredential = ably.ErrInvalidCredential
+
+const ErrInvalidClientID = ably.ErrInvalidClientID
+
+const ErrDeltaDecodingFailed = ably.ErrDeltaDecodingFailed
+
+const ErrMissingPlugin = ably.ErrMissingPlugin
+
+const ErrUnauthorized = ably.ErrUnauthorized
+
+const ErrInvalidCredentials = ably.ErrInvalidCredentials
+
+const ErrIncompatibleCredentials = ably.ErrIncompatibleCredentials
+
+const ErrInvalidUseOfBasicAuthOverNonTLSTransport = ably.ErrInvalidUseOfBasicAuthOverNonTLSTransport
+
+const ErrTokenErrorUnspecified = ably.ErrTokenErrorUnspecified
+
+const ErrErrorFromClientTokenCallback = ably.ErrErrorFromClientTokenCallback
+
+const ErrForbidden = ably.ErrForbidden
+
+const ErrNotFound = ably.ErrNotFound
+
+const ErrMethodNotAllowed = ably.ErrMethodNotAllowed
+
+const ErrInternalError = ably.ErrInternalError
+
+const ErrInternalChannelError = ably.ErrInternalChannelError
+
+const ErrInternalConnectionError = ably.ErrInternalConnectionError
+
+const ErrTimeoutError = ably.ErrTimeoutError
+
+const ErrConnectionFailed = ably.ErrConnectionFailed
+
+const ErrConnectionSuspended = ably.ErrConnectionSuspended
+
+const ErrConnectionClosed = ably.ErrConnectionClosed
+
+const ErrDisconnected = ably.ErrDisconnected
+
+const ErrProtocolError = ably.ErrProtocolError
+
+const ErrChannelOperationFailed = ably.ErrChannelOperationFailed
+
+const ErrChannelOperationFailedInvalidChannelState = ably.ErrChannelOperationFailedInvalidChannelState
+
+const PeriodMinute = ably.PeriodMinute
+
+const PeriodHour = ably.PeriodHour
+
+const PeriodDay = ably.PeriodDay
+
+const PeriodMonth = ably.PeriodMonth
+
+const LogNone = ably.LogNone
+
+const LogError = ably.LogError
+
+const LogWarning = ably.LogWarning
+
+const LogInfo = ably.LogInfo
+
+const LogVerbose = ably.LogVerbose
+
+const LogDebug = ably.LogDebug
+
+const Port = ably.Port
+
+const TLSPort = ably.TLSPort
+
+const Backwards = ably.Backwards
+
+const Forwards = ably.Forwards
+
+// ChannelModePresence allows the attached channel to enter Presence.
+const ChannelModePresence = ably.ChannelModePresence
+
+// ChannelModePublish allows for messages to be published on the attached channel.
+const ChannelModePublish = ably.ChannelModePublish
+
+// ChannelModeSubscribe allows the attached channel to subscribe to messages.
+const ChannelModeSubscribe = ably.ChannelModeSubscribe
+
+// ChannelModePresenceSubscribe allows the attached channel to subscribe to Presence updates.
+const ChannelModePresenceSubscribe = ably.ChannelModePresenceSubscribe
+
+// ChannelModeObjectSubscribe allows the attached channel to subscribe to Objects updates.
+const ChannelModeObjectSubscribe = ably.ChannelModeObjectSubscribe
+
+// ChannelModeObjectPublish allows for objects messages to be published on the attached channel.
+const ChannelModeObjectPublish = ably.ChannelModeObjectPublish
+
+const CipherAES = ably.CipherAES
+
+const CipherCBC = ably.CipherCBC
+
+// AblyAgentHeaderName is the name of the Ably-Agent header (RSC7d), exported for
+// use by test helpers that issue their own requests to Ably.
+const AblyAgentHeaderName = ably.AblyAgentHeaderName
+
+const MessageActionUnknown = ably.MessageActionUnknown
+
+const MessageActionCreate = ably.MessageActionCreate
+
+const MessageActionUpdate = ably.MessageActionUpdate
+
+const MessageActionDelete = ably.MessageActionDelete
+
+const MessageActionMeta = ably.MessageActionMeta
+
+const MessageActionMessageSummary = ably.MessageActionMessageSummary
+
+const MessageActionAppend = ably.MessageActionAppend
+
+// PresenceActionAbsent specifies a member is not present in the channel.
+const PresenceActionAbsent = ably.PresenceActionAbsent
+
+// PresenceActionPresent denotes when subscribing to presence events on a channel that already has members present,
+// this event is emitted for every member already present on the channel before the subscribe listener was registered.
+const PresenceActionPresent = ably.PresenceActionPresent
+
+// PresenceActionEnter denotes that new member has entered the channel.
+const PresenceActionEnter = ably.PresenceActionEnter
+
+// PresenceActionLeave is a member who was present has now left the channel. This may be a result of an explicit
+// request to leave or implicitly when detaching from the channel. Alternatively, if a member's connection is
+// abruptly disconnected and they do not resume their connection within a minute, Ably treats this as a
+// leave event as the client is no longer present.
+const PresenceActionLeave = ably.PresenceActionLeave
+
+// PresenceActionUpdate is a already present member has updated their member data. Being notified of member data
+// updates can be very useful, for example, it can be used to update the status of a user when they are
+// typing a message.
+const PresenceActionUpdate = ably.PresenceActionUpdate
+
+// StatGranularityMinute specifies interval unit over which statistics are gathered as minutes.
+const StatGranularityMinute = ably.StatGranularityMinute
+
+// StatGranularityHour specifies interval unit over which statistics are gathered as hours.
+const StatGranularityHour = ably.StatGranularityHour
+
+// StatGranularityDay specifies interval unit over which statistics are gathered as days.
+const StatGranularityDay = ably.StatGranularityDay
+
+// StatGranularityMonth specifies interval unit over which statistics are gathered as months.
+const StatGranularityMonth = ably.StatGranularityMonth
+
+// ConnectionStateInitialized - A connection with this state has been initialized
+// but no connection has yet been attempted.
+var ConnectionStateInitialized = ably.ConnectionStateInitialized
+
+// ConnectionStateConnecting - A connection attempt has been initiated.
+// The connecting state is entered as soon as the library has completed initialization,
+// and is reentered each time connection is re-attempted following disconnection.
+var ConnectionStateConnecting = ably.ConnectionStateConnecting
+
+// ConnectionStateConnected - A connection exists and is active.
+var ConnectionStateConnected = ably.ConnectionStateConnected
+
+// ConnectionStateDisconnected - A temporary failure condition. No current connection exists
+// because there is no network connectivity or no host is available.
+// The disconnected state is entered if an established connection is dropped,
+// or if a connection attempt was unsuccessful. In the disconnected state the library will periodically attempt
+// to open a new connection (approximately every 15 seconds), anticipating that the connection will
+// be re-established soon and thus connection and channel continuity will be possible.
+// In this state, developers can continue to publish messages as they are automatically placed in a local queue,
+// to be sent as soon as a connection is reestablished. Messages published by other clients
+// while this client is disconnected will be delivered to it upon reconnection, so long as
+// the connection was resumed within 2 minutes. After 2 minutes have elapsed, recovery is no longer possible
+// and the connection will move to the SUSPENDED state.
+var ConnectionStateDisconnected = ably.ConnectionStateDisconnected
+
+// ConnectionStateSuspended - A long term failure condition. No current connection exists
+// because there is no network connectivity or no host is available. The suspended state is entered
+// after a failed connection attempt if there has then been no connection for a period of two minutes.
+// In the suspended state, the library will periodically attempt to open a new connection every 30 seconds.
+// Developers are unable to publish messages in this state. A new connection attempt can also be triggered
+// by an explicit call to Connection.Connect. Once the connection has been re-established,
+// channels will be automatically re-attached. The client has been disconnected for too long for them
+// to resume from where they left off, so if it wants to catch up on messages published by other clients
+// while it was disconnected, it needs to use the History API.
+var ConnectionStateSuspended = ably.ConnectionStateSuspended
+
+// ConnectionStateClosing is when an explicit request by the developer to close the connection has been sent to
+// the Ably service. If a reply is not received from Ably within a short period of time,
+// the connection is forcibly terminated and the connection state becomes CLOSED.
+var ConnectionStateClosing = ably.ConnectionStateClosing
+
+// ConnectionStateClosed - The connection has been explicitly closed by the client.
+// In the closed state, no reconnection attempts are made automatically by the library,
+// and clients may not publish messages. No connection state is preserved by the service or by the library.
+// A new connection attempt can be triggered by an explicit call to Connection.Connect,
+// which results in a new connection.
+var ConnectionStateClosed = ably.ConnectionStateClosed
+
+// ConnectionStateFailed - This state is entered if the client library encounters a failure condition
+// that it cannot recover from. This may be a fatal connection error received from the Ably service,
+// for example an attempt to connect with an incorrect API key, or a local terminal error,
+// for example the token in use has expired and the library does not have any way to renew it.
+// In the failed state, no reconnection attempts are made automatically by the library, and
+// clients may not publish messages. A new connection attempt can be triggered by an explicit call
+// to Connection.Connect.
+var ConnectionStateFailed = ably.ConnectionStateFailed
+
+// ConnectionEventInitialized - A connection with this state has been initialized
+// but no connection has yet been attempted.
+var ConnectionEventInitialized = ably.ConnectionEventInitialized
+
+// ConnectionEventConnecting - A connection attempt has been initiated.
+// The connecting state is entered as soon as the library has completed initialization,
+// and is reentered each time connection is re-attempted following disconnection.
+var ConnectionEventConnecting = ably.ConnectionEventConnecting
+
+// ConnectionEventConnected - A connection exists and is active.
+var ConnectionEventConnected = ably.ConnectionEventConnected
+
+// ConnectionEventDisconnected - A temporary failure condition. No current connection exists
+// because there is no network connectivity or no host is available.
+// The disconnected state is entered if an established connection is dropped,
+// or if a connection attempt was unsuccessful. In the disconnected state the library will periodically attempt
+// to open a new connection (approximately every 15 seconds), anticipating that the connection will
+// be re-established soon and thus connection and channel continuity will be possible.
+// In this state, developers can continue to publish messages as they are automatically placed in a local queue,
+// to be sent as soon as a connection is reestablished. Messages published by other clients
+// while this client is disconnected will be delivered to it upon reconnection, so long as
+// the connection was resumed within 2 minutes. After 2 minutes have elapsed, recovery is no longer possible
+// and the connection will move to the SUSPENDED state.
+var ConnectionEventDisconnected = ably.ConnectionEventDisconnected
+
+// ConnectionEventSuspended - A long term failure condition. No current connection exists
+// because there is no network connectivity or no host is available. The suspended state is entered
+// after a failed connection attempt if there has then been no connection for a period of two minutes.
+// In the suspended state, the library will periodically attempt to open a new connection every 30 seconds.
+// Developers are unable to publish messages in this state. A new connection attempt can also be triggered
+// by an explicit call to Connection.Connect. Once the connection has been re-established,
+// channels will be automatically re-attached. The client has been disconnected for too long for them
+// to resume from where they left off, so if it wants to catch up on messages published by other clients
+// while it was disconnected, it needs to use the History API.
+var ConnectionEventSuspended = ably.ConnectionEventSuspended
+
+// ConnectionEventClosing is when an explicit request by the developer to close the connection has been sent to
+// the Ably service. If a reply is not received from Ably within a short period of time,
+// the connection is forcibly terminated and the connection state becomes CLOSED.
+var ConnectionEventClosing = ably.ConnectionEventClosing
+
+// ConnectionEventClosed - The connection has been explicitly closed by the client.
+// In the closed state, no reconnection attempts are made automatically by the library,
+// and clients may not publish messages. No connection state is preserved by the service or by the library.
+// A new connection attempt can be triggered by an explicit call to Connection.Connect,
+// which results in a new connection.
+var ConnectionEventClosed = ably.ConnectionEventClosed
+
+// ConnectionEventFailed - This state is entered if the client library encounters a failure condition
+// that it cannot recover from. This may be a fatal connection error received from the Ably service,
+// for example an attempt to connect with an incorrect API key, or a local terminal error,
+// for example the token in use has expired and the library does not have any way to renew it.
+// In the failed state, no reconnection attempts are made automatically by the library, and
+// clients may not publish messages. A new connection attempt can be triggered by an explicit call
+// to Connection.Connect.
+var ConnectionEventFailed = ably.ConnectionEventFailed
+
+// ConnectionEventUpdate is an event for changes to connection conditions for which the
+// [ConnectionState] does not change (RTN4h).
+var ConnectionEventUpdate = ably.ConnectionEventUpdate
+
+// ChannelStateInitialized is when the channel has been initialized but no attach has yet been attempted.
+var ChannelStateInitialized = ably.ChannelStateInitialized
+
+// ChannelStateAttaching is when the attach has been initiated by sending a request to Ably.
+// This is a transient state, followed either by a transition to ATTACHED, SUSPENDED, or FAILED.
+var ChannelStateAttaching = ably.ChannelStateAttaching
+
+// ChannelStateAttached is when the attach has succeeded.
+// In the ATTACHED state a client may publish and subscribe to messages, or be present on the channel.
+var ChannelStateAttached = ably.ChannelStateAttached
+
+// ChannelStateDetaching is when a detach has been initiated on an ATTACHED channel by sending a request to Ably.
+// This is a transient state, followed either by a transition to DETACHED or FAILED.
+var ChannelStateDetaching = ably.ChannelStateDetaching
+
+// ChannelStateDetached os when the channel, having previously been ATTACHED, has been detached by the user.
+var ChannelStateDetached = ably.ChannelStateDetached
+
+// ChannelStateSuspended is when the channel, having previously been ATTACHED, has lost continuity,
+// usually due to the client being disconnected from Ably for longer than two minutes.
+// It will automatically attempt to reattach as soon as connectivity is restored.
+var ChannelStateSuspended = ably.ChannelStateSuspended
+
+// ChannelStateFailed is an indefinite failure condition.
+// This state is entered if a channel error has been received from the Ably service,
+// such as an attempt to attach without the necessary access rights.
+var ChannelStateFailed = ably.ChannelStateFailed
+
+// ChannelEventInitialized is when the channel has been initialized but no attach has yet been attempted.
+var ChannelEventInitialized = ably.ChannelEventInitialized
+
+// ChannelEventAttaching is when the attach has been initiated by sending a request to Ably.
+// This is a transient state, followed either by a transition to ATTACHED, SUSPENDED, or FAILED.
+var ChannelEventAttaching = ably.ChannelEventAttaching
+
+// ChannelEventAttached is when the attach has succeeded.
+// In the ATTACHED state a client may publish and subscribe to messages, or be present on the channel.
+var ChannelEventAttached = ably.ChannelEventAttached
+
+// ChannelEventDetaching is when a detach has been initiated on an ATTACHED channel by sending a request to Ably.
+// This is a transient state, followed either by a transition to DETACHED or FAILED.
+var ChannelEventDetaching = ably.ChannelEventDetaching
+
+// ChannelEventDetached os when the channel, having previously been ATTACHED, has been detached by the user.
+var ChannelEventDetached = ably.ChannelEventDetached
+
+// ChannelEventSuspended is when the channel, having previously been ATTACHED, has lost continuity,
+// usually due to the client being disconnected from Ably for longer than two minutes.
+// It will automatically attempt to reattach as soon as connectivity is restored.
+var ChannelEventSuspended = ably.ChannelEventSuspended
+
+// ChannelEventFailed is an indefinite failure condition.
+// This state is entered if a channel error has been received from the Ably service,
+// such as an attempt to attach without the necessary access rights.
+var ChannelEventFailed = ably.ChannelEventFailed
+
+// ChannelEventUpdate is an event for changes to channel conditions that do not result
+// in a change in [ChannelState] (RTL2g).
+var ChannelEventUpdate = ably.ChannelEventUpdate
+
+// Functions.
+
+// AgentIdentifier returns the value the library sends in the Ably-Agent header
+// (RSC7d) for the given additional agents. It is exported so that test helpers
+// can identify their own requests to Ably with the same agent string the client
+// uses, without duplicating the library version.
+func AgentIdentifier(agents map[string]string) string {
+ return ably.AgentIdentifier(agents)
+}
+
+// AuthWithCallback is used for setting AuthCallback function using [AuthOption].
+//
+// AuthCallback function is called when a new token is required.
+// The role of the callback is to obtain a fresh token, one of
+//
+// 1. An Ably Token string (https://ably.com/docs/core-features/authentication#token-process)
+// 2. A signed [TokenRequest] (https://ably.com/docs/core-features/authentication#token-request-process)
+// 3. An [TokenDetails] (https://ably.com/docs/core-features/authentication#token-process)
+// 4. [An Ably JWT].
+//
+// See the [authentication doc] for more details and associated API calls (RSA4a, RSA4, TO3j5, AO2b).
+//
+// [authentication doc]: https://ably.com/docs/core-features/authentication
+// [An Ably JWT]: https://ably.com/docs/core-features/authentication#ably-jwt-process
+func AuthWithCallback(authCallback func(a0 context.Context, a1 TokenParams) (Tokener, error)) AuthOption {
+ return ably.AuthWithCallback(authCallback)
+}
+
+// AuthWithDefaultTokenParams is used for setting DefaultTokenParams token using [AuthOption].
+// DefaultTokenParams when provided, it overrides the client library defaults when issuing new Ably Tokens
+// for multiple Ably [TokenRequest] (TO3j11).
+func AuthWithDefaultTokenParams(params TokenParams) AuthOption {
+ return ably.AuthWithDefaultTokenParams(params)
+}
+
+// AuthWithHeaders is used for setting AuthHeaders using [AuthOption].
+// AuthHeaders are key-value pair HTTP request headers to be added to any request made to the AuthURL.
+// Useful when an application requires these to be added to validate the request or implement the response.
+// If the authHeaders object contains an authorization key, then withCredentials is set on the XHR request.
+// (RSA8c3, TO3j8, AO2e).
+func AuthWithHeaders(headers http.Header) AuthOption {
+ return ably.AuthWithHeaders(headers)
+}
+
+// AuthWithKey is used for setting root/non-root apikey using [AuthOption].
+// Key is a full API key string, as obtained from the Ably dashboard.
+// Use this option if you wish to use Basic authentication, or wish to be able to issue Ably Tokens
+// without needing to defer to a separate entity to sign multiple [TokenRequest].
+// Read more about [Basic authentication] (RSA11, RSA14, TO3j1, AO2a).
+//
+// [Basic authentication]: https://ably.com/docs/core-features/authentication#basic-authentication
+func AuthWithKey(key string) AuthOption {
+ return ably.AuthWithKey(key)
+}
+
+// AuthWithMethod is used for setting AuthMethod using [AuthOption]
+// AuthMethod specifies HTTP verb to use for any request made to the AuthURL, either GET or POST for
+// getting token information [TokenRequest] or [TokenDetails].
+// The default value is GET (RSA8c, TO3j7, AO2d).
+func AuthWithMethod(method string) AuthOption {
+ return ably.AuthWithMethod(method)
+}
+
+// AuthWithParams is used for setting AuthParams using [AuthOption].
+// AuthParams are key-value pair HTTP query params to be added to any request made to the AuthURL.
+// When the authMethod is GET, query params are added to the URL, whereas when authMethod is POST,
+// the params are sent as URL encoded form data. Useful when an application requires these to be added to
+// validate the request or implement the response (RSA8c3, RSA8c1, TO3j9, AO2f).
+func AuthWithParams(params url.Values) AuthOption {
+ return ably.AuthWithParams(params)
+}
+
+// AuthWithQueryTime is used for setting UseQueryTime token using [AuthOption].
+// UseQueryTime when set to true, the library queries the Ably servers for the current time when
+// issuing multiple [TokenRequest] instead of relying on a locally-available time of day.
+// Knowing the time accurately is needed to create valid signed Ably [TokenRequest],
+// so this option is useful for library instances on auth servers where for some reason
+// the server clock cannot be kept synchronized through normal means,
+// such as an NTP daemon. The server is queried for the current time once per client library instance
+// (which stores the offset from the local clock), so if using this option you should avoid instancing
+// a new version of the library for each request.
+// The default is false (RSA9d, TO3j10, AO2a).
+func AuthWithQueryTime(queryTime bool) AuthOption {
+ return ably.AuthWithQueryTime(queryTime)
+}
+
+// AuthWithToken is used for setting authenticated token using [AuthOption].
+// This can either be a token string (obtained from the token property of a [TokenDetails] component of
+// an Ably [TokenRequest] response, or a JSON Web Token satisfying the Ably requirements for JWTs).
+//
+// This option is mostly useful for testing: since tokens are short-lived,
+// in production you almost always want to use an authentication method that enables the client library
+// to renew the token automatically when the previous one expires, such as AuthURL or authCallback.
+// Read more about [Token authentication] (RSA4a, RSA4, TO3j2).
+//
+// [Token authentication]: https://ably.com/docs/core-features/authentication#token-authentication
+func AuthWithToken(token string) AuthOption {
+ return ably.AuthWithToken(token)
+}
+
+// AuthWithTokenDetails is used for setting authenticated [TokenDetails] object using [AuthOption].
+// Only TokenDetails.Token can be set via token string (obtained from the token property of a [TokenDetails]
+// component of an Ably [TokenRequest] response, or a JSON Web Token satisfying the Ably requirements for JWTs).
+// This option is mostly useful for testing: since tokens are short-lived,
+// in production you almost always want to use an authentication method that enables the client library
+// to renew the token automatically when the previous one expires, such as AuthURL or authCallback.
+// Read more about [Token authentication] (RSA4a, RSA4, TO3j3).
+//
+// [Token authentication]: https://ably.com/docs/core-features/authentication#token-authentication
+func AuthWithTokenDetails(details *TokenDetails) AuthOption {
+ return ably.AuthWithTokenDetails(details)
+}
+
+// AuthWithURL is used for setting AuthURL using [AuthOption].
+// AuthURL is a url that library will use to obtain
+// 1. An Ably Token string (https://ably.com/docs/core-features/authentication#token-process)
+// 2. A signed [TokenRequest] (https://ably.com/docs/core-features/authentication#token-request-process)
+// 3. An [TokenDetails] (https://ably.com/docs/core-features/authentication#token-process)
+// 4. [An Ably JWT].
+//
+// See the [authentication doc] for more details and associated API calls (RSA4a, RSA4, RSA8c, TO3j6, AO2c).
+//
+// This enables a client to obtain token requests from another entity,
+// so tokens can be renewed without the client requiring access to keys.
+//
+// If AuthURL is non-empty and AuthCallback is nil, the Ably library
+// builds a req (*http.Request) which then is issued against the given AuthURL
+// in order to obtain authentication token. The response is expected to
+// carry a single token string in the payload when Content-Type header
+// is "text/plain" or JSON-encoded *TokenDetails when the header
+// is "application/json".
+//
+// The req is built with the following values:
+//
+// GET requests:
+//
+// - req.URL.RawQuery is encoded from *TokenParams and AuthParams
+// - req.Header is set to AuthHeaders
+//
+// POST requests:
+//
+// - req.Header is set to AuthHeaders
+// - Content-Type is set to "application/x-www-form-urlencoded" and
+// the payload is encoded from *TokenParams and AuthParams
+//
+// [authentication doc]: https://ably.com/docs/core-features/authentication
+// [An Ably JWT]: https://ably.com/docs/core-features/authentication#ably-jwt-process
+func AuthWithURL(url string) AuthOption {
+ return ably.AuthWithURL(url)
+}
+
+// AuthWithUseTokenAuth is used for setting UseTokenAuth using [AuthOption].
+// UseTokenAuth when set to true, forces token authentication to be used by the library.
+// If a clientId is not specified in the [ClientOption] or [TokenParams],
+// then the Ably Token issued is anonymous (RSA4, RSA14, TO3j4).
+func AuthWithUseTokenAuth(use bool) AuthOption {
+ return ably.AuthWithUseTokenAuth(use)
+}
+
+// ChannelWithCipher requests encryption for this channel when not null, and specifies encryption-related parameters
+// (such as algorithm, chaining mode, key length and key). See [an example] (RSL5a, TB2b).
+//
+// [an example]: https://ably.com/docs/realtime/encryption#getting-started
+func ChannelWithCipher(params CipherParams) ChannelOption {
+ return ably.ChannelWithCipher(params)
+}
+
+// ChannelWithCipherKey is a constructor that takes private key as a argument.
+// It is used to encrypt and decrypt payloads (TB3)
+func ChannelWithCipherKey(key []byte) ChannelOption {
+ return ably.ChannelWithCipherKey(key)
+}
+
+// ChannelWithModes set an array of [ChannelMode] to a channel (TB2d).
+func ChannelWithModes(modes ...ChannelMode) ChannelOption {
+ return ably.ChannelWithModes(modes...)
+}
+
+// ChannelWithParams sets channel parameters that configure the behavior of the channel (TB2c).
+func ChannelWithParams(key string, value string) ChannelOption {
+ return ably.ChannelWithParams(key, value)
+}
+
+// ChannelWithVCDiff sets channel parameters for VCDIFF delta compression.
+// This is a convenience method that internally sets "delta" to "vcdiff" parameters.
+func ChannelWithVCDiff() ChannelOption {
+ return ably.ChannelWithVCDiff()
+}
+
+func DecodeRecoveryKey(recoveryKey string) (*RecoveryKeyContext, error) {
+ return ably.DecodeRecoveryKey(recoveryKey)
+}
+
+// DefaultCipherParams returns CipherParams with fields set to default values.
+// This generates random secret key and iv values
+func DefaultCipherParams() (*CipherParams, error) {
+ return ably.DefaultCipherParams()
+}
+
+// GetPresenceWithClientID filters the list of returned presence members by a specific client using its ID (RSP3a2).
+func GetPresenceWithClientID(clientID string) GetPresenceOption {
+ return ably.GetPresenceWithClientID(clientID)
+}
+
+// GetPresenceWithConnectionID filters the list of returned presence members by a specific connection using its ID.
+// RSP3a3
+func GetPresenceWithConnectionID(connectionID string) GetPresenceOption {
+ return ably.GetPresenceWithConnectionID(connectionID)
+}
+
+// GetPresenceWithLimit sets an upper limit on the number of messages returned.
+// The default is 100, and the maximum is 1000 (RSP3a).
+func GetPresenceWithLimit(limit int) GetPresenceOption {
+ return ably.GetPresenceWithLimit(limit)
+}
+
+// HistoryWithDirection sets the order for which messages are returned in.
+// Valid values are backwards which orders messages from most recent to oldest,
+// or forwards which orders messages from oldest to most recent.
+// The default is backwards (RSL2b2).
+func HistoryWithDirection(d Direction) HistoryOption {
+ return ably.HistoryWithDirection(d)
+}
+
+// HistoryWithEnd sets time until messages are retrieved, specified as milliseconds since the Unix epoch (RSL2b1).
+func HistoryWithEnd(t time.Time) HistoryOption {
+ return ably.HistoryWithEnd(t)
+}
+
+// HistoryWithLimit sets an upper limit on the number of messages returned.
+// The default is 100, and the maximum is 1000 (RSL2b3).
+func HistoryWithLimit(limit int) HistoryOption {
+ return ably.HistoryWithLimit(limit)
+}
+
+// HistoryWithStart sets time from which messages are retrieved, specified as milliseconds since the Unix epoch.
+// RSL2b1
+func HistoryWithStart(t time.Time) HistoryOption {
+ return ably.HistoryWithStart(t)
+}
+
+// NewVCDiffPlugin creates a new AblyVCDiffDecoder plugin instance.
+// Used publicly to set the VCDiff plugin in the client options.
+//
+// Example:
+//
+// client, err := NewHTTPClient(WithVCDiffPlugin(NewVCDiffPlugin()))
+func NewVCDiffPlugin() VCDiffDecoder {
+ return ably.NewVCDiffPlugin()
+}
+
+// PresenceGetWithWaitForSync sets whether to wait for a full presence set synchronization between Ably
+// and the clients on the channel to complete before returning the results.
+// Synchronization begins as soon as the channel is [ChannelStateAttached].
+// When set to true the results will be returned as soon as the sync is complete.
+// When set to false the current list of members will be returned without the sync completing.
+// The default is true (RTP11c1).
+func PresenceGetWithWaitForSync(wait bool) PresenceGetOption {
+ return ably.PresenceGetWithWaitForSync(wait)
+}
+
+// PresenceHistoryWithDirection sets the order for which messages are returned in.
+// Valid values are backwards which orders messages from most recent to oldest,
+// or forwards which orders messages from oldest to most recent.
+// The default is backwards (RSP4b2).
+func PresenceHistoryWithDirection(d Direction) PresenceHistoryOption {
+ return ably.PresenceHistoryWithDirection(d)
+}
+
+// PresenceHistoryWithEnd sets the time until messages are retrieved, specified as milliseconds since the Unix epoch.
+// (RSP4b1)
+func PresenceHistoryWithEnd(t time.Time) PresenceHistoryOption {
+ return ably.PresenceHistoryWithEnd(t)
+}
+
+// PresenceHistoryWithLimit sets an upper limit on the number of messages returned.
+// The default is 100, and the maximum is 1000 (RSP4b3).
+func PresenceHistoryWithLimit(limit int) PresenceHistoryOption {
+ return ably.PresenceHistoryWithLimit(limit)
+}
+
+// PresenceHistoryWithStart sets the time from which messages are retrieved, specified as milliseconds
+// since the Unix epoch (RSP4b1).
+func PresenceHistoryWithStart(t time.Time) PresenceHistoryOption {
+ return ably.PresenceHistoryWithStart(t)
+}
+
+// PublishWithConnectionKey allows a message to be published for a specified connectionKey.
+func PublishWithConnectionKey(connectionKey string) PublishMultipleOption {
+ return ably.PublishWithConnectionKey(connectionKey)
+}
+
+// PublishWithParams adds query parameters to the resulting HTTP request to the REST API.
+func PublishWithParams(params map[string]string) PublishMultipleOption {
+ return ably.PublishWithParams(params)
+}
+
+// RequestWithBody sets the JSON body of the request.
+func RequestWithBody(body interface{}) RequestOption {
+ return ably.RequestWithBody(body)
+}
+
+// RequestWithHeaders sets additional HTTP headers to include in the request.
+func RequestWithHeaders(headers http.Header) RequestOption {
+ return ably.RequestWithHeaders(headers)
+}
+
+// RequestWithParams sets the parameters to include in the URL query of the request.
+// The parameters depend on the endpoint being queried.
+// See the REST API reference for the available parameters of each endpoint.
+func RequestWithParams(params url.Values) RequestOption {
+ return ably.RequestWithParams(params)
+}
+
+// StatsWithDirection sets the order for which stats are returned in. Valid values are backwards which
+// orders stats from most recent to oldest, or forwards which orders stats from oldest to most recent.
+// The default is backwards (RSC6b2).
+func StatsWithDirection(d Direction) StatsOption {
+ return ably.StatsWithDirection(d)
+}
+
+// StatsWithEnd sets the time until stats are retrieved, specified as milliseconds since the Unix epoch (RSC6b1).
+func StatsWithEnd(t time.Time) StatsOption {
+ return ably.StatsWithEnd(t)
+}
+
+// StatsWithLimit sets an upper limit on the number of stats returned.
+// The default is 100, and the maximum is 1000 (RSC6b3).
+func StatsWithLimit(limit int) StatsOption {
+ return ably.StatsWithLimit(limit)
+}
+
+// StatsWithStart sets the time from which stats are retrieved, specified as milliseconds since the Unix epoch (RSC6b1).
+func StatsWithStart(t time.Time) StatsOption {
+ return ably.StatsWithStart(t)
+}
+
+// StatsWithUnit sets minute, hour, day or month. Based on the unit selected, the given start or end times are
+// rounded down to the start of the relevant interval depending on the unit granularity of the query (RSC6b4).
+func StatsWithUnit(d PeriodUnit) StatsOption {
+ return ably.StatsWithUnit(d)
+}
+
+// UpdateWithClientID sets the client ID for the update operation.
+func UpdateWithClientID(clientID string) UpdateOption {
+ return ably.UpdateWithClientID(clientID)
+}
+
+// UpdateWithDescription sets a description for the update operation.
+func UpdateWithDescription(description string) UpdateOption {
+ return ably.UpdateWithDescription(description)
+}
+
+// UpdateWithMetadata sets metadata for the update operation.
+func UpdateWithMetadata(metadata map[string]string) UpdateOption {
+ return ably.UpdateWithMetadata(metadata)
+}
+
+// UpdateWithParams sets operation params. Over HTTP, these are included as URL query
+// parameters (RSL15a, RSL15f). Over a realtime connection, these are set as protocolMessage.Params (RTL32e).
+func UpdateWithParams(params map[string]string) UpdateOption {
+ return ably.UpdateWithParams(params)
+}
+
+// WithAgents is used to add product/version key-value pairs to include in the
+// agent library identifiers. This must only be used by Ably-authored SDKs.
+// Successive calls merge their entries into any agents already configured
+// rather than replacing them.
+func WithAgents(agents map[string]string) ClientOption {
+ return ably.WithAgents(agents)
+}
+
+// WithAuthCallback is used for setting authCallback function using [ClientOption].
+// AuthCallback function is called when a new token is required.
+// The role of the callback is to obtain a fresh token, one of
+//
+// 1. An Ably Token string (https://ably.com/docs/core-features/authentication#token-process)
+// 2. A signed [TokenRequest] (https://ably.com/docs/core-features/authentication#token-request-process)
+// 3. An [TokenDetails] (https://ably.com/docs/core-features/authentication#token-process)
+// 4. [An Ably JWT].
+//
+// See the [authentication doc] for more details and associated API calls (RSA4a, RSA4, TO3j5, AO2b).
+//
+// [authentication doc]: https://ably.com/docs/core-features/authentication
+// [An Ably JWT]: https://ably.com/docs/core-features/authentication#ably-jwt-process
+func WithAuthCallback(authCallback func(a0 context.Context, a1 TokenParams) (Tokener, error)) ClientOption {
+ return ably.WithAuthCallback(authCallback)
+}
+
+// WithAuthHeaders is used for setting AuthHeaders using [ClientOption].
+// AuthHeaders are key-value pair HTTP request headers to be added to any request made to the AuthURL.
+// Useful when an application requires these to be added to validate the request or implement the response.
+// If the authHeaders object contains an authorization key, then withCredentials is set on the XHR request.
+// (RSA8c3, TO3j8, AO2e).
+func WithAuthHeaders(headers http.Header) ClientOption {
+ return ably.WithAuthHeaders(headers)
+}
+
+// WithAuthMethod is used for setting AuthMethod using [ClientOption].
+// AuthMethod specifies HTTP verb to use for any request made to the AuthURL, either GET or POST for
+// getting token information [TokenRequest] or [TokenDetails].
+// The default value is GET (RSA8c, TO3j7, AO2d)
+func WithAuthMethod(method string) ClientOption {
+ return ably.WithAuthMethod(method)
+}
+
+// WithAuthParams is used for setting AuthParams using [ClientOption].
+// AuthParams are key-value pair HTTP query params to be added to any request made to the AuthURL.
+// When the authMethod is GET, query params are added to the URL, whereas when authMethod is POST,
+// the params are sent as URL encoded form data. Useful when an application requires these to be added to
+// validate the request or implement the response (RSA8c3, RSA8c1, TO3j9, AO2f).
+func WithAuthParams(params url.Values) ClientOption {
+ return ably.WithAuthParams(params)
+}
+
+// WithAuthURL is used for setting AuthURL using [ClientOption].
+// AuthURL is a url that library will use to obtain
+// 1. An Ably Token string (https://ably.com/docs/core-features/authentication#token-process)
+// 2. A signed [TokenRequest] (https://ably.com/docs/core-features/authentication#token-request-process)
+// 3. An [TokenDetails] (https://ably.com/docs/core-features/authentication#token-process)
+// 4. [An Ably JWT].
+//
+// See the [authentication doc] for more details and associated API calls (RSA4a, RSA4, RSA8c, TO3j6, AO2c).
+//
+// This enables a client to obtain token requests from another entity,
+// so tokens can be renewed without the client requiring access to keys.
+//
+// If AuthURL is non-empty and AuthCallback is nil, the Ably library
+// builds a req (*http.Request) which then is issued against the given AuthURL
+// in order to obtain authentication token. The response is expected to
+// carry a single token string in the payload when Content-Type header
+// is "text/plain" or JSON-encoded *TokenDetails when the header
+// is "application/json".
+//
+// The req is built with the following values:
+//
+// GET requests:
+//
+// - req.URL.RawQuery is encoded from *TokenParams and AuthParams
+// - req.Header is set to AuthHeaders
+//
+// POST requests:
+//
+// - req.Header is set to AuthHeaders
+// - Content-Type is set to "application/x-www-form-urlencoded" and
+// the payload is encoded from *TokenParams and AuthParams
+//
+// [authentication doc]: https://ably.com/docs/core-features/authentication
+// [An Ably JWT]: https://ably.com/docs/core-features/authentication#ably-jwt-process
+func WithAuthURL(url string) ClientOption {
+ return ably.WithAuthURL(url)
+}
+
+// WithAutoConnect is used for setting NoConnect using [ClientOption].
+// NoConnect when set to false, the client connects to Ably as soon as it is instantiated.
+// You can set this to true and explicitly connect to Ably using the [Connection]#connect()
+// The default is false (RTC1b, TO3e).
+func WithAutoConnect(autoConnect bool) ClientOption {
+ return ably.WithAutoConnect(autoConnect)
+}
+
+// WithChannelRetryTimeout is used for setting ChannelRetryTimeout using [ClientOption].
+// ChannelRetryTimeout when a channel becomes [ChannelStateSuspended} following a server initiated
+// [ChannelStateDetached], after this delay, if the channel is still [ChannelStateSuspended]
+// and the connection is in [ConnectionStateConnected], the client library will attempt to re-attach
+// the channel automatically.
+// The default is 15 seconds (RTL13b, TO3l7).
+func WithChannelRetryTimeout(d time.Duration) ClientOption {
+ return ably.WithChannelRetryTimeout(d)
+}
+
+// WithClientID is used for setting ClientID using [ClientOption].
+// ClientID is used for identifying this client when publishing messages or for presence purposes.
+// The clientId can be any non-empty string, except it cannot contain a *.
+// This option is primarily intended to be used in situations where the library is instantiated with a key.
+// Note that a clientId may also be implicit in a token used to instantiate the library.
+// An error will be raised if a clientId specified here conflicts with the clientId implicit in the token.
+// (RSC17, RSA4, RSA15, TO3a).
+func WithClientID(clientID string) ClientOption {
+ return ably.WithClientID(clientID)
+}
+
+// WithDefaultTokenParams is used for setting DefaultTokenParams token using [ClientOption].
+// DefaultTokenParams when provided, it overrides the client library defaults when issuing new Ably Tokens
+// for multiple Ably [TokenRequest] (TO3j11).
+func WithDefaultTokenParams(params TokenParams) ClientOption {
+ return ably.WithDefaultTokenParams(params)
+}
+
+// WithDisconnectedRetryTimeout is used for setting DisconnectedRetryTimeout using [ClientOption].
+// DisconnectedRetryTimeout when the connection enters the [ConnectionStateDisconnected] state, after this
+// timeout, if the state is still [ConnectionStateDisconnected], the client library will attempt
+// to reconnect automatically.
+// The default is 15 seconds (TO3l1).
+func WithDisconnectedRetryTimeout(d time.Duration) ClientOption {
+ return ably.WithDisconnectedRetryTimeout(d)
+}
+
+// WithEchoMessages is used for setting NoEcho using [ClientOption].
+// NoEcho if set to true, prevents messages originating from this connection being echoed back
+// on the same connection.
+// The default is false (RTC1a, TO3h).
+func WithEchoMessages(echo bool) ClientOption {
+ return ably.WithEchoMessages(echo)
+}
+
+// WithEndpoint sets the endpoint the client connects to (REC1). It takes one of:
+//
+// - a routing policy name, such as "main", which resolves to [name].realtime.ably.net;
+// - a non-production routing policy name, such as "nonprod:sandbox", which resolves to
+// [name].realtime.ably-nonprod.net;
+// - a hostname, such as "localhost" or "ably.example.com", used as given. A value is
+// treated as a hostname if it contains "." or "::" or is "localhost". A hostname
+// endpoint has no default fallback hosts.
+//
+// Unset, the endpoint is "main". To use a port other than the default, set it with
+// [WithPort] or [WithTLSPort]: "localhost:8080" is not a hostname and does not resolve.
+// See [Platform Customization] for more information.
+//
+// [Platform Customization]: https://ably.com/docs/platform-customization
+func WithEndpoint(endpoint string) ClientOption {
+ return ably.WithEndpoint(endpoint)
+}
+
+// WithExperimentalObjectsPlugin configures the client to use the given plugin to implement
+// [LiveObjects] functionality.
+//
+// NOTE: this option is experimental, the LiveObjects plugin API may change in a
+// backwards incompatible way between minor/patch versions. Once the API has been finalised,
+// a new non-experimental option will be added, and this one will be removed.
+//
+// [LiveObjects]: https://ably.com/docs/liveobjects
+func WithExperimentalObjectsPlugin(plugin ObjectsPlugin) ClientOption {
+ return ably.WithExperimentalObjectsPlugin(plugin)
+}
+
+// WithFallbackHosts is used for setting FallbackHosts using [ClientOption].
+// FallbackHosts is an array of fallback hosts to be used in the case of an error necessitating
+// the use of an alternative host. If you have been provided a set of custom fallback hosts by Ably,
+// please specify them here (RSC15b, RSC15a, TO3k6).
+func WithFallbackHosts(hosts []string) ClientOption {
+ return ably.WithFallbackHosts(hosts)
+}
+
+// WithFallbackRetryTimeout is used for setting FallbackRetryTimeout using [ClientOption].
+// FallbackRetryTimeout is the max time in milliseconds before HTTP requests are retried against the default endpoint.
+// The default is 600 seconds (TO3l10).
+func WithFallbackRetryTimeout(fallbackRetryTimeout time.Duration) ClientOption {
+ return ably.WithFallbackRetryTimeout(fallbackRetryTimeout)
+}
+
+// WithHTTPClient is used for setting HTTPClient using [ClientOption].
+// HTTPClient specifies the client used for HTTP communication by [HTTPClient].
+// When set to nil, a client configured with default settings is used.
+func WithHTTPClient(client *http.Client) ClientOption {
+ return ably.WithHTTPClient(client)
+}
+
+// WithHTTPMaxRetryCount is used for setting HTTPMaxRetryCount using [ClientOption].
+// HTTPMaxRetryCount denotes the maximum number of fallback hosts to use as a fallback when an HTTP request
+// to the primary host is unreachable or indicates that it is unserviceable. The default value is 3 (TO3l5).
+func WithHTTPMaxRetryCount(count int) ClientOption {
+ return ably.WithHTTPMaxRetryCount(count)
+}
+
+// WithHTTPOpenTimeout is used for setting HTTPOpenTimeout using [ClientOption].
+// HTTPOpenTimeout is timeout for opening a connection to Ably to initiate an HTTP request.
+// The default is 4 seconds (TO3l3).
+func WithHTTPOpenTimeout(d time.Duration) ClientOption {
+ return ably.WithHTTPOpenTimeout(d)
+}
+
+// WithHTTPRequestTimeout is used for setting HTTPRequestTimeout using [ClientOption].
+// HTTPRequestTimeout is a timeout for a client performing a complete HTTP request to Ably, including the connection phase.
+// Will only be used if no custom HTTPClient is set.
+// The default is 10 seconds (TO3l4).
+func WithHTTPRequestTimeout(timeout time.Duration) ClientOption {
+ return ably.WithHTTPRequestTimeout(timeout)
+}
+
+// WithIdempotentHTTPPublishing is used for setting IdempotentHTTPPublishing using [ClientOption].
+// IdempotentHTTPPublishing when set to true, enables idempotent publishing by assigning a
+// unique message ID client-side, allowing the Ably servers to discard automatic publish retries
+// following a failure such as a network fault.
+// The default is true (RSL1k1, RTL6a1, TO3n).
+func WithIdempotentHTTPPublishing(idempotent bool) ClientOption {
+ return ably.WithIdempotentHTTPPublishing(idempotent)
+}
+
+// WithInsecureAllowBasicAuthWithoutTLS permits an API key to be used even if the connection
+// will not use TLS, something which would otherwise not be permitted for security reasons.
+func WithInsecureAllowBasicAuthWithoutTLS() ClientOption {
+ return ably.WithInsecureAllowBasicAuthWithoutTLS()
+}
+
+// WithKey is used for setting root/non-root apikey using [ClientOption].
+// Key is a full API key string, as obtained from the Ably dashboard.
+// Use this option if you wish to use Basic authentication, or wish to be able to issue Ably Tokens
+// without needing to defer to a separate entity to sign multiple [TokenRequest].
+// Read more about [Basic authentication] (RSC1, RSA11, RSA14, TO3j1, AO2a).
+//
+// [Basic authentication]: https://ably.com/docs/core-features/authentication#basic-authentication
+func WithKey(key string) ClientOption {
+ return ably.WithKey(key)
+}
+
+// WithLogHandler is used for setting LogHandler using [ClientOption].
+// LogHandler controls the log output of the library. This is a function to handle each line of log output.
+// platform specific (TO3c)
+func WithLogHandler(handler Logger) ClientOption {
+ return ably.WithLogHandler(handler)
+}
+
+// WithLogLevel is used for setting LogLevel using [ClientOption].
+// LogLevel controls the verbosity of the logs output from the library.
+// Levels include verbose, debug, info, warn and error.
+// platform specific (TO3b)
+func WithLogLevel(level LogLevel) ClientOption {
+ return ably.WithLogLevel(level)
+}
+
+// WithPort is used for setting custom Port using [ClientOption].
+// Port is used for non-TLS connections and requests
+func WithPort(port int) ClientOption {
+ return ably.WithPort(port)
+}
+
+// WithQueryTime is used for setting UseQueryTime token using [ClientOption].
+// UseQueryTime when set to true, the library queries the Ably servers for the current time when
+// issuing multiple [TokenRequest] instead of relying on a locally-available time of day.
+// Knowing the time accurately is needed to create valid signed Ably [TokenRequest],
+// so this option is useful for library instances on auth servers where for some reason
+// the server clock cannot be kept synchronized through normal means,
+// such as an NTP daemon. The server is queried for the current time once per client library instance
+// (which stores the offset from the local clock), so if using this option you should avoid instancing
+// a new version of the library for each request.
+// The default is false (RSA9d, TO3j10, AO2a).
+func WithQueryTime(queryTime bool) ClientOption {
+ return ably.WithQueryTime(queryTime)
+}
+
+// WithQueueMessages is used for setting NoQueueing using [ClientOption].
+// NoQueueing if set to true, this disables the default behavior whereby the library queues messages on a
+// connection in the disconnected or connecting states. The default behavior enables applications to
+// submit messages immediately upon instantiating the library without having to wait for the connection
+// to be established. Applications may use this option to disable queueing if they wish to have
+// application-level control over the queueing.
+// The default is false (RTP16b, TO3g).
+func WithQueueMessages(queue bool) ClientOption {
+ return ably.WithQueueMessages(queue)
+}
+
+// WithRealtimeRequestTimeout is used for setting RealtimeRequestTimeout using [ClientOption].
+// RealtimeRequestTimeout is the timeout for the wait of acknowledgement for operations performed via a
+// realtime connection, before the client library considers a request failed and triggers a failure condition.
+// Operations include establishing a connection with Ably, or sending a HEARTBEAT, CONNECT, ATTACH, DETACH or
+// CLOSE request. It is the equivalent of httpRequestTimeout but for realtime operations, rather than HTTP requests.
+// The default is 10 seconds (TO3l11).
+func WithRealtimeRequestTimeout(d time.Duration) ClientOption {
+ return ably.WithRealtimeRequestTimeout(d)
+}
+
+// WithRecover is used for setting Recover using [ClientOption].
+// Recover enables a connection to inherit the state of a previous connection that may have existed
+// under a different instance of the Realtime library. This might typically be used by clients of the browser
+// library to ensure connection state can be preserved when the user refreshes the page.
+// A recovery key string can be explicitly provided, or alternatively if a callback function is provided,
+// the client library will automatically persist the recovery key between page reloads and call the callback
+// when the connection is recoverable. The callback is then responsible for confirming whether the connection
+// should be recovered or not. See connection state recovery for further information (RTC1c, TO3i).
+func WithRecover(key string) ClientOption {
+ return ably.WithRecover(key)
+}
+
+// WithSuspendedRetryTimeout is used for setting SuspendedRetryTimeout using [ClientOption].
+// SuspendedRetryTimeout is the timeout when the connection enters the [ConnectionStateSuspended] state,
+// after this timeout, if the state is still [ConnectionStateSuspended], the client library attempts
+// to reconnect automatically.
+// The default is 30 seconds (RTN14d, TO3l2).
+func WithSuspendedRetryTimeout(d time.Duration) ClientOption {
+ return ably.WithSuspendedRetryTimeout(d)
+}
+
+// WithTLS is used for setting NoTLS using [ClientOption].
+// NoTLS when set to true, the client will use an insecure connection.
+// The default is false, meaning a TLS connection will be used to connect to Ably (RSC18, TO3d).
+func WithTLS(tls bool) ClientOption {
+ return ably.WithTLS(tls)
+}
+
+// WithTLSPort is used for setting TLSPort using [ClientOption].
+// TLSPort enables a non-default Ably port to be specified.
+// This is used for TLS connections and requests and restricted to development environments only.
+// The default value is 80 (TO3k4)>
+func WithTLSPort(port int) ClientOption {
+ return ably.WithTLSPort(port)
+}
+
+// WithToken is used for setting authenticated token using [ClientOption].
+// This can either be a token string (obtained from the token property of a [TokenDetails] component of
+// an Ably [TokenRequest] response, or a JSON Web Token satisfying the Ably requirements for JWTs).
+//
+// This option is mostly useful for testing: since tokens are short-lived,
+// in production you almost always want to use an authentication method that enables the client library
+// to renew the token automatically when the previous one expires, such as AuthURL or authCallback.
+// Read more about [Token authentication] (RSA4a, RSA4, TO3j2).
+//
+// [Token authentication]: https://ably.com/docs/core-features/authentication#token-authentication
+func WithToken(token string) ClientOption {
+ return ably.WithToken(token)
+}
+
+// WithTokenDetails is used for setting authenticated [TokenDetails] object using [ClientOption].
+// Only TokenDetails.Token can be set via token string (obtained from the token property of a [TokenDetails]
+// component of an Ably [TokenRequest] response, or a JSON Web Token satisfying the Ably requirements for JWTs).
+// This option is mostly useful for testing: since tokens are short-lived,
+// in production you almost always want to use an authentication method that enables the client library
+// to renew the token automatically when the previous one expires, such as AuthURL or authCallback.
+// Read more about [Token authentication] (RSA4a, RSA4, TO3j3).
+//
+// [Token authentication]: https://ably.com/docs/core-features/authentication#token-authentication
+func WithTokenDetails(details *TokenDetails) ClientOption {
+ return ably.WithTokenDetails(details)
+}
+
+// WithTransportParams is used for setting TransportParams using [ClientOption].
+// TransportParams is a set of key-value pairs that can be used to pass in arbitrary connection parameters,
+// such as heartbeatInterval or remainPresentFor (RTC1f).
+func WithTransportParams(params url.Values) ClientOption {
+ return ably.WithTransportParams(params)
+}
+
+// WithUseBinaryProtocol is used for setting NoBinaryProtocol using [ClientOption].
+// NoBinaryProtocol when set to true, JSON text encoding is used.
+// When false, the more efficient MsgPack binary encoding is used.
+// The default is true (TO3f).
+func WithUseBinaryProtocol(use bool) ClientOption {
+ return ably.WithUseBinaryProtocol(use)
+}
+
+// WithUseTokenAuth is used for setting UseTokenAuth using [ClientOption].
+// UseTokenAuth when set to true, forces token authentication to be used by the library.
+// If a clientId is not specified in the [ClientOption] or [TokenParams],
+// then the Ably Token issued is anonymous (RSA4, RSA14, TO3j4).
+func WithUseTokenAuth(use bool) ClientOption {
+ return ably.WithUseTokenAuth(use)
+}
+
+// WithVCDiffPlugin configures the client to use the given plugin to decode vcdiff-encoded
+// delta messages (PC3). The plugin must implement the VCDiffDecoder interface.
+// This is required for channels that have delta encoding enabled with params {"delta": "vcdiff"}.
+func WithVCDiffPlugin(plugin VCDiffDecoder) ClientOption {
+ return ably.WithVCDiffPlugin(plugin)
+}
diff --git a/server/pubsub/api_test.go b/server/pubsub/api_test.go
new file mode 100644
index 000000000..dad03e348
--- /dev/null
+++ b/server/pubsub/api_test.go
@@ -0,0 +1,54 @@
+package pubsub_test
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/ably/ably-pubsub-go/server/pubsub"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// TestReExportedAPIIsSelfContained builds both server clients and touches the
+// re-exported API around them, so that a symbol dropped from the
+// surface fails here rather than in a user's build.
+func TestReExportedAPIIsSelfContained(t *testing.T) {
+ http, err := pubsub.NewHTTPClient(
+ pubsub.WithKey("fake:key"),
+ pubsub.WithClientID("me"),
+ pubsub.WithLogLevel(pubsub.LogError),
+ pubsub.WithHTTPRequestTimeout(time.Second),
+ pubsub.WithIdempotentHTTPPublishing(true),
+ )
+ require.NoError(t, err)
+
+ channel := http.Channels.Get("some-channel", pubsub.ChannelWithCipherKey([]byte("0123456789abcdef0123456789abcdef")))
+ assert.Equal(t, "some-channel", channel.Name)
+
+ var (
+ _ *pubsub.HTTPChannels = http.Channels
+ _ *pubsub.HTTPPresence = channel.Presence
+ _ pubsub.HistoryRequest = channel.History(pubsub.HistoryWithLimit(10))
+ _ pubsub.PresenceRequest = channel.Presence.History(pubsub.PresenceHistoryWithLimit(10))
+ _ pubsub.StatsRequest = http.Stats(pubsub.StatsWithUnit(pubsub.PeriodDay))
+ _ pubsub.HTTPRequest = http.Request("GET", "/time", pubsub.RequestWithParams(nil))
+ _ *pubsub.Auth = http.Auth
+ _ func(context.Context) (time.Time, error) = http.Time
+ )
+
+ realtime, err := pubsub.NewRealtimeClient(
+ pubsub.WithKey("fake:key"),
+ pubsub.WithAutoConnect(false),
+ )
+ require.NoError(t, err)
+ t.Cleanup(func() { realtime.Close() })
+
+ assert.Equal(t, pubsub.ConnectionStateInitialized, realtime.Connection.State())
+ assert.Equal(t, pubsub.ChannelStateInitialized, realtime.Channels.Get("some-channel").State())
+
+ _, err = pubsub.NewHTTPClient(pubsub.WithKey("malformed"))
+ var info *pubsub.ErrorInfo
+ require.ErrorAs(t, err, &info)
+ assert.Equal(t, pubsub.ErrInvalidCredential, info.Code)
+}
diff --git a/server/pubsub/reexport_test.go b/server/pubsub/reexport_test.go
new file mode 100644
index 000000000..dbbc8046e
--- /dev/null
+++ b/server/pubsub/reexport_test.go
@@ -0,0 +1,135 @@
+package pubsub_test
+
+import (
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "os"
+ "sort"
+ "strings"
+ "testing"
+)
+
+// The re-exports in server/pubsub/api.go and device/pubsub/api.go are written
+// by hand. These tests fail when internal/ably gains an exported name that an
+// entry point neither re-exports nor lists below, so that leaving a name out
+// is always a recorded decision rather than an oversight.
+
+// notReExported lists the internal/ably names that neither entry point
+// re-exports, with the reason.
+var notReExported = map[string]string{
+ "Realtime": "exposed as RealtimeClient (server) and Client (device)",
+ "NewRealtime": "wrapped by NewRealtimeClient (server) and NewClient (device) to declare the side agent",
+ "WithDial": "its signature names an unexported type, so no caller can supply a usable argument",
+ "ErrChannelReleaseInvalidState": "intentionally internal; callers compare ErrorInfo.Code against 90011",
+}
+
+// deviceOmits lists the names that only an HTTP client can reach. A device
+// client has no HTTP client, so device/pubsub leaves them out.
+var deviceOmits = []string{
+ "ChannelDetails", "ChannelMetrics", "ChannelOccupancy", "ChannelStatus",
+ "GetPresenceOption", "GetPresenceWithClientID", "GetPresenceWithConnectionID", "GetPresenceWithLimit",
+ "HTTPChannel", "HTTPChannels", "HTTPClient", "HTTPPaginatedItems", "HTTPPaginatedResponse",
+ "HTTPPresence", "HTTPRequest", "NewHTTPClient",
+ "PresenceHistoryOption", "PresenceHistoryWithDirection", "PresenceHistoryWithEnd",
+ "PresenceHistoryWithLimit", "PresenceHistoryWithStart",
+ "PresencePaginatedItems", "PresencePaginatedResult", "PresenceRequest",
+ "PublishMultipleOption", "PublishWithConnectionKey", "PublishWithParams",
+ "RequestOption", "RequestWithBody", "RequestWithHeaders", "RequestWithParams",
+}
+
+func TestServerReExportsInternalAPI(t *testing.T) {
+ missing := missingFrom(t, "../../server/pubsub", nil)
+ if len(missing) > 0 {
+ t.Errorf("internal/ably exports names that server/pubsub/api.go does not re-export: %s\n"+
+ "Add them to server/pubsub/api.go, or to notReExported with a reason.", strings.Join(missing, ", "))
+ }
+}
+
+func TestDeviceReExportsInternalAPI(t *testing.T) {
+ omitted := map[string]bool{}
+ for _, name := range deviceOmits {
+ omitted[name] = true
+ }
+ missing := missingFrom(t, "../../device/pubsub", omitted)
+ if len(missing) > 0 {
+ t.Errorf("internal/ably exports names that device/pubsub/api.go does not re-export: %s\n"+
+ "Add them to device/pubsub/api.go, or to deviceOmits if only an HTTP client can reach them.", strings.Join(missing, ", "))
+ }
+
+ device := exportedNames(t, "../../device/pubsub", "", false)
+ for _, name := range deviceOmits {
+ if device[name] {
+ t.Errorf("device/pubsub re-exports %s, which deviceOmits says only an HTTP client can reach", name)
+ }
+ }
+}
+
+// missingFrom returns the internal/ably names, plus the LiveObjects names under
+// their Objects prefix, that the package in dir does not declare and that are
+// neither in notReExported nor in also.
+func missingFrom(t *testing.T, dir string, also map[string]bool) []string {
+ t.Helper()
+ want := exportedNames(t, "../../internal/ably", "", false)
+ for name := range exportedNames(t, "../../internal/ably/objects", "Objects", true) {
+ want[name] = true
+ }
+ have := exportedNames(t, dir, "", false)
+
+ var missing []string
+ for name := range want {
+ if !have[name] && notReExported[name] == "" && !also[name] {
+ missing = append(missing, name)
+ }
+ }
+ sort.Strings(missing)
+ return missing
+}
+
+// exportedNames returns the exported package-level names declared in the
+// non-test files of the package in dir. The prefix and the dropping of
+// underscores give a LiveObjects name its re-exported spelling, so that
+// objects.Operation_MapSet is looked for as ObjectsOperationMapSet.
+func exportedNames(t *testing.T, dir, prefix string, dropUnderscores bool) map[string]bool {
+ t.Helper()
+ pkgs, err := parser.ParseDir(token.NewFileSet(), dir, func(fi os.FileInfo) bool {
+ return !strings.HasSuffix(fi.Name(), "_test.go")
+ }, 0)
+ if err != nil {
+ t.Fatalf("parse %s: %v", dir, err)
+ }
+ names := map[string]bool{}
+ add := func(name string) {
+ if !ast.IsExported(name) {
+ return
+ }
+ if dropUnderscores {
+ name = strings.ReplaceAll(name, "_", "")
+ }
+ names[prefix+name] = true
+ }
+ for _, pkg := range pkgs {
+ for _, file := range pkg.Files {
+ for _, decl := range file.Decls {
+ switch decl := decl.(type) {
+ case *ast.FuncDecl:
+ if decl.Recv == nil {
+ add(decl.Name.Name)
+ }
+ case *ast.GenDecl:
+ for _, spec := range decl.Specs {
+ switch spec := spec.(type) {
+ case *ast.TypeSpec:
+ add(spec.Name.Name)
+ case *ast.ValueSpec:
+ for _, name := range spec.Names {
+ add(name.Name)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return names
+}
diff --git a/server/pubsub/server.go b/server/pubsub/server.go
new file mode 100644
index 000000000..0594314ef
--- /dev/null
+++ b/server/pubsub/server.go
@@ -0,0 +1,41 @@
+// Package pubsub provides the Ably Pub/Sub clients for servers: trusted
+// environments which typically authenticate with an API key and whose
+// connections are exempt from monthly-active-user counting.
+//
+// It is imported from github.com/ably/ably-pubsub-go/server/pubsub. Both entry
+// points are named pubsub, after the product, so that they read the same at
+// the call site and the server segment of the import path alone says which side
+// the code runs on.
+//
+// The rest of the API this package exposes — channels, messages, presence,
+// options, errors — is re-exported from the implementation in internal/ably
+// and lives in api.go.
+package pubsub
+
+import "github.com/ably/ably-pubsub-go/internal/ably"
+
+// agentName declares the side in the Ably-Agent header (RSC7d) so that
+// traffic from clients constructed by this package is classified as
+// server-side.
+const agentName = "ably-pubsub-server"
+
+// HTTPClient is a server Pub/Sub client that operates entirely over HTTP.
+type HTTPClient = ably.HTTPClient
+
+// RealtimeClient is a server Pub/Sub client with a realtime connection.
+type RealtimeClient = ably.Realtime
+
+// NewHTTPClient constructs a server Pub/Sub client that operates entirely
+// over HTTP: publish, history, presence reads, stats, token issuing.
+func NewHTTPClient(opts ...ClientOption) (*HTTPClient, error) {
+ opts = append(opts[:len(opts):len(opts)], ably.WithAgents(map[string]string{agentName: ""}))
+ return ably.NewHTTPClient(opts...)
+}
+
+// NewRealtimeClient constructs a server Pub/Sub client with a persistent
+// realtime connection: everything the HTTP client does, plus subscribing
+// to channels and entering presence.
+func NewRealtimeClient(opts ...ClientOption) (*RealtimeClient, error) {
+ opts = append(opts[:len(opts):len(opts)], ably.WithAgents(map[string]string{agentName: ""}))
+ return ably.NewRealtime(opts...)
+}
diff --git a/server/pubsub/server_test.go b/server/pubsub/server_test.go
new file mode 100644
index 000000000..9c9e82afc
--- /dev/null
+++ b/server/pubsub/server_test.go
@@ -0,0 +1,59 @@
+package pubsub_test
+
+import (
+ "context"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+
+ "github.com/ably/ably-pubsub-go/internal/ably"
+ "github.com/ably/ably-pubsub-go/server/pubsub"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// newAgentRecorder returns a test server which records the Ably-Agent header
+// of each request it receives, along with options pointing a client at it.
+func newAgentRecorder(t *testing.T) (agent *string, opts []ably.ClientOption) {
+ t.Helper()
+ agent = new(string)
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ *agent = r.Header.Get(ably.AblyAgentHeaderName)
+ w.WriteHeader(http.StatusInternalServerError)
+ }))
+ t.Cleanup(ts.Close)
+ u, err := url.Parse(ts.URL)
+ require.NoError(t, err)
+ return agent, []ably.ClientOption{
+ ably.WithEndpoint(u.Host),
+ ably.WithTLS(false),
+ ably.WithUseTokenAuth(true),
+ }
+}
+
+func TestNewHTTPClient_DeclaresServerAgent(t *testing.T) {
+ agent, opts := newAgentRecorder(t)
+
+ client, err := pubsub.NewHTTPClient(opts...)
+ require.NoError(t, err)
+
+ client.Time(context.Background())
+ assert.Equal(t, ably.AgentIdentifier(map[string]string{"ably-pubsub-server": ""}), *agent)
+ // Literal, not derived: the family and side identifiers are what Ably's
+ // traffic classification keys on, so a change to either must fail here.
+ assert.True(t, strings.HasPrefix(*agent, "ably-pubsub-go/"), "wrong SDK family: %q", *agent)
+ assert.True(t, strings.HasSuffix(*agent, " ably-pubsub-server"), "missing side agent: %q", *agent)
+}
+
+func TestNewHTTPClient_MergesUserAgents(t *testing.T) {
+ agent, opts := newAgentRecorder(t)
+
+ client, err := pubsub.NewHTTPClient(append(opts, ably.WithAgents(map[string]string{"foo": "1.2.3"}))...)
+ require.NoError(t, err)
+
+ client.Time(context.Background())
+ // Agents are sorted by product name, so the order is fixed.
+ assert.True(t, strings.HasSuffix(*agent, " ably-pubsub-server foo/1.2.3"), "unexpected agents: %q", *agent)
+}