@localstack/appinspector-ui 1.0.161 → 1.0.163
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.cjs +143 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +143 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2452,6 +2452,13 @@ var ConnectionError = class extends Error {
|
|
|
2452
2452
|
this.cause = cause;
|
|
2453
2453
|
}
|
|
2454
2454
|
};
|
|
2455
|
+
var StaleTokenError = class extends Error {
|
|
2456
|
+
constructor(message, cause) {
|
|
2457
|
+
super(message);
|
|
2458
|
+
this.name = "StaleTokenError";
|
|
2459
|
+
this.cause = cause;
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2455
2462
|
|
|
2456
2463
|
// src/api/use-api.tsx
|
|
2457
2464
|
var import_react = require("react");
|
|
@@ -16957,7 +16964,8 @@ var import_semver2 = __toESM(require_semver2(), 1);
|
|
|
16957
16964
|
var ERROR_MESSAGES = {
|
|
16958
16965
|
APP_INSPECTOR_DISABLED: "App Inspector is currently disabled.",
|
|
16959
16966
|
APP_INSPECTOR_NOT_FOUND: "App Inspector API not found. Please ensure App Inspector is licensed and enabled.",
|
|
16960
|
-
CONNECTION_FAILED: "Failed to connect to LocalStack. Please ensure LocalStack is running and accessible."
|
|
16967
|
+
CONNECTION_FAILED: "Failed to connect to LocalStack. Please ensure LocalStack is running and accessible.",
|
|
16968
|
+
STALE_PAGINATION_TOKEN: "Pagination token is from a previous session."
|
|
16961
16969
|
};
|
|
16962
16970
|
|
|
16963
16971
|
// src/utils/logger.ts
|
|
@@ -17020,6 +17028,16 @@ function unixNanoToMilliseconds(unixNanoString) {
|
|
|
17020
17028
|
// src/api/client.ts
|
|
17021
17029
|
var apiLogger = createScopedLogger("API");
|
|
17022
17030
|
var analyticsLogger = createScopedLogger("ANALYTICS");
|
|
17031
|
+
var isStalePaginationTokenMessage = (body) => {
|
|
17032
|
+
let candidate = body;
|
|
17033
|
+
try {
|
|
17034
|
+
const parsed = JSON.parse(body);
|
|
17035
|
+
const messageField = [parsed.message, parsed.error, parsed.detail].find((value) => typeof value === "string");
|
|
17036
|
+
candidate = typeof messageField === "string" ? messageField : body;
|
|
17037
|
+
} catch {
|
|
17038
|
+
}
|
|
17039
|
+
return candidate.toLowerCase().includes("previous session");
|
|
17040
|
+
};
|
|
17023
17041
|
var makeRequest = async (options) => {
|
|
17024
17042
|
const url = getAppInspectorApiUrl(options.localstackEndpoint, options.endpoint);
|
|
17025
17043
|
try {
|
|
@@ -17043,6 +17061,15 @@ var makeRequest = async (options) => {
|
|
|
17043
17061
|
new Error(`503 from ${url}: ${response.statusText}`)
|
|
17044
17062
|
);
|
|
17045
17063
|
}
|
|
17064
|
+
if (response.status === 400) {
|
|
17065
|
+
const body = await response.text().catch(() => "");
|
|
17066
|
+
if (isStalePaginationTokenMessage(body)) {
|
|
17067
|
+
throw new StaleTokenError(
|
|
17068
|
+
ERROR_MESSAGES.STALE_PAGINATION_TOKEN,
|
|
17069
|
+
new Error(`400 from ${url}: ${response.statusText}`)
|
|
17070
|
+
);
|
|
17071
|
+
}
|
|
17072
|
+
}
|
|
17046
17073
|
throw new Error(`API request failed: ${response.status.toString()} ${response.statusText}`);
|
|
17047
17074
|
}
|
|
17048
17075
|
options.captureHeaders?.(response.headers);
|
|
@@ -19148,6 +19175,35 @@ var AppInspectorContextProvider = (props) => {
|
|
|
19148
19175
|
};
|
|
19149
19176
|
|
|
19150
19177
|
// src/hooks/use-spans-ws.tsx
|
|
19178
|
+
var wsLogger = createScopedLogger("WS");
|
|
19179
|
+
var isLegacySpanFields = (value) => {
|
|
19180
|
+
return typeof value.span_id === "string" && typeof value.trace_id === "string" && typeof value.timestamp === "string";
|
|
19181
|
+
};
|
|
19182
|
+
var parseSpansWebSocketMessage = (raw) => {
|
|
19183
|
+
const parsed = JSON.parse(raw);
|
|
19184
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
19185
|
+
return void 0;
|
|
19186
|
+
}
|
|
19187
|
+
const record = parsed;
|
|
19188
|
+
if (record.type === void 0) {
|
|
19189
|
+
return isLegacySpanFields(record) ? { data: record, type: "span" } : void 0;
|
|
19190
|
+
}
|
|
19191
|
+
if (record.type === "epoch" || record.type === "reset") {
|
|
19192
|
+
return typeof record.epoch === "string" ? { epoch: record.epoch, type: record.type } : void 0;
|
|
19193
|
+
}
|
|
19194
|
+
if (record.type === "span") {
|
|
19195
|
+
const data = record.data;
|
|
19196
|
+
if (typeof data !== "object" || data === null || !isLegacySpanFields(data)) {
|
|
19197
|
+
return void 0;
|
|
19198
|
+
}
|
|
19199
|
+
return {
|
|
19200
|
+
data,
|
|
19201
|
+
epoch: typeof record.epoch === "string" ? record.epoch : void 0,
|
|
19202
|
+
type: "span"
|
|
19203
|
+
};
|
|
19204
|
+
}
|
|
19205
|
+
return void 0;
|
|
19206
|
+
};
|
|
19151
19207
|
function useSpansWebSocket(options) {
|
|
19152
19208
|
const { enabled, onClose, onMessage, onOpen } = options;
|
|
19153
19209
|
const onMessageRef = (0, import_react46.useRef)(onMessage);
|
|
@@ -19169,6 +19225,7 @@ function useSpansWebSocket(options) {
|
|
|
19169
19225
|
if (wsRef.current !== void 0) return;
|
|
19170
19226
|
const url = new URL(getAppInspectorApiUrl(localstackEndpoint, API_ENDPOINTS.WEBSOCKET_SPANS));
|
|
19171
19227
|
url.protocol = globalThis.location.protocol === "https:" ? "wss" : "ws";
|
|
19228
|
+
url.searchParams.set("epoch", "1");
|
|
19172
19229
|
const ws = new WebSocket(url);
|
|
19173
19230
|
wsRef.current = ws;
|
|
19174
19231
|
ws.addEventListener("open", () => {
|
|
@@ -19176,9 +19233,20 @@ function useSpansWebSocket(options) {
|
|
|
19176
19233
|
setConnected(true);
|
|
19177
19234
|
onOpenRef.current();
|
|
19178
19235
|
});
|
|
19179
|
-
ws.addEventListener("message", () => {
|
|
19236
|
+
ws.addEventListener("message", (event) => {
|
|
19180
19237
|
if (wsRef.current !== ws) return;
|
|
19181
|
-
|
|
19238
|
+
let message;
|
|
19239
|
+
try {
|
|
19240
|
+
message = parseSpansWebSocketMessage(event.data);
|
|
19241
|
+
} catch (error) {
|
|
19242
|
+
wsLogger.warn("Failed to parse WS message:", { error });
|
|
19243
|
+
return;
|
|
19244
|
+
}
|
|
19245
|
+
if (message === void 0) {
|
|
19246
|
+
wsLogger.warn("Received unrecognized WS message shape:", { data: String(event.data) });
|
|
19247
|
+
return;
|
|
19248
|
+
}
|
|
19249
|
+
onMessageRef.current(message);
|
|
19182
19250
|
});
|
|
19183
19251
|
ws.addEventListener("close", () => {
|
|
19184
19252
|
if (wsRef.current !== ws) return;
|
|
@@ -19222,7 +19290,7 @@ function useSpansWebSocket(options) {
|
|
|
19222
19290
|
var PAGE_SIZE = 100;
|
|
19223
19291
|
var useSpans = () => {
|
|
19224
19292
|
const api = useAppInspectorApi();
|
|
19225
|
-
const { onWsClose, onWsOpen, wsEnabled } = useAppInspectorStatus();
|
|
19293
|
+
const { onWsClose, onWsOpen, status, wsEnabled } = useAppInspectorStatus();
|
|
19226
19294
|
const [spans, setSpans] = (0, import_react47.useState)();
|
|
19227
19295
|
const [fetchError, setFetchError] = (0, import_react47.useState)();
|
|
19228
19296
|
const [fetchingForward, setFetchingForward] = (0, import_react47.useState)(false);
|
|
@@ -19245,6 +19313,33 @@ var useSpans = () => {
|
|
|
19245
19313
|
const fetchBackward = (0, import_react47.useCallback)(() => {
|
|
19246
19314
|
void paginationBufferRef.current?.fetchBackward();
|
|
19247
19315
|
}, []);
|
|
19316
|
+
const [epoch, setEpoch] = (0, import_react47.useState)();
|
|
19317
|
+
const onEpochSeen = (0, import_react47.useCallback)((observedEpoch) => {
|
|
19318
|
+
if (observedEpoch === null || observedEpoch === void 0) {
|
|
19319
|
+
return;
|
|
19320
|
+
}
|
|
19321
|
+
setEpoch(observedEpoch);
|
|
19322
|
+
}, []);
|
|
19323
|
+
const clearCache = (0, import_react47.useCallback)(() => {
|
|
19324
|
+
paginationBufferRef.current?.clear();
|
|
19325
|
+
setTotalCount(void 0);
|
|
19326
|
+
setErrorCount(void 0);
|
|
19327
|
+
setWarningCount(void 0);
|
|
19328
|
+
void paginationBufferRef.current?.fetchForward();
|
|
19329
|
+
}, []);
|
|
19330
|
+
const hasFetchedWithoutEpochRef = (0, import_react47.useRef)(false);
|
|
19331
|
+
const previousEpochRef = (0, import_react47.useRef)();
|
|
19332
|
+
(0, import_react47.useEffect)(() => {
|
|
19333
|
+
if (epoch === void 0) {
|
|
19334
|
+
return;
|
|
19335
|
+
}
|
|
19336
|
+
const isEpochChange = previousEpochRef.current !== void 0 && previousEpochRef.current !== epoch;
|
|
19337
|
+
const isFirstEpochAfterUnknownBackend = previousEpochRef.current === void 0 && hasFetchedWithoutEpochRef.current;
|
|
19338
|
+
if (isEpochChange || isFirstEpochAfterUnknownBackend) {
|
|
19339
|
+
clearCache();
|
|
19340
|
+
}
|
|
19341
|
+
previousEpochRef.current = epoch;
|
|
19342
|
+
}, [epoch, clearCache]);
|
|
19248
19343
|
const applySearch = (0, import_react47.useCallback)((term) => {
|
|
19249
19344
|
const normalized = term.trim();
|
|
19250
19345
|
if (normalized === searchRef.current) {
|
|
@@ -19264,23 +19359,37 @@ var useSpans = () => {
|
|
|
19264
19359
|
(0, import_react47.useEffect)(() => {
|
|
19265
19360
|
const buffer = createPaginationBuffer({
|
|
19266
19361
|
async fetchPage(token, signal) {
|
|
19267
|
-
const
|
|
19268
|
-
|
|
19269
|
-
|
|
19270
|
-
|
|
19271
|
-
|
|
19272
|
-
|
|
19273
|
-
|
|
19274
|
-
|
|
19275
|
-
|
|
19276
|
-
|
|
19277
|
-
|
|
19278
|
-
|
|
19279
|
-
|
|
19280
|
-
|
|
19281
|
-
|
|
19282
|
-
|
|
19362
|
+
const requestPage = async (paginationToken) => {
|
|
19363
|
+
const response = await api.getSpans({
|
|
19364
|
+
limit: PAGE_SIZE,
|
|
19365
|
+
pagination_token: paginationToken,
|
|
19366
|
+
search: searchRef.current || void 0
|
|
19367
|
+
}, { signal });
|
|
19368
|
+
if (!response.pagination.epoch) {
|
|
19369
|
+
hasFetchedWithoutEpochRef.current = true;
|
|
19370
|
+
}
|
|
19371
|
+
onEpochSeen(response.pagination.epoch);
|
|
19372
|
+
setTotalCount(response.pagination.total_count);
|
|
19373
|
+
setLicenseLimit(response.limits.span_count_limit_license);
|
|
19374
|
+
setSystemLimit(response.limits.span_count_limit_system);
|
|
19375
|
+
setErrorCount(response.error_count);
|
|
19376
|
+
setWarningCount(response.warning_count);
|
|
19377
|
+
return {
|
|
19378
|
+
hasMoreBackward: response.pagination.has_prev,
|
|
19379
|
+
hasMoreForward: response.pagination.has_next,
|
|
19380
|
+
items: response.spans,
|
|
19381
|
+
nextBackwardToken: response.pagination.prev_cursor ?? void 0,
|
|
19382
|
+
nextForwardToken: response.pagination.next_cursor ?? void 0
|
|
19383
|
+
};
|
|
19283
19384
|
};
|
|
19385
|
+
try {
|
|
19386
|
+
return await requestPage(token);
|
|
19387
|
+
} catch (error) {
|
|
19388
|
+
if (error instanceof StaleTokenError && token !== void 0) {
|
|
19389
|
+
return await requestPage();
|
|
19390
|
+
}
|
|
19391
|
+
throw error;
|
|
19392
|
+
}
|
|
19284
19393
|
},
|
|
19285
19394
|
onDataChange: ({ hasMoreBackward: hasMoreBackward2, hasMoreForward: hasMoreForward2, items }) => {
|
|
19286
19395
|
setFetchError(void 0);
|
|
@@ -19291,9 +19400,9 @@ var useSpans = () => {
|
|
|
19291
19400
|
onError: (error) => {
|
|
19292
19401
|
setFetchError(error);
|
|
19293
19402
|
},
|
|
19294
|
-
onStatusChange(
|
|
19295
|
-
setFetchingBackward(
|
|
19296
|
-
setFetchingForward(
|
|
19403
|
+
onStatusChange(status2) {
|
|
19404
|
+
setFetchingBackward(status2.fetchingBackward);
|
|
19405
|
+
setFetchingForward(status2.fetchingForward);
|
|
19297
19406
|
},
|
|
19298
19407
|
// The spans endpoint is sorted by span ingestion time, which might differ from
|
|
19299
19408
|
// the span timestamp. In order to accomodate for this, we will sort the spans
|
|
@@ -19304,7 +19413,7 @@ var useSpans = () => {
|
|
|
19304
19413
|
});
|
|
19305
19414
|
paginationBufferRef.current = buffer;
|
|
19306
19415
|
void buffer.fetchForward();
|
|
19307
|
-
}, [api]);
|
|
19416
|
+
}, [api, onEpochSeen]);
|
|
19308
19417
|
const [clearingSpans, setClearingSpans] = (0, import_react47.useState)(false);
|
|
19309
19418
|
const clearSpans = (0, import_react47.useCallback)(async () => {
|
|
19310
19419
|
if (clearingSpans) {
|
|
@@ -19336,10 +19445,19 @@ var useSpans = () => {
|
|
|
19336
19445
|
void paginationBufferRef.current?.fetchBackward();
|
|
19337
19446
|
}
|
|
19338
19447
|
}, [hasMoreBackward, fetchingBackward, fetchingForward]);
|
|
19448
|
+
const [previousStatusEpoch, setPreviousStatusEpoch] = (0, import_react47.useState)();
|
|
19449
|
+
if (status !== void 0 && status.epoch !== previousStatusEpoch) {
|
|
19450
|
+
setPreviousStatusEpoch(status.epoch);
|
|
19451
|
+
onEpochSeen(status.epoch);
|
|
19452
|
+
}
|
|
19339
19453
|
useSpansWebSocket({
|
|
19340
19454
|
enabled: wsEnabled,
|
|
19341
19455
|
onClose: onWsClose,
|
|
19342
|
-
onMessage: () => {
|
|
19456
|
+
onMessage: (message) => {
|
|
19457
|
+
onEpochSeen(message.epoch);
|
|
19458
|
+
if (message.type !== "span") {
|
|
19459
|
+
return;
|
|
19460
|
+
}
|
|
19343
19461
|
if (clearingSpans) {
|
|
19344
19462
|
return;
|
|
19345
19463
|
}
|