@solarisdk/browser 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @solarisdk/browser
2
2
 
3
- Official client SDK for [Solari Browser](https://solaribrowser.com) — managed,
3
+ Official client SDK for [Solari Browser](https://getsolari.com) — managed,
4
4
  stealth-hardened browsers as an API.
5
5
 
6
6
  ## Install
@@ -85,7 +85,7 @@ page is detected mid-session — no client-side retry required.
85
85
  await solari.launch({ stealth: true, proxy: "smart" });
86
86
  ```
87
87
 
88
- See the [Solari docs](https://docs.solaribrowser.com) for the full proxy,
88
+ See the [Solari docs](https://docs.getsolari.com) for the full proxy,
89
89
  stealth, captcha, and profile reference.
90
90
 
91
91
  ## Errors
@@ -11,7 +11,8 @@ export declare class BrowserSession {
11
11
  get id(): string;
12
12
  /** Plan-tier deadline (ISO 8601 UTC); session auto-releases at this point. */
13
13
  get expiresAt(): string;
14
- /** Resolved proxy credentials, when a proxy was requested. */
14
+ /** Resolved proxy confirmation (country/tier/timezone — no credentials),
15
+ * when a proxy was requested. */
15
16
  get proxy(): Session["proxy"];
16
17
  /** Playwright wire-protocol endpoint (loopback-wrapped). */
17
18
  get wsEndpoint(): string;
@@ -16,7 +16,8 @@ export class BrowserSession {
16
16
  get expiresAt() {
17
17
  return this.session.expiresAt;
18
18
  }
19
- /** Resolved proxy credentials, when a proxy was requested. */
19
+ /** Resolved proxy confirmation (country/tier/timezone — no credentials),
20
+ * when a proxy was requested. */
20
21
  get proxy() {
21
22
  return this.session.proxy;
22
23
  }
package/dist/index.d.ts CHANGED
@@ -56,13 +56,23 @@ export interface ProxyRequest {
56
56
  /** US-only city pin (e.g. "los_angeles"). */
57
57
  city?: string;
58
58
  }
59
- /** Resolved proxy credentials returned on the session response. */
59
+ /**
60
+ * Coarse confirmation of the proxy the gateway resolved for a session.
61
+ *
62
+ * Carries NO credentials by design. Egress is applied server-side (the pool
63
+ * attaches the upstream to your session), so a caller never dials the proxy
64
+ * and never needs its address or account. Earlier builds returned the
65
+ * upstream `server`/`username`/`password` here; that was a disclosure of our
66
+ * proxy vendor's account and has been removed.
67
+ */
60
68
  export interface ResolvedProxyConfig {
61
- server: string;
62
- username: string;
63
- password: string;
69
+ /** Timezone matching the egress country — pass to `newContext({ timezoneId })`
70
+ * if you build your own context and want Intl/Date to line up. */
64
71
  timezoneId: string;
72
+ /** Egress country actually resolved (lowercase ISO code). */
65
73
  country: string;
74
+ /** Tier that actually served the session. A `tier: "mobile"` request can
75
+ * degrade to residential; compare against what you asked for. */
66
76
  tier?: "residential" | "static" | "mobile";
67
77
  }
68
78
  export interface Session {
@@ -75,7 +85,8 @@ export interface Session {
75
85
  expiresAt: string;
76
86
  /** `undefined` = no profile attached; `null` = profile exists but empty. */
77
87
  storageState?: StorageState | null;
78
- /** Present only when a managed proxy was requested. */
88
+ /** Present only when a managed proxy was requested. Confirmation only
89
+ * (country/tier/timezone) — egress is applied server-side. */
79
90
  proxy?: ResolvedProxyConfig;
80
91
  }
81
92
  export interface Profile {
@@ -124,7 +135,7 @@ export interface LaunchOptions extends CreateSessionOptions {
124
135
  /** Hard upper bound on the post-connect probe in ms. Default 2000. */
125
136
  probeTimeoutMs?: number;
126
137
  }
127
- export type SolariErrorCode = "FeatureRequiresPlan" | "ConcurrencyLimitExceeded" | "PlanLimitExceeded" | "BrowserUnhealthy";
138
+ export type SolariErrorCode = "FeatureRequiresPlan" | "ConcurrencyLimitExceeded" | "PlanLimitExceeded" | "BrowserUnhealthy" | "InvalidSessionId";
128
139
  export declare class SolariError extends Error {
129
140
  readonly status?: number | undefined;
130
141
  readonly cause?: unknown | undefined;
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ export class SolariError extends Error {
15
15
  }
16
16
  }
17
17
  const REGION_URLS = {
18
- "us-west": "https://api.solaribrowser.com",
18
+ "us-west": "https://api.getsolari.com",
19
19
  };
20
20
  const DEFAULT_REGION = "us-west";
21
21
  const DEFAULT_MAX_ATTEMPTS = 2;
@@ -120,6 +120,39 @@ class ProfilesResource {
120
120
  };
121
121
  }
122
122
  }
123
+ /**
124
+ * Classify a DELETE /sessions/:id response. Returns null when the
125
+ * release genuinely succeeded (or was a no-op), or the failure detail.
126
+ *
127
+ * Why 404 is not blanket-success here: the gateway acks 204 for any
128
+ * authentic session id, including one whose session is already gone —
129
+ * the handler is idempotent by design and never consults the pool
130
+ * before acking. So a 404 does NOT mean "already released"; it means
131
+ * the gateway *refused* the id (malformed, forged, or another org's)
132
+ * and released nothing, leaving the pool slot held until orphan-grace.
133
+ * Treating that as success is what made expired-session-id releases
134
+ * leak slots silently.
135
+ *
136
+ * A bare 404 with no `code` is still tolerated: that's a pre-
137
+ * InvalidSessionId gateway, where 404 may legitimately mean the session
138
+ * is already gone. Only a 404 the gateway explicitly marked
139
+ * `InvalidSessionId` is surfaced as a failure.
140
+ */
141
+ async function releaseRejection(res) {
142
+ if (res.ok)
143
+ return null;
144
+ const body = await res.text().catch(() => "");
145
+ let code;
146
+ try {
147
+ const parsed = JSON.parse(body);
148
+ if (typeof parsed?.code === "string")
149
+ code = parsed.code;
150
+ }
151
+ catch { }
152
+ if (res.status === 404 && code !== "InvalidSessionId")
153
+ return null;
154
+ return { body, code };
155
+ }
123
156
  class SessionsResource {
124
157
  client;
125
158
  constructor(client) {
@@ -182,11 +215,12 @@ class SessionsResource {
182
215
  /** Fire-and-forget release. Use `releaseAndWait` for confirmation. */
183
216
  release(id) {
184
217
  this.client._unregisterSession(id);
185
- void this.client.request("DELETE", `/sessions/${encodeURIComponent(id)}`).then((res) => {
186
- if (!res.ok && res.status !== 404) {
187
- res.text().catch(() => "").then((body) => {
188
- console.warn(`[Solari] DELETE /sessions/${id} returned ${res.status}: ${body}`);
189
- });
218
+ void this.client.request("DELETE", `/sessions/${encodeURIComponent(id)}`).then(async (res) => {
219
+ const rejection = await releaseRejection(res);
220
+ if (rejection) {
221
+ console.warn(`[Solari] DELETE /sessions/${id} returned ${res.status}: ` +
222
+ `${rejection.body} — session was NOT released; its pool ` +
223
+ `slot stays held until the server-side grace timer reaps it`);
190
224
  }
191
225
  }, (err) => {
192
226
  console.warn(`[Solari] DELETE /sessions/${id} failed (fire-and-forget):`, err);
@@ -195,9 +229,9 @@ class SessionsResource {
195
229
  async releaseAndWait(id) {
196
230
  this.client._unregisterSession(id);
197
231
  const res = await this.client.request("DELETE", `/sessions/${encodeURIComponent(id)}`);
198
- if (!res.ok && res.status !== 404) {
199
- const body = await res.text().catch(() => "");
200
- throw new SolariError(`Solari DELETE /sessions/${id} failed: ${res.status} ${body}`, res.status);
232
+ const rejection = await releaseRejection(res);
233
+ if (rejection) {
234
+ throw new SolariError(`Solari DELETE /sessions/${id} failed: ${res.status} ${rejection.body}`, res.status, undefined, rejection.code);
201
235
  }
202
236
  }
203
237
  /** Presigned URL for the session's replay. Available ~1-3s after `releaseAndWait`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solarisdk/browser",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Official client SDK for Solari Browser.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",