lexmount 0.5.4 → 0.5.6
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 +73 -0
- package/dist/index.d.mts +132 -69
- package/dist/index.d.ts +132 -69
- package/dist/index.js +1066 -825
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1062 -825
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
- package/types/chrome-remote-interface.d.ts +46 -0
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import * as dotenv from "dotenv";
|
|
1
|
+
// src/cdp.ts
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import CDP from "chrome-remote-interface";
|
|
5
4
|
|
|
6
5
|
// src/errors.ts
|
|
7
6
|
var LexmountError = class extends Error {
|
|
@@ -134,1034 +133,1268 @@ function enableLogging(level = "INFO") {
|
|
|
134
133
|
setLogLevel(level);
|
|
135
134
|
}
|
|
136
135
|
|
|
137
|
-
// src/
|
|
136
|
+
// src/cdp.ts
|
|
137
|
+
function hasExplicitPort(url) {
|
|
138
|
+
const authority = url.match(/^[a-z][a-z\d+\-.]*:\/\/([^/?#]+)/i)?.[1] ?? "";
|
|
139
|
+
const host = authority.includes("@") ? authority.slice(authority.lastIndexOf("@") + 1) : authority;
|
|
140
|
+
if (host.startsWith("[")) {
|
|
141
|
+
return /\]:\d+$/.test(host);
|
|
142
|
+
}
|
|
143
|
+
return /:\d+$/.test(host);
|
|
144
|
+
}
|
|
145
|
+
function getDefaultPort(url) {
|
|
146
|
+
const protocol = getProtocol(url);
|
|
147
|
+
if (protocol === "ws") {
|
|
148
|
+
return 80;
|
|
149
|
+
}
|
|
150
|
+
if (protocol === "wss") {
|
|
151
|
+
return 443;
|
|
152
|
+
}
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
155
|
+
function getProtocol(url) {
|
|
156
|
+
return url.match(/^([a-z][a-z\d+\-.]*):\/\//i)?.[1]?.toLowerCase();
|
|
157
|
+
}
|
|
158
|
+
var CDPClient = class extends EventEmitter {
|
|
159
|
+
constructor(session) {
|
|
160
|
+
super();
|
|
161
|
+
this.closed = false;
|
|
162
|
+
const connectUrl = session.connectUrl;
|
|
163
|
+
if (!connectUrl) {
|
|
164
|
+
throw new ValidationError(`Session ${session.sessionId} does not include a CDP connect URL`);
|
|
165
|
+
}
|
|
166
|
+
this.session = session;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Connect the CDP client to the session.
|
|
170
|
+
*/
|
|
171
|
+
async connect() {
|
|
172
|
+
if (this.rawClient && !this.closed) {
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
this.closed = false;
|
|
176
|
+
getLogger().debug(`Connecting to session ${this.session.sessionId} over CDP`);
|
|
177
|
+
const options = {
|
|
178
|
+
target: this.session.connectUrl,
|
|
179
|
+
local: true
|
|
180
|
+
};
|
|
181
|
+
const defaultPort = getDefaultPort(this.session.connectUrl);
|
|
182
|
+
if (!hasExplicitPort(this.session.connectUrl) && defaultPort) {
|
|
183
|
+
options.port = defaultPort;
|
|
184
|
+
}
|
|
185
|
+
if (getProtocol(this.session.connectUrl) === "wss") {
|
|
186
|
+
options.secure = true;
|
|
187
|
+
}
|
|
188
|
+
this.rawClient = await CDP(options);
|
|
189
|
+
this.rawClient.on("event", (message) => {
|
|
190
|
+
this.emit(message.method, message.params, message.sessionId);
|
|
191
|
+
});
|
|
192
|
+
this.rawClient.on("error", (err) => {
|
|
193
|
+
this.emit("error", err);
|
|
194
|
+
});
|
|
195
|
+
this.rawClient.on("disconnect", () => {
|
|
196
|
+
void this.close().catch((err) => {
|
|
197
|
+
getLogger().error(`Failed to close CDP client for session ${this.session.sessionId} after disconnect`, err);
|
|
198
|
+
});
|
|
199
|
+
this.emit("disconnect");
|
|
200
|
+
});
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Send a raw Chrome DevTools Protocol command.
|
|
205
|
+
*
|
|
206
|
+
* Returns `undefined` if called before the client has connected.
|
|
207
|
+
*/
|
|
208
|
+
async send(method, params, sessionId) {
|
|
209
|
+
if (this.closed) {
|
|
210
|
+
throw new ValidationError("Cannot send CDP command after client is closed");
|
|
211
|
+
}
|
|
212
|
+
const rawClient = this.rawClient;
|
|
213
|
+
if (!rawClient) {
|
|
214
|
+
return void 0;
|
|
215
|
+
}
|
|
216
|
+
return rawClient.send(method, params, sessionId);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Close the CDP connection and release internal listeners.
|
|
220
|
+
*/
|
|
221
|
+
async close() {
|
|
222
|
+
if (this.closed) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
this.closed = true;
|
|
226
|
+
if (this.rawClient) {
|
|
227
|
+
this.rawClient.removeAllListeners("event");
|
|
228
|
+
this.rawClient.removeAllListeners("disconnect");
|
|
229
|
+
this.rawClient.removeAllListeners("error");
|
|
230
|
+
await this.rawClient.close();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
async function connectOverCDP(session) {
|
|
235
|
+
const client = new CDPClient(session);
|
|
236
|
+
await client.connect();
|
|
237
|
+
return client;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/sessions.ts
|
|
138
241
|
function asRecord(value) {
|
|
139
242
|
return typeof value === "object" && value !== null ? value : {};
|
|
140
243
|
}
|
|
141
244
|
function getString(value) {
|
|
142
245
|
return typeof value === "string" ? value : void 0;
|
|
143
246
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
149
|
-
return new Date(value).toISOString();
|
|
150
|
-
}
|
|
151
|
-
return null;
|
|
247
|
+
function withRequestedRegion(client, payload) {
|
|
248
|
+
const regionId = client.selectedRegion ?? client.region;
|
|
249
|
+
return regionId ? { ...payload, region_id: regionId } : payload;
|
|
152
250
|
}
|
|
153
|
-
function
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
return "
|
|
251
|
+
function getNumber(value) {
|
|
252
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
253
|
+
}
|
|
254
|
+
function normalizeContextMode(mode) {
|
|
255
|
+
if (mode === "readWrite" || mode === "read_write") {
|
|
256
|
+
return "read_write";
|
|
159
257
|
}
|
|
160
|
-
return
|
|
258
|
+
return "read_only";
|
|
161
259
|
}
|
|
162
|
-
|
|
260
|
+
function delay(ms) {
|
|
261
|
+
return new Promise((resolve) => {
|
|
262
|
+
setTimeout(resolve, ms);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
var PaginationInfo = class {
|
|
266
|
+
constructor(shape) {
|
|
267
|
+
this.currentPage = shape.currentPage;
|
|
268
|
+
this.pageSize = shape.pageSize;
|
|
269
|
+
this.totalCount = shape.totalCount;
|
|
270
|
+
this.totalPages = shape.totalPages;
|
|
271
|
+
this.activeCount = shape.activeCount;
|
|
272
|
+
this.closedCount = shape.closedCount;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
var SessionInfo = class {
|
|
276
|
+
/** @internal */
|
|
163
277
|
constructor(options) {
|
|
278
|
+
this.closed = false;
|
|
164
279
|
this.id = options.id;
|
|
280
|
+
this.sessionId = options.id;
|
|
165
281
|
this.status = options.status;
|
|
166
|
-
this.
|
|
167
|
-
this.
|
|
168
|
-
this.
|
|
282
|
+
this.apiKey = options.apiKey;
|
|
283
|
+
this.projectId = options.projectId;
|
|
284
|
+
this.regionId = options.regionId ?? null;
|
|
285
|
+
this.region_id = this.regionId;
|
|
286
|
+
this.browserType = options.browserType;
|
|
287
|
+
this.createdAt = options.createdAt;
|
|
288
|
+
this.inspectUrl = options.inspectUrl;
|
|
289
|
+
this.containerId = options.containerId;
|
|
290
|
+
this.ws = options.ws ?? null;
|
|
291
|
+
this.client = options.client;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Playwright/CDP connection URL.
|
|
295
|
+
*/
|
|
296
|
+
get connectUrl() {
|
|
297
|
+
return this.ws ?? "";
|
|
169
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Parsed session creation time.
|
|
301
|
+
*/
|
|
170
302
|
get createdAtDate() {
|
|
171
|
-
if (!this.createdAt) {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
303
|
const date = new Date(this.createdAt);
|
|
175
304
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
176
305
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Close the current session.
|
|
308
|
+
*
|
|
309
|
+
* This method is idempotent. Errors are logged and swallowed to keep cleanup safe.
|
|
310
|
+
*/
|
|
311
|
+
async close() {
|
|
312
|
+
if (this.closed) {
|
|
313
|
+
getLogger().debug(`Session ${this.sessionId} is already closed.`);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (!this.client) {
|
|
317
|
+
getLogger().warn(
|
|
318
|
+
`Cannot close session ${this.sessionId}: client was not provided during session creation.`
|
|
319
|
+
);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
getLogger().debug(`Closing session ${this.sessionId}`);
|
|
324
|
+
await this.client.sessions.delete({
|
|
325
|
+
sessionId: this.sessionId,
|
|
326
|
+
projectId: this.projectId
|
|
327
|
+
});
|
|
328
|
+
this.closed = true;
|
|
329
|
+
getLogger().info(`Session closed: ${this.sessionId}`);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
getLogger().warn(`Failed to close session ${this.sessionId}:`, error);
|
|
180
332
|
}
|
|
181
|
-
const date = new Date(this.updatedAt);
|
|
182
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
183
|
-
}
|
|
184
|
-
isLocked() {
|
|
185
|
-
return this.status === "locked";
|
|
186
|
-
}
|
|
187
|
-
isAvailable() {
|
|
188
|
-
return this.status === "available";
|
|
189
333
|
}
|
|
190
334
|
};
|
|
191
|
-
var
|
|
192
|
-
constructor(
|
|
193
|
-
this.
|
|
335
|
+
var SessionListResponse = class {
|
|
336
|
+
constructor(sessions, pagination) {
|
|
337
|
+
this.sessions = sessions;
|
|
338
|
+
this.pagination = pagination;
|
|
194
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Number of sessions in the current page.
|
|
342
|
+
*/
|
|
195
343
|
get length() {
|
|
196
|
-
return this.
|
|
344
|
+
return this.sessions.length;
|
|
197
345
|
}
|
|
198
346
|
[Symbol.iterator]() {
|
|
199
|
-
return this.
|
|
347
|
+
return this.sessions[Symbol.iterator]();
|
|
200
348
|
}
|
|
201
349
|
at(index) {
|
|
202
|
-
return this.
|
|
350
|
+
return this.sessions[index];
|
|
203
351
|
}
|
|
204
352
|
};
|
|
205
|
-
var
|
|
353
|
+
var SessionDownloadInfo = class {
|
|
354
|
+
constructor(shape) {
|
|
355
|
+
this.id = shape.id;
|
|
356
|
+
this.filename = shape.filename;
|
|
357
|
+
this.contentType = shape.contentType;
|
|
358
|
+
this.size = shape.size;
|
|
359
|
+
this.sha256 = shape.sha256;
|
|
360
|
+
this.status = shape.status;
|
|
361
|
+
this.createdAt = shape.createdAt;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
var SessionDownloadsListResponse = class {
|
|
365
|
+
constructor(downloads, summary) {
|
|
366
|
+
this.downloads = downloads;
|
|
367
|
+
this.summary = summary;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
var SessionDownloadsDeleteResponse = class {
|
|
371
|
+
constructor(shape) {
|
|
372
|
+
this.status = shape.status;
|
|
373
|
+
this.deletedCount = shape.deletedCount;
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
var SessionTargetInfo = class {
|
|
377
|
+
constructor(shape) {
|
|
378
|
+
this.id = shape.id;
|
|
379
|
+
this.title = shape.title;
|
|
380
|
+
this.type = shape.type;
|
|
381
|
+
this.url = shape.url;
|
|
382
|
+
this.description = shape.description;
|
|
383
|
+
this.inspectUrl = shape.inspectUrl;
|
|
384
|
+
this.webSocketDebuggerUrl = shape.webSocketDebuggerUrl;
|
|
385
|
+
this.webSocketDebuggerUrlTransformed = shape.webSocketDebuggerUrlTransformed;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
var SessionDownloadsResource = class {
|
|
206
389
|
constructor(client) {
|
|
207
390
|
this.client = client;
|
|
208
391
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
};
|
|
218
|
-
if (options.metadata) {
|
|
219
|
-
payload.metadata = options.metadata;
|
|
220
|
-
}
|
|
221
|
-
const response = await this.client._post(url, payload);
|
|
392
|
+
async list(sessionId, projectId) {
|
|
393
|
+
const response = await this.client._post(
|
|
394
|
+
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads/list`,
|
|
395
|
+
{
|
|
396
|
+
api_key: this.client.apiKey,
|
|
397
|
+
project_id: projectId ?? this.client.projectId
|
|
398
|
+
}
|
|
399
|
+
);
|
|
222
400
|
if (response.status >= 400) {
|
|
223
|
-
this.handleError(response,
|
|
401
|
+
this.handleError("list session downloads", response, sessionId);
|
|
224
402
|
}
|
|
225
403
|
const result = asRecord(response.data);
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
404
|
+
const items = Array.isArray(result.downloads) ? result.downloads : [];
|
|
405
|
+
const downloads = items.map((item) => {
|
|
406
|
+
const download = asRecord(item);
|
|
407
|
+
return new SessionDownloadInfo({
|
|
408
|
+
id: getString(download.id) ?? "",
|
|
409
|
+
filename: getString(download.filename) ?? "",
|
|
410
|
+
contentType: getString(download.content_type) ?? null,
|
|
411
|
+
size: getNumber(download.size) ?? 0,
|
|
412
|
+
sha256: getString(download.sha256) ?? null,
|
|
413
|
+
status: getString(download.status) ?? "available",
|
|
414
|
+
createdAt: getString(download.created_at) ?? ""
|
|
231
415
|
});
|
|
232
|
-
}
|
|
233
|
-
const context = new ContextInfo({
|
|
234
|
-
id: contextId,
|
|
235
|
-
status: normalizeContextStatus(result.locked),
|
|
236
|
-
metadata: result.metadata ?? options.metadata ?? {},
|
|
237
|
-
createdAt: parseTimestamp(result.created_at)
|
|
238
416
|
});
|
|
239
|
-
|
|
240
|
-
return
|
|
417
|
+
const summary = asRecord(result.summary);
|
|
418
|
+
return new SessionDownloadsListResponse(downloads, {
|
|
419
|
+
count: getNumber(summary.count) ?? downloads.length,
|
|
420
|
+
totalSize: getNumber(summary.total_size) ?? 0
|
|
421
|
+
});
|
|
241
422
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
423
|
+
async get(sessionId, downloadId, projectId) {
|
|
424
|
+
const response = await this.client._get(
|
|
425
|
+
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads/${downloadId}`,
|
|
426
|
+
{
|
|
427
|
+
api_key: this.client.apiKey,
|
|
428
|
+
project_id: projectId ?? this.client.projectId
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
responseType: "arraybuffer"
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
if (response.status >= 400) {
|
|
435
|
+
this.handleError("fetch session download", response, sessionId);
|
|
254
436
|
}
|
|
255
|
-
|
|
437
|
+
return Buffer.from(response.data);
|
|
438
|
+
}
|
|
439
|
+
async archive(sessionId, projectId) {
|
|
440
|
+
const response = await this.client._get(
|
|
441
|
+
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads/archive`,
|
|
442
|
+
{
|
|
443
|
+
api_key: this.client.apiKey,
|
|
444
|
+
project_id: projectId ?? this.client.projectId
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
responseType: "arraybuffer"
|
|
448
|
+
}
|
|
449
|
+
);
|
|
256
450
|
if (response.status >= 400) {
|
|
257
|
-
this.handleError(response,
|
|
451
|
+
this.handleError("archive session downloads", response, sessionId);
|
|
258
452
|
}
|
|
259
|
-
|
|
260
|
-
const contexts = Array.isArray(result.contexts) ? result.contexts.map((item) => {
|
|
261
|
-
const context = asRecord(item);
|
|
262
|
-
return new ContextInfo({
|
|
263
|
-
id: getString(context.context_id) ?? "",
|
|
264
|
-
status: normalizeContextStatus(context.locked),
|
|
265
|
-
createdAt: parseTimestamp(context.created_at),
|
|
266
|
-
updatedAt: parseTimestamp(context.updated_at),
|
|
267
|
-
metadata: context.metadata ?? {}
|
|
268
|
-
});
|
|
269
|
-
}) : [];
|
|
270
|
-
getLogger().info(`Retrieved ${contexts.length} contexts`);
|
|
271
|
-
return new ContextListResponse(contexts);
|
|
453
|
+
return Buffer.from(response.data);
|
|
272
454
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
};
|
|
282
|
-
const response = await this.client._post(url, payload);
|
|
455
|
+
async delete(sessionId, projectId) {
|
|
456
|
+
const response = await this.client._delete(
|
|
457
|
+
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads`,
|
|
458
|
+
{
|
|
459
|
+
api_key: this.client.apiKey,
|
|
460
|
+
project_id: projectId ?? this.client.projectId
|
|
461
|
+
}
|
|
462
|
+
);
|
|
283
463
|
if (response.status >= 400) {
|
|
284
|
-
this.handleError(
|
|
464
|
+
this.handleError("delete session downloads", response, sessionId);
|
|
285
465
|
}
|
|
286
466
|
const result = asRecord(response.data);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
status: normalizeContextStatus(context.locked),
|
|
291
|
-
metadata: context.metadata ?? {},
|
|
292
|
-
createdAt: parseTimestamp(context.created_at),
|
|
293
|
-
updatedAt: parseTimestamp(context.updated_at)
|
|
467
|
+
return new SessionDownloadsDeleteResponse({
|
|
468
|
+
status: getString(result.status) ?? "",
|
|
469
|
+
deletedCount: getNumber(result.deleted_count) ?? 0
|
|
294
470
|
});
|
|
295
|
-
|
|
296
|
-
|
|
471
|
+
}
|
|
472
|
+
handleError(action, response, sessionId) {
|
|
473
|
+
const errorData = asRecord(response.data);
|
|
474
|
+
const errorMessage = getString(errorData.error) ?? getString(errorData.message) ?? "Unknown error";
|
|
475
|
+
if (response.status === 401) {
|
|
476
|
+
throw new AuthenticationError(
|
|
477
|
+
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (response.status === 404) {
|
|
481
|
+
throw new SessionNotFoundError(`Session downloads not found for ${sessionId}: ${errorMessage}`);
|
|
482
|
+
}
|
|
483
|
+
throw new APIError(`Failed to ${action}: ${errorMessage}`, {
|
|
484
|
+
statusCode: response.status,
|
|
485
|
+
response: response.data
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
var SessionsResource = class {
|
|
490
|
+
constructor(client) {
|
|
491
|
+
this.client = client;
|
|
492
|
+
this.downloads = new SessionDownloadsResource(client);
|
|
297
493
|
}
|
|
298
494
|
/**
|
|
299
|
-
*
|
|
495
|
+
* Create a new browser session.
|
|
300
496
|
*/
|
|
301
|
-
async
|
|
302
|
-
const url = `${this.client.baseUrl}/instance/v1/contexts/${contextId}/fork`;
|
|
497
|
+
async create(options = {}) {
|
|
303
498
|
const payload = {
|
|
304
499
|
api_key: this.client.apiKey,
|
|
305
|
-
project_id: this.client.projectId
|
|
500
|
+
project_id: options.projectId ?? this.client.projectId,
|
|
501
|
+
browser_mode: options.browserMode ?? "normal"
|
|
306
502
|
};
|
|
307
|
-
|
|
503
|
+
if (options.context) {
|
|
504
|
+
const contextPayload = {
|
|
505
|
+
id: options.context.id,
|
|
506
|
+
mode: normalizeContextMode(options.context.mode)
|
|
507
|
+
};
|
|
508
|
+
payload.context = contextPayload;
|
|
509
|
+
getLogger().debug(
|
|
510
|
+
`Creating session with context (id=${options.context.id}, mode=${contextPayload.mode})`
|
|
511
|
+
);
|
|
512
|
+
} else {
|
|
513
|
+
getLogger().debug(`Creating session with browser_mode=${payload.browser_mode}`);
|
|
514
|
+
}
|
|
515
|
+
if (options.extensionIds && options.extensionIds.length > 0) {
|
|
516
|
+
payload.extension_ids = options.extensionIds;
|
|
517
|
+
}
|
|
518
|
+
if (options.proxy) {
|
|
519
|
+
payload.proxy = this.normalizeProxy(options.proxy);
|
|
520
|
+
}
|
|
521
|
+
if (options.weakLock) {
|
|
522
|
+
payload.weak_lock = true;
|
|
523
|
+
}
|
|
524
|
+
if (options.asyncCreate !== false) {
|
|
525
|
+
return this.createAsync(withRequestedRegion(this.client, payload), options);
|
|
526
|
+
}
|
|
527
|
+
const url = `${this.client.baseUrl}/instance`;
|
|
528
|
+
const response = await this.client._post(url, withRequestedRegion(this.client, payload));
|
|
308
529
|
if (response.status >= 400) {
|
|
309
|
-
this.
|
|
530
|
+
this.handleCreateError(response);
|
|
310
531
|
}
|
|
311
532
|
const result = asRecord(response.data);
|
|
312
|
-
const
|
|
313
|
-
if (!
|
|
314
|
-
throw new APIError("Failed to
|
|
533
|
+
const sessionId = getString(result.session_id);
|
|
534
|
+
if (!sessionId) {
|
|
535
|
+
throw new APIError("Failed to create session: session_id missing from response", {
|
|
315
536
|
statusCode: response.status,
|
|
316
537
|
response: response.data
|
|
317
538
|
});
|
|
318
539
|
}
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
540
|
+
const containerId = getString(result.container_id) ?? null;
|
|
541
|
+
const projectId = options.projectId ?? this.client.projectId;
|
|
542
|
+
const createdSession = await this._getCreatedSession(sessionId, projectId);
|
|
543
|
+
const wsUrl = await this._getWebSocketDebuggerUrl(sessionId);
|
|
544
|
+
getLogger().info(`Session created successfully: id=${sessionId}, container_id=${containerId}`);
|
|
545
|
+
return new SessionInfo({
|
|
546
|
+
id: sessionId,
|
|
547
|
+
status: createdSession?.status ?? "active",
|
|
548
|
+
apiKey: this.client.apiKey,
|
|
549
|
+
projectId,
|
|
550
|
+
regionId: createdSession?.regionId ?? this.client.selectedRegion ?? this.client.region ?? null,
|
|
551
|
+
browserType: createdSession?.browserType ?? (options.browserMode ?? "normal"),
|
|
552
|
+
createdAt: createdSession?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
553
|
+
inspectUrl: createdSession?.inspectUrl ?? `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
|
|
554
|
+
containerId: createdSession?.containerId ?? containerId,
|
|
555
|
+
ws: wsUrl ?? createdSession?.ws ?? null,
|
|
556
|
+
client: this.client
|
|
325
557
|
});
|
|
326
|
-
|
|
327
|
-
|
|
558
|
+
}
|
|
559
|
+
async createAsync(payload, options) {
|
|
560
|
+
const response = await this.client._post(`${this.client.baseUrl}/instance/v2`, payload);
|
|
561
|
+
if (response.status >= 400) {
|
|
562
|
+
this.handleCreateError(response);
|
|
563
|
+
}
|
|
564
|
+
const result = asRecord(response.data);
|
|
565
|
+
const sessionId = getString(result.session_id);
|
|
566
|
+
if (!sessionId) {
|
|
567
|
+
throw new APIError("Failed to create session: session_id missing from response", {
|
|
568
|
+
statusCode: response.status,
|
|
569
|
+
response: response.data
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
const projectId = options.projectId ?? this.client.projectId;
|
|
573
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
574
|
+
const pollTimeoutMs = options.pollTimeoutMs ?? 6e5;
|
|
575
|
+
const deadline = Date.now() + pollTimeoutMs;
|
|
576
|
+
while (Date.now() < deadline) {
|
|
577
|
+
const info = await this.get(sessionId, projectId);
|
|
578
|
+
if (info.status === "active") {
|
|
579
|
+
const wsUrl = await this._getWebSocketDebuggerUrl(sessionId);
|
|
580
|
+
getLogger().info(`Session created successfully (async): id=${sessionId}`);
|
|
581
|
+
return new SessionInfo({
|
|
582
|
+
id: sessionId,
|
|
583
|
+
status: "active",
|
|
584
|
+
apiKey: this.client.apiKey,
|
|
585
|
+
projectId,
|
|
586
|
+
regionId: info.regionId ?? this.client.selectedRegion ?? this.client.region ?? null,
|
|
587
|
+
browserType: info.browserType || (options.browserMode ?? "normal"),
|
|
588
|
+
createdAt: info.createdAt,
|
|
589
|
+
inspectUrl: info.inspectUrl,
|
|
590
|
+
containerId: info.containerId,
|
|
591
|
+
ws: wsUrl ?? info.ws,
|
|
592
|
+
client: this.client
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
if (info.status === "create_failed") {
|
|
596
|
+
this.handleAsyncCreateFailed(payload);
|
|
597
|
+
throw new APIError("Session creation failed", {
|
|
598
|
+
statusCode: 500,
|
|
599
|
+
response: { session_id: sessionId, status: info.status }
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
if (info.status === "closed") {
|
|
603
|
+
throw new APIError("Session closed before activation", {
|
|
604
|
+
statusCode: 500,
|
|
605
|
+
response: { session_id: sessionId, status: info.status }
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
await delay(pollIntervalMs);
|
|
609
|
+
}
|
|
610
|
+
throw new TimeoutError(
|
|
611
|
+
`Timed out waiting for session ${sessionId} to become active after ${pollTimeoutMs}ms`
|
|
612
|
+
);
|
|
328
613
|
}
|
|
329
614
|
/**
|
|
330
|
-
*
|
|
615
|
+
* Fetch one session by id.
|
|
616
|
+
*
|
|
617
|
+
* The upstream API uses POST /instance/session for this lookup, so this SDK method
|
|
618
|
+
* intentionally uses POST even though the operation is read-only.
|
|
331
619
|
*/
|
|
332
|
-
async
|
|
333
|
-
const
|
|
334
|
-
const payload = {
|
|
620
|
+
async get(sessionId, projectId) {
|
|
621
|
+
const response = await this.client._post(`${this.client.baseUrl}/instance/session`, withRequestedRegion(this.client, {
|
|
335
622
|
api_key: this.client.apiKey,
|
|
336
|
-
project_id: this.client.projectId
|
|
337
|
-
|
|
338
|
-
|
|
623
|
+
project_id: projectId ?? this.client.projectId,
|
|
624
|
+
session_id: sessionId
|
|
625
|
+
}));
|
|
339
626
|
if (response.status >= 400) {
|
|
340
|
-
this.
|
|
627
|
+
this.handleGetError(response, sessionId);
|
|
341
628
|
}
|
|
342
|
-
|
|
629
|
+
return this.mapSessionInfo(asRecord(response.data), projectId);
|
|
343
630
|
}
|
|
344
631
|
/**
|
|
345
|
-
*
|
|
632
|
+
* List sessions for the current project.
|
|
346
633
|
*/
|
|
347
|
-
async
|
|
348
|
-
const url = `${this.client.baseUrl}/instance/
|
|
634
|
+
async list(options = {}) {
|
|
635
|
+
const url = `${this.client.baseUrl}/instance/v2/sessions`;
|
|
349
636
|
const payload = {
|
|
350
637
|
api_key: this.client.apiKey,
|
|
351
|
-
project_id: this.client.projectId
|
|
638
|
+
project_id: options.projectId ?? this.client.projectId
|
|
352
639
|
};
|
|
353
|
-
|
|
640
|
+
if (options.status) {
|
|
641
|
+
payload.status = options.status;
|
|
642
|
+
}
|
|
643
|
+
const response = await this.client._post(url, withRequestedRegion(this.client, payload));
|
|
354
644
|
if (response.status >= 400) {
|
|
355
|
-
this.
|
|
645
|
+
this.handleListError(response);
|
|
356
646
|
}
|
|
357
647
|
const result = asRecord(response.data);
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
648
|
+
const sessionsData = Array.isArray(result.sessions) ? result.sessions : [];
|
|
649
|
+
const paginationData = asRecord(result.pagination);
|
|
650
|
+
const sessions = sessionsData.map((item) => this.mapSessionInfo(asRecord(item), options.projectId));
|
|
651
|
+
const pagination = new PaginationInfo({
|
|
652
|
+
currentPage: getNumber(paginationData.currentPage) ?? 1,
|
|
653
|
+
pageSize: getNumber(paginationData.pageSize) ?? sessions.length,
|
|
654
|
+
totalCount: getNumber(paginationData.totalCount) ?? sessions.length,
|
|
655
|
+
totalPages: getNumber(paginationData.totalPages) ?? 1,
|
|
656
|
+
activeCount: getNumber(paginationData.activeCount) ?? 0,
|
|
657
|
+
closedCount: getNumber(paginationData.closedCount) ?? 0
|
|
658
|
+
});
|
|
659
|
+
getLogger().info(
|
|
660
|
+
`Retrieved ${sessions.length} sessions (total: ${pagination.totalCount}, active: ${pagination.activeCount}, closed: ${pagination.closedCount})`
|
|
661
|
+
);
|
|
662
|
+
return new SessionListResponse(sessions, pagination);
|
|
362
663
|
}
|
|
363
|
-
|
|
364
|
-
|
|
664
|
+
/**
|
|
665
|
+
* Delete a browser session.
|
|
666
|
+
*/
|
|
667
|
+
async delete(options) {
|
|
668
|
+
const url = `${this.client.baseUrl}/instance`;
|
|
669
|
+
const payload = withRequestedRegion(this.client, {
|
|
670
|
+
api_key: this.client.apiKey,
|
|
671
|
+
project_id: options.projectId ?? this.client.projectId,
|
|
672
|
+
session_id: options.sessionId
|
|
673
|
+
});
|
|
674
|
+
const response = await this.client._delete(url, payload);
|
|
675
|
+
if (response.status >= 400) {
|
|
676
|
+
this.handleDeleteError(response, options.sessionId);
|
|
677
|
+
}
|
|
678
|
+
getLogger().info(`Session deleted successfully: ${options.sessionId}`);
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* List target/page metadata for a session via `/json`.
|
|
682
|
+
*/
|
|
683
|
+
async listTargets(sessionId) {
|
|
684
|
+
const response = await this.client._get(`${this.client.baseUrl}/json`, {
|
|
685
|
+
session_id: sessionId
|
|
686
|
+
});
|
|
687
|
+
if (response.status >= 400) {
|
|
688
|
+
this.handleListTargetsError(response, sessionId);
|
|
689
|
+
}
|
|
690
|
+
const result = Array.isArray(response.data) ? response.data : [];
|
|
691
|
+
return result.map((item) => this.mapSessionTarget(asRecord(item)));
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Fetch the debugger WebSocket URL for a session.
|
|
695
|
+
*
|
|
696
|
+
* @internal
|
|
697
|
+
*/
|
|
698
|
+
async _getWebSocketDebuggerUrl(sessionId) {
|
|
699
|
+
try {
|
|
700
|
+
const response = await this.client._get(`${this.client.baseUrl}/json/version`, {
|
|
701
|
+
session_id: sessionId
|
|
702
|
+
});
|
|
703
|
+
if (response.status >= 400) {
|
|
704
|
+
getLogger().error("Error getting WebSocket debugger URL:", response.data);
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
const result = asRecord(response.data);
|
|
708
|
+
return getString(result.webSocketDebuggerUrlTransformed) ?? getString(result.webSocketDebuggerUrl) ?? null;
|
|
709
|
+
} catch (error) {
|
|
710
|
+
getLogger().error("Error getting WebSocket debugger URL:", error);
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
normalizeProxy(proxy) {
|
|
715
|
+
if (!proxy.server) {
|
|
716
|
+
throw new ValidationError("proxy.server is required when proxy is provided");
|
|
717
|
+
}
|
|
718
|
+
const normalizedProxy = {
|
|
719
|
+
type: proxy.type ?? "external",
|
|
720
|
+
server: proxy.server
|
|
721
|
+
};
|
|
722
|
+
if (proxy.username) {
|
|
723
|
+
normalizedProxy.username = proxy.username;
|
|
724
|
+
}
|
|
725
|
+
if (proxy.password) {
|
|
726
|
+
normalizedProxy.password = proxy.password;
|
|
727
|
+
}
|
|
728
|
+
return normalizedProxy;
|
|
729
|
+
}
|
|
730
|
+
handleAsyncCreateFailed(payload) {
|
|
731
|
+
const context = asRecord(payload.context);
|
|
732
|
+
const contextId = getString(context.id);
|
|
733
|
+
const contextMode = getString(context.mode);
|
|
734
|
+
if (contextId && contextMode === "read_write" && payload.weak_lock !== true) {
|
|
735
|
+
throw new ContextLockedError("Context is locked by another active session");
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
handleCreateError(response) {
|
|
739
|
+
const errorData = asRecord(response.data);
|
|
365
740
|
const errorMessage = getString(errorData.error) ?? getString(errorData.message) ?? "Unknown error";
|
|
366
741
|
const errorCode = getString(errorData.code);
|
|
367
742
|
const metadata = asRecord(errorData.metadata);
|
|
368
743
|
if (response.status === 401) {
|
|
369
|
-
throw new AuthenticationError(
|
|
744
|
+
throw new AuthenticationError(
|
|
745
|
+
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
if (response.status === 404 && errorCode === "context_not_found") {
|
|
749
|
+
throw new ContextNotFoundError(`Context not found: ${errorMessage}`);
|
|
370
750
|
}
|
|
371
751
|
if (response.status === 404) {
|
|
372
|
-
|
|
373
|
-
throw new ContextNotFoundError(`Context not found${suffix}`);
|
|
752
|
+
throw new SessionNotFoundError(`Resource not found: ${errorMessage}`);
|
|
374
753
|
}
|
|
375
|
-
if (response.status === 409 &&
|
|
754
|
+
if (response.status === 409 && errorCode === "context_locked") {
|
|
376
755
|
throw new ContextLockedError(errorMessage, {
|
|
377
756
|
activeSessionId: getString(metadata.activeSessionId),
|
|
378
|
-
retryAfter:
|
|
757
|
+
retryAfter: getNumber(metadata.retryAfter)
|
|
379
758
|
});
|
|
380
759
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
760
|
+
throw new APIError(`Failed to create session: ${errorMessage}`, {
|
|
761
|
+
statusCode: response.status,
|
|
762
|
+
response: response.data
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
handleGetError(response, sessionId) {
|
|
766
|
+
const errorData = asRecord(response.data);
|
|
767
|
+
const errorMessage = getString(errorData.error) ?? getString(errorData.message) ?? "Unknown error";
|
|
768
|
+
if (response.status === 401) {
|
|
769
|
+
throw new AuthenticationError(
|
|
770
|
+
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
388
771
|
);
|
|
389
772
|
}
|
|
390
|
-
if (response.status ===
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
`Internal server error (500) during ${operation}: ${errorMessage}${details ? `. Details: ${details}` : ""}`,
|
|
394
|
-
{
|
|
395
|
-
statusCode: response.status,
|
|
396
|
-
response: response.data
|
|
397
|
-
}
|
|
773
|
+
if (response.status === 404) {
|
|
774
|
+
throw new SessionNotFoundError(
|
|
775
|
+
`Session not found: ${errorMessage}. Session ID '${sessionId}' may not exist in this project.`
|
|
398
776
|
);
|
|
399
777
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
778
|
+
throw new APIError(`Failed to get session: ${errorMessage}`, {
|
|
779
|
+
statusCode: response.status,
|
|
780
|
+
response: response.data
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
handleListError(response) {
|
|
784
|
+
const errorData = asRecord(response.data);
|
|
785
|
+
const errorMessage = getString(errorData.error) ?? getString(errorData.message) ?? "Unknown error";
|
|
786
|
+
if (response.status === 401) {
|
|
787
|
+
throw new AuthenticationError(
|
|
788
|
+
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
407
789
|
);
|
|
408
790
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
791
|
+
throw new APIError(`Failed to list sessions: ${errorMessage}`, {
|
|
792
|
+
statusCode: response.status,
|
|
793
|
+
response: response.data
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
handleListTargetsError(response, sessionId) {
|
|
797
|
+
const errorData = asRecord(response.data);
|
|
798
|
+
const errorMessage = getString(errorData.error) ?? getString(errorData.message) ?? "Unknown error";
|
|
799
|
+
if (response.status === 401) {
|
|
800
|
+
throw new AuthenticationError(
|
|
801
|
+
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
416
802
|
);
|
|
417
803
|
}
|
|
418
|
-
if (response.status ===
|
|
419
|
-
throw new
|
|
420
|
-
`
|
|
421
|
-
{
|
|
422
|
-
statusCode: response.status,
|
|
423
|
-
response: response.data
|
|
424
|
-
}
|
|
804
|
+
if (response.status === 404) {
|
|
805
|
+
throw new SessionNotFoundError(
|
|
806
|
+
`Session not found: ${errorMessage}. Session ID '${sessionId}' may not exist in this project.`
|
|
425
807
|
);
|
|
426
808
|
}
|
|
427
|
-
throw new APIError(`Failed to
|
|
809
|
+
throw new APIError(`Failed to list session targets: ${errorMessage}`, {
|
|
810
|
+
statusCode: response.status,
|
|
811
|
+
response: response.data
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
handleDeleteError(response, sessionId) {
|
|
815
|
+
const errorData = asRecord(response.data);
|
|
816
|
+
const errorMessage = getString(errorData.error) ?? getString(errorData.message) ?? "Unknown error";
|
|
817
|
+
if (response.status === 401) {
|
|
818
|
+
throw new AuthenticationError(
|
|
819
|
+
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
if (response.status === 404) {
|
|
823
|
+
throw new SessionNotFoundError(
|
|
824
|
+
`Session not found: ${errorMessage}. Session ID '${sessionId}' may have already been deleted or never existed.`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
throw new APIError(`Failed to delete session: ${errorMessage}`, {
|
|
428
828
|
statusCode: response.status,
|
|
429
829
|
response: response.data
|
|
430
830
|
});
|
|
431
831
|
}
|
|
832
|
+
mapSessionInfo(raw, projectId) {
|
|
833
|
+
const sessionId = getString(raw.session_id) ?? getString(raw.id) ?? "";
|
|
834
|
+
return new SessionInfo({
|
|
835
|
+
id: sessionId,
|
|
836
|
+
status: getString(raw.status) ?? "active",
|
|
837
|
+
apiKey: getString(raw.api_key) ?? this.client.apiKey,
|
|
838
|
+
projectId: getString(raw.project_id) ?? (projectId ?? this.client.projectId),
|
|
839
|
+
regionId: getString(raw.region_id) ?? this.client.selectedRegion ?? this.client.region ?? null,
|
|
840
|
+
browserType: getString(raw.browser_type) ?? getString(raw.browser_mode) ?? "normal",
|
|
841
|
+
createdAt: getString(raw.created_at) ?? "",
|
|
842
|
+
inspectUrl: getString(raw.inspect_url) ?? `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
|
|
843
|
+
containerId: getString(raw.container_id) ?? null,
|
|
844
|
+
ws: getString(raw.ws) ?? null,
|
|
845
|
+
client: this.client
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
mapSessionTarget(raw) {
|
|
849
|
+
return new SessionTargetInfo({
|
|
850
|
+
id: getString(raw.id) ?? "",
|
|
851
|
+
title: getString(raw.title) ?? "",
|
|
852
|
+
type: getString(raw.type) ?? "",
|
|
853
|
+
url: getString(raw.url) ?? "",
|
|
854
|
+
description: getString(raw.description) ?? "",
|
|
855
|
+
inspectUrl: getString(raw.inspectUrl) ?? getString(raw.inspect_url) ?? null,
|
|
856
|
+
webSocketDebuggerUrl: getString(raw.webSocketDebuggerUrl) ?? getString(raw.web_socket_debugger_url) ?? null,
|
|
857
|
+
webSocketDebuggerUrlTransformed: getString(raw.webSocketDebuggerUrlTransformed) ?? getString(raw.web_socket_debugger_url_transformed) ?? null
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
async _getCreatedSession(sessionId, projectId) {
|
|
861
|
+
try {
|
|
862
|
+
return await this.get(sessionId, projectId);
|
|
863
|
+
} catch (error) {
|
|
864
|
+
getLogger().warn(
|
|
865
|
+
`Failed to fetch created session ${sessionId}; falling back to locally derived session fields:`,
|
|
866
|
+
error
|
|
867
|
+
);
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
432
871
|
};
|
|
433
872
|
|
|
434
|
-
// src/
|
|
435
|
-
|
|
436
|
-
|
|
873
|
+
// src/auth.ts
|
|
874
|
+
var integratedAuthCallbacks = /* @__PURE__ */ new Map();
|
|
875
|
+
var cdpClients = /* @__PURE__ */ new Map();
|
|
876
|
+
function getIntegratedAuthKey(sessionOrClient) {
|
|
877
|
+
const session = sessionOrClient instanceof SessionInfo ? sessionOrClient : sessionOrClient.session;
|
|
878
|
+
const connectUrl = session.connectUrl;
|
|
879
|
+
if (!connectUrl) {
|
|
880
|
+
throw new ValidationError(`Session ${session.sessionId} does not include a CDP connect URL`);
|
|
881
|
+
}
|
|
882
|
+
return connectUrl;
|
|
883
|
+
}
|
|
884
|
+
async function registerIntegratedAuthCallback(sessionOrClient, callback) {
|
|
885
|
+
const key = getIntegratedAuthKey(sessionOrClient);
|
|
886
|
+
let sessionCallbacks = integratedAuthCallbacks.get(key);
|
|
887
|
+
if (sessionCallbacks?.has(callback)) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
let client = cdpClients.get(key);
|
|
891
|
+
if (!client) {
|
|
892
|
+
if (sessionOrClient instanceof SessionInfo) {
|
|
893
|
+
client = await connectOverCDP(sessionOrClient);
|
|
894
|
+
} else {
|
|
895
|
+
client = sessionOrClient;
|
|
896
|
+
}
|
|
897
|
+
await client.send("Fetch.enable", { handleAuthRequests: true });
|
|
898
|
+
cdpClients.set(key, client);
|
|
899
|
+
client.on("disconnect", () => {
|
|
900
|
+
cdpClients.delete(key);
|
|
901
|
+
integratedAuthCallbacks.delete(key);
|
|
902
|
+
});
|
|
903
|
+
client.on("Fetch.requestPaused", async (event, sessionId) => {
|
|
904
|
+
try {
|
|
905
|
+
await client.send(
|
|
906
|
+
"Fetch.continueRequest",
|
|
907
|
+
{
|
|
908
|
+
requestId: event.requestId
|
|
909
|
+
},
|
|
910
|
+
sessionId
|
|
911
|
+
);
|
|
912
|
+
} catch (error) {
|
|
913
|
+
getLogger().error("Failed to continue paused fetch request", error);
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
client.on("Fetch.integratedAuthRequired", async (params, sessionId) => {
|
|
917
|
+
try {
|
|
918
|
+
const sessionCallbacks2 = integratedAuthCallbacks.get(key);
|
|
919
|
+
if (!sessionCallbacks2) {
|
|
920
|
+
await client.send(
|
|
921
|
+
"Fetch.continueWithIntegratedAuth",
|
|
922
|
+
{
|
|
923
|
+
requestId: params.requestId,
|
|
924
|
+
authChallengeResponse: {
|
|
925
|
+
response: "CancelAuth"
|
|
926
|
+
}
|
|
927
|
+
},
|
|
928
|
+
sessionId
|
|
929
|
+
);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
const sender = async (result) => {
|
|
933
|
+
if (result.cancel) {
|
|
934
|
+
await client.send(
|
|
935
|
+
"Fetch.continueWithIntegratedAuth",
|
|
936
|
+
{
|
|
937
|
+
requestId: params.requestId,
|
|
938
|
+
authChallengeResponse: {
|
|
939
|
+
response: "CancelAuth"
|
|
940
|
+
}
|
|
941
|
+
},
|
|
942
|
+
sessionId
|
|
943
|
+
);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
await client.send(
|
|
947
|
+
"Fetch.continueWithIntegratedAuth",
|
|
948
|
+
{
|
|
949
|
+
requestId: params.requestId,
|
|
950
|
+
authChallengeResponse: {
|
|
951
|
+
response: "ProvideIntegratedCredentials",
|
|
952
|
+
scheme: result.scheme,
|
|
953
|
+
token: result.token
|
|
954
|
+
}
|
|
955
|
+
},
|
|
956
|
+
sessionId
|
|
957
|
+
);
|
|
958
|
+
};
|
|
959
|
+
for (const cb of sessionCallbacks2) {
|
|
960
|
+
try {
|
|
961
|
+
const result = cb(params, sender);
|
|
962
|
+
void Promise.resolve(result).catch((error) => {
|
|
963
|
+
getLogger().error("Integrated auth callback failed", error);
|
|
964
|
+
});
|
|
965
|
+
} catch (error) {
|
|
966
|
+
getLogger().error("Integrated auth callback failed", error);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
} catch (error) {
|
|
970
|
+
getLogger().error("Failed to handle integrated auth request", error);
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
if (!sessionCallbacks) {
|
|
975
|
+
sessionCallbacks = /* @__PURE__ */ new Set();
|
|
976
|
+
integratedAuthCallbacks.set(key, sessionCallbacks);
|
|
977
|
+
}
|
|
978
|
+
sessionCallbacks.add(callback);
|
|
979
|
+
}
|
|
980
|
+
function deregisterIntegratedAuthCallback(sessionOrClient, callback) {
|
|
981
|
+
const key = getIntegratedAuthKey(sessionOrClient);
|
|
982
|
+
const sessionCallbacks = integratedAuthCallbacks.get(key);
|
|
983
|
+
if (!sessionCallbacks?.has(callback)) {
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
sessionCallbacks.delete(callback);
|
|
987
|
+
if (sessionCallbacks.size === 0) {
|
|
988
|
+
integratedAuthCallbacks.delete(key);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// src/client.ts
|
|
993
|
+
import { isAxiosError } from "axios";
|
|
994
|
+
import axios from "axios";
|
|
995
|
+
import * as dotenv from "dotenv";
|
|
996
|
+
|
|
997
|
+
// src/contexts.ts
|
|
437
998
|
function asRecord2(value) {
|
|
438
999
|
return typeof value === "object" && value !== null ? value : {};
|
|
439
1000
|
}
|
|
440
1001
|
function getString2(value) {
|
|
441
1002
|
return typeof value === "string" ? value : void 0;
|
|
442
1003
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
this.name = shape.name;
|
|
447
|
-
this.projectId = shape.projectId;
|
|
448
|
-
this.createdAt = shape.createdAt;
|
|
449
|
-
this.updatedAt = shape.updatedAt;
|
|
1004
|
+
function parseTimestamp(value) {
|
|
1005
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1006
|
+
return value;
|
|
450
1007
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
constructor(client) {
|
|
454
|
-
this.client = client;
|
|
1008
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1009
|
+
return new Date(value).toISOString();
|
|
455
1010
|
}
|
|
456
|
-
|
|
457
|
-
const fileBuffer = await readFile(filePath);
|
|
458
|
-
const form = new FormData();
|
|
459
|
-
const usedProjectId = options.projectId ?? this.client.projectId;
|
|
460
|
-
form.append("project_id", usedProjectId);
|
|
461
|
-
if (options.name) {
|
|
462
|
-
form.append("name", options.name);
|
|
463
|
-
}
|
|
464
|
-
form.append(
|
|
465
|
-
"file",
|
|
466
|
-
new Blob([fileBuffer], { type: "application/octet-stream" }),
|
|
467
|
-
path.basename(filePath)
|
|
468
|
-
);
|
|
469
|
-
const response = await this.client._post(
|
|
470
|
-
`${this.client.baseUrl}/instance/v1/extension/upload`,
|
|
471
|
-
form,
|
|
472
|
-
{
|
|
473
|
-
headers: {
|
|
474
|
-
project_id: usedProjectId
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
);
|
|
478
|
-
if (response.status >= 400) {
|
|
479
|
-
this.handleError("upload extension", response);
|
|
480
|
-
}
|
|
481
|
-
getLogger().info(`Extension uploaded successfully: ${filePath}`);
|
|
482
|
-
return this.parseInfo(response.data);
|
|
483
|
-
}
|
|
484
|
-
async list(options = {}) {
|
|
485
|
-
const response = await this.client._post(`${this.client.baseUrl}/instance/v1/extension/list`, {
|
|
486
|
-
project_id: options.projectId ?? this.client.projectId,
|
|
487
|
-
limit: options.limit ?? 20,
|
|
488
|
-
offset: options.offset ?? 0
|
|
489
|
-
});
|
|
490
|
-
if (response.status >= 400) {
|
|
491
|
-
this.handleError("list extensions", response);
|
|
492
|
-
}
|
|
493
|
-
const result = asRecord2(response.data);
|
|
494
|
-
const items = Array.isArray(result.data) ? result.data : [];
|
|
495
|
-
return items.map((item) => this.parseInfo(item));
|
|
496
|
-
}
|
|
497
|
-
async get(extensionId, projectId) {
|
|
498
|
-
const response = await this.client._post(`${this.client.baseUrl}/instance/v1/extension/info`, {
|
|
499
|
-
project_id: projectId ?? this.client.projectId,
|
|
500
|
-
extension_id: extensionId
|
|
501
|
-
});
|
|
502
|
-
if (response.status >= 400) {
|
|
503
|
-
this.handleError("get extension", response);
|
|
504
|
-
}
|
|
505
|
-
return this.parseInfo(response.data);
|
|
506
|
-
}
|
|
507
|
-
async delete(extensionId) {
|
|
508
|
-
const response = await this.client._delete(
|
|
509
|
-
`${this.client.baseUrl}/instance/v1/extension/${extensionId}`
|
|
510
|
-
);
|
|
511
|
-
if (response.status >= 400) {
|
|
512
|
-
this.handleError("delete extension", response);
|
|
513
|
-
}
|
|
514
|
-
getLogger().info(`Extension deleted successfully: ${extensionId}`);
|
|
515
|
-
}
|
|
516
|
-
parseInfo(data) {
|
|
517
|
-
const item = asRecord2(data);
|
|
518
|
-
return new ExtensionInfo({
|
|
519
|
-
id: getString2(item.id) ?? "",
|
|
520
|
-
name: getString2(item.name) ?? "",
|
|
521
|
-
projectId: getString2(item.project_id) ?? "",
|
|
522
|
-
createdAt: getString2(item.created_at),
|
|
523
|
-
updatedAt: getString2(item.updated_at)
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
handleError(action, response) {
|
|
527
|
-
const errorData = asRecord2(response.data);
|
|
528
|
-
const errorMessage = getString2(errorData.message) ?? getString2(errorData.error) ?? "Unknown error";
|
|
529
|
-
if (response.status === 401) {
|
|
530
|
-
throw new AuthenticationError(
|
|
531
|
-
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
throw new APIError(`Failed to ${action}: ${errorMessage}`, {
|
|
535
|
-
statusCode: response.status,
|
|
536
|
-
response: response.data
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
// src/sessions.ts
|
|
542
|
-
function asRecord3(value) {
|
|
543
|
-
return typeof value === "object" && value !== null ? value : {};
|
|
544
|
-
}
|
|
545
|
-
function getString3(value) {
|
|
546
|
-
return typeof value === "string" ? value : void 0;
|
|
547
|
-
}
|
|
548
|
-
function getNumber(value) {
|
|
549
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1011
|
+
return null;
|
|
550
1012
|
}
|
|
551
|
-
function
|
|
552
|
-
if (
|
|
553
|
-
return "
|
|
1013
|
+
function normalizeContextStatus(value) {
|
|
1014
|
+
if (value === true || value === "locked") {
|
|
1015
|
+
return "locked";
|
|
554
1016
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
function delay(ms) {
|
|
558
|
-
return new Promise((resolve) => {
|
|
559
|
-
setTimeout(resolve, ms);
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
var PaginationInfo = class {
|
|
563
|
-
constructor(shape) {
|
|
564
|
-
this.currentPage = shape.currentPage;
|
|
565
|
-
this.pageSize = shape.pageSize;
|
|
566
|
-
this.totalCount = shape.totalCount;
|
|
567
|
-
this.totalPages = shape.totalPages;
|
|
568
|
-
this.activeCount = shape.activeCount;
|
|
569
|
-
this.closedCount = shape.closedCount;
|
|
1017
|
+
if (value === false || value === "available") {
|
|
1018
|
+
return "available";
|
|
570
1019
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
1020
|
+
return typeof value === "string" ? value : "available";
|
|
1021
|
+
}
|
|
1022
|
+
var ContextInfo = class {
|
|
574
1023
|
constructor(options) {
|
|
575
|
-
this.closed = false;
|
|
576
1024
|
this.id = options.id;
|
|
577
|
-
this.sessionId = options.id;
|
|
578
1025
|
this.status = options.status;
|
|
579
|
-
this.
|
|
580
|
-
this.
|
|
581
|
-
this.
|
|
582
|
-
this.region_id = this.regionId;
|
|
583
|
-
this.browserType = options.browserType;
|
|
584
|
-
this.createdAt = options.createdAt;
|
|
585
|
-
this.inspectUrl = options.inspectUrl;
|
|
586
|
-
this.containerId = options.containerId;
|
|
587
|
-
this.ws = options.ws ?? null;
|
|
588
|
-
this.client = options.client;
|
|
589
|
-
}
|
|
590
|
-
/**
|
|
591
|
-
* Playwright/CDP connection URL.
|
|
592
|
-
*/
|
|
593
|
-
get connectUrl() {
|
|
594
|
-
return this.ws ?? "";
|
|
1026
|
+
this.metadata = options.metadata ?? {};
|
|
1027
|
+
this.createdAt = options.createdAt ?? null;
|
|
1028
|
+
this.updatedAt = options.updatedAt ?? null;
|
|
595
1029
|
}
|
|
596
|
-
/**
|
|
597
|
-
* Parsed session creation time.
|
|
598
|
-
*/
|
|
599
1030
|
get createdAtDate() {
|
|
1031
|
+
if (!this.createdAt) {
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
600
1034
|
const date = new Date(this.createdAt);
|
|
601
1035
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
602
1036
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
* This method is idempotent. Errors are logged and swallowed to keep cleanup safe.
|
|
607
|
-
*/
|
|
608
|
-
async close() {
|
|
609
|
-
if (this.closed) {
|
|
610
|
-
getLogger().debug(`Session ${this.sessionId} is already closed.`);
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
if (!this.client) {
|
|
614
|
-
getLogger().warn(
|
|
615
|
-
`Cannot close session ${this.sessionId}: client was not provided during session creation.`
|
|
616
|
-
);
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
try {
|
|
620
|
-
getLogger().debug(`Closing session ${this.sessionId}`);
|
|
621
|
-
await this.client.sessions.delete({
|
|
622
|
-
sessionId: this.sessionId,
|
|
623
|
-
projectId: this.projectId
|
|
624
|
-
});
|
|
625
|
-
this.closed = true;
|
|
626
|
-
getLogger().info(`Session closed: ${this.sessionId}`);
|
|
627
|
-
} catch (error) {
|
|
628
|
-
getLogger().warn(`Failed to close session ${this.sessionId}:`, error);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
};
|
|
632
|
-
var SessionListResponse = class {
|
|
633
|
-
constructor(sessions, pagination) {
|
|
634
|
-
this.sessions = sessions;
|
|
635
|
-
this.pagination = pagination;
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Number of sessions in the current page.
|
|
639
|
-
*/
|
|
640
|
-
get length() {
|
|
641
|
-
return this.sessions.length;
|
|
642
|
-
}
|
|
643
|
-
[Symbol.iterator]() {
|
|
644
|
-
return this.sessions[Symbol.iterator]();
|
|
645
|
-
}
|
|
646
|
-
at(index) {
|
|
647
|
-
return this.sessions[index];
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
var SessionDownloadInfo = class {
|
|
651
|
-
constructor(shape) {
|
|
652
|
-
this.id = shape.id;
|
|
653
|
-
this.filename = shape.filename;
|
|
654
|
-
this.contentType = shape.contentType;
|
|
655
|
-
this.size = shape.size;
|
|
656
|
-
this.sha256 = shape.sha256;
|
|
657
|
-
this.status = shape.status;
|
|
658
|
-
this.createdAt = shape.createdAt;
|
|
659
|
-
}
|
|
660
|
-
};
|
|
661
|
-
var SessionDownloadsListResponse = class {
|
|
662
|
-
constructor(downloads, summary) {
|
|
663
|
-
this.downloads = downloads;
|
|
664
|
-
this.summary = summary;
|
|
665
|
-
}
|
|
666
|
-
};
|
|
667
|
-
var SessionDownloadsDeleteResponse = class {
|
|
668
|
-
constructor(shape) {
|
|
669
|
-
this.status = shape.status;
|
|
670
|
-
this.deletedCount = shape.deletedCount;
|
|
671
|
-
}
|
|
672
|
-
};
|
|
673
|
-
var SessionTargetInfo = class {
|
|
674
|
-
constructor(shape) {
|
|
675
|
-
this.id = shape.id;
|
|
676
|
-
this.title = shape.title;
|
|
677
|
-
this.type = shape.type;
|
|
678
|
-
this.url = shape.url;
|
|
679
|
-
this.description = shape.description;
|
|
680
|
-
this.inspectUrl = shape.inspectUrl;
|
|
681
|
-
this.webSocketDebuggerUrl = shape.webSocketDebuggerUrl;
|
|
682
|
-
this.webSocketDebuggerUrlTransformed = shape.webSocketDebuggerUrlTransformed;
|
|
683
|
-
}
|
|
684
|
-
};
|
|
685
|
-
var SessionDownloadsResource = class {
|
|
686
|
-
constructor(client) {
|
|
687
|
-
this.client = client;
|
|
688
|
-
}
|
|
689
|
-
async list(sessionId, projectId) {
|
|
690
|
-
const response = await this.client._post(
|
|
691
|
-
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads/list`,
|
|
692
|
-
{
|
|
693
|
-
api_key: this.client.apiKey,
|
|
694
|
-
project_id: projectId ?? this.client.projectId
|
|
695
|
-
}
|
|
696
|
-
);
|
|
697
|
-
if (response.status >= 400) {
|
|
698
|
-
this.handleError("list session downloads", response, sessionId);
|
|
699
|
-
}
|
|
700
|
-
const result = asRecord3(response.data);
|
|
701
|
-
const items = Array.isArray(result.downloads) ? result.downloads : [];
|
|
702
|
-
const downloads = items.map((item) => {
|
|
703
|
-
const download = asRecord3(item);
|
|
704
|
-
return new SessionDownloadInfo({
|
|
705
|
-
id: getString3(download.id) ?? "",
|
|
706
|
-
filename: getString3(download.filename) ?? "",
|
|
707
|
-
contentType: getString3(download.content_type) ?? null,
|
|
708
|
-
size: getNumber(download.size) ?? 0,
|
|
709
|
-
sha256: getString3(download.sha256) ?? null,
|
|
710
|
-
status: getString3(download.status) ?? "available",
|
|
711
|
-
createdAt: getString3(download.created_at) ?? ""
|
|
712
|
-
});
|
|
713
|
-
});
|
|
714
|
-
const summary = asRecord3(result.summary);
|
|
715
|
-
return new SessionDownloadsListResponse(downloads, {
|
|
716
|
-
count: getNumber(summary.count) ?? downloads.length,
|
|
717
|
-
totalSize: getNumber(summary.total_size) ?? 0
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
async get(sessionId, downloadId, projectId) {
|
|
721
|
-
const response = await this.client._get(
|
|
722
|
-
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads/${downloadId}`,
|
|
723
|
-
{
|
|
724
|
-
api_key: this.client.apiKey,
|
|
725
|
-
project_id: projectId ?? this.client.projectId
|
|
726
|
-
},
|
|
727
|
-
{
|
|
728
|
-
responseType: "arraybuffer"
|
|
729
|
-
}
|
|
730
|
-
);
|
|
731
|
-
if (response.status >= 400) {
|
|
732
|
-
this.handleError("fetch session download", response, sessionId);
|
|
733
|
-
}
|
|
734
|
-
return Buffer.from(response.data);
|
|
735
|
-
}
|
|
736
|
-
async archive(sessionId, projectId) {
|
|
737
|
-
const response = await this.client._get(
|
|
738
|
-
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads/archive`,
|
|
739
|
-
{
|
|
740
|
-
api_key: this.client.apiKey,
|
|
741
|
-
project_id: projectId ?? this.client.projectId
|
|
742
|
-
},
|
|
743
|
-
{
|
|
744
|
-
responseType: "arraybuffer"
|
|
745
|
-
}
|
|
746
|
-
);
|
|
747
|
-
if (response.status >= 400) {
|
|
748
|
-
this.handleError("archive session downloads", response, sessionId);
|
|
749
|
-
}
|
|
750
|
-
return Buffer.from(response.data);
|
|
751
|
-
}
|
|
752
|
-
async delete(sessionId, projectId) {
|
|
753
|
-
const response = await this.client._delete(
|
|
754
|
-
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads`,
|
|
755
|
-
{
|
|
756
|
-
api_key: this.client.apiKey,
|
|
757
|
-
project_id: projectId ?? this.client.projectId
|
|
758
|
-
}
|
|
759
|
-
);
|
|
760
|
-
if (response.status >= 400) {
|
|
761
|
-
this.handleError("delete session downloads", response, sessionId);
|
|
762
|
-
}
|
|
763
|
-
const result = asRecord3(response.data);
|
|
764
|
-
return new SessionDownloadsDeleteResponse({
|
|
765
|
-
status: getString3(result.status) ?? "",
|
|
766
|
-
deletedCount: getNumber(result.deleted_count) ?? 0
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
handleError(action, response, sessionId) {
|
|
770
|
-
const errorData = asRecord3(response.data);
|
|
771
|
-
const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
|
|
772
|
-
if (response.status === 401) {
|
|
773
|
-
throw new AuthenticationError(
|
|
774
|
-
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
if (response.status === 404) {
|
|
778
|
-
throw new SessionNotFoundError(`Session downloads not found for ${sessionId}: ${errorMessage}`);
|
|
1037
|
+
get updatedAtDate() {
|
|
1038
|
+
if (!this.updatedAt) {
|
|
1039
|
+
return null;
|
|
779
1040
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1041
|
+
const date = new Date(this.updatedAt);
|
|
1042
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
1043
|
+
}
|
|
1044
|
+
isLocked() {
|
|
1045
|
+
return this.status === "locked";
|
|
1046
|
+
}
|
|
1047
|
+
isAvailable() {
|
|
1048
|
+
return this.status === "available";
|
|
784
1049
|
}
|
|
785
1050
|
};
|
|
786
|
-
var
|
|
1051
|
+
var ContextListResponse = class {
|
|
1052
|
+
constructor(data) {
|
|
1053
|
+
this.data = data;
|
|
1054
|
+
}
|
|
1055
|
+
get length() {
|
|
1056
|
+
return this.data.length;
|
|
1057
|
+
}
|
|
1058
|
+
[Symbol.iterator]() {
|
|
1059
|
+
return this.data[Symbol.iterator]();
|
|
1060
|
+
}
|
|
1061
|
+
at(index) {
|
|
1062
|
+
return this.data[index];
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
var ContextsResource = class {
|
|
787
1066
|
constructor(client) {
|
|
788
1067
|
this.client = client;
|
|
789
|
-
this.downloads = new SessionDownloadsResource(client);
|
|
790
1068
|
}
|
|
791
1069
|
/**
|
|
792
|
-
* Create a new
|
|
1070
|
+
* Create a new persistent context.
|
|
793
1071
|
*/
|
|
794
1072
|
async create(options = {}) {
|
|
1073
|
+
const url = `${this.client.baseUrl}/instance/v1/contexts/create-context`;
|
|
795
1074
|
const payload = {
|
|
796
1075
|
api_key: this.client.apiKey,
|
|
797
|
-
project_id:
|
|
798
|
-
browser_mode: options.browserMode ?? "normal"
|
|
1076
|
+
project_id: this.client.projectId
|
|
799
1077
|
};
|
|
800
|
-
if (options.
|
|
801
|
-
|
|
802
|
-
id: options.context.id,
|
|
803
|
-
mode: normalizeContextMode(options.context.mode)
|
|
804
|
-
};
|
|
805
|
-
payload.context = contextPayload;
|
|
806
|
-
getLogger().debug(
|
|
807
|
-
`Creating session with context (id=${options.context.id}, mode=${contextPayload.mode})`
|
|
808
|
-
);
|
|
809
|
-
} else {
|
|
810
|
-
getLogger().debug(`Creating session with browser_mode=${payload.browser_mode}`);
|
|
811
|
-
}
|
|
812
|
-
if (options.extensionIds && options.extensionIds.length > 0) {
|
|
813
|
-
payload.extension_ids = options.extensionIds;
|
|
814
|
-
}
|
|
815
|
-
if (options.proxy) {
|
|
816
|
-
payload.proxy = this.normalizeProxy(options.proxy);
|
|
817
|
-
}
|
|
818
|
-
if (options.weakLock) {
|
|
819
|
-
payload.weak_lock = true;
|
|
820
|
-
}
|
|
821
|
-
if (options.asyncCreate !== false) {
|
|
822
|
-
return this.createAsync(payload, options);
|
|
1078
|
+
if (options.metadata) {
|
|
1079
|
+
payload.metadata = options.metadata;
|
|
823
1080
|
}
|
|
824
|
-
const url = `${this.client.baseUrl}/instance`;
|
|
825
1081
|
const response = await this.client._post(url, payload);
|
|
826
1082
|
if (response.status >= 400) {
|
|
827
|
-
this.
|
|
1083
|
+
this.handleError(response, "create");
|
|
828
1084
|
}
|
|
829
|
-
const result =
|
|
830
|
-
const
|
|
831
|
-
if (!
|
|
832
|
-
throw new APIError("Failed to create
|
|
1085
|
+
const result = asRecord2(response.data);
|
|
1086
|
+
const contextId = getString2(result.context_id);
|
|
1087
|
+
if (!contextId) {
|
|
1088
|
+
throw new APIError("Failed to create context: context_id missing from response", {
|
|
833
1089
|
statusCode: response.status,
|
|
834
1090
|
response: response.data
|
|
835
1091
|
});
|
|
836
1092
|
}
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
return new SessionInfo({
|
|
843
|
-
id: sessionId,
|
|
844
|
-
status: createdSession?.status ?? "active",
|
|
845
|
-
apiKey: this.client.apiKey,
|
|
846
|
-
projectId,
|
|
847
|
-
browserType: createdSession?.browserType ?? (options.browserMode ?? "normal"),
|
|
848
|
-
createdAt: createdSession?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
849
|
-
inspectUrl: createdSession?.inspectUrl ?? `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
|
|
850
|
-
containerId: createdSession?.containerId ?? containerId,
|
|
851
|
-
ws: wsUrl ?? createdSession?.ws ?? null,
|
|
852
|
-
client: this.client
|
|
1093
|
+
const context = new ContextInfo({
|
|
1094
|
+
id: contextId,
|
|
1095
|
+
status: normalizeContextStatus(result.locked),
|
|
1096
|
+
metadata: result.metadata ?? options.metadata ?? {},
|
|
1097
|
+
createdAt: parseTimestamp(result.created_at)
|
|
853
1098
|
});
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
const response = await this.client._post(`${this.client.baseUrl}/instance/v2`, payload);
|
|
857
|
-
if (response.status >= 400) {
|
|
858
|
-
this.handleCreateError(response);
|
|
859
|
-
}
|
|
860
|
-
const result = asRecord3(response.data);
|
|
861
|
-
const sessionId = getString3(result.session_id);
|
|
862
|
-
if (!sessionId) {
|
|
863
|
-
throw new APIError("Failed to create session: session_id missing from response", {
|
|
864
|
-
statusCode: response.status,
|
|
865
|
-
response: response.data
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
const projectId = options.projectId ?? this.client.projectId;
|
|
869
|
-
const pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
870
|
-
const pollTimeoutMs = options.pollTimeoutMs ?? 6e5;
|
|
871
|
-
const deadline = Date.now() + pollTimeoutMs;
|
|
872
|
-
while (Date.now() < deadline) {
|
|
873
|
-
const info = await this.get(sessionId, projectId);
|
|
874
|
-
if (info.status === "active") {
|
|
875
|
-
const wsUrl = await this._getWebSocketDebuggerUrl(sessionId);
|
|
876
|
-
getLogger().info(`Session created successfully (async): id=${sessionId}`);
|
|
877
|
-
return new SessionInfo({
|
|
878
|
-
id: sessionId,
|
|
879
|
-
status: "active",
|
|
880
|
-
apiKey: this.client.apiKey,
|
|
881
|
-
projectId,
|
|
882
|
-
browserType: info.browserType || (options.browserMode ?? "normal"),
|
|
883
|
-
createdAt: info.createdAt,
|
|
884
|
-
inspectUrl: info.inspectUrl,
|
|
885
|
-
containerId: info.containerId,
|
|
886
|
-
ws: wsUrl ?? info.ws,
|
|
887
|
-
client: this.client
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
if (info.status === "create_failed") {
|
|
891
|
-
this.handleAsyncCreateFailed(payload);
|
|
892
|
-
throw new APIError("Session creation failed", {
|
|
893
|
-
statusCode: 500,
|
|
894
|
-
response: { session_id: sessionId, status: info.status }
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
if (info.status === "closed") {
|
|
898
|
-
throw new APIError("Session closed before activation", {
|
|
899
|
-
statusCode: 500,
|
|
900
|
-
response: { session_id: sessionId, status: info.status }
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
await delay(pollIntervalMs);
|
|
904
|
-
}
|
|
905
|
-
throw new TimeoutError(
|
|
906
|
-
`Timed out waiting for session ${sessionId} to become active after ${pollTimeoutMs}ms`
|
|
907
|
-
);
|
|
1099
|
+
getLogger().info(`Successfully created context '${context.id}'`);
|
|
1100
|
+
return context;
|
|
908
1101
|
}
|
|
909
1102
|
/**
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
* The upstream API uses POST /instance/session for this lookup, so this SDK method
|
|
913
|
-
* intentionally uses POST even though the operation is read-only.
|
|
1103
|
+
* List contexts in the current project.
|
|
914
1104
|
*/
|
|
915
|
-
async
|
|
916
|
-
const
|
|
1105
|
+
async list(options = {}) {
|
|
1106
|
+
const url = `${this.client.baseUrl}/instance/v1/contexts/list-contexts`;
|
|
1107
|
+
const payload = {
|
|
917
1108
|
api_key: this.client.apiKey,
|
|
918
|
-
project_id:
|
|
919
|
-
|
|
920
|
-
}
|
|
1109
|
+
project_id: this.client.projectId,
|
|
1110
|
+
limit: options.limit ?? 20
|
|
1111
|
+
};
|
|
1112
|
+
if (options.status) {
|
|
1113
|
+
payload.status = options.status;
|
|
1114
|
+
}
|
|
1115
|
+
const response = await this.client._post(url, payload);
|
|
921
1116
|
if (response.status >= 400) {
|
|
922
|
-
this.
|
|
1117
|
+
this.handleError(response, "list");
|
|
923
1118
|
}
|
|
924
|
-
|
|
1119
|
+
const result = asRecord2(response.data);
|
|
1120
|
+
const contexts = Array.isArray(result.contexts) ? result.contexts.map((item) => {
|
|
1121
|
+
const context = asRecord2(item);
|
|
1122
|
+
return new ContextInfo({
|
|
1123
|
+
id: getString2(context.context_id) ?? "",
|
|
1124
|
+
status: normalizeContextStatus(context.locked),
|
|
1125
|
+
createdAt: parseTimestamp(context.created_at),
|
|
1126
|
+
updatedAt: parseTimestamp(context.updated_at),
|
|
1127
|
+
metadata: context.metadata ?? {}
|
|
1128
|
+
});
|
|
1129
|
+
}) : [];
|
|
1130
|
+
getLogger().info(`Retrieved ${contexts.length} contexts`);
|
|
1131
|
+
return new ContextListResponse(contexts);
|
|
925
1132
|
}
|
|
926
1133
|
/**
|
|
927
|
-
*
|
|
1134
|
+
* Fetch a single context.
|
|
928
1135
|
*/
|
|
929
|
-
async
|
|
930
|
-
const url = `${this.client.baseUrl}/instance/
|
|
1136
|
+
async get(contextId) {
|
|
1137
|
+
const url = `${this.client.baseUrl}/instance/v1/contexts/${contextId}`;
|
|
931
1138
|
const payload = {
|
|
932
1139
|
api_key: this.client.apiKey,
|
|
933
|
-
project_id:
|
|
1140
|
+
project_id: this.client.projectId
|
|
934
1141
|
};
|
|
935
|
-
if (options.status) {
|
|
936
|
-
payload.status = options.status;
|
|
937
|
-
}
|
|
938
1142
|
const response = await this.client._post(url, payload);
|
|
939
1143
|
if (response.status >= 400) {
|
|
940
|
-
this.
|
|
1144
|
+
this.handleError(response, "get", contextId);
|
|
941
1145
|
}
|
|
942
|
-
const result =
|
|
943
|
-
const
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
totalPages: getNumber(paginationData.totalPages) ?? 1,
|
|
951
|
-
activeCount: getNumber(paginationData.activeCount) ?? 0,
|
|
952
|
-
closedCount: getNumber(paginationData.closedCount) ?? 0
|
|
1146
|
+
const result = asRecord2(response.data);
|
|
1147
|
+
const context = asRecord2(result.context);
|
|
1148
|
+
const contextInfo = new ContextInfo({
|
|
1149
|
+
id: getString2(context.context_id) ?? contextId,
|
|
1150
|
+
status: normalizeContextStatus(context.locked),
|
|
1151
|
+
metadata: context.metadata ?? {},
|
|
1152
|
+
createdAt: parseTimestamp(context.created_at),
|
|
1153
|
+
updatedAt: parseTimestamp(context.updated_at)
|
|
953
1154
|
});
|
|
954
|
-
getLogger().info(
|
|
955
|
-
|
|
956
|
-
);
|
|
957
|
-
return new SessionListResponse(sessions, pagination);
|
|
1155
|
+
getLogger().info(`Retrieved context '${contextInfo.id}' (status: ${contextInfo.status})`);
|
|
1156
|
+
return contextInfo;
|
|
958
1157
|
}
|
|
959
1158
|
/**
|
|
960
|
-
*
|
|
1159
|
+
* Fork an existing persistent context into a new context.
|
|
961
1160
|
*/
|
|
962
|
-
async
|
|
963
|
-
const url = `${this.client.baseUrl}/instance`;
|
|
1161
|
+
async fork(contextId) {
|
|
1162
|
+
const url = `${this.client.baseUrl}/instance/v1/contexts/${contextId}/fork`;
|
|
964
1163
|
const payload = {
|
|
965
1164
|
api_key: this.client.apiKey,
|
|
966
|
-
project_id:
|
|
967
|
-
session_id: options.sessionId
|
|
1165
|
+
project_id: this.client.projectId
|
|
968
1166
|
};
|
|
969
|
-
const response = await this.client.
|
|
1167
|
+
const response = await this.client._post(url, payload);
|
|
970
1168
|
if (response.status >= 400) {
|
|
971
|
-
this.
|
|
1169
|
+
this.handleError(response, "fork", contextId);
|
|
972
1170
|
}
|
|
973
|
-
|
|
1171
|
+
const result = asRecord2(response.data);
|
|
1172
|
+
const forkedContextId = getString2(result.context_id);
|
|
1173
|
+
if (!forkedContextId) {
|
|
1174
|
+
throw new APIError("Failed to fork context: context_id missing from response", {
|
|
1175
|
+
statusCode: response.status,
|
|
1176
|
+
response: response.data
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
const contextInfo = new ContextInfo({
|
|
1180
|
+
id: forkedContextId,
|
|
1181
|
+
status: normalizeContextStatus(result.locked),
|
|
1182
|
+
metadata: result.metadata ?? {},
|
|
1183
|
+
createdAt: parseTimestamp(result.created_at),
|
|
1184
|
+
updatedAt: parseTimestamp(result.updated_at)
|
|
1185
|
+
});
|
|
1186
|
+
getLogger().info(`Successfully forked context '${contextId}' -> '${contextInfo.id}'`);
|
|
1187
|
+
return contextInfo;
|
|
974
1188
|
}
|
|
975
1189
|
/**
|
|
976
|
-
*
|
|
1190
|
+
* Delete a persistent context.
|
|
977
1191
|
*/
|
|
978
|
-
async
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
1192
|
+
async delete(contextId) {
|
|
1193
|
+
const url = `${this.client.baseUrl}/instance/v1/contexts/${contextId}`;
|
|
1194
|
+
const payload = {
|
|
1195
|
+
api_key: this.client.apiKey,
|
|
1196
|
+
project_id: this.client.projectId
|
|
1197
|
+
};
|
|
1198
|
+
const response = await this.client._delete(url, payload);
|
|
982
1199
|
if (response.status >= 400) {
|
|
983
|
-
this.
|
|
1200
|
+
this.handleError(response, "delete", contextId);
|
|
984
1201
|
}
|
|
985
|
-
|
|
986
|
-
return result.map((item) => this.mapSessionTarget(asRecord3(item)));
|
|
1202
|
+
getLogger().info(`Successfully deleted context '${contextId}'`);
|
|
987
1203
|
}
|
|
988
1204
|
/**
|
|
989
|
-
*
|
|
990
|
-
*
|
|
991
|
-
* @internal
|
|
1205
|
+
* Force-release a stuck lock on a context.
|
|
992
1206
|
*/
|
|
993
|
-
async
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
if (response.status >= 400) {
|
|
999
|
-
getLogger().error("Error getting WebSocket debugger URL:", response.data);
|
|
1000
|
-
return null;
|
|
1001
|
-
}
|
|
1002
|
-
const result = asRecord3(response.data);
|
|
1003
|
-
return getString3(result.webSocketDebuggerUrlTransformed) ?? getString3(result.webSocketDebuggerUrl) ?? null;
|
|
1004
|
-
} catch (error) {
|
|
1005
|
-
getLogger().error("Error getting WebSocket debugger URL:", error);
|
|
1006
|
-
return null;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
normalizeProxy(proxy) {
|
|
1010
|
-
if (!proxy.server) {
|
|
1011
|
-
throw new ValidationError("proxy.server is required when proxy is provided");
|
|
1012
|
-
}
|
|
1013
|
-
const normalizedProxy = {
|
|
1014
|
-
type: proxy.type ?? "external",
|
|
1015
|
-
server: proxy.server
|
|
1207
|
+
async forceRelease(contextId) {
|
|
1208
|
+
const url = `${this.client.baseUrl}/instance/v1/contexts/${contextId}/force-release`;
|
|
1209
|
+
const payload = {
|
|
1210
|
+
api_key: this.client.apiKey,
|
|
1211
|
+
project_id: this.client.projectId
|
|
1016
1212
|
};
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
if (proxy.password) {
|
|
1021
|
-
normalizedProxy.password = proxy.password;
|
|
1022
|
-
}
|
|
1023
|
-
return normalizedProxy;
|
|
1024
|
-
}
|
|
1025
|
-
handleAsyncCreateFailed(payload) {
|
|
1026
|
-
const context = asRecord3(payload.context);
|
|
1027
|
-
const contextId = getString3(context.id);
|
|
1028
|
-
const contextMode = getString3(context.mode);
|
|
1029
|
-
if (contextId && contextMode === "read_write" && payload.weak_lock !== true) {
|
|
1030
|
-
throw new ContextLockedError("Context is locked by another active session");
|
|
1213
|
+
const response = await this.client._post(url, payload);
|
|
1214
|
+
if (response.status >= 400) {
|
|
1215
|
+
this.handleError(response, "force_release", contextId);
|
|
1031
1216
|
}
|
|
1217
|
+
const result = asRecord2(response.data);
|
|
1218
|
+
return {
|
|
1219
|
+
status: getString2(result.status) ?? "unlocked",
|
|
1220
|
+
message: getString2(result.message) ?? "Lock released successfully"
|
|
1221
|
+
};
|
|
1032
1222
|
}
|
|
1033
|
-
|
|
1034
|
-
const errorData =
|
|
1035
|
-
const errorMessage =
|
|
1036
|
-
const errorCode =
|
|
1037
|
-
const metadata =
|
|
1223
|
+
handleError(response, operation, contextId) {
|
|
1224
|
+
const errorData = asRecord2(response.data);
|
|
1225
|
+
const errorMessage = getString2(errorData.error) ?? getString2(errorData.message) ?? "Unknown error";
|
|
1226
|
+
const errorCode = getString2(errorData.code);
|
|
1227
|
+
const metadata = asRecord2(errorData.metadata);
|
|
1038
1228
|
if (response.status === 401) {
|
|
1039
|
-
throw new AuthenticationError(
|
|
1040
|
-
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
1041
|
-
);
|
|
1042
|
-
}
|
|
1043
|
-
if (response.status === 404 && errorCode === "context_not_found") {
|
|
1044
|
-
throw new ContextNotFoundError(`Context not found: ${errorMessage}`);
|
|
1229
|
+
throw new AuthenticationError(`Authentication failed: ${errorMessage}`);
|
|
1045
1230
|
}
|
|
1046
1231
|
if (response.status === 404) {
|
|
1047
|
-
|
|
1232
|
+
const suffix = contextId ? `: ${contextId}` : "";
|
|
1233
|
+
throw new ContextNotFoundError(`Context not found${suffix}`);
|
|
1048
1234
|
}
|
|
1049
|
-
if (response.status === 409 && errorCode === "context_locked") {
|
|
1235
|
+
if (response.status === 409 && (errorCode === "context_locked" || errorCode === "CONTEXT_FORK_SOURCE_LOCKED")) {
|
|
1050
1236
|
throw new ContextLockedError(errorMessage, {
|
|
1051
|
-
activeSessionId:
|
|
1052
|
-
retryAfter:
|
|
1237
|
+
activeSessionId: getString2(metadata.activeSessionId),
|
|
1238
|
+
retryAfter: typeof metadata.retryAfter === "number" ? metadata.retryAfter : void 0
|
|
1053
1239
|
});
|
|
1054
1240
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
|
|
1063
|
-
if (response.status === 401) {
|
|
1064
|
-
throw new AuthenticationError(
|
|
1065
|
-
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
1241
|
+
if (response.status === 409 && errorCode === "session_active") {
|
|
1242
|
+
throw new APIError(
|
|
1243
|
+
`Cannot ${operation} context: ${errorMessage} (session is active)`,
|
|
1244
|
+
{
|
|
1245
|
+
statusCode: response.status,
|
|
1246
|
+
response: response.data
|
|
1247
|
+
}
|
|
1066
1248
|
);
|
|
1067
1249
|
}
|
|
1068
|
-
if (response.status ===
|
|
1069
|
-
|
|
1070
|
-
|
|
1250
|
+
if (response.status === 500) {
|
|
1251
|
+
const details = getString2(errorData.details);
|
|
1252
|
+
throw new APIError(
|
|
1253
|
+
`Internal server error (500) during ${operation}: ${errorMessage}${details ? `. Details: ${details}` : ""}`,
|
|
1254
|
+
{
|
|
1255
|
+
statusCode: response.status,
|
|
1256
|
+
response: response.data
|
|
1257
|
+
}
|
|
1071
1258
|
);
|
|
1072
1259
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
|
|
1081
|
-
if (response.status === 401) {
|
|
1082
|
-
throw new AuthenticationError(
|
|
1083
|
-
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
1260
|
+
if (response.status === 502) {
|
|
1261
|
+
throw new APIError(
|
|
1262
|
+
`Server gateway error (502) during ${operation}: The service may be temporarily unavailable. Please retry.`,
|
|
1263
|
+
{
|
|
1264
|
+
statusCode: response.status,
|
|
1265
|
+
response: response.data
|
|
1266
|
+
}
|
|
1084
1267
|
);
|
|
1085
1268
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
|
|
1094
|
-
if (response.status === 401) {
|
|
1095
|
-
throw new AuthenticationError(
|
|
1096
|
-
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
1269
|
+
if (response.status === 503) {
|
|
1270
|
+
throw new APIError(
|
|
1271
|
+
`Service unavailable (503) during ${operation}: The service is temporarily unavailable. Please retry later.`,
|
|
1272
|
+
{
|
|
1273
|
+
statusCode: response.status,
|
|
1274
|
+
response: response.data
|
|
1275
|
+
}
|
|
1097
1276
|
);
|
|
1098
1277
|
}
|
|
1099
|
-
if (response.status ===
|
|
1100
|
-
throw new
|
|
1101
|
-
`
|
|
1278
|
+
if (response.status === 504) {
|
|
1279
|
+
throw new APIError(
|
|
1280
|
+
`Gateway timeout (504) during ${operation}: The request timed out. Please retry.`,
|
|
1281
|
+
{
|
|
1282
|
+
statusCode: response.status,
|
|
1283
|
+
response: response.data
|
|
1284
|
+
}
|
|
1102
1285
|
);
|
|
1103
1286
|
}
|
|
1104
|
-
throw new APIError(`Failed to
|
|
1287
|
+
throw new APIError(`Failed to ${operation} context: ${errorMessage}`, {
|
|
1105
1288
|
statusCode: response.status,
|
|
1106
1289
|
response: response.data
|
|
1107
1290
|
});
|
|
1108
1291
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
// src/extensions.ts
|
|
1295
|
+
import { readFile } from "fs/promises";
|
|
1296
|
+
import path from "path";
|
|
1297
|
+
function asRecord3(value) {
|
|
1298
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
1299
|
+
}
|
|
1300
|
+
function getString3(value) {
|
|
1301
|
+
return typeof value === "string" ? value : void 0;
|
|
1302
|
+
}
|
|
1303
|
+
var ExtensionInfo = class {
|
|
1304
|
+
constructor(shape) {
|
|
1305
|
+
this.id = shape.id;
|
|
1306
|
+
this.name = shape.name;
|
|
1307
|
+
this.projectId = shape.projectId;
|
|
1308
|
+
this.createdAt = shape.createdAt;
|
|
1309
|
+
this.updatedAt = shape.updatedAt;
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
var ExtensionsResource = class {
|
|
1313
|
+
constructor(client) {
|
|
1314
|
+
this.client = client;
|
|
1315
|
+
}
|
|
1316
|
+
async upload(filePath, options = {}) {
|
|
1317
|
+
const fileBuffer = await readFile(filePath);
|
|
1318
|
+
const form = new FormData();
|
|
1319
|
+
const usedProjectId = options.projectId ?? this.client.projectId;
|
|
1320
|
+
form.append("project_id", usedProjectId);
|
|
1321
|
+
if (options.name) {
|
|
1322
|
+
form.append("name", options.name);
|
|
1116
1323
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
)
|
|
1324
|
+
form.append(
|
|
1325
|
+
"file",
|
|
1326
|
+
new Blob([fileBuffer], { type: "application/octet-stream" }),
|
|
1327
|
+
path.basename(filePath)
|
|
1328
|
+
);
|
|
1329
|
+
const response = await this.client._post(
|
|
1330
|
+
`${this.client.baseUrl}/instance/v1/extension/upload`,
|
|
1331
|
+
form,
|
|
1332
|
+
{
|
|
1333
|
+
headers: {
|
|
1334
|
+
project_id: usedProjectId
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
);
|
|
1338
|
+
if (response.status >= 400) {
|
|
1339
|
+
this.handleError("upload extension", response);
|
|
1121
1340
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1341
|
+
getLogger().info(`Extension uploaded successfully: ${filePath}`);
|
|
1342
|
+
return this.parseInfo(response.data);
|
|
1343
|
+
}
|
|
1344
|
+
async list(options = {}) {
|
|
1345
|
+
const response = await this.client._post(`${this.client.baseUrl}/instance/v1/extension/list`, {
|
|
1346
|
+
project_id: options.projectId ?? this.client.projectId,
|
|
1347
|
+
limit: options.limit ?? 20,
|
|
1348
|
+
offset: options.offset ?? 0
|
|
1125
1349
|
});
|
|
1350
|
+
if (response.status >= 400) {
|
|
1351
|
+
this.handleError("list extensions", response);
|
|
1352
|
+
}
|
|
1353
|
+
const result = asRecord3(response.data);
|
|
1354
|
+
const items = Array.isArray(result.data) ? result.data : [];
|
|
1355
|
+
return items.map((item) => this.parseInfo(item));
|
|
1126
1356
|
}
|
|
1127
|
-
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
status: getString3(raw.status) ?? "active",
|
|
1132
|
-
apiKey: getString3(raw.api_key) ?? this.client.apiKey,
|
|
1133
|
-
projectId: getString3(raw.project_id) ?? (projectId ?? this.client.projectId),
|
|
1134
|
-
regionId: getString3(raw.region_id) ?? this.client.selectedRegion ?? this.client.region ?? null,
|
|
1135
|
-
browserType: getString3(raw.browser_type) ?? getString3(raw.browser_mode) ?? "normal",
|
|
1136
|
-
createdAt: getString3(raw.created_at) ?? "",
|
|
1137
|
-
inspectUrl: getString3(raw.inspect_url) ?? `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
|
|
1138
|
-
containerId: getString3(raw.container_id) ?? null,
|
|
1139
|
-
ws: getString3(raw.ws) ?? null,
|
|
1140
|
-
client: this.client
|
|
1357
|
+
async get(extensionId, projectId) {
|
|
1358
|
+
const response = await this.client._post(`${this.client.baseUrl}/instance/v1/extension/info`, {
|
|
1359
|
+
project_id: projectId ?? this.client.projectId,
|
|
1360
|
+
extension_id: extensionId
|
|
1141
1361
|
});
|
|
1362
|
+
if (response.status >= 400) {
|
|
1363
|
+
this.handleError("get extension", response);
|
|
1364
|
+
}
|
|
1365
|
+
return this.parseInfo(response.data);
|
|
1142
1366
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1367
|
+
async delete(extensionId) {
|
|
1368
|
+
const response = await this.client._delete(
|
|
1369
|
+
`${this.client.baseUrl}/instance/v1/extension/${extensionId}`
|
|
1370
|
+
);
|
|
1371
|
+
if (response.status >= 400) {
|
|
1372
|
+
this.handleError("delete extension", response);
|
|
1373
|
+
}
|
|
1374
|
+
getLogger().info(`Extension deleted successfully: ${extensionId}`);
|
|
1375
|
+
}
|
|
1376
|
+
parseInfo(data) {
|
|
1377
|
+
const item = asRecord3(data);
|
|
1378
|
+
return new ExtensionInfo({
|
|
1379
|
+
id: getString3(item.id) ?? "",
|
|
1380
|
+
name: getString3(item.name) ?? "",
|
|
1381
|
+
projectId: getString3(item.project_id) ?? "",
|
|
1382
|
+
createdAt: getString3(item.created_at),
|
|
1383
|
+
updatedAt: getString3(item.updated_at)
|
|
1153
1384
|
});
|
|
1154
1385
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
`
|
|
1161
|
-
error
|
|
1386
|
+
handleError(action, response) {
|
|
1387
|
+
const errorData = asRecord3(response.data);
|
|
1388
|
+
const errorMessage = getString3(errorData.message) ?? getString3(errorData.error) ?? "Unknown error";
|
|
1389
|
+
if (response.status === 401) {
|
|
1390
|
+
throw new AuthenticationError(
|
|
1391
|
+
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
1162
1392
|
);
|
|
1163
|
-
return null;
|
|
1164
1393
|
}
|
|
1394
|
+
throw new APIError(`Failed to ${action}: ${errorMessage}`, {
|
|
1395
|
+
statusCode: response.status,
|
|
1396
|
+
response: response.data
|
|
1397
|
+
});
|
|
1165
1398
|
}
|
|
1166
1399
|
};
|
|
1167
1400
|
|
|
@@ -1432,10 +1665,11 @@ var Lexmount = class {
|
|
|
1432
1665
|
};
|
|
1433
1666
|
|
|
1434
1667
|
// src/index.ts
|
|
1435
|
-
var VERSION = "0.5.
|
|
1668
|
+
var VERSION = "0.5.6";
|
|
1436
1669
|
export {
|
|
1437
1670
|
APIError,
|
|
1438
1671
|
AuthenticationError,
|
|
1672
|
+
CDPClient,
|
|
1439
1673
|
ContextInfo,
|
|
1440
1674
|
ContextListResponse,
|
|
1441
1675
|
ContextLockedError,
|
|
@@ -1460,10 +1694,13 @@ export {
|
|
|
1460
1694
|
TimeoutError,
|
|
1461
1695
|
VERSION,
|
|
1462
1696
|
ValidationError,
|
|
1697
|
+
connectOverCDP,
|
|
1463
1698
|
Lexmount as default,
|
|
1699
|
+
deregisterIntegratedAuthCallback,
|
|
1464
1700
|
disableLogging,
|
|
1465
1701
|
enableLogging,
|
|
1466
1702
|
getLogger,
|
|
1703
|
+
registerIntegratedAuthCallback,
|
|
1467
1704
|
setLogLevel
|
|
1468
1705
|
};
|
|
1469
1706
|
//# sourceMappingURL=index.mjs.map
|