lexmount 0.2.7 → 0.5.2
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 +69 -2
- package/dist/index.d.ts +69 -2
- package/dist/index.js +210 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +211 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
-
import {
|
|
3
|
-
isAxiosError
|
|
4
|
-
} from "axios";
|
|
2
|
+
import { isAxiosError } from "axios";
|
|
5
3
|
import axios from "axios";
|
|
6
4
|
import * as dotenv from "dotenv";
|
|
7
5
|
|
|
@@ -556,6 +554,11 @@ function normalizeContextMode(mode) {
|
|
|
556
554
|
}
|
|
557
555
|
return "read_only";
|
|
558
556
|
}
|
|
557
|
+
function delay(ms) {
|
|
558
|
+
return new Promise((resolve) => {
|
|
559
|
+
setTimeout(resolve, ms);
|
|
560
|
+
});
|
|
561
|
+
}
|
|
559
562
|
var PaginationInfo = class {
|
|
560
563
|
constructor(shape) {
|
|
561
564
|
this.currentPage = shape.currentPage;
|
|
@@ -787,7 +790,6 @@ var SessionsResource = class {
|
|
|
787
790
|
* Create a new browser session.
|
|
788
791
|
*/
|
|
789
792
|
async create(options = {}) {
|
|
790
|
-
const url = `${this.client.baseUrl}/instance`;
|
|
791
793
|
const payload = {
|
|
792
794
|
api_key: this.client.apiKey,
|
|
793
795
|
project_id: options.projectId ?? this.client.projectId,
|
|
@@ -811,6 +813,13 @@ var SessionsResource = class {
|
|
|
811
813
|
if (options.proxy) {
|
|
812
814
|
payload.proxy = this.normalizeProxy(options.proxy);
|
|
813
815
|
}
|
|
816
|
+
if (options.weakLock) {
|
|
817
|
+
payload.weak_lock = true;
|
|
818
|
+
}
|
|
819
|
+
if (options.asyncCreate !== false) {
|
|
820
|
+
return this.createAsync(payload, options);
|
|
821
|
+
}
|
|
822
|
+
const url = `${this.client.baseUrl}/instance`;
|
|
814
823
|
const response = await this.client._post(url, payload);
|
|
815
824
|
if (response.status >= 400) {
|
|
816
825
|
this.handleCreateError(response);
|
|
@@ -841,6 +850,59 @@ var SessionsResource = class {
|
|
|
841
850
|
client: this.client
|
|
842
851
|
});
|
|
843
852
|
}
|
|
853
|
+
async createAsync(payload, options) {
|
|
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
|
+
throw new APIError("Session creation failed", {
|
|
890
|
+
statusCode: 500,
|
|
891
|
+
response: { session_id: sessionId, status: info.status }
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
if (info.status === "closed") {
|
|
895
|
+
throw new APIError("Session closed before activation", {
|
|
896
|
+
statusCode: 500,
|
|
897
|
+
response: { session_id: sessionId, status: info.status }
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
await delay(pollIntervalMs);
|
|
901
|
+
}
|
|
902
|
+
throw new TimeoutError(
|
|
903
|
+
`Timed out waiting for session ${sessionId} to become active after ${pollTimeoutMs}ms`
|
|
904
|
+
);
|
|
905
|
+
}
|
|
844
906
|
/**
|
|
845
907
|
* Fetch one session by id.
|
|
846
908
|
*
|
|
@@ -1095,6 +1157,7 @@ var SessionsResource = class {
|
|
|
1095
1157
|
dotenv.config();
|
|
1096
1158
|
var Lexmount = class {
|
|
1097
1159
|
constructor(config2 = {}) {
|
|
1160
|
+
this.regionResolutionAttempted = false;
|
|
1098
1161
|
this.apiKey = config2.apiKey ?? process.env.LEXMOUNT_API_KEY ?? "";
|
|
1099
1162
|
if (!this.apiKey) {
|
|
1100
1163
|
throw new ValidationError(
|
|
@@ -1108,6 +1171,7 @@ var Lexmount = class {
|
|
|
1108
1171
|
);
|
|
1109
1172
|
}
|
|
1110
1173
|
this.baseUrl = (config2.baseUrl ?? process.env.LEXMOUNT_BASE_URL ?? "https://api.lexmount.cn").replace(/\/$/, "");
|
|
1174
|
+
this.region = config2.region;
|
|
1111
1175
|
if (config2.logLevel) {
|
|
1112
1176
|
setLogLevel(config2.logLevel);
|
|
1113
1177
|
}
|
|
@@ -1125,6 +1189,7 @@ var Lexmount = class {
|
|
|
1125
1189
|
* @internal
|
|
1126
1190
|
*/
|
|
1127
1191
|
async _post(url, data, config2 = {}) {
|
|
1192
|
+
await this.ensureRegionResolved();
|
|
1128
1193
|
return this.request("POST", url, { ...config2, data });
|
|
1129
1194
|
}
|
|
1130
1195
|
/**
|
|
@@ -1133,6 +1198,7 @@ var Lexmount = class {
|
|
|
1133
1198
|
* @internal
|
|
1134
1199
|
*/
|
|
1135
1200
|
async _get(url, params, config2 = {}) {
|
|
1201
|
+
await this.ensureRegionResolved();
|
|
1136
1202
|
return this.request("GET", url, { ...config2, params });
|
|
1137
1203
|
}
|
|
1138
1204
|
/**
|
|
@@ -1141,8 +1207,51 @@ var Lexmount = class {
|
|
|
1141
1207
|
* @internal
|
|
1142
1208
|
*/
|
|
1143
1209
|
async _delete(url, data, config2 = {}) {
|
|
1210
|
+
await this.ensureRegionResolved();
|
|
1144
1211
|
return this.request("DELETE", url, { ...config2, data });
|
|
1145
1212
|
}
|
|
1213
|
+
async regionInfo() {
|
|
1214
|
+
await this.ensureRegionResolved();
|
|
1215
|
+
return {
|
|
1216
|
+
requestedRegion: this.region,
|
|
1217
|
+
selectedRegion: this.selectedRegion,
|
|
1218
|
+
baseUrl: this.baseUrl,
|
|
1219
|
+
endpointBaseUrl: this.endpointBaseUrl,
|
|
1220
|
+
host: this.selectedHost,
|
|
1221
|
+
resolved: this.regionResolutionAttempted,
|
|
1222
|
+
usingRegion: this.selectedRegion !== void 0
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
async catalogInfo() {
|
|
1226
|
+
try {
|
|
1227
|
+
const response = await this.httpClient.get(
|
|
1228
|
+
`${this.baseUrl}/v1/regions/catalog`
|
|
1229
|
+
);
|
|
1230
|
+
if (response.status !== 200) {
|
|
1231
|
+
return {
|
|
1232
|
+
baseUrl: this.baseUrl,
|
|
1233
|
+
available: false,
|
|
1234
|
+
statusCode: response.status,
|
|
1235
|
+
regions: []
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
const regions = Array.isArray(response.data?.regions) ? response.data.regions : [];
|
|
1239
|
+
return {
|
|
1240
|
+
baseUrl: this.baseUrl,
|
|
1241
|
+
available: Array.isArray(response.data?.regions),
|
|
1242
|
+
statusCode: response.status,
|
|
1243
|
+
regions,
|
|
1244
|
+
error: Array.isArray(response.data?.regions) ? void 0 : "invalid catalog regions"
|
|
1245
|
+
};
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
return {
|
|
1248
|
+
baseUrl: this.baseUrl,
|
|
1249
|
+
available: false,
|
|
1250
|
+
regions: [],
|
|
1251
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1146
1255
|
/**
|
|
1147
1256
|
* Close the client.
|
|
1148
1257
|
*
|
|
@@ -1160,7 +1269,8 @@ var Lexmount = class {
|
|
|
1160
1269
|
async request(method, url, config2) {
|
|
1161
1270
|
const logger2 = getLogger();
|
|
1162
1271
|
const startTime = Date.now();
|
|
1163
|
-
|
|
1272
|
+
const requestUrl = this.rewriteUrl(url);
|
|
1273
|
+
logger2.debug(`${method} request to ${requestUrl}`);
|
|
1164
1274
|
try {
|
|
1165
1275
|
const headers = {
|
|
1166
1276
|
api_key: this.apiKey,
|
|
@@ -1170,12 +1280,14 @@ var Lexmount = class {
|
|
|
1170
1280
|
};
|
|
1171
1281
|
const response = await this.httpClient.request({
|
|
1172
1282
|
method,
|
|
1173
|
-
url,
|
|
1283
|
+
url: requestUrl,
|
|
1174
1284
|
...config2,
|
|
1175
1285
|
headers
|
|
1176
1286
|
});
|
|
1177
1287
|
const elapsed = Date.now() - startTime;
|
|
1178
|
-
logger2.debug(
|
|
1288
|
+
logger2.debug(
|
|
1289
|
+
`${method} response: status=${response.status}, duration=${elapsed.toFixed(2)}ms`
|
|
1290
|
+
);
|
|
1179
1291
|
return response;
|
|
1180
1292
|
} catch (error) {
|
|
1181
1293
|
const elapsed = Date.now() - startTime;
|
|
@@ -1214,10 +1326,101 @@ var Lexmount = class {
|
|
|
1214
1326
|
"Content-Type": "application/json"
|
|
1215
1327
|
};
|
|
1216
1328
|
}
|
|
1329
|
+
async ensureRegionResolved() {
|
|
1330
|
+
if (this.regionResolutionAttempted) {
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (!this.regionResolutionPromise) {
|
|
1334
|
+
this.regionResolutionPromise = this.resolveRegion().finally(() => {
|
|
1335
|
+
this.regionResolutionAttempted = true;
|
|
1336
|
+
this.regionResolutionPromise = void 0;
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
await this.regionResolutionPromise;
|
|
1340
|
+
}
|
|
1341
|
+
async resolveRegion() {
|
|
1342
|
+
const logger2 = getLogger();
|
|
1343
|
+
try {
|
|
1344
|
+
const response = await this.httpClient.get(
|
|
1345
|
+
`${this.baseUrl}/v1/regions/catalog`
|
|
1346
|
+
);
|
|
1347
|
+
if (response.status !== 200 || !Array.isArray(response.data?.regions)) {
|
|
1348
|
+
logger2.debug(
|
|
1349
|
+
`Region catalog unavailable, falling back to baseUrl: status=${response.status}`
|
|
1350
|
+
);
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
const selected = this.selectCatalogRegion(response.data.regions);
|
|
1354
|
+
if (!selected) {
|
|
1355
|
+
if (this.region) {
|
|
1356
|
+
logger2.warn(`Requested region not found in catalog: ${this.region}`);
|
|
1357
|
+
} else {
|
|
1358
|
+
logger2.debug("No default catalog region, falling back to baseUrl");
|
|
1359
|
+
}
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
const endpointBaseUrl = await this.probeRegionHost(selected);
|
|
1363
|
+
if (!endpointBaseUrl) {
|
|
1364
|
+
logger2.debug("Region probe failed, falling back to baseUrl");
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
this.selectedRegion = selected.region_id;
|
|
1368
|
+
this.selectedHost = selected.host;
|
|
1369
|
+
this.endpointBaseUrl = endpointBaseUrl;
|
|
1370
|
+
} catch (error) {
|
|
1371
|
+
logger2.debug("Region resolution failed, falling back to baseUrl:", error);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
selectCatalogRegion(regions) {
|
|
1375
|
+
if (this.region) {
|
|
1376
|
+
return regions.find((region) => region.region_id === this.region);
|
|
1377
|
+
}
|
|
1378
|
+
const defaultRegions = regions.filter((region) => region.default === true);
|
|
1379
|
+
return defaultRegions.length === 1 ? defaultRegions[0] : void 0;
|
|
1380
|
+
}
|
|
1381
|
+
async probeRegionHost(region) {
|
|
1382
|
+
const logger2 = getLogger();
|
|
1383
|
+
const host = String(region.host || "").trim();
|
|
1384
|
+
if (!host) {
|
|
1385
|
+
return void 0;
|
|
1386
|
+
}
|
|
1387
|
+
if (!this.isAllowedCatalogHost(host)) {
|
|
1388
|
+
logger2.warn(`Ignoring invalid region catalog host: ${host}`);
|
|
1389
|
+
return void 0;
|
|
1390
|
+
}
|
|
1391
|
+
const endpointBaseUrl = `https://${host}`;
|
|
1392
|
+
const probePath = String(region.probe_path || "/v1/region/probe").replace(/^(?!\/)/, "/");
|
|
1393
|
+
try {
|
|
1394
|
+
const response = await this.httpClient.get(`${endpointBaseUrl}${probePath}`);
|
|
1395
|
+
return response.status === 200 ? endpointBaseUrl : void 0;
|
|
1396
|
+
} catch {
|
|
1397
|
+
return void 0;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
isAllowedCatalogHost(host) {
|
|
1401
|
+
let parsed;
|
|
1402
|
+
try {
|
|
1403
|
+
parsed = new URL(`https://${host}`);
|
|
1404
|
+
} catch {
|
|
1405
|
+
return false;
|
|
1406
|
+
}
|
|
1407
|
+
if (parsed.username || parsed.password || parsed.port || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
1408
|
+
return false;
|
|
1409
|
+
}
|
|
1410
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
1411
|
+
const allowedDomains = ["lexmount.cn", "lexmount.com", "lexmount.net"];
|
|
1412
|
+
return allowedDomains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`));
|
|
1413
|
+
}
|
|
1414
|
+
rewriteUrl(url) {
|
|
1415
|
+
if (!this.endpointBaseUrl || !url.startsWith(this.baseUrl)) {
|
|
1416
|
+
return url;
|
|
1417
|
+
}
|
|
1418
|
+
return `${this.endpointBaseUrl}${url.slice(this.baseUrl.length)}`;
|
|
1419
|
+
}
|
|
1217
1420
|
};
|
|
1218
1421
|
|
|
1219
1422
|
// src/index.ts
|
|
1220
|
-
var VERSION = "0.2
|
|
1423
|
+
var VERSION = "0.5.2";
|
|
1221
1424
|
export {
|
|
1222
1425
|
APIError,
|
|
1223
1426
|
AuthenticationError,
|