@computesdk/steel 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -97,11 +97,11 @@ function mapSessionOptions(options) {
97
97
  params.profileId = options.profileId;
98
98
  params.persistProfile = true;
99
99
  }
100
- if (options.stealth) {
101
- params.stealthConfig = { humanizeInteractions: true };
100
+ if (options.stealth !== void 0) {
101
+ params.stealthConfig = { humanizeInteractions: options.stealth };
102
102
  }
103
- if (options.proxies === true) {
104
- params.useProxy = true;
103
+ if (typeof options.proxies === "boolean") {
104
+ params.useProxy = options.proxies;
105
105
  } else if (Array.isArray(options.proxies) && options.proxies.length > 0) {
106
106
  applyProxy(params, options.proxies[0]);
107
107
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Steel Browser Provider - Factory-based Implementation\n *\n * Cloud browser sessions with stealth mode, proxies, persistent profiles,\n * extensions, and session recordings via the Steel platform.\n */\n\nimport Steel, { toFile } from 'steel-sdk';\nimport type {\n Session as SteelSession,\n SessionCreateParams,\n Sessionslist,\n} from 'steel-sdk/resources/sessions';\nimport { defineBrowserProvider } from '@computesdk/provider';\n\nimport type {\n BrowserSession,\n CreateBrowserSessionOptions,\n BrowserExtension,\n BrowserLog,\n BrowserRecording,\n ProxyConfig,\n} from '@computesdk/provider';\n\n/**\n * Steel-specific configuration options\n */\nexport interface SteelConfig {\n /** Steel API key — falls back to STEEL_API_KEY env var */\n apiKey?: string;\n /** Optional API base URL override (defaults to https://api.steel.dev) */\n baseUrl?: string;\n}\n\n/**\n * The native Steel session object returned by their SDK\n */\ntype NativeSteelSession = SteelSession;\n\n/**\n * Resolve config values from explicit config or environment variables\n */\nfunction resolveConfig(config: SteelConfig) {\n const apiKey = config.apiKey || (typeof process !== 'undefined' && process.env?.STEEL_API_KEY) || '';\n\n if (!apiKey) {\n throw new Error(\n `Missing Steel API key. Provide 'apiKey' in config or set STEEL_API_KEY environment variable. ` +\n `Get your API key from https://app.steel.dev/settings/api-keys`\n );\n }\n\n return { apiKey, baseUrl: config.baseUrl };\n}\n\n/**\n * Create a Steel SDK client from config\n */\nfunction createClient(config: SteelConfig): Steel {\n const { apiKey, baseUrl } = resolveConfig(config);\n return new Steel({ steelAPIKey: apiKey, baseURL: baseUrl ?? undefined });\n}\n\n/**\n * Steel's `useProxy` field accepts either a boolean, a geolocation object,\n * or a custom server. Apply a single ProxyConfig onto the create-session params.\n * Steel only supports one proxy per session, so when multiple are passed\n * we honor the first one.\n */\nfunction applyProxy(params: SessionCreateParams, proxy: ProxyConfig) {\n if (proxy.type === 'custom' && proxy.server) {\n // Steel uses a separate proxyUrl field for custom proxies; this overrides\n // useProxy and disables Steel-provided proxies in favor of the user's.\n const url = new URL(proxy.server);\n if (proxy.username) url.username = proxy.username;\n if (proxy.password) url.password = proxy.password;\n params.proxyUrl = url.toString();\n return;\n }\n\n if (proxy.geolocation?.country || proxy.geolocation?.state || proxy.geolocation?.city) {\n params.useProxy = {\n geolocation: {\n country: proxy.geolocation.country as any,\n ...(proxy.geolocation.state ? { state: proxy.geolocation.state as any } : {}),\n ...(proxy.geolocation.city ? { city: proxy.geolocation.city as any } : {}),\n },\n };\n return;\n }\n\n params.useProxy = true;\n}\n\nconst warnedUnsupported = new Set<string>();\nfunction warnOnce(field: string, reason: string) {\n if (warnedUnsupported.has(field)) return;\n warnedUnsupported.add(field);\n console.warn(`[@computesdk/steel] '${field}' is ignored: ${reason}`);\n}\n\n/**\n * Map ComputeSDK session options to Steel create-session params.\n *\n * The following CreateBrowserSessionOptions fields are intentionally not\n * forwarded because Steel's API has no equivalent knob:\n * - `recording`: Steel always records sessions; fetch via the recording manager.\n * - `logging`: Steel always captures console logs; fetch via the logs manager.\n * - `keepAlive`: Steel sessions run until `timeout` elapses or are released\n * explicitly; there is no separate keep-alive flag. Use a long\n * `timeout` to approximate the behavior.\n * - `userMetadata`: Steel does not support arbitrary user metadata on sessions.\n *\n * If a caller passes a non-default value for any of these, we warn once per\n * field so the silent drop is at least visible in logs.\n */\nfunction mapSessionOptions(options?: CreateBrowserSessionOptions): SessionCreateParams {\n if (!options) return {};\n\n if (options.keepAlive === true) {\n warnOnce('keepAlive', 'Steel has no keep-alive flag; use a long `timeout` instead.');\n }\n if (options.recording === false) {\n warnOnce('recording', 'Steel always records sessions and cannot be disabled per session.');\n }\n if (options.logging === false) {\n warnOnce('logging', 'Steel always captures console logs and cannot be disabled per session.');\n }\n if (options.userMetadata && Object.keys(options.userMetadata).length > 0) {\n warnOnce('userMetadata', 'Steel does not support user metadata on sessions.');\n }\n\n const params: SessionCreateParams = {};\n\n if (options.viewport) params.dimensions = { width: options.viewport.width, height: options.viewport.height };\n if (options.extensionIds && options.extensionIds.length > 0) params.extensionIds = options.extensionIds;\n if (options.region) params.region = options.region as SessionCreateParams['region'];\n // Steel session timeout is in milliseconds (default 5 minutes)\n if (options.timeout !== undefined) params.timeout = options.timeout * 1000;\n if (options.profileId) {\n params.profileId = options.profileId;\n params.persistProfile = true;\n }\n\n // Stealth wraps a few related flags. We toggle humanizeInteractions as the\n // most user-visible knob; advanced users can override via options pass-through.\n if (options.stealth) {\n params.stealthConfig = { humanizeInteractions: true };\n }\n\n if (options.proxies === true) {\n params.useProxy = true;\n } else if (Array.isArray(options.proxies) && options.proxies.length > 0) {\n applyProxy(params, options.proxies[0]!);\n }\n\n return params;\n}\n\n/**\n * Map Steel's session status onto our standard set\n */\nfunction mapStatus(status: string | undefined): BrowserSession['status'] {\n switch (status) {\n case 'live': return 'running';\n case 'released': return 'completed';\n case 'failed': return 'failed';\n default: return 'created';\n }\n}\n\n/**\n * Append the apiKey (and sessionId if missing) to Steel's websocketUrl so\n * callers can pass it directly to playwright/puppeteer. Steel returns\n * `wss://connect.steel.dev?sessionId=...` and requires `apiKey` to be\n * appended for authentication.\n *\n * SECURITY: the returned URL embeds the account-wide Steel API key as a\n * query parameter. Treat it as a secret — do not log it, persist it, or\n * forward it to systems that retain URLs (access logs, error trackers,\n * tracing). Anyone with this URL has full access to the Steel account, not\n * just this session.\n */\nfunction buildConnectUrl(websocketUrl: string, apiKey: string, sessionId: string): string {\n const url = new URL(websocketUrl);\n if (!url.searchParams.has('apiKey')) url.searchParams.set('apiKey', apiKey);\n if (!url.searchParams.has('sessionId')) url.searchParams.set('sessionId', sessionId);\n return url.toString();\n}\n\n/**\n * Create a Steel browser provider instance\n *\n * SECURITY NOTE: Steel's CDP endpoint authenticates via an `apiKey` query\n * parameter on the WebSocket URL. As a result, every `connectUrl` returned\n * by this provider (from `session.create()` and `session.getById()`, and\n * `provider.getConnectUrl()`) embeds the account-wide Steel API key. Treat\n * `connectUrl` as a secret: do not log, persist, or forward it to systems\n * that retain URLs. `session.list()` deliberately omits `connectUrl` to\n * avoid amplifying this leak surface — call `provider.getConnectUrl(id)` on\n * demand instead.\n *\n * @example\n * ```ts\n * import { steel } from '@computesdk/steel';\n * import { chromium } from 'playwright-core';\n *\n * const st = steel({ apiKey: process.env.STEEL_API_KEY });\n *\n * // Create a stealth browser session\n * const session = await st.session.create({ stealth: true, proxies: true });\n *\n * // Connect with Playwright (do not log session.connectUrl — it contains the API key)\n * const browser = await chromium.connectOverCDP(session.connectUrl);\n * const page = browser.contexts()[0].pages()[0];\n * await page.goto('https://example.com');\n *\n * // Release the session\n * await session.destroy();\n * ```\n */\nexport const steel = defineBrowserProvider<NativeSteelSession, SteelConfig>({\n name: 'steel',\n methods: {\n // ─── Session Lifecycle ─────────────────────────────────────────────\n session: {\n create: async (config, options) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n const session = await client.sessions.create(mapSessionOptions(options));\n return {\n session,\n sessionId: session.id,\n connectUrl: buildConnectUrl(session.websocketUrl, apiKey, session.id),\n status: mapStatus(session.status),\n };\n },\n\n getById: async (config, sessionId) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n try {\n const session = await client.sessions.retrieve(sessionId);\n return {\n session,\n sessionId: session.id,\n connectUrl: buildConnectUrl(session.websocketUrl, apiKey, session.id),\n status: mapStatus(session.status),\n };\n } catch {\n return null;\n }\n },\n\n list: async (config) => {\n const client = createClient(config);\n const page = await client.sessions.list();\n const sessions = (page as unknown as { sessions: Sessionslist['sessions'] }).sessions ?? [];\n // We deliberately omit connectUrl from list entries: the URL would\n // need to embed the account-wide Steel API key, and emitting it once\n // per session inflates the leak surface (logs, traces, etc.). Callers\n // that need a connectable URL should use getConnectUrl(sessionId).\n return sessions.map((s) => ({\n session: s as NativeSteelSession,\n sessionId: s.id,\n status: mapStatus(s.status),\n }));\n },\n\n destroy: async (config, sessionId) => {\n const client = createClient(config);\n await client.sessions.release(sessionId);\n },\n\n getConnectUrl: async (config, sessionId) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n const session = await client.sessions.retrieve(sessionId);\n return buildConnectUrl(session.websocketUrl, apiKey, session.id);\n },\n },\n\n // ─── Extensions ────────────────────────────────────────────────────\n extension: {\n create: async (config, options) => {\n const client = createClient(config);\n const buf = typeof options.file === 'string'\n ? Buffer.from(options.file, 'utf-8')\n : Buffer.from(options.file);\n const file = await toFile(buf, options.name ?? 'extension.zip');\n const result = await client.extensions.upload({ file });\n return {\n extensionId: result.id,\n name: result.name,\n } satisfies BrowserExtension;\n },\n\n get: async (config, extensionId) => {\n const client = createClient(config);\n // Steel exposes only a list endpoint for extension lookup — filter the result.\n const response = await client.extensions.list();\n const found = response.extensions.find((e) => e.id === extensionId);\n if (!found) return null;\n return { extensionId: found.id, name: found.name } satisfies BrowserExtension;\n },\n\n delete: async (config, extensionId) => {\n const client = createClient(config);\n await client.extensions.delete(extensionId);\n },\n },\n\n // ─── Logs (RRWeb session events) ───────────────────────────────────\n // Steel doesn't expose a console-log endpoint — the closest equivalent\n // is the RRWeb event stream from `sessions.events`. We surface those as\n // log entries so callers have a uniform shape.\n logs: {\n list: async (config, sessionId) => {\n const client = createClient(config);\n const events = await client.sessions.events(sessionId);\n return (events as Array<Record<string, unknown>>).map((event) => {\n const ts = typeof event.timestamp === 'number' ? event.timestamp : Date.now();\n return {\n timestamp: new Date(ts),\n level: 'info' as BrowserLog['level'],\n message: typeof event.type === 'string' || typeof event.type === 'number'\n ? `event:${String(event.type)}`\n : 'event',\n data: event,\n } satisfies BrowserLog;\n });\n },\n },\n\n // ─── Recordings ────────────────────────────────────────────────────\n recording: {\n get: async (config, sessionId) => {\n const client = createClient(config);\n try {\n const events = await client.sessions.events(sessionId);\n if (!events || (Array.isArray(events) && events.length === 0)) return null;\n return {\n recordingId: sessionId,\n sessionId,\n format: 'rrweb',\n data: events,\n } satisfies BrowserRecording;\n } catch {\n return null;\n }\n },\n },\n },\n});\n"],"mappings":"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","names":["Steel"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Steel Browser Provider - Factory-based Implementation\n *\n * Cloud browser sessions with stealth mode, proxies, persistent profiles,\n * extensions, and session recordings via the Steel platform.\n */\n\nimport Steel, { toFile } from 'steel-sdk';\nimport type {\n Session as SteelSession,\n SessionCreateParams,\n Sessionslist,\n} from 'steel-sdk/resources/sessions';\nimport { defineBrowserProvider } from '@computesdk/provider';\n\nimport type {\n BrowserSession,\n CreateBrowserSessionOptions,\n BrowserExtension,\n BrowserLog,\n BrowserRecording,\n ProxyConfig,\n} from '@computesdk/provider';\n\n/**\n * Steel-specific configuration options\n */\nexport interface SteelConfig {\n /** Steel API key — falls back to STEEL_API_KEY env var */\n apiKey?: string;\n /** Optional API base URL override (defaults to https://api.steel.dev) */\n baseUrl?: string;\n}\n\n/**\n * The native Steel session object returned by their SDK\n */\ntype NativeSteelSession = SteelSession;\n\n/**\n * Resolve config values from explicit config or environment variables\n */\nfunction resolveConfig(config: SteelConfig) {\n const apiKey = config.apiKey || (typeof process !== 'undefined' && process.env?.STEEL_API_KEY) || '';\n\n if (!apiKey) {\n throw new Error(\n `Missing Steel API key. Provide 'apiKey' in config or set STEEL_API_KEY environment variable. ` +\n `Get your API key from https://app.steel.dev/settings/api-keys`\n );\n }\n\n return { apiKey, baseUrl: config.baseUrl };\n}\n\n/**\n * Create a Steel SDK client from config\n */\nfunction createClient(config: SteelConfig): Steel {\n const { apiKey, baseUrl } = resolveConfig(config);\n return new Steel({ steelAPIKey: apiKey, baseURL: baseUrl ?? undefined });\n}\n\n/**\n * Steel's `useProxy` field accepts either a boolean, a geolocation object,\n * or a custom server. Apply a single ProxyConfig onto the create-session params.\n * Steel only supports one proxy per session, so when multiple are passed\n * we honor the first one.\n */\nfunction applyProxy(params: SessionCreateParams, proxy: ProxyConfig) {\n if (proxy.type === 'custom' && proxy.server) {\n // Steel uses a separate proxyUrl field for custom proxies; this overrides\n // useProxy and disables Steel-provided proxies in favor of the user's.\n const url = new URL(proxy.server);\n if (proxy.username) url.username = proxy.username;\n if (proxy.password) url.password = proxy.password;\n params.proxyUrl = url.toString();\n return;\n }\n\n if (proxy.geolocation?.country || proxy.geolocation?.state || proxy.geolocation?.city) {\n params.useProxy = {\n geolocation: {\n country: proxy.geolocation.country as any,\n ...(proxy.geolocation.state ? { state: proxy.geolocation.state as any } : {}),\n ...(proxy.geolocation.city ? { city: proxy.geolocation.city as any } : {}),\n },\n };\n return;\n }\n\n params.useProxy = true;\n}\n\nconst warnedUnsupported = new Set<string>();\nfunction warnOnce(field: string, reason: string) {\n if (warnedUnsupported.has(field)) return;\n warnedUnsupported.add(field);\n console.warn(`[@computesdk/steel] '${field}' is ignored: ${reason}`);\n}\n\n/**\n * Map ComputeSDK session options to Steel create-session params.\n *\n * The following CreateBrowserSessionOptions fields are intentionally not\n * forwarded because Steel's API has no equivalent knob:\n * - `recording`: Steel always records sessions; fetch via the recording manager.\n * - `logging`: Steel always captures console logs; fetch via the logs manager.\n * - `keepAlive`: Steel sessions run until `timeout` elapses or are released\n * explicitly; there is no separate keep-alive flag. Use a long\n * `timeout` to approximate the behavior.\n * - `userMetadata`: Steel does not support arbitrary user metadata on sessions.\n *\n * If a caller passes a non-default value for any of these, we warn once per\n * field so the silent drop is at least visible in logs.\n */\nfunction mapSessionOptions(options?: CreateBrowserSessionOptions): SessionCreateParams {\n if (!options) return {};\n\n if (options.keepAlive === true) {\n warnOnce('keepAlive', 'Steel has no keep-alive flag; use a long `timeout` instead.');\n }\n if (options.recording === false) {\n warnOnce('recording', 'Steel always records sessions and cannot be disabled per session.');\n }\n if (options.logging === false) {\n warnOnce('logging', 'Steel always captures console logs and cannot be disabled per session.');\n }\n if (options.userMetadata && Object.keys(options.userMetadata).length > 0) {\n warnOnce('userMetadata', 'Steel does not support user metadata on sessions.');\n }\n\n const params: SessionCreateParams = {};\n\n if (options.viewport) params.dimensions = { width: options.viewport.width, height: options.viewport.height };\n if (options.extensionIds && options.extensionIds.length > 0) params.extensionIds = options.extensionIds;\n if (options.region) params.region = options.region as SessionCreateParams['region'];\n // Steel session timeout is in milliseconds (default 5 minutes)\n if (options.timeout !== undefined) params.timeout = options.timeout * 1000;\n if (options.profileId) {\n params.profileId = options.profileId;\n params.persistProfile = true;\n }\n\n // Stealth wraps a few related flags. We toggle humanizeInteractions as the\n // most user-visible knob and preserve explicit false values.\n if (options.stealth !== undefined) {\n params.stealthConfig = { humanizeInteractions: options.stealth };\n }\n\n if (typeof options.proxies === 'boolean') {\n params.useProxy = options.proxies;\n } else if (Array.isArray(options.proxies) && options.proxies.length > 0) {\n applyProxy(params, options.proxies[0]!);\n }\n\n return params;\n}\n\n/**\n * Map Steel's session status onto our standard set\n */\nfunction mapStatus(status: string | undefined): BrowserSession['status'] {\n switch (status) {\n case 'live': return 'running';\n case 'released': return 'completed';\n case 'failed': return 'failed';\n default: return 'created';\n }\n}\n\n/**\n * Append the apiKey (and sessionId if missing) to Steel's websocketUrl so\n * callers can pass it directly to playwright/puppeteer. Steel returns\n * `wss://connect.steel.dev?sessionId=...` and requires `apiKey` to be\n * appended for authentication.\n *\n * SECURITY: the returned URL embeds the account-wide Steel API key as a\n * query parameter. Treat it as a secret — do not log it, persist it, or\n * forward it to systems that retain URLs (access logs, error trackers,\n * tracing). Anyone with this URL has full access to the Steel account, not\n * just this session.\n */\nfunction buildConnectUrl(websocketUrl: string, apiKey: string, sessionId: string): string {\n const url = new URL(websocketUrl);\n if (!url.searchParams.has('apiKey')) url.searchParams.set('apiKey', apiKey);\n if (!url.searchParams.has('sessionId')) url.searchParams.set('sessionId', sessionId);\n return url.toString();\n}\n\n/**\n * Create a Steel browser provider instance\n *\n * SECURITY NOTE: Steel's CDP endpoint authenticates via an `apiKey` query\n * parameter on the WebSocket URL. As a result, every `connectUrl` returned\n * by this provider (from `session.create()` and `session.getById()`, and\n * `provider.getConnectUrl()`) embeds the account-wide Steel API key. Treat\n * `connectUrl` as a secret: do not log, persist, or forward it to systems\n * that retain URLs. `session.list()` deliberately omits `connectUrl` to\n * avoid amplifying this leak surface — call `provider.getConnectUrl(id)` on\n * demand instead.\n *\n * @example\n * ```ts\n * import { steel } from '@computesdk/steel';\n * import { chromium } from 'playwright-core';\n *\n * const st = steel({ apiKey: process.env.STEEL_API_KEY });\n *\n * // Create a stealth browser session\n * const session = await st.session.create({ stealth: true, proxies: true });\n *\n * // Connect with Playwright (do not log session.connectUrl — it contains the API key)\n * const browser = await chromium.connectOverCDP(session.connectUrl);\n * const page = browser.contexts()[0].pages()[0];\n * await page.goto('https://example.com');\n *\n * // Release the session\n * await session.destroy();\n * ```\n */\nexport const steel = defineBrowserProvider<NativeSteelSession, SteelConfig>({\n name: 'steel',\n methods: {\n // ─── Session Lifecycle ─────────────────────────────────────────────\n session: {\n create: async (config, options) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n const session = await client.sessions.create(mapSessionOptions(options));\n return {\n session,\n sessionId: session.id,\n connectUrl: buildConnectUrl(session.websocketUrl, apiKey, session.id),\n status: mapStatus(session.status),\n };\n },\n\n getById: async (config, sessionId) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n try {\n const session = await client.sessions.retrieve(sessionId);\n return {\n session,\n sessionId: session.id,\n connectUrl: buildConnectUrl(session.websocketUrl, apiKey, session.id),\n status: mapStatus(session.status),\n };\n } catch {\n return null;\n }\n },\n\n list: async (config) => {\n const client = createClient(config);\n const page = await client.sessions.list();\n const sessions = (page as unknown as { sessions: Sessionslist['sessions'] }).sessions ?? [];\n // We deliberately omit connectUrl from list entries: the URL would\n // need to embed the account-wide Steel API key, and emitting it once\n // per session inflates the leak surface (logs, traces, etc.). Callers\n // that need a connectable URL should use getConnectUrl(sessionId).\n return sessions.map((s) => ({\n session: s as NativeSteelSession,\n sessionId: s.id,\n status: mapStatus(s.status),\n }));\n },\n\n destroy: async (config, sessionId) => {\n const client = createClient(config);\n await client.sessions.release(sessionId);\n },\n\n getConnectUrl: async (config, sessionId) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n const session = await client.sessions.retrieve(sessionId);\n return buildConnectUrl(session.websocketUrl, apiKey, session.id);\n },\n },\n\n // ─── Extensions ────────────────────────────────────────────────────\n extension: {\n create: async (config, options) => {\n const client = createClient(config);\n const buf = typeof options.file === 'string'\n ? Buffer.from(options.file, 'utf-8')\n : Buffer.from(options.file);\n const file = await toFile(buf, options.name ?? 'extension.zip');\n const result = await client.extensions.upload({ file });\n return {\n extensionId: result.id,\n name: result.name,\n } satisfies BrowserExtension;\n },\n\n get: async (config, extensionId) => {\n const client = createClient(config);\n // Steel exposes only a list endpoint for extension lookup — filter the result.\n const response = await client.extensions.list();\n const found = response.extensions.find((e) => e.id === extensionId);\n if (!found) return null;\n return { extensionId: found.id, name: found.name } satisfies BrowserExtension;\n },\n\n delete: async (config, extensionId) => {\n const client = createClient(config);\n await client.extensions.delete(extensionId);\n },\n },\n\n // ─── Logs (RRWeb session events) ───────────────────────────────────\n // Steel doesn't expose a console-log endpoint — the closest equivalent\n // is the RRWeb event stream from `sessions.events`. We surface those as\n // log entries so callers have a uniform shape.\n logs: {\n list: async (config, sessionId) => {\n const client = createClient(config);\n const events = await client.sessions.events(sessionId);\n return (events as Array<Record<string, unknown>>).map((event) => {\n const ts = typeof event.timestamp === 'number' ? event.timestamp : Date.now();\n return {\n timestamp: new Date(ts),\n level: 'info' as BrowserLog['level'],\n message: typeof event.type === 'string' || typeof event.type === 'number'\n ? `event:${String(event.type)}`\n : 'event',\n data: event,\n } satisfies BrowserLog;\n });\n },\n },\n\n // ─── Recordings ────────────────────────────────────────────────────\n recording: {\n get: async (config, sessionId) => {\n const client = createClient(config);\n try {\n const events = await client.sessions.events(sessionId);\n if (!events || (Array.isArray(events) && events.length === 0)) return null;\n return {\n recordingId: sessionId,\n sessionId,\n format: 'rrweb',\n data: events,\n } satisfies BrowserRecording;\n } catch {\n return null;\n }\n },\n },\n },\n});\n"],"mappings":"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","names":["Steel"]}
package/dist/index.js CHANGED
@@ -63,11 +63,11 @@ function mapSessionOptions(options) {
63
63
  params.profileId = options.profileId;
64
64
  params.persistProfile = true;
65
65
  }
66
- if (options.stealth) {
67
- params.stealthConfig = { humanizeInteractions: true };
66
+ if (options.stealth !== void 0) {
67
+ params.stealthConfig = { humanizeInteractions: options.stealth };
68
68
  }
69
- if (options.proxies === true) {
70
- params.useProxy = true;
69
+ if (typeof options.proxies === "boolean") {
70
+ params.useProxy = options.proxies;
71
71
  } else if (Array.isArray(options.proxies) && options.proxies.length > 0) {
72
72
  applyProxy(params, options.proxies[0]);
73
73
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Steel Browser Provider - Factory-based Implementation\n *\n * Cloud browser sessions with stealth mode, proxies, persistent profiles,\n * extensions, and session recordings via the Steel platform.\n */\n\nimport Steel, { toFile } from 'steel-sdk';\nimport type {\n Session as SteelSession,\n SessionCreateParams,\n Sessionslist,\n} from 'steel-sdk/resources/sessions';\nimport { defineBrowserProvider } from '@computesdk/provider';\n\nimport type {\n BrowserSession,\n CreateBrowserSessionOptions,\n BrowserExtension,\n BrowserLog,\n BrowserRecording,\n ProxyConfig,\n} from '@computesdk/provider';\n\n/**\n * Steel-specific configuration options\n */\nexport interface SteelConfig {\n /** Steel API key — falls back to STEEL_API_KEY env var */\n apiKey?: string;\n /** Optional API base URL override (defaults to https://api.steel.dev) */\n baseUrl?: string;\n}\n\n/**\n * The native Steel session object returned by their SDK\n */\ntype NativeSteelSession = SteelSession;\n\n/**\n * Resolve config values from explicit config or environment variables\n */\nfunction resolveConfig(config: SteelConfig) {\n const apiKey = config.apiKey || (typeof process !== 'undefined' && process.env?.STEEL_API_KEY) || '';\n\n if (!apiKey) {\n throw new Error(\n `Missing Steel API key. Provide 'apiKey' in config or set STEEL_API_KEY environment variable. ` +\n `Get your API key from https://app.steel.dev/settings/api-keys`\n );\n }\n\n return { apiKey, baseUrl: config.baseUrl };\n}\n\n/**\n * Create a Steel SDK client from config\n */\nfunction createClient(config: SteelConfig): Steel {\n const { apiKey, baseUrl } = resolveConfig(config);\n return new Steel({ steelAPIKey: apiKey, baseURL: baseUrl ?? undefined });\n}\n\n/**\n * Steel's `useProxy` field accepts either a boolean, a geolocation object,\n * or a custom server. Apply a single ProxyConfig onto the create-session params.\n * Steel only supports one proxy per session, so when multiple are passed\n * we honor the first one.\n */\nfunction applyProxy(params: SessionCreateParams, proxy: ProxyConfig) {\n if (proxy.type === 'custom' && proxy.server) {\n // Steel uses a separate proxyUrl field for custom proxies; this overrides\n // useProxy and disables Steel-provided proxies in favor of the user's.\n const url = new URL(proxy.server);\n if (proxy.username) url.username = proxy.username;\n if (proxy.password) url.password = proxy.password;\n params.proxyUrl = url.toString();\n return;\n }\n\n if (proxy.geolocation?.country || proxy.geolocation?.state || proxy.geolocation?.city) {\n params.useProxy = {\n geolocation: {\n country: proxy.geolocation.country as any,\n ...(proxy.geolocation.state ? { state: proxy.geolocation.state as any } : {}),\n ...(proxy.geolocation.city ? { city: proxy.geolocation.city as any } : {}),\n },\n };\n return;\n }\n\n params.useProxy = true;\n}\n\nconst warnedUnsupported = new Set<string>();\nfunction warnOnce(field: string, reason: string) {\n if (warnedUnsupported.has(field)) return;\n warnedUnsupported.add(field);\n console.warn(`[@computesdk/steel] '${field}' is ignored: ${reason}`);\n}\n\n/**\n * Map ComputeSDK session options to Steel create-session params.\n *\n * The following CreateBrowserSessionOptions fields are intentionally not\n * forwarded because Steel's API has no equivalent knob:\n * - `recording`: Steel always records sessions; fetch via the recording manager.\n * - `logging`: Steel always captures console logs; fetch via the logs manager.\n * - `keepAlive`: Steel sessions run until `timeout` elapses or are released\n * explicitly; there is no separate keep-alive flag. Use a long\n * `timeout` to approximate the behavior.\n * - `userMetadata`: Steel does not support arbitrary user metadata on sessions.\n *\n * If a caller passes a non-default value for any of these, we warn once per\n * field so the silent drop is at least visible in logs.\n */\nfunction mapSessionOptions(options?: CreateBrowserSessionOptions): SessionCreateParams {\n if (!options) return {};\n\n if (options.keepAlive === true) {\n warnOnce('keepAlive', 'Steel has no keep-alive flag; use a long `timeout` instead.');\n }\n if (options.recording === false) {\n warnOnce('recording', 'Steel always records sessions and cannot be disabled per session.');\n }\n if (options.logging === false) {\n warnOnce('logging', 'Steel always captures console logs and cannot be disabled per session.');\n }\n if (options.userMetadata && Object.keys(options.userMetadata).length > 0) {\n warnOnce('userMetadata', 'Steel does not support user metadata on sessions.');\n }\n\n const params: SessionCreateParams = {};\n\n if (options.viewport) params.dimensions = { width: options.viewport.width, height: options.viewport.height };\n if (options.extensionIds && options.extensionIds.length > 0) params.extensionIds = options.extensionIds;\n if (options.region) params.region = options.region as SessionCreateParams['region'];\n // Steel session timeout is in milliseconds (default 5 minutes)\n if (options.timeout !== undefined) params.timeout = options.timeout * 1000;\n if (options.profileId) {\n params.profileId = options.profileId;\n params.persistProfile = true;\n }\n\n // Stealth wraps a few related flags. We toggle humanizeInteractions as the\n // most user-visible knob; advanced users can override via options pass-through.\n if (options.stealth) {\n params.stealthConfig = { humanizeInteractions: true };\n }\n\n if (options.proxies === true) {\n params.useProxy = true;\n } else if (Array.isArray(options.proxies) && options.proxies.length > 0) {\n applyProxy(params, options.proxies[0]!);\n }\n\n return params;\n}\n\n/**\n * Map Steel's session status onto our standard set\n */\nfunction mapStatus(status: string | undefined): BrowserSession['status'] {\n switch (status) {\n case 'live': return 'running';\n case 'released': return 'completed';\n case 'failed': return 'failed';\n default: return 'created';\n }\n}\n\n/**\n * Append the apiKey (and sessionId if missing) to Steel's websocketUrl so\n * callers can pass it directly to playwright/puppeteer. Steel returns\n * `wss://connect.steel.dev?sessionId=...` and requires `apiKey` to be\n * appended for authentication.\n *\n * SECURITY: the returned URL embeds the account-wide Steel API key as a\n * query parameter. Treat it as a secret — do not log it, persist it, or\n * forward it to systems that retain URLs (access logs, error trackers,\n * tracing). Anyone with this URL has full access to the Steel account, not\n * just this session.\n */\nfunction buildConnectUrl(websocketUrl: string, apiKey: string, sessionId: string): string {\n const url = new URL(websocketUrl);\n if (!url.searchParams.has('apiKey')) url.searchParams.set('apiKey', apiKey);\n if (!url.searchParams.has('sessionId')) url.searchParams.set('sessionId', sessionId);\n return url.toString();\n}\n\n/**\n * Create a Steel browser provider instance\n *\n * SECURITY NOTE: Steel's CDP endpoint authenticates via an `apiKey` query\n * parameter on the WebSocket URL. As a result, every `connectUrl` returned\n * by this provider (from `session.create()` and `session.getById()`, and\n * `provider.getConnectUrl()`) embeds the account-wide Steel API key. Treat\n * `connectUrl` as a secret: do not log, persist, or forward it to systems\n * that retain URLs. `session.list()` deliberately omits `connectUrl` to\n * avoid amplifying this leak surface — call `provider.getConnectUrl(id)` on\n * demand instead.\n *\n * @example\n * ```ts\n * import { steel } from '@computesdk/steel';\n * import { chromium } from 'playwright-core';\n *\n * const st = steel({ apiKey: process.env.STEEL_API_KEY });\n *\n * // Create a stealth browser session\n * const session = await st.session.create({ stealth: true, proxies: true });\n *\n * // Connect with Playwright (do not log session.connectUrl — it contains the API key)\n * const browser = await chromium.connectOverCDP(session.connectUrl);\n * const page = browser.contexts()[0].pages()[0];\n * await page.goto('https://example.com');\n *\n * // Release the session\n * await session.destroy();\n * ```\n */\nexport const steel = defineBrowserProvider<NativeSteelSession, SteelConfig>({\n name: 'steel',\n methods: {\n // ─── Session Lifecycle ─────────────────────────────────────────────\n session: {\n create: async (config, options) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n const session = await client.sessions.create(mapSessionOptions(options));\n return {\n session,\n sessionId: session.id,\n connectUrl: buildConnectUrl(session.websocketUrl, apiKey, session.id),\n status: mapStatus(session.status),\n };\n },\n\n getById: async (config, sessionId) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n try {\n const session = await client.sessions.retrieve(sessionId);\n return {\n session,\n sessionId: session.id,\n connectUrl: buildConnectUrl(session.websocketUrl, apiKey, session.id),\n status: mapStatus(session.status),\n };\n } catch {\n return null;\n }\n },\n\n list: async (config) => {\n const client = createClient(config);\n const page = await client.sessions.list();\n const sessions = (page as unknown as { sessions: Sessionslist['sessions'] }).sessions ?? [];\n // We deliberately omit connectUrl from list entries: the URL would\n // need to embed the account-wide Steel API key, and emitting it once\n // per session inflates the leak surface (logs, traces, etc.). Callers\n // that need a connectable URL should use getConnectUrl(sessionId).\n return sessions.map((s) => ({\n session: s as NativeSteelSession,\n sessionId: s.id,\n status: mapStatus(s.status),\n }));\n },\n\n destroy: async (config, sessionId) => {\n const client = createClient(config);\n await client.sessions.release(sessionId);\n },\n\n getConnectUrl: async (config, sessionId) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n const session = await client.sessions.retrieve(sessionId);\n return buildConnectUrl(session.websocketUrl, apiKey, session.id);\n },\n },\n\n // ─── Extensions ────────────────────────────────────────────────────\n extension: {\n create: async (config, options) => {\n const client = createClient(config);\n const buf = typeof options.file === 'string'\n ? Buffer.from(options.file, 'utf-8')\n : Buffer.from(options.file);\n const file = await toFile(buf, options.name ?? 'extension.zip');\n const result = await client.extensions.upload({ file });\n return {\n extensionId: result.id,\n name: result.name,\n } satisfies BrowserExtension;\n },\n\n get: async (config, extensionId) => {\n const client = createClient(config);\n // Steel exposes only a list endpoint for extension lookup — filter the result.\n const response = await client.extensions.list();\n const found = response.extensions.find((e) => e.id === extensionId);\n if (!found) return null;\n return { extensionId: found.id, name: found.name } satisfies BrowserExtension;\n },\n\n delete: async (config, extensionId) => {\n const client = createClient(config);\n await client.extensions.delete(extensionId);\n },\n },\n\n // ─── Logs (RRWeb session events) ───────────────────────────────────\n // Steel doesn't expose a console-log endpoint — the closest equivalent\n // is the RRWeb event stream from `sessions.events`. We surface those as\n // log entries so callers have a uniform shape.\n logs: {\n list: async (config, sessionId) => {\n const client = createClient(config);\n const events = await client.sessions.events(sessionId);\n return (events as Array<Record<string, unknown>>).map((event) => {\n const ts = typeof event.timestamp === 'number' ? event.timestamp : Date.now();\n return {\n timestamp: new Date(ts),\n level: 'info' as BrowserLog['level'],\n message: typeof event.type === 'string' || typeof event.type === 'number'\n ? `event:${String(event.type)}`\n : 'event',\n data: event,\n } satisfies BrowserLog;\n });\n },\n },\n\n // ─── Recordings ────────────────────────────────────────────────────\n recording: {\n get: async (config, sessionId) => {\n const client = createClient(config);\n try {\n const events = await client.sessions.events(sessionId);\n if (!events || (Array.isArray(events) && events.length === 0)) return null;\n return {\n recordingId: sessionId,\n sessionId,\n format: 'rrweb',\n data: events,\n } satisfies BrowserRecording;\n } catch {\n return null;\n }\n },\n },\n },\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Steel Browser Provider - Factory-based Implementation\n *\n * Cloud browser sessions with stealth mode, proxies, persistent profiles,\n * extensions, and session recordings via the Steel platform.\n */\n\nimport Steel, { toFile } from 'steel-sdk';\nimport type {\n Session as SteelSession,\n SessionCreateParams,\n Sessionslist,\n} from 'steel-sdk/resources/sessions';\nimport { defineBrowserProvider } from '@computesdk/provider';\n\nimport type {\n BrowserSession,\n CreateBrowserSessionOptions,\n BrowserExtension,\n BrowserLog,\n BrowserRecording,\n ProxyConfig,\n} from '@computesdk/provider';\n\n/**\n * Steel-specific configuration options\n */\nexport interface SteelConfig {\n /** Steel API key — falls back to STEEL_API_KEY env var */\n apiKey?: string;\n /** Optional API base URL override (defaults to https://api.steel.dev) */\n baseUrl?: string;\n}\n\n/**\n * The native Steel session object returned by their SDK\n */\ntype NativeSteelSession = SteelSession;\n\n/**\n * Resolve config values from explicit config or environment variables\n */\nfunction resolveConfig(config: SteelConfig) {\n const apiKey = config.apiKey || (typeof process !== 'undefined' && process.env?.STEEL_API_KEY) || '';\n\n if (!apiKey) {\n throw new Error(\n `Missing Steel API key. Provide 'apiKey' in config or set STEEL_API_KEY environment variable. ` +\n `Get your API key from https://app.steel.dev/settings/api-keys`\n );\n }\n\n return { apiKey, baseUrl: config.baseUrl };\n}\n\n/**\n * Create a Steel SDK client from config\n */\nfunction createClient(config: SteelConfig): Steel {\n const { apiKey, baseUrl } = resolveConfig(config);\n return new Steel({ steelAPIKey: apiKey, baseURL: baseUrl ?? undefined });\n}\n\n/**\n * Steel's `useProxy` field accepts either a boolean, a geolocation object,\n * or a custom server. Apply a single ProxyConfig onto the create-session params.\n * Steel only supports one proxy per session, so when multiple are passed\n * we honor the first one.\n */\nfunction applyProxy(params: SessionCreateParams, proxy: ProxyConfig) {\n if (proxy.type === 'custom' && proxy.server) {\n // Steel uses a separate proxyUrl field for custom proxies; this overrides\n // useProxy and disables Steel-provided proxies in favor of the user's.\n const url = new URL(proxy.server);\n if (proxy.username) url.username = proxy.username;\n if (proxy.password) url.password = proxy.password;\n params.proxyUrl = url.toString();\n return;\n }\n\n if (proxy.geolocation?.country || proxy.geolocation?.state || proxy.geolocation?.city) {\n params.useProxy = {\n geolocation: {\n country: proxy.geolocation.country as any,\n ...(proxy.geolocation.state ? { state: proxy.geolocation.state as any } : {}),\n ...(proxy.geolocation.city ? { city: proxy.geolocation.city as any } : {}),\n },\n };\n return;\n }\n\n params.useProxy = true;\n}\n\nconst warnedUnsupported = new Set<string>();\nfunction warnOnce(field: string, reason: string) {\n if (warnedUnsupported.has(field)) return;\n warnedUnsupported.add(field);\n console.warn(`[@computesdk/steel] '${field}' is ignored: ${reason}`);\n}\n\n/**\n * Map ComputeSDK session options to Steel create-session params.\n *\n * The following CreateBrowserSessionOptions fields are intentionally not\n * forwarded because Steel's API has no equivalent knob:\n * - `recording`: Steel always records sessions; fetch via the recording manager.\n * - `logging`: Steel always captures console logs; fetch via the logs manager.\n * - `keepAlive`: Steel sessions run until `timeout` elapses or are released\n * explicitly; there is no separate keep-alive flag. Use a long\n * `timeout` to approximate the behavior.\n * - `userMetadata`: Steel does not support arbitrary user metadata on sessions.\n *\n * If a caller passes a non-default value for any of these, we warn once per\n * field so the silent drop is at least visible in logs.\n */\nfunction mapSessionOptions(options?: CreateBrowserSessionOptions): SessionCreateParams {\n if (!options) return {};\n\n if (options.keepAlive === true) {\n warnOnce('keepAlive', 'Steel has no keep-alive flag; use a long `timeout` instead.');\n }\n if (options.recording === false) {\n warnOnce('recording', 'Steel always records sessions and cannot be disabled per session.');\n }\n if (options.logging === false) {\n warnOnce('logging', 'Steel always captures console logs and cannot be disabled per session.');\n }\n if (options.userMetadata && Object.keys(options.userMetadata).length > 0) {\n warnOnce('userMetadata', 'Steel does not support user metadata on sessions.');\n }\n\n const params: SessionCreateParams = {};\n\n if (options.viewport) params.dimensions = { width: options.viewport.width, height: options.viewport.height };\n if (options.extensionIds && options.extensionIds.length > 0) params.extensionIds = options.extensionIds;\n if (options.region) params.region = options.region as SessionCreateParams['region'];\n // Steel session timeout is in milliseconds (default 5 minutes)\n if (options.timeout !== undefined) params.timeout = options.timeout * 1000;\n if (options.profileId) {\n params.profileId = options.profileId;\n params.persistProfile = true;\n }\n\n // Stealth wraps a few related flags. We toggle humanizeInteractions as the\n // most user-visible knob and preserve explicit false values.\n if (options.stealth !== undefined) {\n params.stealthConfig = { humanizeInteractions: options.stealth };\n }\n\n if (typeof options.proxies === 'boolean') {\n params.useProxy = options.proxies;\n } else if (Array.isArray(options.proxies) && options.proxies.length > 0) {\n applyProxy(params, options.proxies[0]!);\n }\n\n return params;\n}\n\n/**\n * Map Steel's session status onto our standard set\n */\nfunction mapStatus(status: string | undefined): BrowserSession['status'] {\n switch (status) {\n case 'live': return 'running';\n case 'released': return 'completed';\n case 'failed': return 'failed';\n default: return 'created';\n }\n}\n\n/**\n * Append the apiKey (and sessionId if missing) to Steel's websocketUrl so\n * callers can pass it directly to playwright/puppeteer. Steel returns\n * `wss://connect.steel.dev?sessionId=...` and requires `apiKey` to be\n * appended for authentication.\n *\n * SECURITY: the returned URL embeds the account-wide Steel API key as a\n * query parameter. Treat it as a secret — do not log it, persist it, or\n * forward it to systems that retain URLs (access logs, error trackers,\n * tracing). Anyone with this URL has full access to the Steel account, not\n * just this session.\n */\nfunction buildConnectUrl(websocketUrl: string, apiKey: string, sessionId: string): string {\n const url = new URL(websocketUrl);\n if (!url.searchParams.has('apiKey')) url.searchParams.set('apiKey', apiKey);\n if (!url.searchParams.has('sessionId')) url.searchParams.set('sessionId', sessionId);\n return url.toString();\n}\n\n/**\n * Create a Steel browser provider instance\n *\n * SECURITY NOTE: Steel's CDP endpoint authenticates via an `apiKey` query\n * parameter on the WebSocket URL. As a result, every `connectUrl` returned\n * by this provider (from `session.create()` and `session.getById()`, and\n * `provider.getConnectUrl()`) embeds the account-wide Steel API key. Treat\n * `connectUrl` as a secret: do not log, persist, or forward it to systems\n * that retain URLs. `session.list()` deliberately omits `connectUrl` to\n * avoid amplifying this leak surface — call `provider.getConnectUrl(id)` on\n * demand instead.\n *\n * @example\n * ```ts\n * import { steel } from '@computesdk/steel';\n * import { chromium } from 'playwright-core';\n *\n * const st = steel({ apiKey: process.env.STEEL_API_KEY });\n *\n * // Create a stealth browser session\n * const session = await st.session.create({ stealth: true, proxies: true });\n *\n * // Connect with Playwright (do not log session.connectUrl — it contains the API key)\n * const browser = await chromium.connectOverCDP(session.connectUrl);\n * const page = browser.contexts()[0].pages()[0];\n * await page.goto('https://example.com');\n *\n * // Release the session\n * await session.destroy();\n * ```\n */\nexport const steel = defineBrowserProvider<NativeSteelSession, SteelConfig>({\n name: 'steel',\n methods: {\n // ─── Session Lifecycle ─────────────────────────────────────────────\n session: {\n create: async (config, options) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n const session = await client.sessions.create(mapSessionOptions(options));\n return {\n session,\n sessionId: session.id,\n connectUrl: buildConnectUrl(session.websocketUrl, apiKey, session.id),\n status: mapStatus(session.status),\n };\n },\n\n getById: async (config, sessionId) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n try {\n const session = await client.sessions.retrieve(sessionId);\n return {\n session,\n sessionId: session.id,\n connectUrl: buildConnectUrl(session.websocketUrl, apiKey, session.id),\n status: mapStatus(session.status),\n };\n } catch {\n return null;\n }\n },\n\n list: async (config) => {\n const client = createClient(config);\n const page = await client.sessions.list();\n const sessions = (page as unknown as { sessions: Sessionslist['sessions'] }).sessions ?? [];\n // We deliberately omit connectUrl from list entries: the URL would\n // need to embed the account-wide Steel API key, and emitting it once\n // per session inflates the leak surface (logs, traces, etc.). Callers\n // that need a connectable URL should use getConnectUrl(sessionId).\n return sessions.map((s) => ({\n session: s as NativeSteelSession,\n sessionId: s.id,\n status: mapStatus(s.status),\n }));\n },\n\n destroy: async (config, sessionId) => {\n const client = createClient(config);\n await client.sessions.release(sessionId);\n },\n\n getConnectUrl: async (config, sessionId) => {\n const { apiKey } = resolveConfig(config);\n const client = createClient(config);\n const session = await client.sessions.retrieve(sessionId);\n return buildConnectUrl(session.websocketUrl, apiKey, session.id);\n },\n },\n\n // ─── Extensions ────────────────────────────────────────────────────\n extension: {\n create: async (config, options) => {\n const client = createClient(config);\n const buf = typeof options.file === 'string'\n ? Buffer.from(options.file, 'utf-8')\n : Buffer.from(options.file);\n const file = await toFile(buf, options.name ?? 'extension.zip');\n const result = await client.extensions.upload({ file });\n return {\n extensionId: result.id,\n name: result.name,\n } satisfies BrowserExtension;\n },\n\n get: async (config, extensionId) => {\n const client = createClient(config);\n // Steel exposes only a list endpoint for extension lookup — filter the result.\n const response = await client.extensions.list();\n const found = response.extensions.find((e) => e.id === extensionId);\n if (!found) return null;\n return { extensionId: found.id, name: found.name } satisfies BrowserExtension;\n },\n\n delete: async (config, extensionId) => {\n const client = createClient(config);\n await client.extensions.delete(extensionId);\n },\n },\n\n // ─── Logs (RRWeb session events) ───────────────────────────────────\n // Steel doesn't expose a console-log endpoint — the closest equivalent\n // is the RRWeb event stream from `sessions.events`. We surface those as\n // log entries so callers have a uniform shape.\n logs: {\n list: async (config, sessionId) => {\n const client = createClient(config);\n const events = await client.sessions.events(sessionId);\n return (events as Array<Record<string, unknown>>).map((event) => {\n const ts = typeof event.timestamp === 'number' ? event.timestamp : Date.now();\n return {\n timestamp: new Date(ts),\n level: 'info' as BrowserLog['level'],\n message: typeof event.type === 'string' || typeof event.type === 'number'\n ? `event:${String(event.type)}`\n : 'event',\n data: event,\n } satisfies BrowserLog;\n });\n },\n },\n\n // ─── Recordings ────────────────────────────────────────────────────\n recording: {\n get: async (config, sessionId) => {\n const client = createClient(config);\n try {\n const events = await client.sessions.events(sessionId);\n if (!events || (Array.isArray(events) && events.length === 0)) return null;\n return {\n recordingId: sessionId,\n sessionId,\n format: 'rrweb',\n data: events,\n } satisfies BrowserRecording;\n } catch {\n return null;\n }\n },\n },\n },\n});\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@computesdk/steel",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Steel browser provider for ComputeSDK - cloud browser sessions with stealth mode, proxies, profiles, and session recording",
5
5
  "author": "Garrison",
6
6
  "license": "MIT",