diff --git a/extensions/tlon/src/urbit/channel-client.ts b/extensions/tlon/src/urbit/channel-client.ts index 1b38f7d7c..fb8af656a 100644 --- a/extensions/tlon/src/urbit/channel-client.ts +++ b/extensions/tlon/src/urbit/channel-client.ts @@ -1,5 +1,5 @@ import type { LookupFn, SsrFPolicy } from "openclaw/plugin-sdk"; -import { ensureUrbitChannelOpen } from "./channel-ops.js"; +import { ensureUrbitChannelOpen, pokeUrbitChannel, scryUrbitPath } from "./channel-ops.js"; import { getUrbitContext, normalizeUrbitCookie } from "./context.js"; import { urbitFetch } from "./fetch.js"; @@ -70,69 +70,35 @@ export class UrbitChannelClient { async poke(params: { app: string; mark: string; json: unknown }): Promise { await this.open(); - const pokeId = Date.now(); - const pokeData = { - id: pokeId, - action: "poke", - ship: this.ship, - app: params.app, - mark: params.mark, - json: params.json, - }; - - const { response, release } = await urbitFetch({ - baseUrl: this.baseUrl, - path: this.channelPath, - init: { - method: "PUT", - headers: { - "Content-Type": "application/json", - Cookie: this.cookie, - }, - body: JSON.stringify([pokeData]), - }, - ssrfPolicy: this.ssrfPolicy, - lookupFn: this.lookupFn, - fetchImpl: this.fetchImpl, - timeoutMs: 30_000, - auditContext: "tlon-urbit-poke", - }); - - try { - if (!response.ok && response.status !== 204) { - const errorText = await response.text().catch(() => ""); - throw new Error(`Poke failed: ${response.status}${errorText ? ` - ${errorText}` : ""}`); - } - return pokeId; - } finally { - await release(); + const channelId = this.channelId; + if (!channelId) { + throw new Error("Channel not opened"); } + return await pokeUrbitChannel( + { + baseUrl: this.baseUrl, + cookie: this.cookie, + ship: this.ship, + channelId, + ssrfPolicy: this.ssrfPolicy, + lookupFn: this.lookupFn, + fetchImpl: this.fetchImpl, + }, + { ...params, auditContext: "tlon-urbit-poke" }, + ); } async scry(path: string): Promise { - const scryPath = `/~/scry${path}`; - const { response, release } = await urbitFetch({ - baseUrl: this.baseUrl, - path: scryPath, - init: { - method: "GET", - headers: { Cookie: this.cookie }, + return await scryUrbitPath( + { + baseUrl: this.baseUrl, + cookie: this.cookie, + ssrfPolicy: this.ssrfPolicy, + lookupFn: this.lookupFn, + fetchImpl: this.fetchImpl, }, - ssrfPolicy: this.ssrfPolicy, - lookupFn: this.lookupFn, - fetchImpl: this.fetchImpl, - timeoutMs: 30_000, - auditContext: "tlon-urbit-scry", - }); - - try { - if (!response.ok) { - throw new Error(`Scry failed: ${response.status} for path ${path}`); - } - return await response.json(); - } finally { - await release(); - } + { path, auditContext: "tlon-urbit-scry" }, + ); } async getOurName(): Promise { diff --git a/extensions/tlon/src/urbit/channel-ops.ts b/extensions/tlon/src/urbit/channel-ops.ts index f62d870fc..077e8d018 100644 --- a/extensions/tlon/src/urbit/channel-ops.ts +++ b/extensions/tlon/src/urbit/channel-ops.ts @@ -12,6 +12,78 @@ export type UrbitChannelDeps = { fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise; }; +export async function pokeUrbitChannel( + deps: UrbitChannelDeps, + params: { app: string; mark: string; json: unknown; auditContext: string }, +): Promise { + const pokeId = Date.now(); + const pokeData = { + id: pokeId, + action: "poke", + ship: deps.ship, + app: params.app, + mark: params.mark, + json: params.json, + }; + + const { response, release } = await urbitFetch({ + baseUrl: deps.baseUrl, + path: `/~/channel/${deps.channelId}`, + init: { + method: "PUT", + headers: { + "Content-Type": "application/json", + Cookie: deps.cookie, + }, + body: JSON.stringify([pokeData]), + }, + ssrfPolicy: deps.ssrfPolicy, + lookupFn: deps.lookupFn, + fetchImpl: deps.fetchImpl, + timeoutMs: 30_000, + auditContext: params.auditContext, + }); + + try { + if (!response.ok && response.status !== 204) { + const errorText = await response.text().catch(() => ""); + throw new Error(`Poke failed: ${response.status}${errorText ? ` - ${errorText}` : ""}`); + } + return pokeId; + } finally { + await release(); + } +} + +export async function scryUrbitPath( + deps: Pick, + params: { path: string; auditContext: string }, +): Promise { + const scryPath = `/~/scry${params.path}`; + const { response, release } = await urbitFetch({ + baseUrl: deps.baseUrl, + path: scryPath, + init: { + method: "GET", + headers: { Cookie: deps.cookie }, + }, + ssrfPolicy: deps.ssrfPolicy, + lookupFn: deps.lookupFn, + fetchImpl: deps.fetchImpl, + timeoutMs: 30_000, + auditContext: params.auditContext, + }); + + try { + if (!response.ok) { + throw new Error(`Scry failed: ${response.status} for path ${params.path}`); + } + return await response.json(); + } finally { + await release(); + } +} + export async function createUrbitChannel( deps: UrbitChannelDeps, params: { body: unknown; auditContext: string }, diff --git a/extensions/tlon/src/urbit/sse-client.ts b/extensions/tlon/src/urbit/sse-client.ts index f4a1b8fdf..a379d1680 100644 --- a/extensions/tlon/src/urbit/sse-client.ts +++ b/extensions/tlon/src/urbit/sse-client.ts @@ -1,6 +1,6 @@ import type { LookupFn, SsrFPolicy } from "openclaw/plugin-sdk"; import { Readable } from "node:stream"; -import { ensureUrbitChannelOpen } from "./channel-ops.js"; +import { ensureUrbitChannelOpen, pokeUrbitChannel, scryUrbitPath } from "./channel-ops.js"; import { getUrbitContext, normalizeUrbitCookie } from "./context.js"; import { urbitFetch } from "./fetch.js"; @@ -290,71 +290,31 @@ export class UrbitSSEClient { } async poke(params: { app: string; mark: string; json: unknown }) { - const pokeId = Date.now(); - const pokeData = { - id: pokeId, - action: "poke", - ship: this.ship, - app: params.app, - mark: params.mark, - json: params.json, - }; - - const { response, release } = await urbitFetch({ - baseUrl: this.url, - path: `/~/channel/${this.channelId}`, - init: { - method: "PUT", - headers: { - "Content-Type": "application/json", - Cookie: this.cookie, - }, - body: JSON.stringify([pokeData]), + return await pokeUrbitChannel( + { + baseUrl: this.url, + cookie: this.cookie, + ship: this.ship, + channelId: this.channelId, + ssrfPolicy: this.ssrfPolicy, + lookupFn: this.lookupFn, + fetchImpl: this.fetchImpl, }, - ssrfPolicy: this.ssrfPolicy, - lookupFn: this.lookupFn, - fetchImpl: this.fetchImpl, - timeoutMs: 30_000, - auditContext: "tlon-urbit-poke", - }); - - try { - if (!response.ok && response.status !== 204) { - const errorText = await response.text().catch(() => ""); - throw new Error(`Poke failed: ${response.status}${errorText ? ` - ${errorText}` : ""}`); - } - } finally { - await release(); - } - - return pokeId; + { ...params, auditContext: "tlon-urbit-poke" }, + ); } async scry(path: string) { - const { response, release } = await urbitFetch({ - baseUrl: this.url, - path: `/~/scry${path}`, - init: { - method: "GET", - headers: { - Cookie: this.cookie, - }, + return await scryUrbitPath( + { + baseUrl: this.url, + cookie: this.cookie, + ssrfPolicy: this.ssrfPolicy, + lookupFn: this.lookupFn, + fetchImpl: this.fetchImpl, }, - ssrfPolicy: this.ssrfPolicy, - lookupFn: this.lookupFn, - fetchImpl: this.fetchImpl, - timeoutMs: 30_000, - auditContext: "tlon-urbit-scry", - }); - - try { - if (!response.ok) { - throw new Error(`Scry failed: ${response.status} for path ${path}`); - } - return await response.json(); - } finally { - await release(); - } + { path, auditContext: "tlon-urbit-scry" }, + ); } async attemptReconnect() {