lexmount 0.2.6 → 0.5.1

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.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
 
@@ -665,6 +663,18 @@ var SessionDownloadsDeleteResponse = class {
665
663
  this.deletedCount = shape.deletedCount;
666
664
  }
667
665
  };
666
+ var SessionTargetInfo = class {
667
+ constructor(shape) {
668
+ this.id = shape.id;
669
+ this.title = shape.title;
670
+ this.type = shape.type;
671
+ this.url = shape.url;
672
+ this.description = shape.description;
673
+ this.inspectUrl = shape.inspectUrl;
674
+ this.webSocketDebuggerUrl = shape.webSocketDebuggerUrl;
675
+ this.webSocketDebuggerUrlTransformed = shape.webSocketDebuggerUrlTransformed;
676
+ }
677
+ };
668
678
  var SessionDownloadsResource = class {
669
679
  constructor(client) {
670
680
  this.client = client;
@@ -895,6 +905,19 @@ var SessionsResource = class {
895
905
  }
896
906
  getLogger().info(`Session deleted successfully: ${options.sessionId}`);
897
907
  }
908
+ /**
909
+ * List target/page metadata for a session via `/json`.
910
+ */
911
+ async listTargets(sessionId) {
912
+ const response = await this.client._get(`${this.client.baseUrl}/json`, {
913
+ session_id: sessionId
914
+ });
915
+ if (response.status >= 400) {
916
+ this.handleListTargetsError(response, sessionId);
917
+ }
918
+ const result = Array.isArray(response.data) ? response.data : [];
919
+ return result.map((item) => this.mapSessionTarget(asRecord3(item)));
920
+ }
898
921
  /**
899
922
  * Fetch the debugger WebSocket URL for a session.
900
923
  *
@@ -990,6 +1013,24 @@ var SessionsResource = class {
990
1013
  response: response.data
991
1014
  });
992
1015
  }
1016
+ handleListTargetsError(response, sessionId) {
1017
+ const errorData = asRecord3(response.data);
1018
+ const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
1019
+ if (response.status === 401) {
1020
+ throw new AuthenticationError(
1021
+ `Authentication failed: ${errorMessage}. Please check your API key and project ID.`
1022
+ );
1023
+ }
1024
+ if (response.status === 404) {
1025
+ throw new SessionNotFoundError(
1026
+ `Session not found: ${errorMessage}. Session ID '${sessionId}' may not exist in this project.`
1027
+ );
1028
+ }
1029
+ throw new APIError(`Failed to list session targets: ${errorMessage}`, {
1030
+ statusCode: response.status,
1031
+ response: response.data
1032
+ });
1033
+ }
993
1034
  handleDeleteError(response, sessionId) {
994
1035
  const errorData = asRecord3(response.data);
995
1036
  const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
@@ -1023,6 +1064,18 @@ var SessionsResource = class {
1023
1064
  client: this.client
1024
1065
  });
1025
1066
  }
1067
+ mapSessionTarget(raw) {
1068
+ return new SessionTargetInfo({
1069
+ id: getString3(raw.id) ?? "",
1070
+ title: getString3(raw.title) ?? "",
1071
+ type: getString3(raw.type) ?? "",
1072
+ url: getString3(raw.url) ?? "",
1073
+ description: getString3(raw.description) ?? "",
1074
+ inspectUrl: getString3(raw.inspectUrl) ?? getString3(raw.inspect_url) ?? null,
1075
+ webSocketDebuggerUrl: getString3(raw.webSocketDebuggerUrl) ?? getString3(raw.web_socket_debugger_url) ?? null,
1076
+ webSocketDebuggerUrlTransformed: getString3(raw.webSocketDebuggerUrlTransformed) ?? getString3(raw.web_socket_debugger_url_transformed) ?? null
1077
+ });
1078
+ }
1026
1079
  async _getCreatedSession(sessionId, projectId) {
1027
1080
  try {
1028
1081
  return await this.get(sessionId, projectId);
@@ -1040,6 +1093,7 @@ var SessionsResource = class {
1040
1093
  dotenv.config();
1041
1094
  var Lexmount = class {
1042
1095
  constructor(config2 = {}) {
1096
+ this.regionResolutionAttempted = false;
1043
1097
  this.apiKey = config2.apiKey ?? process.env.LEXMOUNT_API_KEY ?? "";
1044
1098
  if (!this.apiKey) {
1045
1099
  throw new ValidationError(
@@ -1053,6 +1107,7 @@ var Lexmount = class {
1053
1107
  );
1054
1108
  }
1055
1109
  this.baseUrl = (config2.baseUrl ?? process.env.LEXMOUNT_BASE_URL ?? "https://api.lexmount.cn").replace(/\/$/, "");
1110
+ this.region = config2.region;
1056
1111
  if (config2.logLevel) {
1057
1112
  setLogLevel(config2.logLevel);
1058
1113
  }
@@ -1070,6 +1125,7 @@ var Lexmount = class {
1070
1125
  * @internal
1071
1126
  */
1072
1127
  async _post(url, data, config2 = {}) {
1128
+ await this.ensureRegionResolved();
1073
1129
  return this.request("POST", url, { ...config2, data });
1074
1130
  }
1075
1131
  /**
@@ -1078,6 +1134,7 @@ var Lexmount = class {
1078
1134
  * @internal
1079
1135
  */
1080
1136
  async _get(url, params, config2 = {}) {
1137
+ await this.ensureRegionResolved();
1081
1138
  return this.request("GET", url, { ...config2, params });
1082
1139
  }
1083
1140
  /**
@@ -1086,8 +1143,51 @@ var Lexmount = class {
1086
1143
  * @internal
1087
1144
  */
1088
1145
  async _delete(url, data, config2 = {}) {
1146
+ await this.ensureRegionResolved();
1089
1147
  return this.request("DELETE", url, { ...config2, data });
1090
1148
  }
1149
+ async regionInfo() {
1150
+ await this.ensureRegionResolved();
1151
+ return {
1152
+ requestedRegion: this.region,
1153
+ selectedRegion: this.selectedRegion,
1154
+ baseUrl: this.baseUrl,
1155
+ endpointBaseUrl: this.endpointBaseUrl,
1156
+ host: this.selectedHost,
1157
+ resolved: this.regionResolutionAttempted,
1158
+ usingRegion: this.selectedRegion !== void 0
1159
+ };
1160
+ }
1161
+ async catalogInfo() {
1162
+ try {
1163
+ const response = await this.httpClient.get(
1164
+ `${this.baseUrl}/v1/regions/catalog`
1165
+ );
1166
+ if (response.status !== 200) {
1167
+ return {
1168
+ baseUrl: this.baseUrl,
1169
+ available: false,
1170
+ statusCode: response.status,
1171
+ regions: []
1172
+ };
1173
+ }
1174
+ const regions = Array.isArray(response.data?.regions) ? response.data.regions : [];
1175
+ return {
1176
+ baseUrl: this.baseUrl,
1177
+ available: Array.isArray(response.data?.regions),
1178
+ statusCode: response.status,
1179
+ regions,
1180
+ error: Array.isArray(response.data?.regions) ? void 0 : "invalid catalog regions"
1181
+ };
1182
+ } catch (error) {
1183
+ return {
1184
+ baseUrl: this.baseUrl,
1185
+ available: false,
1186
+ regions: [],
1187
+ error: error instanceof Error ? error.message : String(error)
1188
+ };
1189
+ }
1190
+ }
1091
1191
  /**
1092
1192
  * Close the client.
1093
1193
  *
@@ -1105,7 +1205,8 @@ var Lexmount = class {
1105
1205
  async request(method, url, config2) {
1106
1206
  const logger2 = getLogger();
1107
1207
  const startTime = Date.now();
1108
- logger2.debug(`${method} request to ${url}`);
1208
+ const requestUrl = this.rewriteUrl(url);
1209
+ logger2.debug(`${method} request to ${requestUrl}`);
1109
1210
  try {
1110
1211
  const headers = {
1111
1212
  api_key: this.apiKey,
@@ -1115,12 +1216,14 @@ var Lexmount = class {
1115
1216
  };
1116
1217
  const response = await this.httpClient.request({
1117
1218
  method,
1118
- url,
1219
+ url: requestUrl,
1119
1220
  ...config2,
1120
1221
  headers
1121
1222
  });
1122
1223
  const elapsed = Date.now() - startTime;
1123
- logger2.debug(`${method} response: status=${response.status}, duration=${elapsed.toFixed(2)}ms`);
1224
+ logger2.debug(
1225
+ `${method} response: status=${response.status}, duration=${elapsed.toFixed(2)}ms`
1226
+ );
1124
1227
  return response;
1125
1228
  } catch (error) {
1126
1229
  const elapsed = Date.now() - startTime;
@@ -1159,10 +1262,101 @@ var Lexmount = class {
1159
1262
  "Content-Type": "application/json"
1160
1263
  };
1161
1264
  }
1265
+ async ensureRegionResolved() {
1266
+ if (this.regionResolutionAttempted) {
1267
+ return;
1268
+ }
1269
+ if (!this.regionResolutionPromise) {
1270
+ this.regionResolutionPromise = this.resolveRegion().finally(() => {
1271
+ this.regionResolutionAttempted = true;
1272
+ this.regionResolutionPromise = void 0;
1273
+ });
1274
+ }
1275
+ await this.regionResolutionPromise;
1276
+ }
1277
+ async resolveRegion() {
1278
+ const logger2 = getLogger();
1279
+ try {
1280
+ const response = await this.httpClient.get(
1281
+ `${this.baseUrl}/v1/regions/catalog`
1282
+ );
1283
+ if (response.status !== 200 || !Array.isArray(response.data?.regions)) {
1284
+ logger2.debug(
1285
+ `Region catalog unavailable, falling back to baseUrl: status=${response.status}`
1286
+ );
1287
+ return;
1288
+ }
1289
+ const selected = this.selectCatalogRegion(response.data.regions);
1290
+ if (!selected) {
1291
+ if (this.region) {
1292
+ logger2.warn(`Requested region not found in catalog: ${this.region}`);
1293
+ } else {
1294
+ logger2.debug("No default catalog region, falling back to baseUrl");
1295
+ }
1296
+ return;
1297
+ }
1298
+ const endpointBaseUrl = await this.probeRegionHost(selected);
1299
+ if (!endpointBaseUrl) {
1300
+ logger2.debug("Region probe failed, falling back to baseUrl");
1301
+ return;
1302
+ }
1303
+ this.selectedRegion = selected.region_id;
1304
+ this.selectedHost = selected.host;
1305
+ this.endpointBaseUrl = endpointBaseUrl;
1306
+ } catch (error) {
1307
+ logger2.debug("Region resolution failed, falling back to baseUrl:", error);
1308
+ }
1309
+ }
1310
+ selectCatalogRegion(regions) {
1311
+ if (this.region) {
1312
+ return regions.find((region) => region.region_id === this.region);
1313
+ }
1314
+ const defaultRegions = regions.filter((region) => region.default === true);
1315
+ return defaultRegions.length === 1 ? defaultRegions[0] : void 0;
1316
+ }
1317
+ async probeRegionHost(region) {
1318
+ const logger2 = getLogger();
1319
+ const host = String(region.host || "").trim();
1320
+ if (!host) {
1321
+ return void 0;
1322
+ }
1323
+ if (!this.isAllowedCatalogHost(host)) {
1324
+ logger2.warn(`Ignoring invalid region catalog host: ${host}`);
1325
+ return void 0;
1326
+ }
1327
+ const endpointBaseUrl = `https://${host}`;
1328
+ const probePath = String(region.probe_path || "/v1/region/probe").replace(/^(?!\/)/, "/");
1329
+ try {
1330
+ const response = await this.httpClient.get(`${endpointBaseUrl}${probePath}`);
1331
+ return response.status === 200 ? endpointBaseUrl : void 0;
1332
+ } catch {
1333
+ return void 0;
1334
+ }
1335
+ }
1336
+ isAllowedCatalogHost(host) {
1337
+ let parsed;
1338
+ try {
1339
+ parsed = new URL(`https://${host}`);
1340
+ } catch {
1341
+ return false;
1342
+ }
1343
+ if (parsed.username || parsed.password || parsed.port || parsed.pathname !== "/" || parsed.search || parsed.hash) {
1344
+ return false;
1345
+ }
1346
+ const hostname = parsed.hostname.toLowerCase();
1347
+ const allowedDomains = ["lexmount.cn", "lexmount.com", "lexmount.net"];
1348
+ return allowedDomains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`));
1349
+ }
1350
+ rewriteUrl(url) {
1351
+ if (!this.endpointBaseUrl || !url.startsWith(this.baseUrl)) {
1352
+ return url;
1353
+ }
1354
+ return `${this.endpointBaseUrl}${url.slice(this.baseUrl.length)}`;
1355
+ }
1162
1356
  };
1163
1357
 
1164
1358
  // src/index.ts
1165
- var VERSION = "0.2.5";
1359
+ var VERSION = "0.5.1";
1166
1360
  export {
1167
1361
  APIError,
1168
1362
  AuthenticationError,
@@ -1185,6 +1379,7 @@ export {
1185
1379
  SessionInfo,
1186
1380
  SessionListResponse,
1187
1381
  SessionNotFoundError,
1382
+ SessionTargetInfo,
1188
1383
  SessionsResource,
1189
1384
  TimeoutError,
1190
1385
  VERSION,