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