@computesdk/steel 0.2.23 → 0.2.25
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 +31 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +31 -5
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -125,6 +125,30 @@ function buildConnectUrl(websocketUrl, apiKey, sessionId) {
|
|
|
125
125
|
if (!url.searchParams.has("sessionId")) url.searchParams.set("sessionId", sessionId);
|
|
126
126
|
return url.toString();
|
|
127
127
|
}
|
|
128
|
+
function isMissingSessionError(error) {
|
|
129
|
+
return error instanceof import_steel_sdk.default.APIError && [400, 404, 422].includes(error.status);
|
|
130
|
+
}
|
|
131
|
+
var PROPAGATION_WINDOW_MS = 3e4;
|
|
132
|
+
var recentlyCreated = /* @__PURE__ */ new Map();
|
|
133
|
+
function rememberCreated(sessionId) {
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
for (const [id, at] of recentlyCreated) {
|
|
136
|
+
if (now - at > PROPAGATION_WINDOW_MS) recentlyCreated.delete(id);
|
|
137
|
+
}
|
|
138
|
+
recentlyCreated.set(sessionId, now);
|
|
139
|
+
}
|
|
140
|
+
async function withPropagationRetry(sessionId, fn) {
|
|
141
|
+
for (; ; ) {
|
|
142
|
+
try {
|
|
143
|
+
return await fn();
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const createdAt = recentlyCreated.get(sessionId);
|
|
146
|
+
const pending = createdAt !== void 0 && Date.now() - createdAt < PROPAGATION_WINDOW_MS;
|
|
147
|
+
if (!pending || !(error instanceof import_steel_sdk.default.NotFoundError)) throw error;
|
|
148
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
128
152
|
var steel = (0, import_provider.defineBrowserProvider)({
|
|
129
153
|
name: "steel",
|
|
130
154
|
methods: {
|
|
@@ -134,6 +158,7 @@ var steel = (0, import_provider.defineBrowserProvider)({
|
|
|
134
158
|
const { apiKey } = resolveConfig(config);
|
|
135
159
|
const client = createClient(config);
|
|
136
160
|
const session = await client.sessions.create(mapSessionOptions(options));
|
|
161
|
+
rememberCreated(session.id);
|
|
137
162
|
return {
|
|
138
163
|
session,
|
|
139
164
|
sessionId: session.id,
|
|
@@ -145,15 +170,16 @@ var steel = (0, import_provider.defineBrowserProvider)({
|
|
|
145
170
|
const { apiKey } = resolveConfig(config);
|
|
146
171
|
const client = createClient(config);
|
|
147
172
|
try {
|
|
148
|
-
const session = await client.sessions.retrieve(sessionId);
|
|
173
|
+
const session = await withPropagationRetry(sessionId, () => client.sessions.retrieve(sessionId));
|
|
149
174
|
return {
|
|
150
175
|
session,
|
|
151
176
|
sessionId: session.id,
|
|
152
177
|
connectUrl: buildConnectUrl(session.websocketUrl, apiKey, session.id),
|
|
153
178
|
status: mapStatus(session.status)
|
|
154
179
|
};
|
|
155
|
-
} catch {
|
|
156
|
-
return null;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (isMissingSessionError(error)) return null;
|
|
182
|
+
throw error;
|
|
157
183
|
}
|
|
158
184
|
},
|
|
159
185
|
list: async (config) => {
|
|
@@ -168,12 +194,12 @@ var steel = (0, import_provider.defineBrowserProvider)({
|
|
|
168
194
|
},
|
|
169
195
|
destroy: async (config, sessionId) => {
|
|
170
196
|
const client = createClient(config);
|
|
171
|
-
await client.sessions.release(sessionId);
|
|
197
|
+
await withPropagationRetry(sessionId, () => client.sessions.release(sessionId));
|
|
172
198
|
},
|
|
173
199
|
getConnectUrl: async (config, sessionId) => {
|
|
174
200
|
const { apiKey } = resolveConfig(config);
|
|
175
201
|
const client = createClient(config);
|
|
176
|
-
const session = await client.sessions.retrieve(sessionId);
|
|
202
|
+
const session = await withPropagationRetry(sessionId, () => client.sessions.retrieve(sessionId));
|
|
177
203
|
return buildConnectUrl(session.websocketUrl, apiKey, session.id);
|
|
178
204
|
}
|
|
179
205
|
},
|
package/dist/index.cjs.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 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"]}
|
|
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 * Steel answers 404 for unknown session ids and 400/422 for ids that are not\n * valid UUIDs; any other failure is a real error and must surface.\n */\nfunction isMissingSessionError(error: unknown): boolean {\n return error instanceof Steel.APIError && [400, 404, 422].includes(error.status as number);\n}\n\n/**\n * Steel's read/release endpoints answer 404 for a few seconds after a session\n * is created. Sessions this process just created are retried through that\n * window; unknown ids fail fast.\n */\nconst PROPAGATION_WINDOW_MS = 30_000;\nconst recentlyCreated = new Map<string, number>();\n\nfunction rememberCreated(sessionId: string) {\n const now = Date.now();\n for (const [id, at] of recentlyCreated) {\n if (now - at > PROPAGATION_WINDOW_MS) recentlyCreated.delete(id);\n }\n recentlyCreated.set(sessionId, now);\n}\n\nasync function withPropagationRetry<T>(sessionId: string, fn: () => Promise<T>): Promise<T> {\n for (;;) {\n try {\n return await fn();\n } catch (error) {\n const createdAt = recentlyCreated.get(sessionId);\n const pending = createdAt !== undefined && Date.now() - createdAt < PROPAGATION_WINDOW_MS;\n if (!pending || !(error instanceof Steel.NotFoundError)) throw error;\n await new Promise((resolve) => setTimeout(resolve, 500));\n }\n }\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 rememberCreated(session.id);\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 withPropagationRetry(sessionId, () => 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 (error) {\n if (isMissingSessionError(error)) return null;\n throw error;\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 withPropagationRetry(sessionId, () => 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 withPropagationRetry(sessionId, () => 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
|
@@ -91,6 +91,30 @@ function buildConnectUrl(websocketUrl, apiKey, sessionId) {
|
|
|
91
91
|
if (!url.searchParams.has("sessionId")) url.searchParams.set("sessionId", sessionId);
|
|
92
92
|
return url.toString();
|
|
93
93
|
}
|
|
94
|
+
function isMissingSessionError(error) {
|
|
95
|
+
return error instanceof Steel.APIError && [400, 404, 422].includes(error.status);
|
|
96
|
+
}
|
|
97
|
+
var PROPAGATION_WINDOW_MS = 3e4;
|
|
98
|
+
var recentlyCreated = /* @__PURE__ */ new Map();
|
|
99
|
+
function rememberCreated(sessionId) {
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
for (const [id, at] of recentlyCreated) {
|
|
102
|
+
if (now - at > PROPAGATION_WINDOW_MS) recentlyCreated.delete(id);
|
|
103
|
+
}
|
|
104
|
+
recentlyCreated.set(sessionId, now);
|
|
105
|
+
}
|
|
106
|
+
async function withPropagationRetry(sessionId, fn) {
|
|
107
|
+
for (; ; ) {
|
|
108
|
+
try {
|
|
109
|
+
return await fn();
|
|
110
|
+
} catch (error) {
|
|
111
|
+
const createdAt = recentlyCreated.get(sessionId);
|
|
112
|
+
const pending = createdAt !== void 0 && Date.now() - createdAt < PROPAGATION_WINDOW_MS;
|
|
113
|
+
if (!pending || !(error instanceof Steel.NotFoundError)) throw error;
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
94
118
|
var steel = defineBrowserProvider({
|
|
95
119
|
name: "steel",
|
|
96
120
|
methods: {
|
|
@@ -100,6 +124,7 @@ var steel = defineBrowserProvider({
|
|
|
100
124
|
const { apiKey } = resolveConfig(config);
|
|
101
125
|
const client = createClient(config);
|
|
102
126
|
const session = await client.sessions.create(mapSessionOptions(options));
|
|
127
|
+
rememberCreated(session.id);
|
|
103
128
|
return {
|
|
104
129
|
session,
|
|
105
130
|
sessionId: session.id,
|
|
@@ -111,15 +136,16 @@ var steel = defineBrowserProvider({
|
|
|
111
136
|
const { apiKey } = resolveConfig(config);
|
|
112
137
|
const client = createClient(config);
|
|
113
138
|
try {
|
|
114
|
-
const session = await client.sessions.retrieve(sessionId);
|
|
139
|
+
const session = await withPropagationRetry(sessionId, () => client.sessions.retrieve(sessionId));
|
|
115
140
|
return {
|
|
116
141
|
session,
|
|
117
142
|
sessionId: session.id,
|
|
118
143
|
connectUrl: buildConnectUrl(session.websocketUrl, apiKey, session.id),
|
|
119
144
|
status: mapStatus(session.status)
|
|
120
145
|
};
|
|
121
|
-
} catch {
|
|
122
|
-
return null;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (isMissingSessionError(error)) return null;
|
|
148
|
+
throw error;
|
|
123
149
|
}
|
|
124
150
|
},
|
|
125
151
|
list: async (config) => {
|
|
@@ -134,12 +160,12 @@ var steel = defineBrowserProvider({
|
|
|
134
160
|
},
|
|
135
161
|
destroy: async (config, sessionId) => {
|
|
136
162
|
const client = createClient(config);
|
|
137
|
-
await client.sessions.release(sessionId);
|
|
163
|
+
await withPropagationRetry(sessionId, () => client.sessions.release(sessionId));
|
|
138
164
|
},
|
|
139
165
|
getConnectUrl: async (config, sessionId) => {
|
|
140
166
|
const { apiKey } = resolveConfig(config);
|
|
141
167
|
const client = createClient(config);
|
|
142
|
-
const session = await client.sessions.retrieve(sessionId);
|
|
168
|
+
const session = await withPropagationRetry(sessionId, () => client.sessions.retrieve(sessionId));
|
|
143
169
|
return buildConnectUrl(session.websocketUrl, apiKey, session.id);
|
|
144
170
|
}
|
|
145
171
|
},
|
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 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":[]}
|
|
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 * Steel answers 404 for unknown session ids and 400/422 for ids that are not\n * valid UUIDs; any other failure is a real error and must surface.\n */\nfunction isMissingSessionError(error: unknown): boolean {\n return error instanceof Steel.APIError && [400, 404, 422].includes(error.status as number);\n}\n\n/**\n * Steel's read/release endpoints answer 404 for a few seconds after a session\n * is created. Sessions this process just created are retried through that\n * window; unknown ids fail fast.\n */\nconst PROPAGATION_WINDOW_MS = 30_000;\nconst recentlyCreated = new Map<string, number>();\n\nfunction rememberCreated(sessionId: string) {\n const now = Date.now();\n for (const [id, at] of recentlyCreated) {\n if (now - at > PROPAGATION_WINDOW_MS) recentlyCreated.delete(id);\n }\n recentlyCreated.set(sessionId, now);\n}\n\nasync function withPropagationRetry<T>(sessionId: string, fn: () => Promise<T>): Promise<T> {\n for (;;) {\n try {\n return await fn();\n } catch (error) {\n const createdAt = recentlyCreated.get(sessionId);\n const pending = createdAt !== undefined && Date.now() - createdAt < PROPAGATION_WINDOW_MS;\n if (!pending || !(error instanceof Steel.NotFoundError)) throw error;\n await new Promise((resolve) => setTimeout(resolve, 500));\n }\n }\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 rememberCreated(session.id);\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 withPropagationRetry(sessionId, () => 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 (error) {\n if (isMissingSessionError(error)) return null;\n throw error;\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 withPropagationRetry(sessionId, () => 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 withPropagationRetry(sessionId, () => 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.
|
|
3
|
+
"version": "0.2.25",
|
|
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",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"dotenv": "^16.0.0",
|
|
23
23
|
"playwright-core": "^1.40.0",
|
|
24
24
|
"steel-sdk": "^0.18.0",
|
|
25
|
-
"
|
|
26
|
-
"computesdk": "
|
|
25
|
+
"computesdk": "4.1.17",
|
|
26
|
+
"@computesdk/provider": "2.1.23"
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|
|
29
29
|
"computesdk",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"tsup": "^8.0.0",
|
|
54
54
|
"typescript": "^5.0.0",
|
|
55
55
|
"vitest": "^1.0.0",
|
|
56
|
-
"@computesdk/test-utils": "2.0.
|
|
56
|
+
"@computesdk/test-utils": "2.0.6"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "tsup",
|