socketmode

package
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 5, 2026 License: BSD-3-Clause Imports: 16 Imported by: 0

README

Socket Mode

Socket Mode allows your bot to receive real-time events from Dooray via a persistent WebSocket connection, without setting up a public webhook endpoint.

Prerequisites

  • Agent Token: DOORAY_AGENT_TOKEN environment variable (issued from Dooray agent settings)
  • Domain (optional): DOORAY_DOMAIN environment variable (e.g. company)

Quick Start

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/dooray-go/dooray-sdk/socketmode"
)

func main() {
    agent := socketmode.NewAgent(os.Getenv("DOORAY_AGENT_TOKEN"),
        socketmode.WithDomain(os.Getenv("DOORAY_DOMAIN")),
    )

    agent.OnMessenger(func(req *socketmode.SocketModeRequest) {
        if text := req.Text(); text != "" {
            req.Reply("Echo: " + text)
        }
    })

    if err := agent.Run(); err != nil {
        log.Fatal(err)
    }
}

Agent Options

Option Description Default
WithBaseURL(url) Dooray API base URL https://api.dooray.com
WithDomain(domain) Dooray domain name (empty)
WithHTTPClient(client) Custom *http.Client for REST API calls http.DefaultClient
WithLogger(logger) Custom *log.Logger stdout with [dooray-socketmode] prefix
WithPingInterval(d) WebSocket ping interval 30s
WithReconnectBackoff(min, max) Reconnect backoff range 1s ~ 30s
agent := socketmode.NewAgent(token,
    socketmode.WithBaseURL("https://api.dooray.com"),
    socketmode.WithDomain("mycompany"),
    socketmode.WithPingInterval(20*time.Second),
    socketmode.WithReconnectBackoff(2*time.Second, 60*time.Second),
)

Event Handlers

Service-specific handlers

Register handlers for a specific Dooray service. Each handler receives all events from that service.

// Messenger events (messages, reactions, etc.)
agent.OnMessenger(func(req *socketmode.SocketModeRequest) {
    fmt.Printf("Messenger event: type=%s action=%s\n", req.Type, req.Action)
})

// Task events (create, update, status change, etc.)
agent.OnTask(func(req *socketmode.SocketModeRequest) {
    fmt.Printf("Task event: type=%s action=%s\n", req.Type, req.Action)
})

// Wiki events (page create, update, etc.)
agent.OnWiki(func(req *socketmode.SocketModeRequest) {
    fmt.Printf("Wiki event: type=%s action=%s\n", req.Type, req.Action)
})
Filtered handler with On()

Register a handler with fine-grained filtering. Empty string matches all values for that field.

// Only handle new message creation in messenger
agent.On("messenger", "message", "create", func(req *socketmode.SocketModeRequest) {
    fmt.Printf("New message: %s\n", req.Text())
})

// Handle all task events regardless of type and action
agent.On("task", "", "", func(req *socketmode.SocketModeRequest) {
    fmt.Println("Something happened in task service")
})

// Handle all "create" actions across all services
agent.On("", "", "create", func(req *socketmode.SocketModeRequest) {
    fmt.Printf("Something was created in %s\n", req.Service)
})

SocketModeRequest

Method Return Description
Text() string Message text from entity data
ChannelID() string Channel ID from entity data
SenderID() string Sender member ID from entity data
IsMessage() bool True if messenger message event
IsType(t) bool Check event type
IsAction(a) bool Check event action
IsService(s) bool Check event service
Reply(text) error Send a reply to the originating channel
Fields
type SocketModeRequest struct {
    EnvelopeID string              // Unique message identifier
    Type       string              // Event type (e.g. "message")
    Service    string              // Service name ("messenger", "task", "wiki")
    Action     string              // Action type ("create", "update", "delete")
    Payload    map[string]any      // Raw event payload
    Entity     *Entity             // Event entity (type + data)
    Actor      *Actor              // Who triggered the event (type + data)
    ActionData *DataWrapper        // Additional action details
}
Entity data for messenger events
agent.OnMessenger(func(req *socketmode.SocketModeRequest) {
    if req.Entity != nil {
        data := req.Entity.Data
        fmt.Println("id:", data["id"])
        fmt.Println("channelId:", data["channelId"])
        fmt.Println("senderId:", data["senderId"])
        fmt.Println("text:", data["text"])
        fmt.Println("sentAt:", data["sentAt"])
    }
})

Using the Messenger Client

The agent exposes the underlying openapi/messenger.Messenger client for direct REST API calls.

agent := socketmode.NewAgent(token)
m := agent.Messenger()

// Send a message to a channel
m.SendMessage(token, "channel-id", &messenger.SendMessageRequest{
    Text: "Hello from bot!",
})

// Send a direct message to a user
m.DirectSend(token, &messenger.DirectSendRequest{
    Text:                 "Hello!",
    OrganizationMemberId: "member-id",
})

Graceful Shutdown

Run() handles SIGINT and SIGTERM automatically. Use RunContext() for custom context control.

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour)
defer cancel()

if err := agent.RunContext(ctx); err != nil {
    log.Fatal(err)
}

Connection Lifecycle

  1. POST /agent/v1/websocket/connect to obtain a WebSocket URL
  2. Connect via WebSocket with dooray-api authorization header
  3. Receive events and auto-acknowledge with envelope_id
  4. Ping/pong keeps the connection alive
  5. On disconnect, reconnect with exponential backoff (jitter included)

