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 @@ ![Ably Pub/Sub Go Header](/image/goSDK-github.png) -[![Go Reference](https://pkg.go.dev/badge/github.com/ably/ably-go/ably.svg)](https://pkg.go.dev/github.com/ably/ably-go/ably) +[![Go Reference](https://pkg.go.dev/badge/github.com/ably/ably-pubsub-go/server/pubsub.svg)](https://pkg.go.dev/github.com/ably/ably-pubsub-go/server/pubsub) [![License](https://badgen.net/github/license/ably/ably-go)](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) +}