Documentation
¶
Overview ¶
Package realtime is the Go SDK for the Foony Realtime service: the WebSocket Client (a Channels.Get(name) map with per-channel Subscribe / Publish / Presence), the request/response Rest client for backends, CreateJWT for server-side token minting, and the Cipher helpers for end-to-end encryption.
Index ¶
- Constants
- func CreateJWT(key string, params CreateJWTParams) (string, error)
- func GenerateRandomKey(bits int) (string, error)
- func IsCipherEncoding(encoding string) bool
- type Auth
- type BatchChannelResult
- type BatchMessage
- type BatchOptions
- type BatchPublishResult
- type BatchSpec
- type Capability
- type Channel
- func (ch *Channel) Attach(ctx context.Context) error
- func (ch *Channel) Detach(ctx context.Context) error
- func (ch *Channel) Flush()
- func (ch *Channel) History(ctx context.Context, params HistoryParams) (*HistoryResult, error)
- func (ch *Channel) Off()
- func (ch *Channel) On(listener func(ChannelStateChange)) func()
- func (ch *Channel) OnEvent(event ChannelEvent, listener func(ChannelStateChange)) func()
- func (ch *Channel) Once(event ChannelEvent, listener func(ChannelStateChange)) func()
- func (ch *Channel) Publish(ctx context.Context, name string, data any, options ...PublishOption) error
- func (ch *Channel) PublishBatch(ctx context.Context, messages []BatchMessage, options ...PublishOption) error
- func (ch *Channel) State() ChannelState
- func (ch *Channel) Subscribe(listener func(*Message), names ...string) func()
- func (ch *Channel) Unsubscribe()
- type ChannelEvent
- type ChannelOption
- type ChannelState
- type ChannelStateChange
- type Channels
- type Cipher
- type CipherAlgorithm
- type CipherParams
- type Client
- type Connection
- func (c *Connection) ClientID() string
- func (c *Connection) Close()
- func (c *Connection) Connect(ctx context.Context) error
- func (c *Connection) ID() string
- func (c *Connection) Off()
- func (c *Connection) On(listener func(ConnectionStateChange)) func()
- func (c *Connection) OnState(state ConnectionState, listener func(ConnectionStateChange)) func()
- func (c *Connection) Once(state ConnectionState, listener func(ConnectionStateChange)) func()
- func (c *Connection) State() ConnectionState
- type ConnectionState
- type ConnectionStateChange
- type CreateJWTParams
- type EncryptResult
- type HistoryParams
- type HistoryResult
- type Message
- type Options
- type PaginatedResult
- type Presence
- func (p *Presence) Enter(ctx context.Context, data any) error
- func (p *Presence) Leave(ctx context.Context) error
- func (p *Presence) Off()
- func (p *Presence) On(action PresenceAction, listener func(*PresenceEvent)) func()
- func (p *Presence) Once(action PresenceAction, listener func(*PresenceEvent)) func()
- func (p *Presence) Subscribe(listener func(*PresenceEvent)) func()
- func (p *Presence) Update(ctx context.Context, data any) error
- type PresenceAction
- type PresenceEvent
- type PresenceMember
- type PublishOption
- type PublishResult
- type Rest
- type RestAuth
- type RestChannel
- func (ch *RestChannel) History(ctx context.Context, params RestHistoryParams) (*PaginatedResult[RestMessage], error)
- func (ch *RestChannel) Publish(ctx context.Context, name string, data any) (*PublishResult, error)
- func (ch *RestChannel) PublishMessages(ctx context.Context, messages ...RestPublishMessage) (*PublishResult, error)
- type RestChannels
- type RestError
- type RestHistoryParams
- type RestMessage
- type RestOptions
- type RestPresence
- type RestPresenceParams
- type RestPublishMessage
- type ServerError
- type TokenDetails
- type TokenParams
Constants ¶
const ( // CodeBadFrame means a malformed or unparseable frame. CodeBadFrame = 40001 // CodeBadAuth means authentication failed (bad token or key). CodeBadAuth = 40101 // CodeAuthExpired means previously valid auth has expired. Re-authenticate. CodeAuthExpired = 40102 // CodeForbidden means authenticated but not permitted for this channel or action. CodeForbidden = 40300 // CodeCapability means the token's capability does not grant the requested action. CodeCapability = 40301 // CodeChannelDenied means the token's capability does not grant access to this // specific channel. CodeChannelDenied = 40302 // CodeNotFound means a referenced resource (e.g. a channel) does not exist. CodeNotFound = 40400 // CodeRateLimited means too many requests. The publish or connection rate limit was // exceeded. CodeRateLimited = 42900 // CodeServer means an unexpected server-side error. CodeServer = 50000 // CodeBootstrap means the edge could not bootstrap its streams. Retry later. CodeBootstrap = 50001 )
Error codes the server uses on error frames, mirrored one-for-one from the server's wire package (the canonical source). Compare against ServerError.Code.
const DefaultEndpoint = "realtime.foony.io"
DefaultEndpoint is the Foony Realtime endpoint used when Options.Endpoint is empty.
Variables ¶
This section is empty.
Functions ¶
func CreateJWT ¶
func CreateJWT(key string, params CreateJWTParams) (string, error)
CreateJWT signs a JWT locally with key, with no network call. The token's kid header is the public key name ("appSlug.publicKeyId") so the edge can look up the secret to verify it. The payload carries only the subject, capability, and expiry, and no secret. It returns the compact "header.payload.signature" string, and an error when the key is missing or malformed, or when TTL is negative.
// In your token endpoint. The key stays server-side.
token, err := realtime.CreateJWT(os.Getenv("FOONY_API_KEY"), realtime.CreateJWTParams{
ClientID: userID,
Capability: realtime.Capability{"chat:" + userID + ":*": {"subscribe", "publish"}},
})
func GenerateRandomKey ¶
GenerateRandomKey generates a random base64-encoded AES key. Share the returned string between the clients that should be able to read a channel. Never send this to our backend. bits is the key size: 128 or 256 (use 256 unless you have a reason not to).
func IsCipherEncoding ¶
IsCipherEncoding reports whether encoding indicates a ciphered payload that needs a Cipher to read.
Types ¶
type Auth ¶
type Auth struct {
// contains filtered or unexported fields
}
Auth is the token-minting namespace on a Client. It signs with the client's key. See https://foony.io/docs/auth for when to mint tokens yourself.
func (*Auth) CreateJWT ¶
func (a *Auth) CreateJWT(params CreateJWTParams) (string, error)
CreateJWT mints a short-lived JWT scoped to params.Capability, signed with the client's API key. This is local, with no network call. It returns an error when the client was not constructed with a key. Use the package-level CreateJWT to sign with an explicit key.
type BatchChannelResult ¶
type BatchChannelResult struct {
// Channel is the channel this result is for.
Channel string
// Err is set when this channel's publish failed.
Err error
}
BatchChannelResult is one channel's outcome from Client.BatchPublish.
type BatchMessage ¶
type BatchMessage struct {
// Name is the application-level event name.
Name string
// Data is the JSON-serializable payload.
Data any
}
BatchMessage is one message in a batch publish: its own event name and payload.
type BatchOptions ¶
type BatchOptions struct {
// Interval is the minimum gap between batch sends, applied as a throttle. A
// publish is sent right away unless a batch went out within the last Interval, in
// which case it waits until the window is up. Publishes spaced further apart than
// Interval are never batched together and add no latency. Only fast bursts get
// grouped into one batch. Defaults to 10ms when zero.
Interval time.Duration
// MaxMessages flushes early once this many messages are buffered. Defaults to 200
// when zero.
MaxMessages int
}
BatchOptions configures automatic publish batching. Single Publish calls are always auto-batched, buffered and flushed as one batch frame (one stored, dedupable message), which massively raises per-channel throughput for little to no latency cost. Batching is always on. PublishBatch and Client.BatchPublish are never batched further (they assume the caller is managing batching).
type BatchPublishResult ¶
type BatchPublishResult struct {
// SuccessCount is the number of channels published successfully.
SuccessCount int
// FailureCount is the number of channels that failed to publish.
FailureCount int
// Results has one entry per channel. Err is set when that channel's publish
// failed.
Results []BatchChannelResult
}
BatchPublishResult is the per-channel result set from Client.BatchPublish.
type BatchSpec ¶
type BatchSpec struct {
// Channels are the channel names to which Messages will be published.
Channels []string
// Messages are published to every channel in Channels.
Messages []BatchMessage
}
BatchSpec is one batch-publish spec: send Messages to each of Channels.
type Capability ¶
Capability maps a channel pattern to its allowed operations (e.g. {"chat:site:*": {"subscribe", "publish"}}).
type Channel ¶
type Channel struct {
// Name is the channel name this instance is bound to (e.g. "chat:room-1").
Name string
// Presence announces membership on this channel and listens on who comes and goes.
Presence *Presence
// contains filtered or unexported fields
}
Channel is a named channel. Subscribe to receive its messages, publish to send them, and use Channel.Presence to see who is there. Get instances via client.Channels.Get(name). The same name always returns the same instance on a given client.
The channel has two separate listener surfaces: On / Once / Off listen on the channel's lifecycle ChannelState, while Subscribe / Unsubscribe receive application messages.
channel := client.Channels.Get("chat:room-1")
channel.Subscribe(func(message *realtime.Message) {
fmt.Println(message.Name, string(message.Data))
}, "greeting")
err := channel.Publish(ctx, "greeting", map[string]string{"text": "hi"})
func (*Channel) Attach ¶
Attach ensures the server is subscribed to this channel. Subscribe and the presence methods call this implicitly, so calling it yourself is optional. It is useful for surfacing attach errors before the first message arrives. It returns nil once the server confirms the channel subscription, the server's error when the token lacks the subscribe capability (the channel moves to ChannelFailed and is not retried), and the transport error when the request fails in transit (the channel moves to ChannelSuspended and re-attaches on reconnect).
func (*Channel) Detach ¶
Detach detaches from the server: stop receiving messages and presence events. Buffered auto-batched publishes are flushed first. Local listeners are preserved, call Off or Unsubscribe to clear them. It returns nil once the server confirms the detach, and the request's error when it fails, though the channel is marked detached either way.
func (*Channel) Flush ¶
func (ch *Channel) Flush()
Flush sends any buffered (auto-batched) publishes now, as a single batch frame. This runs automatically once the throttle window elapses, when the buffer is full, and on Detach. Call it to force an immediate send. A no-op when nothing is buffered.
func (*Channel) History ¶
func (ch *Channel) History(ctx context.Context, params HistoryParams) (*HistoryResult, error)
History fetches recent messages for this channel, oldest first. History is a one-shot read and does not interleave with the live subscription. How far back it reaches depends on each message's retention, see https://foony.io/docs/history. On a channel with a cipher, messages are decrypted before they are returned. It returns the server's error when history cannot be read, for example a missing history capability.
func (*Channel) On ¶
func (ch *Channel) On(listener func(ChannelStateChange)) func()
On registers a listener for every channel state event and returns its unsubscribe function.
func (*Channel) OnEvent ¶
func (ch *Channel) OnEvent(event ChannelEvent, listener func(ChannelStateChange)) func()
OnEvent registers a listener for one ChannelEvent (a state name or ChannelEventUpdate) and returns its unsubscribe function.
func (*Channel) Once ¶
func (ch *Channel) Once(event ChannelEvent, listener func(ChannelStateChange)) func()
Once registers a listener invoked one time for the next matching event, and returns its unsubscribe function.
func (*Channel) Publish ¶
func (ch *Channel) Publish(ctx context.Context, name string, data any, options ...PublishOption) error
Publish publishes one message to the channel. On a channel with a cipher, data is end-to-end encrypted before it is sent. data is marshaled with json.Marshal (pass a json.RawMessage to send pre-encoded JSON). It returns nil once the server acks the publish. Unless Options.DisableQueueing is set, publishes made while the connection is down are queued locally and sent on reconnect. It returns the server's error when the service refuses the publish (for example a token without the publish capability), and an immediate error when the connection is closing, closed, or failed. With DisableQueueing, any connection state but connected returns an error immediately.
Pass WithEphemeral to send a fire-and-forget message: delivered live to current subscribers but never stored, so it is excluded from history and reconnect replay.
func (*Channel) PublishBatch ¶
func (ch *Channel) PublishBatch(ctx context.Context, messages []BatchMessage, options ...PublishOption) error
PublishBatch publishes a batch of messages in a single frame under one message id. This is an atomic batch. The server stores and dedups it as one durable message, while subscribers receive the members individually. This counts as 1 message for the purposes of usage limits / quotas (message size limits still apply). Pass WithEphemeral to make the whole batch fire-and-forget.
func (*Channel) State ¶
func (ch *Channel) State() ChannelState
State returns the current ChannelState. Listen on changes with Channel.On.
func (*Channel) Subscribe ¶
Subscribe registers a listener for messages on this channel: every message when no names are given, otherwise only messages whose name matches one of names. It implicitly attaches if needed and returns an unsubscribe function. Subscribe itself does not block, so attach failures do not surface here: call Channel.Attach first if you want to observe them.
func (*Channel) Unsubscribe ¶
func (ch *Channel) Unsubscribe()
Unsubscribe removes every message listener on this channel. To remove one listener, call the function Subscribe returned.
type ChannelEvent ¶
type ChannelEvent string
ChannelEvent is an event name channel state listeners can filter on: every ChannelState plus ChannelEventUpdate.
const ChannelEventUpdate ChannelEvent = "update"
ChannelEventUpdate is a change that is not a state transition. The channel stayed in the same state but something was updated, for example the server reported a resume outcome while the channel was already attached. Check Resumed on the payload: false means messages may have been missed beyond retention, so reload state or read history.
type ChannelOption ¶
type ChannelOption func(*channelSettings)
ChannelOption customizes a channel on its first Channels.Get.
func WithBatchOptions ¶
func WithBatchOptions(batch BatchOptions) ChannelOption
WithBatchOptions tunes the channel's auto-batching, overriding the Options.Batch default.
func WithCipher ¶
func WithCipher(cipher *Cipher) ChannelOption
WithCipher enables end-to-end payload encryption on the channel with the given Cipher. This prevents the Foony backend from seeing the plaintext data of messages published to this channel. The cipher key should be kept private and never shared with the public or our backend.
type ChannelState ¶
type ChannelState string
ChannelState is a channel lifecycle state. A healthy channel follows the states in order: initialized -> attaching -> attached -> detaching -> detached -> attaching, and so on. A channel in the failed state is not retried and is not re-attached.
const ( // ChannelInitialized means the channel was created locally and no attach has been // attempted yet. ChannelInitialized ChannelState = "initialized" // ChannelAttaching means an attach was requested and is awaiting server // confirmation. ChannelAttaching ChannelState = "attaching" // ChannelAttached means messages and presence for this channel are flowing. ChannelAttached ChannelState = "attached" // ChannelDetaching means a detach was requested and is awaiting server // confirmation. ChannelDetaching ChannelState = "detaching" // ChannelDetached means no messages or presence are delivered until re-attached. ChannelDetached ChannelState = "detached" // ChannelSuspended means the channel was temporarily lost, for example because the // connection dropped. The SDK re-attaches on reconnect. You can keep publishing: // unless Options.DisableQueueing is set, publishes are queued locally and sent // once reconnected. ChannelSuspended ChannelState = "suspended" // ChannelFailed means the attach failed with an error a retry will not fix, for // example a missing capability, and the SDK will not retry it. Call Attach to try // again manually, for example after obtaining a token with more capabilities. ChannelFailed ChannelState = "failed" )
Channel lifecycle states.
type ChannelStateChange ¶
type ChannelStateChange struct {
// Current is the state the channel is now in.
Current ChannelState
// Previous is the state the channel was in immediately before this event.
Previous ChannelState
// Reason is the error that caused the transition, when the event was error-driven.
Reason error
// Resumed is true when the channel resumed without missing messages (e.g. after a
// reconnect).
Resumed bool
}
ChannelStateChange is the payload delivered to channel state listeners.
type Channels ¶
type Channels struct {
// contains filtered or unexported fields
}
Channels is the channel registry of one Client: Get returns the stable instance for a name, Release removes it.
func (*Channels) Get ¶
func (chans *Channels) Get(name string, options ...ChannelOption) *Channel
Get returns the Channel named name, creating it on first use. The same name always returns the same instance. name is 1 to 255 characters from "A-Z a-z 0-9 : - _" and may not start with a ':'. Colons express hierarchy (e.g. "chat:rooms:42"), dots are not allowed. Get panics on an invalid name: the server's grammar is enforced client-side so a bad name fails loudly here instead of attach-looping against BadFrame rejections.
Options (e.g. WithCipher) apply when the channel is first created. Passing different options to a later Get of the same name returns the existing instance unchanged.
type Cipher ¶
type Cipher struct {
// contains filtered or unexported fields
}
Cipher is an AES-GCM cipher for one channel. It encrypts a JSON-serializable value into an (encoding, data) pair and back.
func NewCipher ¶
func NewCipher(params CipherParams) (*Cipher, error)
NewCipher builds a Cipher from params. It returns an error when the key is not 16 or 32 bytes, or when CipherParams.Algorithm contradicts the key length (a caller asking for AES-256 with a 16-byte key must not silently get AES-128).
func (*Cipher) Decrypt ¶
func (c *Cipher) Decrypt(encoding string, data json.RawMessage) (json.RawMessage, error)
Decrypt decrypts a data payload carried under encoding back to its plaintext JSON. data is the raw JSON value from the wire (a base64 JSON string). It returns an error on a bad key, tampering, or an unsupported encoding.
type CipherAlgorithm ¶
type CipherAlgorithm string
CipherAlgorithm is the algorithm label accepted in CipherParams. The key length picks 128 vs 256.
const ( // AES256GCM is AES-256-GCM (a 32-byte key). AES256GCM CipherAlgorithm = "aes-256-gcm" // AES128GCM is AES-128-GCM (a 16-byte key). AES128GCM CipherAlgorithm = "aes-128-gcm" )
The supported cipher algorithms.
type CipherParams ¶
type CipherParams struct {
// Key is the secret key as raw bytes: 16 (AES-128) or 32 (AES-256). Exactly one of
// Key and KeyBase64 must be set.
Key []byte
// KeyBase64 is the secret key as a base64 string, as produced by
// [GenerateRandomKey]. Exactly one of Key and KeyBase64 must be set.
KeyBase64 string
// Algorithm optionally declares the intended strength. The key length is what
// actually selects AES-128 vs AES-256, and [NewCipher] returns an error when this
// label contradicts it.
Algorithm CipherAlgorithm
}
CipherParams are the parameters for channel encryption. Pass the built Cipher to Channels.Get with WithCipher. The key should be kept private and never shared with the public or our backend.
type Client ¶
type Client struct {
// Connection is the underlying transport. Listen on lifecycle state with
// Connection.On and read it with Connection.State.
Connection *Connection
// Auth is the token-minting namespace. It signs with the client's key. See
// https://foony.io/docs/auth for when to mint tokens yourself.
Auth *Auth
// Channels is the map-like accessor for channels: a stable instance per name.
Channels *Channels
}
Client is the realtime client and the entry point for app code. It owns one WebSocket Connection (opened lazily on first use) and a map of Channel instances retrieved via Channels.Get(name). See https://foony.io/docs/getting-started for the auth options and a full walkthrough.
// Prefer AuthCallback over Key for apps distributed to users.
client, err := realtime.New(realtime.Options{
AuthCallback: func(ctx context.Context) (string, error) {
return fetchTokenFromYourServer(ctx)
},
})
channel := client.Channels.Get("chat:room-1")
channel.Subscribe(func(message *realtime.Message) {
fmt.Println(message.Name, string(message.Data))
})
err = channel.Publish(ctx, "greeting", map[string]string{"text": "hi"})
func New ¶
New builds a Client from options. Exactly one of Options.Key, Options.Token, or Options.AuthCallback must be set, or New returns an error. The connection is opened lazily on first use. Call Client.Connect to open it eagerly.
func (*Client) BatchPublish ¶
BatchPublish publishes messages to many channels in one call. Each BatchSpec sends its Messages to each of its Channels, and all messages to a single channel go as one idempotent batch frame. BatchPublish is publish-only and does not handle end-to-end encryption. If you need that, use Channel.Publish (which is the preferred method of publishing all messages).
A BatchPublish is limited to at most 100 distinct channels per call and at most 1000 messages per channel. It returns an error before sending anything when a limit is exceeded. Otherwise it returns a BatchPublishResult: a channel that fails shows up there as an Err entry rather than failing the call, so one channel failing does not fail the others.
func (*Client) Close ¶
func (c *Client) Close()
Close closes the WebSocket and releases every channel. The connection is closed when it returns. Publishes still awaiting an ack fail with a "connection closed" error.
func (*Client) Connect ¶
Connect eagerly opens the WebSocket and completes the auth handshake. Calling this is optional: channels connect and attach lazily on first use. Connect is idempotent, and concurrent calls wait on the same in-flight attempt. It returns nil once the connection is connected, and the handshake error when auth fails (for example a bad key, or an expired static Token with no AuthCallback to re-mint one).
type Connection ¶
type Connection struct {
// contains filtered or unexported fields
}
Connection is the transport layer. One Client owns one Connection and all of its channels share it. Listen on lifecycle changes with Connection.On, which delivers every ConnectionState transition.
func (*Connection) ClientID ¶
func (c *Connection) ClientID() string
ClientID returns the client id this connection is authenticated as, or "" before the auth handshake completes. Never "" once connected: the server resolves it from the JWT's subject (Token and AuthCallback auth), from Options.ClientID (key auth), or assigns one when neither names a client.
func (*Connection) Close ¶
func (c *Connection) Close()
Close closes the WebSocket and releases resources. The connection is closed when it returns. Requests and publishes still awaiting an ack are rejected with a "connection closed" error.
func (*Connection) Connect ¶
func (c *Connection) Connect(ctx context.Context) error
Connect opens the WebSocket and completes the auth handshake. Connect is idempotent, and concurrent calls wait on the same in-flight attempt (ctx cancels the caller's wait, not the shared attempt). It returns nil once the connection is connected, and the handshake error when auth fails, for example a bad key or an expired static Token with no AuthCallback to re-mint one.
func (*Connection) ID ¶
func (c *Connection) ID() string
ID returns the server-issued connection id, or "" before the auth handshake completes.
func (*Connection) On ¶
func (c *Connection) On(listener func(ConnectionStateChange)) func()
On registers a listener for every connection state change and returns its unsubscribe function.
func (*Connection) OnState ¶
func (c *Connection) OnState(state ConnectionState, listener func(ConnectionStateChange)) func()
OnState registers a listener for changes into one state and returns its unsubscribe function.
func (*Connection) Once ¶
func (c *Connection) Once(state ConnectionState, listener func(ConnectionStateChange)) func()
Once registers a listener invoked one time for the next change into state, and returns its unsubscribe function.
func (*Connection) State ¶
func (c *Connection) State() ConnectionState
State returns the current ConnectionState. Listen on changes with Connection.On.
type ConnectionState ¶
type ConnectionState string
ConnectionState is a connection lifecycle state. Observe transitions with Connection.On.
const ( // ConnectionInitialized means the connection was created locally and no connect has // been attempted yet. ConnectionInitialized ConnectionState = "initialized" // ConnectionConnecting means the WebSocket is opening and the auth handshake is in // flight. Publishes made now are queued unless Options.DisableQueueing is set. ConnectionConnecting ConnectionState = "connecting" // ConnectionConnected means connected and authenticated. Messages flow, and // [Connection.ID] and [Connection.ClientID] are populated. ConnectionConnected ConnectionState = "connected" // ConnectionDisconnected means the connection dropped unexpectedly. The state // change's Reason says why. Unless Options.DisableAutoReconnect is set, the SDK // retries with exponential backoff, starting at Options.InitialReconnectDelay // (1 second) and doubling up to Options.MaxReconnectDelay (30 seconds). You can // keep publishing: unless Options.DisableQueueing is set, publishes queue locally // and are sent on reconnect, and channels re-attach and replay the messages they // missed (within retention). ConnectionDisconnected ConnectionState = "disconnected" // ConnectionClosing means Close was called and the socket is shutting down. ConnectionClosing ConnectionState = "closing" // ConnectionClosed means the connection was closed by Close. Publishes that were // awaiting an ack have been rejected. ConnectionClosed ConnectionState = "closed" // ConnectionFailed means a failure the SDK will not retry on its own, for example a // bad or expired credential with no AuthCallback to re-mint one. The state change's // Reason carries the error. An explicit Connect starts a fresh attempt. ConnectionFailed ConnectionState = "failed" )
Connection lifecycle states.
type ConnectionStateChange ¶
type ConnectionStateChange struct {
// Current is the state the connection is now in.
Current ConnectionState
// Reason is the error that caused the transition, when the event was error-driven.
Reason error
}
ConnectionStateChange is the payload delivered to connection state listeners.
type CreateJWTParams ¶
type CreateJWTParams struct {
// Capability is the capability the token grants (e.g.
// {"chat:site:*": {"subscribe"}}). Must be a subset of the signing key's own
// capability or the edge rejects it on connect. Exactly one of Capability and
// CapabilityJSON must be set.
Capability Capability
// CapabilityJSON is the capability as a pre-serialized JSON string, as an
// alternative to Capability.
CapabilityJSON string
// ClientID identifies the end user the token represents. Echoed back as the
// connection's client id.
ClientID string
// TTL is the token lifetime. Defaults to one hour when zero, short enough to bound
// a leaked token.
TTL time.Duration
}
CreateJWTParams describe the token to mint.
type EncryptResult ¶
type EncryptResult struct {
// Encoding is the transport encoding describing Data, e.g.
// "cipher+aes-256-gcm/base64".
Encoding string
// Data is the base64 of iv + ciphertext + tag.
Data string
}
EncryptResult is the output of Cipher.Encrypt: a transport encoding and the encrypted data.
type HistoryParams ¶
type HistoryParams struct {
// Limit caps how many messages are returned. The server applies its own cap and a
// default when zero.
Limit int
// Before, a message serial (see [Message.Serial]), pages backward: only messages
// with a serial strictly below it are returned. Zero means start from the newest.
Before uint64
}
HistoryParams are the query params for Channel.History.
type HistoryResult ¶
type HistoryResult struct {
// Messages are the matching messages, ordered oldest-first.
Messages []*Message
// More is true when older messages remain beyond this page. Pass the oldest
// message's Serial as [HistoryParams.Before] to fetch them.
More bool
}
HistoryResult is one page of channel history.
type Message ¶
type Message struct {
// Channel is the channel the message was published to.
Channel string
// Name is the application-level event name the message was published under.
Name string
// Data is the message payload as published (decrypted on a channel with a cipher).
// Unmarshal it into your own type with json.Unmarshal.
Data json.RawMessage
// Timestamp is the server publish time in milliseconds since the Unix epoch.
Timestamp int64
// ID is the unique message id, for dedup and idempotent publishing. Batch members
// share their batch's id with a ":<index>" suffix.
ID string
// ClientID is the client id of the publisher, when known.
ClientID string
// Encoding says how Data is encoded (e.g. "cipher+aes-256-gcm/base64" when a
// message could not be decrypted). Empty for plain JSON.
Encoding string
// Serial is the contiguous per-channel serial (0 for ephemeral/unsequenced
// messages). The SDK uses it to detect gaps, as the resume cursor, and it is
// history's Before cursor to page backward from this message.
Serial uint64
// Ephemeral marks a fire-and-forget message: not stored in history and not
// replayed on resume.
Ephemeral bool
}
Message is one message delivered to subscribers or returned from history.
type Options ¶
type Options struct {
// Endpoint is the Realtime edge host or an absolute ws(s) URL. Defaults to
// "realtime.foony.io", which resolves to wss://realtime.foony.io.
Endpoint string
// Key is a Realtime API key in "appSlug.publicKeyId:privateKey" form. The key is a
// long-lived secret, so use it only in server-side code and trusted quick starts.
// Never ship it in client-side code distributed to users: those should use
// short-lived JWTs from AuthCallback.
Key string
// ClientID is the client id to attach when authenticating with Key. With token
// auth the client id comes from the JWT's subject instead, and this option is not
// sent.
ClientID string
// Token is a static JWT to send in the auth handshake. Mutually exclusive with
// AuthCallback. Useful for local dev and short scripts. A static token is never
// renewed: once it expires, the connection ends in the terminal
// [ConnectionFailed] state, so use AuthCallback for anything long-running.
Token string
// AuthCallback returns a fresh JWT. Called once on connect and again on every
// reconnect. This is the recommended auth method for anything long-running,
// because the SDK can renew the token whenever it needs one. See the auth docs:
// https://foony.io/docs/auth
AuthCallback func(ctx context.Context) (string, error)
// DisableAutoReconnect stops the SDK from reconnecting after unexpected
// disconnects. If false (the default), the SDK reconnects with exponential
// backoff. If true, a dropped connection stays down until you call Connect again.
// An auth error that cannot be recovered (a bad or expired static Token, or a bad
// Key, with no AuthCallback to re-mint a credential) still ends in the terminal
// [ConnectionFailed] state rather than retrying.
DisableAutoReconnect bool
// InitialReconnectDelay is the initial backoff for reconnects. The delay doubles
// each attempt up to MaxReconnectDelay. Defaults to 1 second when zero.
InitialReconnectDelay time.Duration
// MaxReconnectDelay caps the reconnect backoff. Defaults to 30 seconds when zero.
MaxReconnectDelay time.Duration
// DisableQueueing rejects publishes made while the connection is establishing or
// temporarily down. If false (the default), those publishes are queued locally and
// flushed on (re)connect. If true, publishing while not connected returns an error
// immediately.
DisableQueueing bool
// Batch configures the always-on auto-batching applied to every channel,
// overridable per channel with [WithBatchOptions]. Defaults are documented on
// [BatchOptions].
Batch *BatchOptions
}
Options configures a Client. Exactly one of Key, Token, or AuthCallback must be set, or New returns an error.
type PaginatedResult ¶
type PaginatedResult[T any] struct { // Items are the items on this page. Items []T // contains filtered or unexported fields }
PaginatedResult is one page of a paginated response. Items is the current page, and Next fetches the following page (older messages for a newest-first history).
func (*PaginatedResult[T]) HasNext ¶
func (p *PaginatedResult[T]) HasNext() bool
HasNext reports whether another page exists.
func (*PaginatedResult[T]) IsLast ¶
func (p *PaginatedResult[T]) IsLast() bool
IsLast reports whether this is the final page.
func (*PaginatedResult[T]) Next ¶
func (p *PaginatedResult[T]) Next(ctx context.Context) (*PaginatedResult[T], error)
Next fetches the next page. It returns nil, nil when this is the last one, and a RestError when the fetch fails.
type Presence ¶
type Presence struct {
// contains filtered or unexported fields
}
Presence is the presence surface for one channel. Announce this connection with Presence.Enter / Presence.Update / Presence.Leave, and listen on who comes and goes with Presence.On. Reached via Channel.Presence. See https://foony.io/docs/presence for the full model.
channel.Presence.On(realtime.PresenceEnter, func(member *realtime.PresenceEvent) {
fmt.Println(member.ClientID, "joined")
})
err := channel.Presence.Enter(ctx, map[string]string{"status": "online"})
func (*Presence) Enter ¶
Enter announces this connection as present on the channel, with optional data (a display name, a status) shown to other members. Pass nil for no data. It implicitly attaches the channel. The membership is remembered, and the SDK re-enters it automatically after a reconnect. It returns nil once the server acks the entry, and the server's error when the token lacks the presence capability.
func (*Presence) Leave ¶
Leave removes this connection's presence entry and stops the automatic re-entry on reconnect. It returns nil once the server acks the leave, and the request's error when it fails.
func (*Presence) Off ¶
func (p *Presence) Off()
Off removes every presence listener, dropping the server-side watcher.
func (*Presence) On ¶
func (p *Presence) On(action PresenceAction, listener func(*PresenceEvent)) func()
On registers a listener for presence events with a matching action and returns its unsubscribe function. Like Presence.Subscribe, the first listener opens the server-side presence watcher.
func (*Presence) Once ¶
func (p *Presence) Once(action PresenceAction, listener func(*PresenceEvent)) func()
Once registers a listener invoked one time for the next presence event with a matching action, and returns its unsubscribe function.
func (*Presence) Subscribe ¶
func (p *Presence) Subscribe(listener func(*PresenceEvent)) func()
Subscribe registers a listener for every presence event and returns its unsubscribe function. Adding the first listener asks the server for presence on this channel: an initial member snapshot, then live transitions. This is independent of a message Subscribe, so a channel used only for messages never opens a presence watcher, and the watcher is dropped again when the last presence listener is removed.
type PresenceAction ¶
type PresenceAction string
PresenceAction is a presence transition: a member entered, left, or updated its data.
const ( // PresenceEnter means a member announced itself present on the channel. PresenceEnter PresenceAction = "enter" // PresenceLeave means a member's presence entry was removed. PresenceLeave PresenceAction = "leave" // PresenceUpdate means a member replaced the data on its presence entry. PresenceUpdate PresenceAction = "update" )
The recognized presence transition values.
type PresenceEvent ¶
type PresenceEvent struct {
// Channel is the channel the presence transition occurred on.
Channel string
// Action says which transition occurred (enter, leave, or update).
Action PresenceAction
// ClientID is the client id of the member whose presence changed.
ClientID string
// ConnectionID is the connection id of the member whose presence changed.
ConnectionID string
// Data is the presence payload supplied on enter/update, if any (decrypted on a
// channel with a cipher).
Data json.RawMessage
// Encoding says how Data is encoded when it could not be decrypted. Empty for
// plain JSON.
Encoding string
// Timestamp is the transition time in milliseconds since the Unix epoch.
Timestamp int64
}
PresenceEvent is one presence transition delivered to presence listeners.
type PresenceMember ¶
type PresenceMember struct {
// ClientID is the member's clientId.
ClientID string `json:"clientId"`
// ConnectionID is the member's connection id (one clientId may hold several).
ConnectionID string `json:"connectionId"`
// Action is always "present" in a snapshot.
Action string `json:"action"`
// Data is the presence payload (decrypted when the channel has a cipher).
Data json.RawMessage `json:"data"`
// Encoding is the remaining payload encoding when the data could not be decoded.
Encoding string `json:"encoding"`
// Timestamp is when the member last entered or updated, in ms since the Unix
// epoch.
Timestamp int64 `json:"timestamp"`
}
PresenceMember is one current member returned from RestPresence.Get.
type PublishOption ¶
type PublishOption func(*publishSettings)
PublishOption customizes one publish call.
func WithEphemeral ¶
func WithEphemeral() PublishOption
WithEphemeral marks the publish fire-and-forget: delivered live to current subscribers but never stored, so it is excluded from history and reconnect replay. For transient events on a channel that otherwise persists.
type PublishResult ¶
type PublishResult struct {
// MessageID is the server-assigned (or echoed) message id. A batch publish shares
// one id.
MessageID string `json:"messageId"`
// Serial is the contiguous per-channel serial for durable publishes. Zero for
// ephemeral ones.
Serial uint64 `json:"serial"`
}
PublishResult is the result of a successful REST publish.
type Rest ¶
type Rest struct {
// Auth mints tokens against the service, authenticated by this client's key.
Auth *RestAuth
// Channels is the map-like accessor for channels: a stable instance per name.
Channels *RestChannels
// contains filtered or unexported fields
}
Rest is the REST client. Construct it with NewRest and use Channels.Get(name) for publish, history, and presence reads. Use the WebSocket Client instead when you need to receive live messages.
// Server-side: an API key is the simplest auth method here.
rest, err := realtime.NewRest(realtime.RestOptions{Key: os.Getenv("REALTIME_API_KEY")})
channel := rest.Channels.Get("chat:room-1")
_, err = channel.Publish(ctx, "greeting", map[string]string{"text": "hi"})
page, err := channel.History(ctx, realtime.RestHistoryParams{Limit: 10})
fmt.Println(page.Items)
type RestAuth ¶
type RestAuth struct {
// contains filtered or unexported fields
}
RestAuth mints tokens against the service, from a Rest client's Auth field.
func (*RestAuth) RequestToken ¶
func (a *RestAuth) RequestToken(ctx context.Context, params TokenParams) (*TokenDetails, error)
RequestToken asks the service to mint a client JWT from this client's API key. The granted capability must be a subset of the key's own. It returns the TokenDetails, whose Expires lets callers cache the token. It returns an error when this client has no Key, and a RestError when the service refuses, for example a capability outside the key's grant. See https://foony.io/docs/auth for the full token flow.
type RestChannel ¶
type RestChannel struct {
// Name is the channel name this instance is bound to.
Name string
// Presence reads this channel's current members.
Presence *RestPresence
// contains filtered or unexported fields
}
RestChannel is a channel handle for REST operations: publish, history, and presence. Obtained from Channels.Get(name). It holds no server-side state.
func (*RestChannel) History ¶
func (ch *RestChannel) History(ctx context.Context, params RestHistoryParams) (*PaginatedResult[RestMessage], error)
History reads the channel's message history, newest first by default. Batch publishes come back as one item per message, sharing the batch's id and serial. On a channel with a cipher, messages are decrypted before they are returned. How far back history reaches depends on each message's retention, see https://foony.io/docs/history. It returns one page. Page through older messages with the result's Next. It returns a RestError when history cannot be read.
func (*RestChannel) Publish ¶
func (ch *RestChannel) Publish(ctx context.Context, name string, data any) (*PublishResult, error)
Publish publishes one message from an event name plus payload. On a channel with a cipher, the payload is end-to-end encrypted before it is sent. It returns the PublishResult once the service has accepted the message durably, and a RestError when the request fails, for example a key without the publish capability.
func (*RestChannel) PublishMessages ¶
func (ch *RestChannel) PublishMessages(ctx context.Context, messages ...RestPublishMessage) (*PublishResult, error)
PublishMessages publishes one or more RestPublishMessage values, which can also set ClientID, ID, or Ephemeral. Several messages are stored and delivered as one atomic batch under one id.
type RestChannels ¶
type RestChannels struct {
// contains filtered or unexported fields
}
RestChannels is the channel registry of one Rest client.
func (*RestChannels) Get ¶
func (chans *RestChannels) Get(name string, options ...ChannelOption) *RestChannel
Get returns the RestChannel named name, creating it on first use. Options (e.g. WithCipher) apply when the channel is first created.
func (*RestChannels) Release ¶
func (chans *RestChannels) Release(name string)
Release removes the channel instance for name. A no-op when it doesn't exist.
type RestError ¶
type RestError struct {
// Code is the machine-readable protocol code (the same table as the Code*
// constants).
Code int
// StatusCode is the HTTP status of the response.
StatusCode int
// Message is a human-readable error description.
Message string
}
RestError is the error for any non-2xx REST response. Check it with errors.As.
type RestHistoryParams ¶
type RestHistoryParams struct {
// Limit is the page size. The service default is 100 when zero.
Limit int
// Before is an exclusive serial cursor: return only messages with a serial
// strictly below it. Pass the oldest Serial you already have (see
// [RestMessage.Serial]) to page backward.
Before uint64
// Direction is "backwards" (newest first, the default) or "forwards" (oldest
// first).
Direction string
}
RestHistoryParams are the query params for RestChannel.History.
type RestMessage ¶
type RestMessage struct {
// ID is the message id. Batch members share their batch's id.
ID string `json:"id"`
// Name is the application-level event name.
Name string `json:"name"`
// Data is the payload (decrypted when the channel has a cipher).
Data json.RawMessage `json:"data"`
// Timestamp is the publish time, in ms since the Unix epoch.
Timestamp int64 `json:"timestamp"`
// ClientID is the publisher's clientId.
ClientID string `json:"clientId"`
// Encoding is the remaining payload encoding, e.g. a cipher tag when no cipher is
// configured.
Encoding string `json:"encoding"`
// Serial is the contiguous per-channel serial (zero for unsequenced messages).
Serial uint64 `json:"serial"`
}
RestMessage is one message returned from RestChannel.History.
type RestOptions ¶
type RestOptions struct {
// Endpoint is the service host or an absolute http(s) URL. Defaults to
// "realtime.foony.io", which resolves to https://realtime.foony.io.
Endpoint string
// Key is a Realtime API key in "appSlug.publicKeyId:privateKey" form. This is the
// preferred (and simplest) auth method for server-side callers. The key is a
// long-lived secret, so keep it server-side.
Key string
// Token is a static JWT, sent as a Bearer token. Mutually exclusive with
// AuthCallback.
Token string
// AuthCallback returns a fresh JWT. Called before the first request and again
// whenever the service reports the current token expired.
AuthCallback func(ctx context.Context) (string, error)
// ClientID is the default clientId stamped on published messages that don't set
// their own. Only useful with key auth, to attribute a backend's publishes to a
// user. When omitted, the service attributes each publish to the authenticated
// identity, so token-auth callers never need to set this: the token's clientId
// applies automatically, and a differing value is rejected.
ClientID string
// HTTPClient overrides the HTTP client. Mostly useful in tests. Defaults to
// http.DefaultClient.
HTTPClient *http.Client
}
RestOptions configures a Rest client. One of Key, Token, or AuthCallback is required.
type RestPresence ¶
type RestPresence struct {
// contains filtered or unexported fields
}
RestPresence reads presence for one channel, from the channel's Presence field.
func (*RestPresence) Get ¶
func (p *RestPresence) Get(ctx context.Context, params RestPresenceParams) (*PaginatedResult[PresenceMember], error)
Get fetches the channel's current members. The snapshot is complete (presence sets are bounded), so the result is a single page. Members' Data is decrypted when the channel has a cipher. It returns a RestError when the request fails.
type RestPresenceParams ¶
type RestPresenceParams struct {
// Limit caps the number of members returned.
Limit int
// ClientID returns only members with this clientId.
ClientID string
// ConnectionID returns only the member on this connection.
ConnectionID string
}
RestPresenceParams are the query params for RestPresence.Get.
type RestPublishMessage ¶
type RestPublishMessage struct {
// Name is the application-level event name.
Name string `json:"name"`
// Data is the JSON-serializable payload.
Data any `json:"data"`
// ClientID attributes the message to a clientId. Only useful with key auth, which
// may name any user. Token auth needs no value here (the token's clientId applies
// automatically) and anything else is rejected.
ClientID string `json:"clientId,omitempty"`
// ID is a stable id reused across resends so the server can drop duplicates of
// the same publish. Single-message publishes only.
ID string `json:"id,omitempty"`
// Ephemeral marks the message fire-and-forget: delivered live but excluded from
// history and resume.
Ephemeral bool `json:"ephemeral,omitempty"`
}
RestPublishMessage is one message to publish over REST.
type ServerError ¶
type ServerError struct {
// Code is the machine-readable error code. See the Code* constants.
Code int
// Message is a human-readable error description for logging and debugging.
Message string
}
ServerError is a protocol or authorization error the service returned for a request. Check the machine-readable ServerError.Code against the Code* constants with errors.As:
var serverErr *realtime.ServerError
if errors.As(err, &serverErr) && serverErr.Code == realtime.CodeCapability {
// the token does not grant this action
}
func (*ServerError) Error ¶
func (e *ServerError) Error() string
Error formats the error as "server error <code>: <message>", matching the realtime-js SDK's error strings.
type TokenDetails ¶
type TokenDetails struct {
// Token is the signed JWT to authenticate WebSocket or REST calls with.
Token string `json:"token"`
// KeyName is the name of the key that requested it, "appSlug.publicKeyId".
KeyName string `json:"keyName"`
// Issued is the issue time, in ms since the Unix epoch.
Issued int64 `json:"issued"`
// Expires is the expiry time, in ms since the Unix epoch.
Expires int64 `json:"expires"`
// ClientID is the clientId the token authenticates as.
ClientID string `json:"clientId"`
// Capability is the granted capability as a JSON string.
Capability string `json:"capability"`
}
TokenDetails is a service-issued token plus the metadata needed to cache it until expiry.
type TokenParams ¶
type TokenParams struct {
// ClientID is the clientId the token authenticates as. Required.
ClientID string
// TTL is the token lifetime. Defaults to one hour when zero, and the service caps
// it at 24 hours.
TTL time.Duration
// Capability is the capability to grant. It must be a subset of the key's own
// capability, which is also the default when nil.
Capability Capability
}
TokenParams are the params for RestAuth.RequestToken.