Documentation

Index

Constants

View Source
const (
	ServiceMessenger = "messenger"
	ServiceTask      = "task"
	ServiceWiki      = "wiki"
)

Variables

View Source
var (
	ErrNoChannel   = errors.New("socketmode: no channel ID available for reply")
	ErrNoWebClient = errors.New("socketmode: web client not initialized")
	ErrNoToken     = errors.New("socketmode: agent token is required")
	ErrNoDomain    = errors.New("socketmode: domain is required (e.g. WithDomain(\"company.dooray.com\"))")
)

Functions

This section is empty.

Types

type Actor

type Actor struct {
	Type string                 `json:"type"`
	Data map[string]interface{} `json:"data"`
}

Actor wraps information about the user who triggered the event.

type Agent

type Agent struct {
	// contains filtered or unexported fields
}

Agent manages a WebSocket connection to Dooray and dispatches events to handlers.

func NewAgent

func NewAgent(agentToken string, opts ...Option) *Agent

NewAgent creates a new socket mode Agent. The agentToken is the DOORAY_AGENT_TOKEN used for authentication.

func (*Agent) Messenger

func (a *Agent) Messenger() *messenger.Messenger

Messenger returns the underlying Messenger client for making REST API calls.

func (*Agent) On

func (a *Agent) On(service, typ, action string, fn HandlerFunc)

On registers a handler with optional filtering by service, type, and action. Empty strings match all values for that field.

func (*Agent) OnMessenger

func (a *Agent) OnMessenger(fn HandlerFunc)

OnMessenger registers a handler for all messenger service events.

func (*Agent) OnTask

func (a *Agent) OnTask(fn HandlerFunc)

OnTask registers a handler for all task service events.

func (*Agent) OnWiki

func (a *Agent) OnWiki(fn HandlerFunc)

OnWiki registers a handler for all wiki service events.

func (*Agent) Run

func (a *Agent) Run() error

Run starts the WebSocket connection and blocks until the context is cancelled or a termination signal is received.

func (*Agent) RunContext

func (a *Agent) RunContext(ctx context.Context) error

RunContext starts the WebSocket connection with the given context.

type DataWrapper

type DataWrapper struct {
	Type string                 `json:"type"`
	Data map[string]interface{} `json:"data"`
}

DataWrapper wraps additional action detail data.

type Entity

type Entity struct {
	Type string                 `json:"type"`
	Data map[string]interface{} `json:"data"`
}

Entity wraps the event entity data.

type HandlerFunc

type HandlerFunc func(req *SocketModeRequest)

HandlerFunc is the function signature for event handlers.

type Option

type Option func(*Agent)

Option configures the Agent.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL sets the Dooray API base URL.

func WithDomain

func WithDomain(domain string) Option

WithDomain sets the Dooray domain (e.g. "company").

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets a custom HTTP client for REST API calls.

func WithLogger

func WithLogger(logger *log.Logger) Option

WithLogger sets a custom logger.

func WithPingInterval

func WithPingInterval(d time.Duration) Option

WithPingInterval sets the WebSocket ping interval.

func WithReconnectBackoff

func WithReconnectBackoff(min, max time.Duration) Option

WithReconnectBackoff sets the min and max reconnect backoff durations.

type SocketModeRequest

type SocketModeRequest struct {
	EnvelopeID string                 `json:"envelope_id"`
	Type       string                 `json:"type"`
	Service    string                 `json:"service"`
	Action     string                 `json:"action"`
	Payload    map[string]interface{} `json:"payload"`
	Entity     *Entity                `json:"entity"`
	Actor      *Actor                 `json:"actor"`
	ActionData *DataWrapper           `json:"actionData"`
	// contains filtered or unexported fields
}

SocketModeRequest represents an incoming event from the Dooray WebSocket connection.

func (*SocketModeRequest) ChannelID

func (r *SocketModeRequest) ChannelID() string

ChannelID extracts the channel ID from the entity data.

func (*SocketModeRequest) IsAction

func (r *SocketModeRequest) IsAction(a string) bool

IsAction checks if the event action matches.

func (*SocketModeRequest) IsBotMessage

func (r *SocketModeRequest) IsBotMessage() bool

IsBotMessage returns true if the message was sent by this bot itself. It compares the sender ID with the bot's own organizationMemberId obtained during socket mode token exchange. Use this to avoid infinite loops when the bot replies to its own messages.

func (*SocketModeRequest) IsMessage

func (r *SocketModeRequest) IsMessage() bool

IsMessage returns true if the event is a messenger message.

func (*SocketModeRequest) IsService

func (r *SocketModeRequest) IsService(s string) bool

IsService checks if the event service matches.

func (*SocketModeRequest) IsType

func (r *SocketModeRequest) IsType(t string) bool

IsType checks if the event type matches.

func (*SocketModeRequest) Reply

func (r *SocketModeRequest) Reply(text string) error

Reply sends a text message back to the channel where the event originated. Only works for messenger events with a channel ID.

func (*SocketModeRequest) SenderID

func (r *SocketModeRequest) SenderID() string

SenderID extracts the sender member ID from the entity data.

func (*SocketModeRequest) Text

func (r *SocketModeRequest) Text() string

Text extracts the message text from the entity data.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL