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