branch:
playground-agent.ts
1504 bytesRaw
import { Agent, type Connection, type ConnectionContext } from "agents";

const IDLE_TIMEOUT_SECONDS = 15 * 60;
const IDLE_CALLBACK = "onIdleTimeout";

/**
 * Base class for all playground demo agents.
 *
 * Adds automatic self-cleanup: when all WebSocket connections close, a
 * 15-minute idle timer starts. If no one reconnects before it fires, the
 * agent calls this.destroy() to drop its SQLite tables and abort the
 * Durable Object — freeing resources from abandoned demo sessions.
 *
 * The timer is a durable schedule (persisted in SQLite), so it survives
 * hibernation. On reconnect we look it up by callback name via
 * getSchedules() and cancel it — no in-memory state needed.
 *
 * Agents that override onConnect/onClose must call super to preserve
 * this behavior (see ConnectionsAgent and RoomAgent).
 */
export class PlaygroundAgent<
  E extends Cloudflare.Env = Env,
  State = unknown
> extends Agent<E, State> {
  onConnect(_connection: Connection, _ctx: ConnectionContext) {
    for (const schedule of this.getSchedules()) {
      if (schedule.callback === IDLE_CALLBACK) {
        this.cancelSchedule(schedule.id);
      }
    }
  }

  onClose(_connection: Connection) {
    const remaining = [...this.getConnections()].length;
    if (remaining === 0) {
      this.schedule(IDLE_TIMEOUT_SECONDS, IDLE_CALLBACK, {});
    }
  }

  async onIdleTimeout() {
    const remaining = [...this.getConnections()].length;
    if (remaining === 0) {
      await this.destroy();
    }
  }
}