{"version":3,"sources":["../../src/session-relay/client.ts"],"sourcesContent":["import { getSessionRelayUrl } from \"./endpoints\";\nimport { SessionRelayError } from \"./errors\";\nimport { buildSessionRelayWeb3SignedHeader } from \"./signing\";\nimport type {\n  SessionRelayApproveRequest,\n  SessionRelayBuilderClient,\n  SessionRelayBuilderClientOptions,\n  SessionRelayClaimRequest,\n  SessionRelayClaimResponse,\n  SessionRelayClient,\n  SessionRelayClientOptions,\n  SessionRelayDenyRequest,\n  SessionRelayFetch,\n  SessionRelayInitParams,\n  SessionRelayInitResult,\n  SessionRelayPollResult,\n} from \"./types\";\n\nconst DEFAULT_POLL_INTERVAL_MS = 2_000;\nconst DEFAULT_POLL_TIMEOUT_MS = 15 * 60_000;\n\nfunction stripTrailingSlash(url: string): string {\n  return url.replace(/\\/+$/, \"\");\n}\n\nfunction resolveFetch(fetchFn?: SessionRelayFetch): SessionRelayFetch {\n  const resolved = fetchFn ?? (globalThis.fetch as SessionRelayFetch);\n  if (!resolved) {\n    throw new SessionRelayError(\n      \"No fetch implementation available. Pass `fetchFn` to the Session Relay client.\",\n      { relayErrorCode: \"SESSION_RELAY_CONFIG_ERROR\" },\n    );\n  }\n  return resolved;\n}\n\nfunction endpoint(options: SessionRelayClientOptions): string {\n  return stripTrailingSlash(options.baseUrl ?? getSessionRelayUrl(options.env));\n}\n\nasync function parseJson(res: Awaited<ReturnType<SessionRelayFetch>>) {\n  if (res.status === 204) return undefined;\n\n  if (res.text) {\n    const text = await res.text();\n    if (text.length === 0) return undefined;\n    try {\n      return JSON.parse(text) as unknown;\n    } catch {\n      throw new SessionRelayError(\n        `Session Relay returned a non-JSON response (HTTP ${res.status})`,\n        { status: res.status },\n      );\n    }\n  }\n\n  try {\n    return await res.json();\n  } catch {\n    throw new SessionRelayError(\n      `Session Relay returned a non-JSON response (HTTP ${res.status})`,\n      { status: res.status },\n    );\n  }\n}\n\nfunction relayErrorFromBody(\n  body: unknown,\n):\n  | { message?: string; code?: number; errorCode?: string; details?: unknown }\n  | undefined {\n  if (body === null || typeof body !== \"object\" || !(\"error\" in body)) {\n    return undefined;\n  }\n  const error = (body as { error?: unknown }).error;\n  if (error === null || typeof error !== \"object\") return undefined;\n  return error as {\n    message?: string;\n    code?: number;\n    errorCode?: string;\n    details?: unknown;\n  };\n}\n\nasync function relayFetch<T>(\n  fetchFn: SessionRelayFetch,\n  url: string,\n  init: { method: string; headers?: Record<string, string>; body?: string },\n): Promise<T> {\n  let res: Awaited<ReturnType<SessionRelayFetch>>;\n  try {\n    res = await fetchFn(url, init);\n  } catch (err) {\n    throw new SessionRelayError(\n      \"Network error communicating with Session Relay\",\n      {\n        body: err,\n      },\n    );\n  }\n\n  const body = await parseJson(res);\n  if (!res.ok) {\n    const relayError = relayErrorFromBody(body);\n    throw new SessionRelayError(\n      relayError?.message ??\n        `Session Relay request failed (HTTP ${res.status})`,\n      {\n        status: res.status,\n        relayCode: relayError?.code,\n        relayErrorCode: relayError?.errorCode,\n        body,\n      },\n    );\n  }\n\n  return body as T;\n}\n\nasync function pollSession(\n  fetchFn: SessionRelayFetch,\n  baseUrl: string,\n  sessionId: string,\n): Promise<SessionRelayPollResult> {\n  return relayFetch<SessionRelayPollResult>(\n    fetchFn,\n    `${baseUrl}/v1/session/${encodeURIComponent(sessionId)}/poll`,\n    { method: \"GET\" },\n  );\n}\n\nasync function pollUntilComplete(\n  fetchFn: SessionRelayFetch,\n  baseUrl: string,\n  sessionId: string,\n  opts?: { intervalMs?: number; timeoutMs?: number },\n): Promise<SessionRelayPollResult> {\n  const intervalMs = opts?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n  const timeoutMs = opts?.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;\n  const deadline = Date.now() + timeoutMs;\n\n  while (Date.now() < deadline) {\n    const result = await pollSession(fetchFn, baseUrl, sessionId);\n    if (\n      result.status === \"approved\" ||\n      result.status === \"denied\" ||\n      result.status === \"expired\"\n    ) {\n      return result;\n    }\n    await new Promise((resolve) => setTimeout(resolve, intervalMs));\n  }\n\n  throw new SessionRelayError(\"Polling timed out\", {\n    relayErrorCode: \"SESSION_RELAY_POLL_TIMEOUT\",\n  });\n}\n\n/**\n * Create a Session Relay client for app-side claim/approve/deny calls and\n * unsigned polling.\n *\n * @remarks\n * This is a Vana service integration client, not a protocol-core primitive.\n * It intentionally models the current Session Relay HTTP API while leaving\n * user-facing grant orchestration to the app using it.\n */\nexport function createSessionRelayClient(\n  options: SessionRelayClientOptions = {},\n): SessionRelayClient {\n  const fetchFn = resolveFetch(options.fetchFn);\n  const baseUrl = endpoint(options);\n\n  return {\n    claimSession(\n      request: SessionRelayClaimRequest,\n    ): Promise<SessionRelayClaimResponse> {\n      return relayFetch<SessionRelayClaimResponse>(\n        fetchFn,\n        `${baseUrl}/v1/session/claim`,\n        {\n          method: \"POST\",\n          headers: { \"Content-Type\": \"application/json\" },\n          body: JSON.stringify(request),\n        },\n      );\n    },\n\n    async approveSession(\n      sessionId: string,\n      request: SessionRelayApproveRequest,\n    ): Promise<void> {\n      await relayFetch<unknown>(\n        fetchFn,\n        `${baseUrl}/v1/session/${encodeURIComponent(sessionId)}/approve`,\n        {\n          method: \"POST\",\n          headers: { \"Content-Type\": \"application/json\" },\n          body: JSON.stringify(request),\n        },\n      );\n    },\n\n    async denySession(\n      sessionId: string,\n      request: SessionRelayDenyRequest,\n    ): Promise<void> {\n      await relayFetch<unknown>(\n        fetchFn,\n        `${baseUrl}/v1/session/${encodeURIComponent(sessionId)}/deny`,\n        {\n          method: \"POST\",\n          headers: { \"Content-Type\": \"application/json\" },\n          body: JSON.stringify(request),\n        },\n      );\n    },\n\n    pollSession(sessionId: string): Promise<SessionRelayPollResult> {\n      return pollSession(fetchFn, baseUrl, sessionId);\n    },\n\n    pollUntilComplete(\n      sessionId: string,\n      opts?: { intervalMs?: number; timeoutMs?: number },\n    ): Promise<SessionRelayPollResult> {\n      return pollUntilComplete(fetchFn, baseUrl, sessionId, opts);\n    },\n  };\n}\n\n/**\n * Create a builder-side Session Relay client for signed session init and\n * unsigned polling.\n */\nexport function createSessionRelayBuilderClient(\n  options: SessionRelayBuilderClientOptions,\n): SessionRelayBuilderClient {\n  const fetchFn = resolveFetch(options.fetchFn);\n  const baseUrl = endpoint(options);\n\n  return {\n    async initSession(\n      params: SessionRelayInitParams,\n    ): Promise<SessionRelayInitResult> {\n      const path = \"/v1/session/init\";\n      const body = JSON.stringify({\n        granteeAddress: options.granteeAddress,\n        scopes: params.scopes,\n        ...(params.webhookUrl && { webhookUrl: params.webhookUrl }),\n        ...(params.appUserId && { appUserId: params.appUserId }),\n      });\n      const iat = options.now?.() ?? Math.floor(Date.now() / 1000);\n      const authHeader = await buildSessionRelayWeb3SignedHeader({\n        signMessage: options.signMessage,\n        aud: baseUrl,\n        method: \"POST\",\n        uri: path,\n        body,\n        iat,\n        exp: iat + 300,\n      });\n\n      return relayFetch<SessionRelayInitResult>(fetchFn, `${baseUrl}${path}`, {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n          Authorization: authHeader,\n        },\n        body,\n      });\n    },\n\n    pollSession(sessionId: string): Promise<SessionRelayPollResult> {\n      return pollSession(fetchFn, baseUrl, sessionId);\n    },\n\n    pollUntilComplete(\n      sessionId: string,\n      opts?: { intervalMs?: number; timeoutMs?: number },\n    ): Promise<SessionRelayPollResult> {\n      return pollUntilComplete(fetchFn, baseUrl, sessionId, opts);\n    },\n  };\n}\n"],"mappings":"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","names":[]}