Documentation
¶
Overview ¶
Package session is the client-side engine: it dials the relay, runs the create/join handshake, performs the PAKE + group-key exchange, and moves encrypted service envelopes. It compiles natively (tests, headless tools) and to WASM (the browser core) — no syscall/js here, ever.
Index ¶
- Constants
- type Client
- func (c *Client) BecomeHost()
- func (c *Client) Broadcast(serviceID string, body []byte) error
- func (c *Client) ClaimHost() error
- func (c *Client) Close() error
- func (c *Client) CloseNow() error
- func (c *Client) Events() <-chan Event
- func (c *Client) HostID() wire.ParticipantID
- func (c *Client) Reconnect(ctx context.Context) error
- func (c *Client) RekeyWithNewHost() error
- func (c *Client) Role() Role
- func (c *Client) RotateForMigration()
- func (c *Client) Self() wire.ParticipantID
- func (c *Client) SendTo(to wire.ParticipantID, serviceID string, body []byte) error
- func (c *Client) SetHostID(id wire.ParticipantID)
- type Closed
- type Event
- type Frame
- type MemberJoined
- type MemberKeyed
- type MemberLeft
- type Option
- type Role
- type RolePolicy
Constants ¶
const DefaultProtocol = "parley/v1"
DefaultProtocol is the domain-separation label used when no WithProtocol option is given. Applications with their own deployed protocol pass their label instead; the label feeds both session-ID derivation and the PAKE key schedule, so all ends of a session must agree on it.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is one end of a live session.
func Host ¶
Host creates a new session on the relay and returns a keyed client plus the freshly generated code phrase. Joiner roles are assigned by the configured RolePolicy (see WithRolePolicy); the default seats observers as RoleObserver and everyone else as RoleMember.
func Join ¶
Join connects to an existing session with its phrase. It returns once the handshake completes and the client is keyed; a wrong phrase surfaces as crypto.ErrUnwrap. A joiner that wants to watch rather than participate passes WithObserver.
func (*Client) BecomeHost ¶
func (c *Client) BecomeHost()
BecomeHost promotes this client to session host in place after a host migration: role→RoleHost, hostID→self. The group key and keyed flag are untouched — the successor already holds the key and wraps that same key to future joiners (no re-key). Called on the routing goroutine during promotion.
func (*Client) ClaimHost ¶
ClaimHost tells the relay this client is the new host, so it routes future joiners' handshake here. Safe from any goroutine (writeFrame holds writeMu).
func (*Client) Close ¶
Close tears the connection down gracefully (a normal-closure "bye"); the relay treats this as leaving for good — no grace, no reconnect.
func (*Client) CloseNow ¶
CloseNow drops the connection abruptly, with no close handshake — as a real network loss does. The relay classifies this as unexpected and holds the slot for its grace window, so a subsequent Reconnect can reclaim it. The read loop emits Closed("connection lost") and exits.
func (*Client) HostID ¶
func (c *Client) HostID() wire.ParticipantID
HostID returns the session host's participant ID. Locked because host migration can reassign it from another goroutine (see SetHostID/BecomeHost).
func (*Client) Reconnect ¶
Reconnect re-dials the relay and reclaims this client's slot after an unexpected drop, preserving the participant id, role, group key, and per- service send sequence — so peers see an uninterrupted sender and no re-key is needed. It returns once keyed traffic can flow again; the caller then rebinds its routing layer onto the client. A rejected reclaim (grace expired, relay restarted, bad token) returns an error the caller should treat as terminal.
Precondition: the previous readLoop has ended (the events channel closed), which is exactly the state after a Closed event — so no goroutine is touching the client when Reconnect runs.
func (*Client) RekeyWithNewHost ¶
RekeyWithNewHost is called on a surviving non-host member after a host migration: the promoted host shares no pairwise key with it, so it re-runs the PAKE (over the phrase everyone still holds) to establish one; the host replies (KindRekeyPake2) and then delivers the rotated group key (KindRekey). No-op if we are the host or not keyed.
func (*Client) RotateForMigration ¶
func (c *Client) RotateForMigration()
RotateForMigration is called on the promoted host immediately after BecomeHost: it mints a fresh group key the departed host does NOT hold, so once survivors re-fetch it (see RekeyWithNewHost) the departed host is locked out of subsequent traffic. The old key is retained in the ring so in-flight frames still open, and the pairwise map is reset (a promoted host holds none) to refill as survivors re-PAKE. Runs on the routing goroutine, before any survivor's re-PAKE can traverse the network, so the fresh key is in place when the re-PAKE responder wraps it.
func (*Client) Self ¶
func (c *Client) Self() wire.ParticipantID
Self returns this client's participant ID (valid after construction).
func (*Client) SetHostID ¶
func (c *Client) SetHostID(id wire.ParticipantID)
SetHostID re-points a non-host survivor at the newly elected host so its ctl accepts the new host's announces and its services address the right authority.
type Event ¶
type Event any
Event is anything the session surfaces to the layer above (a service mux or UI bridge): MemberJoined, MemberKeyed, MemberLeft, Frame, Closed.
type Frame ¶
type Frame struct {
From wire.ParticipantID
Envelope wire.Envelope
}
Frame is one decrypted service envelope from a peer.
type MemberJoined ¶
type MemberJoined struct{ ID wire.ParticipantID }
MemberJoined fires when the relay announces a new participant. For the host it fires before that member is keyed.
type MemberKeyed ¶
type MemberKeyed struct {
ID wire.ParticipantID
Role Role
}
MemberKeyed fires on the host once a joiner completes the handshake.
type MemberLeft ¶
type MemberLeft struct{ ID wire.ParticipantID }
MemberLeft fires when a participant disconnects.
type Option ¶
type Option func(*config)
Option configures Host and Join.
func WithObserver ¶ added in v0.2.0
func WithObserver() Option
WithObserver marks a Join as an observer: the joiner asks the host to seat it with an observer role rather than a member seat. The intent rides the first handshake flight; the host's RolePolicy decides what to do with it. No effect on Host.
func WithProtocol ¶
WithProtocol sets the application's domain-separation label (for example "myapp/v1"). Changing an application's label is a protocol version bump: clients with different labels derive different session IDs and keys and cannot talk to each other.
func WithRolePolicy ¶ added in v0.2.0
func WithRolePolicy(p RolePolicy) Option
WithRolePolicy installs the policy used to assign roles to joiners when this end is the session host. Pass it to Join as well as Host: after a host migration the promoted joiner becomes the role assigner.
type Role ¶
type Role uint8
Role is assigned by the host when it wraps the group key for a joiner. The zero value means "not keyed yet". The byte value rides inside the encrypted handshake, so it is application protocol: every end and every version of an application must agree on the numbering. RoleNone and RoleHost are reserved by the library; values 2..255 belong to the application's RolePolicy (RoleMember and RoleObserver are the built-in default vocabulary).
func DefaultRolePolicy ¶ added in v0.2.0
func DefaultRolePolicy(_ wire.ParticipantID, observer bool, _ map[wire.ParticipantID]Role) Role
DefaultRolePolicy seats observers as RoleObserver and everyone else as RoleMember.
type RolePolicy ¶ added in v0.2.0
type RolePolicy func(joiner wire.ParticipantID, observer bool, assigned map[wire.ParticipantID]Role) Role
RolePolicy decides the role for a newly keyed joiner. The host calls it once per completed handshake, on the session's read goroutine, with the joiner's participant ID, whether the joiner asked to observe (see WithObserver), and a snapshot of the roles this host has already assigned to its joiners (the host itself is not in the map). Implementations must be fast, must not block, and must not call back into the Client — a stalled policy stalls the whole session end. RoleNone and RoleHost are reserved: a policy returning either is clamped to RoleMember. Any other value (2..255) is delivered verbatim, so applications may define their own role vocabulary on top of Role.