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