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.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,1031 +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
|
-
payload.status = options.status;
|
|
254
|
-
}
|
|
255
|
-
const response = await this.client._post(url, payload);
|
|
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
|
+
);
|
|
256
430
|
if (response.status >= 400) {
|
|
257
|
-
this.handleError(response,
|
|
431
|
+
this.handleError("fetch session download", response, sessionId);
|
|
258
432
|
}
|
|
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);
|
|
433
|
+
return Buffer.from(response.data);
|
|
272
434
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
+
);
|
|
283
446
|
if (response.status >= 400) {
|
|
284
|
-
this.handleError(
|
|
447
|
+
this.handleError("archive session downloads", response, sessionId);
|
|
448
|
+
}
|
|
449
|
+
return Buffer.from(response.data);
|
|
450
|
+
}
|
|
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
|
+
);
|
|
459
|
+
if (response.status >= 400) {
|
|
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.createdAt = options.createdAt;
|
|
583
|
-
this.inspectUrl = options.inspectUrl;
|
|
584
|
-
this.containerId = options.containerId;
|
|
585
|
-
this.ws = options.ws ?? null;
|
|
586
|
-
this.client = options.client;
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Playwright/CDP connection URL.
|
|
590
|
-
*/
|
|
591
|
-
get connectUrl() {
|
|
592
|
-
return this.ws ?? "";
|
|
1020
|
+
this.metadata = options.metadata ?? {};
|
|
1021
|
+
this.createdAt = options.createdAt ?? null;
|
|
1022
|
+
this.updatedAt = options.updatedAt ?? null;
|
|
593
1023
|
}
|
|
594
|
-
/**
|
|
595
|
-
* Parsed session creation time.
|
|
596
|
-
*/
|
|
597
1024
|
get createdAtDate() {
|
|
1025
|
+
if (!this.createdAt) {
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
598
1028
|
const date = new Date(this.createdAt);
|
|
599
1029
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
600
1030
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
* This method is idempotent. Errors are logged and swallowed to keep cleanup safe.
|
|
605
|
-
*/
|
|
606
|
-
async close() {
|
|
607
|
-
if (this.closed) {
|
|
608
|
-
getLogger().debug(`Session ${this.sessionId} is already closed.`);
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
if (!this.client) {
|
|
612
|
-
getLogger().warn(
|
|
613
|
-
`Cannot close session ${this.sessionId}: client was not provided during session creation.`
|
|
614
|
-
);
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
try {
|
|
618
|
-
getLogger().debug(`Closing session ${this.sessionId}`);
|
|
619
|
-
await this.client.sessions.delete({
|
|
620
|
-
sessionId: this.sessionId,
|
|
621
|
-
projectId: this.projectId
|
|
622
|
-
});
|
|
623
|
-
this.closed = true;
|
|
624
|
-
getLogger().info(`Session closed: ${this.sessionId}`);
|
|
625
|
-
} catch (error) {
|
|
626
|
-
getLogger().warn(`Failed to close session ${this.sessionId}:`, error);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
var SessionListResponse = class {
|
|
631
|
-
constructor(sessions, pagination) {
|
|
632
|
-
this.sessions = sessions;
|
|
633
|
-
this.pagination = pagination;
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Number of sessions in the current page.
|
|
637
|
-
*/
|
|
638
|
-
get length() {
|
|
639
|
-
return this.sessions.length;
|
|
640
|
-
}
|
|
641
|
-
[Symbol.iterator]() {
|
|
642
|
-
return this.sessions[Symbol.iterator]();
|
|
643
|
-
}
|
|
644
|
-
at(index) {
|
|
645
|
-
return this.sessions[index];
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
var SessionDownloadInfo = class {
|
|
649
|
-
constructor(shape) {
|
|
650
|
-
this.id = shape.id;
|
|
651
|
-
this.filename = shape.filename;
|
|
652
|
-
this.contentType = shape.contentType;
|
|
653
|
-
this.size = shape.size;
|
|
654
|
-
this.sha256 = shape.sha256;
|
|
655
|
-
this.status = shape.status;
|
|
656
|
-
this.createdAt = shape.createdAt;
|
|
657
|
-
}
|
|
658
|
-
};
|
|
659
|
-
var SessionDownloadsListResponse = class {
|
|
660
|
-
constructor(downloads, summary) {
|
|
661
|
-
this.downloads = downloads;
|
|
662
|
-
this.summary = summary;
|
|
663
|
-
}
|
|
664
|
-
};
|
|
665
|
-
var SessionDownloadsDeleteResponse = class {
|
|
666
|
-
constructor(shape) {
|
|
667
|
-
this.status = shape.status;
|
|
668
|
-
this.deletedCount = shape.deletedCount;
|
|
669
|
-
}
|
|
670
|
-
};
|
|
671
|
-
var SessionTargetInfo = class {
|
|
672
|
-
constructor(shape) {
|
|
673
|
-
this.id = shape.id;
|
|
674
|
-
this.title = shape.title;
|
|
675
|
-
this.type = shape.type;
|
|
676
|
-
this.url = shape.url;
|
|
677
|
-
this.description = shape.description;
|
|
678
|
-
this.inspectUrl = shape.inspectUrl;
|
|
679
|
-
this.webSocketDebuggerUrl = shape.webSocketDebuggerUrl;
|
|
680
|
-
this.webSocketDebuggerUrlTransformed = shape.webSocketDebuggerUrlTransformed;
|
|
681
|
-
}
|
|
682
|
-
};
|
|
683
|
-
var SessionDownloadsResource = class {
|
|
684
|
-
constructor(client) {
|
|
685
|
-
this.client = client;
|
|
686
|
-
}
|
|
687
|
-
async list(sessionId, projectId) {
|
|
688
|
-
const response = await this.client._post(
|
|
689
|
-
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads/list`,
|
|
690
|
-
{
|
|
691
|
-
api_key: this.client.apiKey,
|
|
692
|
-
project_id: projectId ?? this.client.projectId
|
|
693
|
-
}
|
|
694
|
-
);
|
|
695
|
-
if (response.status >= 400) {
|
|
696
|
-
this.handleError("list session downloads", response, sessionId);
|
|
697
|
-
}
|
|
698
|
-
const result = asRecord3(response.data);
|
|
699
|
-
const items = Array.isArray(result.downloads) ? result.downloads : [];
|
|
700
|
-
const downloads = items.map((item) => {
|
|
701
|
-
const download = asRecord3(item);
|
|
702
|
-
return new SessionDownloadInfo({
|
|
703
|
-
id: getString3(download.id) ?? "",
|
|
704
|
-
filename: getString3(download.filename) ?? "",
|
|
705
|
-
contentType: getString3(download.content_type) ?? null,
|
|
706
|
-
size: getNumber(download.size) ?? 0,
|
|
707
|
-
sha256: getString3(download.sha256) ?? null,
|
|
708
|
-
status: getString3(download.status) ?? "available",
|
|
709
|
-
createdAt: getString3(download.created_at) ?? ""
|
|
710
|
-
});
|
|
711
|
-
});
|
|
712
|
-
const summary = asRecord3(result.summary);
|
|
713
|
-
return new SessionDownloadsListResponse(downloads, {
|
|
714
|
-
count: getNumber(summary.count) ?? downloads.length,
|
|
715
|
-
totalSize: getNumber(summary.total_size) ?? 0
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
async get(sessionId, downloadId, projectId) {
|
|
719
|
-
const response = await this.client._get(
|
|
720
|
-
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads/${downloadId}`,
|
|
721
|
-
{
|
|
722
|
-
api_key: this.client.apiKey,
|
|
723
|
-
project_id: projectId ?? this.client.projectId
|
|
724
|
-
},
|
|
725
|
-
{
|
|
726
|
-
responseType: "arraybuffer"
|
|
727
|
-
}
|
|
728
|
-
);
|
|
729
|
-
if (response.status >= 400) {
|
|
730
|
-
this.handleError("fetch session download", response, sessionId);
|
|
731
|
-
}
|
|
732
|
-
return Buffer.from(response.data);
|
|
733
|
-
}
|
|
734
|
-
async archive(sessionId, projectId) {
|
|
735
|
-
const response = await this.client._get(
|
|
736
|
-
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads/archive`,
|
|
737
|
-
{
|
|
738
|
-
api_key: this.client.apiKey,
|
|
739
|
-
project_id: projectId ?? this.client.projectId
|
|
740
|
-
},
|
|
741
|
-
{
|
|
742
|
-
responseType: "arraybuffer"
|
|
743
|
-
}
|
|
744
|
-
);
|
|
745
|
-
if (response.status >= 400) {
|
|
746
|
-
this.handleError("archive session downloads", response, sessionId);
|
|
747
|
-
}
|
|
748
|
-
return Buffer.from(response.data);
|
|
749
|
-
}
|
|
750
|
-
async delete(sessionId, projectId) {
|
|
751
|
-
const response = await this.client._delete(
|
|
752
|
-
`${this.client.baseUrl}/instance/v1/sessions/${sessionId}/downloads`,
|
|
753
|
-
{
|
|
754
|
-
api_key: this.client.apiKey,
|
|
755
|
-
project_id: projectId ?? this.client.projectId
|
|
756
|
-
}
|
|
757
|
-
);
|
|
758
|
-
if (response.status >= 400) {
|
|
759
|
-
this.handleError("delete session downloads", response, sessionId);
|
|
760
|
-
}
|
|
761
|
-
const result = asRecord3(response.data);
|
|
762
|
-
return new SessionDownloadsDeleteResponse({
|
|
763
|
-
status: getString3(result.status) ?? "",
|
|
764
|
-
deletedCount: getNumber(result.deleted_count) ?? 0
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
handleError(action, response, sessionId) {
|
|
768
|
-
const errorData = asRecord3(response.data);
|
|
769
|
-
const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
|
|
770
|
-
if (response.status === 401) {
|
|
771
|
-
throw new AuthenticationError(
|
|
772
|
-
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
773
|
-
);
|
|
774
|
-
}
|
|
775
|
-
if (response.status === 404) {
|
|
776
|
-
throw new SessionNotFoundError(`Session downloads not found for ${sessionId}: ${errorMessage}`);
|
|
1031
|
+
get updatedAtDate() {
|
|
1032
|
+
if (!this.updatedAt) {
|
|
1033
|
+
return null;
|
|
777
1034
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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";
|
|
782
1043
|
}
|
|
783
1044
|
};
|
|
784
|
-
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 {
|
|
785
1060
|
constructor(client) {
|
|
786
1061
|
this.client = client;
|
|
787
|
-
this.downloads = new SessionDownloadsResource(client);
|
|
788
1062
|
}
|
|
789
1063
|
/**
|
|
790
|
-
* Create a new
|
|
1064
|
+
* Create a new persistent context.
|
|
791
1065
|
*/
|
|
792
1066
|
async create(options = {}) {
|
|
1067
|
+
const url = `${this.client.baseUrl}/instance/v1/contexts/create-context`;
|
|
793
1068
|
const payload = {
|
|
794
1069
|
api_key: this.client.apiKey,
|
|
795
|
-
project_id:
|
|
796
|
-
browser_mode: options.browserMode ?? "normal"
|
|
1070
|
+
project_id: this.client.projectId
|
|
797
1071
|
};
|
|
798
|
-
if (options.
|
|
799
|
-
|
|
800
|
-
id: options.context.id,
|
|
801
|
-
mode: normalizeContextMode(options.context.mode)
|
|
802
|
-
};
|
|
803
|
-
payload.context = contextPayload;
|
|
804
|
-
getLogger().debug(
|
|
805
|
-
`Creating session with context (id=${options.context.id}, mode=${contextPayload.mode})`
|
|
806
|
-
);
|
|
807
|
-
} else {
|
|
808
|
-
getLogger().debug(`Creating session with browser_mode=${payload.browser_mode}`);
|
|
809
|
-
}
|
|
810
|
-
if (options.extensionIds && options.extensionIds.length > 0) {
|
|
811
|
-
payload.extension_ids = options.extensionIds;
|
|
812
|
-
}
|
|
813
|
-
if (options.proxy) {
|
|
814
|
-
payload.proxy = this.normalizeProxy(options.proxy);
|
|
815
|
-
}
|
|
816
|
-
if (options.weakLock) {
|
|
817
|
-
payload.weak_lock = true;
|
|
818
|
-
}
|
|
819
|
-
if (options.asyncCreate !== false) {
|
|
820
|
-
return this.createAsync(payload, options);
|
|
1072
|
+
if (options.metadata) {
|
|
1073
|
+
payload.metadata = options.metadata;
|
|
821
1074
|
}
|
|
822
|
-
const url = `${this.client.baseUrl}/instance`;
|
|
823
1075
|
const response = await this.client._post(url, payload);
|
|
824
1076
|
if (response.status >= 400) {
|
|
825
|
-
this.
|
|
1077
|
+
this.handleError(response, "create");
|
|
826
1078
|
}
|
|
827
|
-
const result =
|
|
828
|
-
const
|
|
829
|
-
if (!
|
|
830
|
-
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", {
|
|
831
1083
|
statusCode: response.status,
|
|
832
1084
|
response: response.data
|
|
833
1085
|
});
|
|
834
1086
|
}
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
return new SessionInfo({
|
|
841
|
-
id: sessionId,
|
|
842
|
-
status: createdSession?.status ?? "active",
|
|
843
|
-
apiKey: this.client.apiKey,
|
|
844
|
-
projectId,
|
|
845
|
-
browserType: createdSession?.browserType ?? (options.browserMode ?? "normal"),
|
|
846
|
-
createdAt: createdSession?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
847
|
-
inspectUrl: createdSession?.inspectUrl ?? `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
|
|
848
|
-
containerId: createdSession?.containerId ?? containerId,
|
|
849
|
-
ws: wsUrl ?? createdSession?.ws ?? null,
|
|
850
|
-
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)
|
|
851
1092
|
});
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
const response = await this.client._post(`${this.client.baseUrl}/instance/v2`, payload);
|
|
855
|
-
if (response.status >= 400) {
|
|
856
|
-
this.handleCreateError(response);
|
|
857
|
-
}
|
|
858
|
-
const result = asRecord3(response.data);
|
|
859
|
-
const sessionId = getString3(result.session_id);
|
|
860
|
-
if (!sessionId) {
|
|
861
|
-
throw new APIError("Failed to create session: session_id missing from response", {
|
|
862
|
-
statusCode: response.status,
|
|
863
|
-
response: response.data
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
const projectId = options.projectId ?? this.client.projectId;
|
|
867
|
-
const pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
868
|
-
const pollTimeoutMs = options.pollTimeoutMs ?? 6e5;
|
|
869
|
-
const deadline = Date.now() + pollTimeoutMs;
|
|
870
|
-
while (Date.now() < deadline) {
|
|
871
|
-
const info = await this.get(sessionId, projectId);
|
|
872
|
-
if (info.status === "active") {
|
|
873
|
-
const wsUrl = await this._getWebSocketDebuggerUrl(sessionId);
|
|
874
|
-
getLogger().info(`Session created successfully (async): id=${sessionId}`);
|
|
875
|
-
return new SessionInfo({
|
|
876
|
-
id: sessionId,
|
|
877
|
-
status: "active",
|
|
878
|
-
apiKey: this.client.apiKey,
|
|
879
|
-
projectId,
|
|
880
|
-
browserType: info.browserType || (options.browserMode ?? "normal"),
|
|
881
|
-
createdAt: info.createdAt,
|
|
882
|
-
inspectUrl: info.inspectUrl,
|
|
883
|
-
containerId: info.containerId,
|
|
884
|
-
ws: wsUrl ?? info.ws,
|
|
885
|
-
client: this.client
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
if (info.status === "create_failed") {
|
|
889
|
-
this.handleAsyncCreateFailed(payload);
|
|
890
|
-
throw new APIError("Session creation failed", {
|
|
891
|
-
statusCode: 500,
|
|
892
|
-
response: { session_id: sessionId, status: info.status }
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
|
-
if (info.status === "closed") {
|
|
896
|
-
throw new APIError("Session closed before activation", {
|
|
897
|
-
statusCode: 500,
|
|
898
|
-
response: { session_id: sessionId, status: info.status }
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
await delay(pollIntervalMs);
|
|
902
|
-
}
|
|
903
|
-
throw new TimeoutError(
|
|
904
|
-
`Timed out waiting for session ${sessionId} to become active after ${pollTimeoutMs}ms`
|
|
905
|
-
);
|
|
1093
|
+
getLogger().info(`Successfully created context '${context.id}'`);
|
|
1094
|
+
return context;
|
|
906
1095
|
}
|
|
907
1096
|
/**
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
* The upstream API uses POST /instance/session for this lookup, so this SDK method
|
|
911
|
-
* intentionally uses POST even though the operation is read-only.
|
|
1097
|
+
* List contexts in the current project.
|
|
912
1098
|
*/
|
|
913
|
-
async
|
|
914
|
-
const
|
|
1099
|
+
async list(options = {}) {
|
|
1100
|
+
const url = `${this.client.baseUrl}/instance/v1/contexts/list-contexts`;
|
|
1101
|
+
const payload = {
|
|
915
1102
|
api_key: this.client.apiKey,
|
|
916
|
-
project_id:
|
|
917
|
-
|
|
918
|
-
}
|
|
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);
|
|
919
1110
|
if (response.status >= 400) {
|
|
920
|
-
this.
|
|
1111
|
+
this.handleError(response, "list");
|
|
921
1112
|
}
|
|
922
|
-
|
|
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);
|
|
923
1126
|
}
|
|
924
1127
|
/**
|
|
925
|
-
*
|
|
1128
|
+
* Fetch a single context.
|
|
926
1129
|
*/
|
|
927
|
-
async
|
|
928
|
-
const url = `${this.client.baseUrl}/instance/
|
|
1130
|
+
async get(contextId) {
|
|
1131
|
+
const url = `${this.client.baseUrl}/instance/v1/contexts/${contextId}`;
|
|
929
1132
|
const payload = {
|
|
930
1133
|
api_key: this.client.apiKey,
|
|
931
|
-
project_id:
|
|
1134
|
+
project_id: this.client.projectId
|
|
932
1135
|
};
|
|
933
|
-
if (options.status) {
|
|
934
|
-
payload.status = options.status;
|
|
935
|
-
}
|
|
936
1136
|
const response = await this.client._post(url, payload);
|
|
937
1137
|
if (response.status >= 400) {
|
|
938
|
-
this.
|
|
1138
|
+
this.handleError(response, "get", contextId);
|
|
939
1139
|
}
|
|
940
|
-
const result =
|
|
941
|
-
const
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
totalPages: getNumber(paginationData.totalPages) ?? 1,
|
|
949
|
-
activeCount: getNumber(paginationData.activeCount) ?? 0,
|
|
950
|
-
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)
|
|
951
1148
|
});
|
|
952
|
-
getLogger().info(
|
|
953
|
-
|
|
954
|
-
);
|
|
955
|
-
return new SessionListResponse(sessions, pagination);
|
|
1149
|
+
getLogger().info(`Retrieved context '${contextInfo.id}' (status: ${contextInfo.status})`);
|
|
1150
|
+
return contextInfo;
|
|
956
1151
|
}
|
|
957
1152
|
/**
|
|
958
|
-
*
|
|
1153
|
+
* Fork an existing persistent context into a new context.
|
|
959
1154
|
*/
|
|
960
|
-
async
|
|
961
|
-
const url = `${this.client.baseUrl}/instance`;
|
|
1155
|
+
async fork(contextId) {
|
|
1156
|
+
const url = `${this.client.baseUrl}/instance/v1/contexts/${contextId}/fork`;
|
|
962
1157
|
const payload = {
|
|
963
1158
|
api_key: this.client.apiKey,
|
|
964
|
-
project_id:
|
|
965
|
-
session_id: options.sessionId
|
|
1159
|
+
project_id: this.client.projectId
|
|
966
1160
|
};
|
|
967
|
-
const response = await this.client.
|
|
1161
|
+
const response = await this.client._post(url, payload);
|
|
968
1162
|
if (response.status >= 400) {
|
|
969
|
-
this.
|
|
1163
|
+
this.handleError(response, "fork", contextId);
|
|
970
1164
|
}
|
|
971
|
-
|
|
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;
|
|
972
1182
|
}
|
|
973
1183
|
/**
|
|
974
|
-
*
|
|
1184
|
+
* Delete a persistent context.
|
|
975
1185
|
*/
|
|
976
|
-
async
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
|
|
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);
|
|
980
1193
|
if (response.status >= 400) {
|
|
981
|
-
this.
|
|
1194
|
+
this.handleError(response, "delete", contextId);
|
|
982
1195
|
}
|
|
983
|
-
|
|
984
|
-
return result.map((item) => this.mapSessionTarget(asRecord3(item)));
|
|
1196
|
+
getLogger().info(`Successfully deleted context '${contextId}'`);
|
|
985
1197
|
}
|
|
986
1198
|
/**
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
* @internal
|
|
1199
|
+
* Force-release a stuck lock on a context.
|
|
990
1200
|
*/
|
|
991
|
-
async
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
if (response.status >= 400) {
|
|
997
|
-
getLogger().error("Error getting WebSocket debugger URL:", response.data);
|
|
998
|
-
return null;
|
|
999
|
-
}
|
|
1000
|
-
const result = asRecord3(response.data);
|
|
1001
|
-
return getString3(result.webSocketDebuggerUrlTransformed) ?? getString3(result.webSocketDebuggerUrl) ?? null;
|
|
1002
|
-
} catch (error) {
|
|
1003
|
-
getLogger().error("Error getting WebSocket debugger URL:", error);
|
|
1004
|
-
return null;
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
normalizeProxy(proxy) {
|
|
1008
|
-
if (!proxy.server) {
|
|
1009
|
-
throw new ValidationError("proxy.server is required when proxy is provided");
|
|
1010
|
-
}
|
|
1011
|
-
const normalizedProxy = {
|
|
1012
|
-
type: proxy.type ?? "external",
|
|
1013
|
-
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
|
|
1014
1206
|
};
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
if (proxy.password) {
|
|
1019
|
-
normalizedProxy.password = proxy.password;
|
|
1020
|
-
}
|
|
1021
|
-
return normalizedProxy;
|
|
1022
|
-
}
|
|
1023
|
-
handleAsyncCreateFailed(payload) {
|
|
1024
|
-
const context = asRecord3(payload.context);
|
|
1025
|
-
const contextId = getString3(context.id);
|
|
1026
|
-
const contextMode = getString3(context.mode);
|
|
1027
|
-
if (contextId && contextMode === "read_write" && payload.weak_lock !== true) {
|
|
1028
|
-
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);
|
|
1029
1210
|
}
|
|
1211
|
+
const result = asRecord2(response.data);
|
|
1212
|
+
return {
|
|
1213
|
+
status: getString2(result.status) ?? "unlocked",
|
|
1214
|
+
message: getString2(result.message) ?? "Lock released successfully"
|
|
1215
|
+
};
|
|
1030
1216
|
}
|
|
1031
|
-
|
|
1032
|
-
const errorData =
|
|
1033
|
-
const errorMessage =
|
|
1034
|
-
const errorCode =
|
|
1035
|
-
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);
|
|
1036
1222
|
if (response.status === 401) {
|
|
1037
|
-
throw new AuthenticationError(
|
|
1038
|
-
`Authentication failed: ${errorMessage}. Please check your API key and project ID.`
|
|
1039
|
-
);
|
|
1040
|
-
}
|
|
1041
|
-
if (response.status === 404 && errorCode === "context_not_found") {
|
|
1042
|
-
throw new ContextNotFoundError(`Context not found: ${errorMessage}`);
|
|
1223
|
+
throw new AuthenticationError(`Authentication failed: ${errorMessage}`);
|
|
1043
1224
|
}
|
|
1044
1225
|
if (response.status === 404) {
|
|
1045
|
-
|
|
1226
|
+
const suffix = contextId ? `: ${contextId}` : "";
|
|
1227
|
+
throw new ContextNotFoundError(`Context not found${suffix}`);
|
|
1046
1228
|
}
|
|
1047
|
-
if (response.status === 409 && errorCode === "context_locked") {
|
|
1229
|
+
if (response.status === 409 && (errorCode === "context_locked" || errorCode === "CONTEXT_FORK_SOURCE_LOCKED")) {
|
|
1048
1230
|
throw new ContextLockedError(errorMessage, {
|
|
1049
|
-
activeSessionId:
|
|
1050
|
-
retryAfter:
|
|
1231
|
+
activeSessionId: getString2(metadata.activeSessionId),
|
|
1232
|
+
retryAfter: typeof metadata.retryAfter === "number" ? metadata.retryAfter : void 0
|
|
1051
1233
|
});
|
|
1052
1234
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
|
|
1061
|
-
if (response.status === 401) {
|
|
1062
|
-
throw new AuthenticationError(
|
|
1063
|
-
`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
|
+
}
|
|
1064
1242
|
);
|
|
1065
1243
|
}
|
|
1066
|
-
if (response.status ===
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
+
}
|
|
1069
1252
|
);
|
|
1070
1253
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
|
|
1079
|
-
if (response.status === 401) {
|
|
1080
|
-
throw new AuthenticationError(
|
|
1081
|
-
`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
|
+
}
|
|
1082
1261
|
);
|
|
1083
1262
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
|
|
1092
|
-
if (response.status === 401) {
|
|
1093
|
-
throw new AuthenticationError(
|
|
1094
|
-
`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
|
+
}
|
|
1095
1270
|
);
|
|
1096
1271
|
}
|
|
1097
|
-
if (response.status ===
|
|
1098
|
-
throw new
|
|
1099
|
-
`
|
|
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
|
+
}
|
|
1100
1279
|
);
|
|
1101
1280
|
}
|
|
1102
|
-
throw new APIError(`Failed to
|
|
1281
|
+
throw new APIError(`Failed to ${operation} context: ${errorMessage}`, {
|
|
1103
1282
|
statusCode: response.status,
|
|
1104
1283
|
response: response.data
|
|
1105
1284
|
});
|
|
1106
1285
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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);
|
|
1114
1317
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
)
|
|
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);
|
|
1119
1334
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
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
|
|
1123
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));
|
|
1124
1350
|
}
|
|
1125
|
-
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
status: getString3(raw.status) ?? "active",
|
|
1130
|
-
apiKey: getString3(raw.api_key) ?? this.client.apiKey,
|
|
1131
|
-
projectId: getString3(raw.project_id) ?? (projectId ?? this.client.projectId),
|
|
1132
|
-
browserType: getString3(raw.browser_type) ?? getString3(raw.browser_mode) ?? "normal",
|
|
1133
|
-
createdAt: getString3(raw.created_at) ?? "",
|
|
1134
|
-
inspectUrl: getString3(raw.inspect_url) ?? `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
|
|
1135
|
-
containerId: getString3(raw.container_id) ?? null,
|
|
1136
|
-
ws: getString3(raw.ws) ?? null,
|
|
1137
|
-
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
|
|
1138
1355
|
});
|
|
1356
|
+
if (response.status >= 400) {
|
|
1357
|
+
this.handleError("get extension", response);
|
|
1358
|
+
}
|
|
1359
|
+
return this.parseInfo(response.data);
|
|
1139
1360
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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)
|
|
1150
1378
|
});
|
|
1151
1379
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
`
|
|
1158
|
-
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.`
|
|
1159
1386
|
);
|
|
1160
|
-
return null;
|
|
1161
1387
|
}
|
|
1388
|
+
throw new APIError(`Failed to ${action}: ${errorMessage}`, {
|
|
1389
|
+
statusCode: response.status,
|
|
1390
|
+
response: response.data
|
|
1391
|
+
});
|
|
1162
1392
|
}
|
|
1163
1393
|
};
|
|
1164
1394
|
|
|
@@ -1429,10 +1659,11 @@ var Lexmount = class {
|
|
|
1429
1659
|
};
|
|
1430
1660
|
|
|
1431
1661
|
// src/index.ts
|
|
1432
|
-
var VERSION = "0.5.
|
|
1662
|
+
var VERSION = "0.5.5";
|
|
1433
1663
|
export {
|
|
1434
1664
|
APIError,
|
|
1435
1665
|
AuthenticationError,
|
|
1666
|
+
CDPClient,
|
|
1436
1667
|
ContextInfo,
|
|
1437
1668
|
ContextListResponse,
|
|
1438
1669
|
ContextLockedError,
|
|
@@ -1457,10 +1688,13 @@ export {
|
|
|
1457
1688
|
TimeoutError,
|
|
1458
1689
|
VERSION,
|
|
1459
1690
|
ValidationError,
|
|
1691
|
+
connectOverCDP,
|
|
1460
1692
|
Lexmount as default,
|
|
1693
|
+
deregisterIntegratedAuthCallback,
|
|
1461
1694
|
disableLogging,
|
|
1462
1695
|
enableLogging,
|
|
1463
1696
|
getLogger,
|
|
1697
|
+
registerIntegratedAuthCallback,
|
|
1464
1698
|
setLogLevel
|
|
1465
1699
|
};
|
|
1466
1700
|
//# sourceMappingURL=index.mjs.map
|