Documentation
¶
Overview ¶
Package transport implements the cross-platform local IPC between the bus daemon and consume clients. The wire is \n-delimited JSON frames over a Unix domain socket (darwin/linux) or Windows Named Pipe; the abstraction keeps both endpoints (bus side / consume side) and both platforms behind one Listener/Dialer interface.
Frame protocol (see plan §9):
consume → bus hello, bye(reason=client_done), heartbeat, status_req
bus → consume hello_ack, event, source_state, bye(reason=shutdown),
heartbeat, status_resp
Frames are independent JSON objects; no length prefix, no checksum. Maximum single-frame size is enforced by the Reader to bound memory and prevent a buggy/malicious peer from causing OOM (MaxFrameBytes).
Index ¶
- Constants
- Variables
- func Dial(path string) (net.Conn, error)
- type Bye
- type Counters
- type Event
- type FrameType
- type Heartbeat
- type Hello
- type HelloAck
- type HelloRole
- type Listener
- type Reader
- type SourceState
- type StatusBus
- type StatusConsumer
- type StatusReq
- type StatusResp
- type StatusSource
- type Writer
Constants ¶
const MaxFrameBytes = 1 << 20 // 1 MiB
MaxFrameBytes caps a single frame's wire size. 1 MiB accommodates large event payloads (kanban cards, doc snapshots) with headroom while still preventing a buggy/malicious peer from causing OOM via an unbounded line. Callers writing frames larger than this should split their work, not bump the cap — the SDK enforces its own cloud-side cap that is well below this.
Variables ¶
var ErrFrameTooLarge = errors.New("transport: frame exceeds MaxFrameBytes")
ErrFrameTooLarge is returned by Reader.Read when an incoming frame exceeds MaxFrameBytes. The underlying connection is left in an indeterminate state (the rest of the oversized frame is NOT drained) — callers should close the connection on receipt of this error.
Functions ¶
Types ¶
type Bye ¶
Bye is sent by either side at graceful shutdown. Reason is free-form for logs; structured shutdown causes are encoded in the value:
"client_done" — consume reached --max-events/--duration or SIGINT "shutdown" — bus SIGTERM/SIGINT "idle_timeout" — bus IdleTimeout fired with no consumers "stop_request" — bus received explicit Stop RPC
type Counters ¶
Counters is the shared "received / dropped" pair used by per-consumer and per-event-type rollups.
type Event ¶
type Event struct {
Type FrameType `json:"type"`
Seq uint64 `json:"seq"` // per-consumer monotonic, restarts at 1 on reconnect
EventID string `json:"event_id"`
EventBornTime int64 `json:"event_born_time"`
EventCorpID string `json:"event_corp_id,omitempty"`
EventType string `json:"event_type"`
EventUnifiedAppID string `json:"event_unified_app_id,omitempty"`
EventScope string `json:"event_scope,omitempty"`
SubscribeID string `json:"subscribe_id,omitempty"`
SourceID string `json:"source_id,omitempty"`
RuleType string `json:"rule_type,omitempty"`
Data string `json:"data"`
Headers map[string]string `json:"headers,omitempty"`
ReceivedAtUnixMS int64 `json:"received_at_unix_ms"`
}
Event wraps one delivered RawEvent for the wire. We keep the payload as a string (not nested JSON) so the bus does not need to parse / re-encode.
type FrameType ¶
type FrameType string
FrameType discriminates the JSON frames flowing over the IPC channel. Wire values are stable strings — bumping any of these is a protocol breaking change requiring a coordinated bus + consume rollout.
const ( FrameTypeHello FrameType = "hello" // consume → bus FrameTypeHelloAck FrameType = "hello_ack" // bus → consume FrameTypeEvent FrameType = "event" // bus → consume FrameTypeHeartbeat FrameType = "heartbeat" // bidirectional FrameTypeSourceState FrameType = "source_state" // bus → consume FrameTypeBye FrameType = "bye" // bidirectional FrameTypeStatusReq FrameType = "status_req" // consume/ad-hoc → bus FrameTypeStatusResp FrameType = "status_resp" // bus → consume/ad-hoc )
type Heartbeat ¶
type Heartbeat struct {
Type FrameType `json:"type"`
}
Heartbeat is bidirectional and stateless. It exists only to give both endpoints a chance to notice a dead peer via Read failure.
type Hello ¶
type Hello struct {
Type FrameType `json:"type"`
ConsumerPID int `json:"consumer_pid"`
EventTypes []string `json:"event_types,omitempty"` // wildcard list ("im.*", "approval.*"); empty = catch-all
Filter string `json:"filter,omitempty"` // optional regex over event_type
SubscribeID string `json:"subscribe_id,omitempty"` // optional personal subscription isolation key; empty = no subscribe_id filter
Compact bool `json:"compact,omitempty"` // hint to status output; bus does not transform payloads
// Role distinguishes a real consumer (registered for events) from an
// ad-hoc tooling connection (status/list/stop). Ad-hoc connections do
// NOT register with the Hub.
Role HelloRole `json:"role,omitempty"`
}
Hello is the first frame a consumer sends after dialing the bus. The bus uses event_types + filter to do server-side pushdown (plan §1 decision: only events matching this filter are written to this consumer's sendCh).
type HelloAck ¶
type HelloAck struct {
Type FrameType `json:"type"`
BusPID int `json:"bus_pid"`
SourceState string `json:"source_state"` // mirrors source.State string value
StateSource string `json:"state_source"` // "hook" | "inferred"
ClientIDSource string `json:"client_id_source"` // auth.CredentialSource string
ClientSecretSource string `json:"client_secret_source"` // auth.CredentialSource string
IdleTimeoutSecs int `json:"idle_timeout_secs,omitempty"` // bus's IdleTimeout for diagnostics
}
HelloAck is the bus's reply on accepted Hello. SourceState/StateSource mirror the source.Machine snapshot at connect time; CredentialsSource fields tell users which channel actually supplied the credentials in use (env / app_config / keychain / plain_config), see plan "凭证来源拆字段".
type Listener ¶
type Listener interface {
// Accept blocks until a peer connects or the listener is closed.
Accept() (net.Conn, error)
// Close unbinds the endpoint and unblocks pending Accept calls.
Close() error
// Endpoint returns the human-readable address (Unix path or Windows pipe
// name) — only used in log messages and status output.
Endpoint() string
}
Listener is the bus-side accept loop. Implementations bind a Unix socket (darwin/linux) or Windows Named Pipe at the given Endpoint; calling Close removes the underlying socket file on Unix.
func Listen ¶
Listen binds an IPC endpoint at path. On Unix path is a filesystem socket path (caller must ensure the parent directory exists with mode 0700). On Windows path is a Named Pipe name like `\\.\pipe\dws-event-<edition>-<hash>`.
Stale Unix sockets (left behind by a crashed bus) are unlinked automatically before bind. Caller MUST hold the bus.lock before calling Listen so this unlink is race-safe against a still-running bus.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader reads \n-delimited JSON frames from an underlying byte stream. Wrap each connection (one per direction) in a Reader; Reader is not safe for concurrent use.
func NewReader ¶
NewReader returns a Reader with a buffer sized to MaxFrameBytes so a single frame can fit in the buffer without growing.
type SourceState ¶
type SourceState struct {
Type FrameType `json:"type"`
State string `json:"state"` // source.State string
StateSource string `json:"state_source"` // hook | inferred
Attempt int `json:"attempt,omitempty"`
}
SourceState is pushed bus → consume whenever the connection state machine transitions to / from connected. Consumers may render it; v1 they just forward to stderr when not --quiet.
type StatusBus ¶
type StatusBus struct {
PID int `json:"pid"`
UptimeSecs int64 `json:"uptime_secs"`
IdleTimeoutSec int `json:"idle_timeout_secs"`
ClientID string `json:"client_id"`
Edition string `json:"edition"`
SourceKind dwsevent.SourceKind `json:"source_kind,omitempty"`
IdentityHash string `json:"identity_hash,omitempty"`
SourceID string `json:"source_id,omitempty"`
}
StatusBus is the bus daemon's identity / lifecycle view.
type StatusConsumer ¶
type StatusConsumer struct {
PID int `json:"pid"`
EventTypes []string `json:"event_types,omitempty"`
Filter string `json:"filter,omitempty"`
SubscribeID string `json:"subscribe_id,omitempty"`
SubscribedAtMS int64 `json:"subscribed_at_ms"`
Received uint64 `json:"received"`
Dropped uint64 `json:"dropped"`
}
StatusConsumer is one consumer's per-IPC-connection view.
type StatusReq ¶
type StatusReq struct {
Type FrameType `json:"type"`
}
StatusReq is an empty JSON frame ad-hoc tooling sends after Hello to request a full StatusResp. Bus replies with one StatusResp then closes the connection.
type StatusResp ¶
type StatusResp struct {
Type FrameType `json:"type"`
Bus StatusBus `json:"bus"`
SourceState StatusSource `json:"source_state"`
Consumers []StatusConsumer `json:"consumers"`
PerEventTypeCounters map[string]Counters `json:"per_event_type"`
}
StatusResp is the bus's snapshot view for `dws event status` rendering. Counts are accumulated since bus start; per-consumer entries are sorted by PID for deterministic output.
type StatusSource ¶
type StatusSource struct {
State string `json:"state"`
Source string `json:"source"`
LastEventAtMS int64 `json:"last_event_at_ms,omitempty"`
LastReconnectMS int64 `json:"last_reconnect_at_ms,omitempty"`
ReconnectCount int `json:"reconnect_count"`
}
StatusSource is the source.Machine snapshot at status RPC time.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer marshals values to JSON and writes them as \n-terminated frames. Writer is not safe for concurrent use; callers wanting a fan-in writer must serialise externally.