package pluginsdk import ( "context" "encoding/json" "fmt" "time" ) // Host makes calls to Cletus on behalf of the request being handled. Use // it only inside the handler it was passed to. type Host struct { call func(ctx context.Context, method string, params any) (json.RawMessage, error) } // NewTestHost returns a Host whose calls are answered by fn, for unit tests // of handlers. fn receives the method and the params as JSON. func NewTestHost(fn func(method string, params json.RawMessage) (any, error)) *Host { return &Host{call: func(_ context.Context, method string, params any) (json.RawMessage, error) { raw, err := json.Marshal(params) if err != nil { return nil, err } result, err := fn(method, raw) if err != nil { return nil, err } return json.Marshal(result) }} } // Call makes any host call in docs/host-api.md and decodes its result into // out (which may be nil). func (h *Host) Call(ctx context.Context, method string, params, out any) error { if params == nil { params = struct{}{} } raw, err := h.call(ctx, method, params) if err != nil { return err } if out == nil || len(raw) == 0 { return nil } if err := json.Unmarshal(raw, out); err != nil { return fmt.Errorf("%s: unexpected result: %w", method, err) } return nil } // ResolvePath returns the real path of a file the user chose // (host.files.resolve; needs the files permission). Resolve every path // before reading it. func (h *Host) ResolvePath(ctx context.Context, path string) (string, error) { var resolved string err := h.Call(ctx, "host.files.resolve", map[string]string{"path": path}, &resolved) return resolved, err } // CanWrite reports whether the user's current rules allow writing path // (host.files.can_write; needs the files permission). func (h *Host) CanWrite(ctx context.Context, path string) (bool, error) { var allowed bool err := h.Call(ctx, "host.files.can_write", map[string]string{"path": path}, &allowed) return allowed, err } // Event sends an event to the plugin's screen. name must start with the // plugin id and a colon ("acme.gauge-check:progress"). func (h *Host) Event(ctx context.Context, name string, args ...any) error { if args == nil { args = []any{} } return h.Call(ctx, "host.event", map[string]any{"name": name, "args": args}, nil) } // Published is a link to a file in the data folder that the screen can fetch. type Published struct { Token string `json:"token"` URL string `json:"url"` ExpiresAt string `json:"expiresAt"` } // Publish makes a file in the data folder fetchable by the screen for ttl // (1 second to 1 hour). The file must not change while published. func (h *Host) Publish(ctx context.Context, path, contentType string, ttl time.Duration) (Published, error) { var out Published err := h.Call(ctx, "host.data.publish", map[string]any{"path": path, "contentType": contentType, "ttl": int(ttl / time.Second)}, &out) return out, err }