@localstack/appinspector-ui 1.0.162 → 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.js
CHANGED
|
@@ -2448,6 +2448,13 @@ var ConnectionError = class extends Error {
|
|
|
2448
2448
|
this.cause = cause;
|
|
2449
2449
|
}
|
|
2450
2450
|
};
|
|
2451
|
+
var StaleTokenError = class extends Error {
|
|
2452
|
+
constructor(message, cause) {
|
|
2453
|
+
super(message);
|
|
2454
|
+
this.name = "StaleTokenError";
|
|
2455
|
+
this.cause = cause;
|
|
2456
|
+
}
|
|
2457
|
+
};
|
|
2451
2458
|
|
|
2452
2459
|
// src/api/use-api.tsx
|
|
2453
2460
|
import { createContext, useContext } from "react";
|
|
@@ -16981,7 +16988,8 @@ var import_semver2 = __toESM(require_semver2(), 1);
|
|
|
16981
16988
|
var ERROR_MESSAGES = {
|
|
16982
16989
|
APP_INSPECTOR_DISABLED: "App Inspector is currently disabled.",
|
|
16983
16990
|
APP_INSPECTOR_NOT_FOUND: "App Inspector API not found. Please ensure App Inspector is licensed and enabled.",
|
|
16984
|
-
CONNECTION_FAILED: "Failed to connect to LocalStack. Please ensure LocalStack is running and accessible."
|
|
16991
|
+
CONNECTION_FAILED: "Failed to connect to LocalStack. Please ensure LocalStack is running and accessible.",
|
|
16992
|
+
STALE_PAGINATION_TOKEN: "Pagination token is from a previous session."
|
|
16985
16993
|
};
|
|
16986
16994
|
|
|
16987
16995
|
// src/utils/logger.ts
|
|
@@ -17044,6 +17052,16 @@ function unixNanoToMilliseconds(unixNanoString) {
|
|
|
17044
17052
|
// src/api/client.ts
|
|
17045
17053
|
var apiLogger = createScopedLogger("API");
|
|
17046
17054
|
var analyticsLogger = createScopedLogger("ANALYTICS");
|
|
17055
|
+
var isStalePaginationTokenMessage = (body) => {
|
|
17056
|
+
let candidate = body;
|
|
17057
|
+
try {
|
|
17058
|
+
const parsed = JSON.parse(body);
|
|
17059
|
+
const messageField = [parsed.message, parsed.error, parsed.detail].find((value) => typeof value === "string");
|
|
17060
|
+
candidate = typeof messageField === "string" ? messageField : body;
|
|
17061
|
+
} catch {
|
|
17062
|
+
}
|
|
17063
|
+
return candidate.toLowerCase().includes("previous session");
|
|
17064
|
+
};
|
|
17047
17065
|
var makeRequest = async (options) => {
|
|
17048
17066
|
const url = getAppInspectorApiUrl(options.localstackEndpoint, options.endpoint);
|
|
17049
17067
|
try {
|
|
@@ -17067,6 +17085,15 @@ var makeRequest = async (options) => {
|
|
|
17067
17085
|
new Error(`503 from ${url}: ${response.statusText}`)
|
|
17068
17086
|
);
|
|
17069
17087
|
}
|
|
17088
|
+
if (response.status === 400) {
|
|
17089
|
+
const body = await response.text().catch(() => "");
|
|
17090
|
+
if (isStalePaginationTokenMessage(body)) {
|
|
17091
|
+
throw new StaleTokenError(
|
|
17092
|
+
ERROR_MESSAGES.STALE_PAGINATION_TOKEN,
|
|
17093
|
+
new Error(`400 from ${url}: ${response.statusText}`)
|
|
17094
|
+
);
|
|
17095
|
+
}
|
|
17096
|
+
}
|
|
17070
17097
|
throw new Error(`API request failed: ${response.status.toString()} ${response.statusText}`);
|
|
17071
17098
|
}
|
|
17072
17099
|
options.captureHeaders?.(response.headers);
|
|
@@ -19172,6 +19199,35 @@ var AppInspectorContextProvider = (props) => {
|
|
|
19172
19199
|
};
|
|
19173
19200
|
|
|
19174
19201
|
// src/hooks/use-spans-ws.tsx
|
|
19202
|
+
var wsLogger = createScopedLogger("WS");
|
|
19203
|
+
var isLegacySpanFields = (value) => {
|
|
19204
|
+
return typeof value.span_id === "string" && typeof value.trace_id === "string" && typeof value.timestamp === "string";
|
|
19205
|
+
};
|
|
19206
|
+
var parseSpansWebSocketMessage = (raw) => {
|
|
19207
|
+
const parsed = JSON.parse(raw);
|
|
19208
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
19209
|
+
return void 0;
|
|
19210
|
+
}
|
|
19211
|
+
const record = parsed;
|
|
19212
|
+
if (record.type === void 0) {
|
|
19213
|
+
return isLegacySpanFields(record) ? { data: record, type: "span" } : void 0;
|
|
19214
|
+
}
|
|
19215
|
+
if (record.type === "epoch" || record.type === "reset") {
|
|
19216
|
+
return typeof record.epoch === "string" ? { epoch: record.epoch, type: record.type } : void 0;
|
|
19217
|
+
}
|
|
19218
|
+
if (record.type === "span") {
|
|
19219
|
+
const data = record.data;
|
|
19220
|
+
if (typeof data !== "object" || data === null || !isLegacySpanFields(data)) {
|
|
19221
|
+
return void 0;
|
|
19222
|
+
}
|
|
19223
|
+
return {
|
|
19224
|
+
data,
|
|
19225
|
+
epoch: typeof record.epoch === "string" ? record.epoch : void 0,
|
|
19226
|
+
type: "span"
|
|
19227
|
+
};
|
|
19228
|
+
}
|
|
19229
|
+
return void 0;
|
|
19230
|
+
};
|
|
19175
19231
|
function useSpansWebSocket(options) {
|
|
19176
19232
|
const { enabled, onClose, onMessage, onOpen } = options;
|
|
19177
19233
|
const onMessageRef = useRef9(onMessage);
|
|
@@ -19193,6 +19249,7 @@ function useSpansWebSocket(options) {
|
|
|
19193
19249
|
if (wsRef.current !== void 0) return;
|
|
19194
19250
|
const url = new URL(getAppInspectorApiUrl(localstackEndpoint, API_ENDPOINTS.WEBSOCKET_SPANS));
|
|
19195
19251
|
url.protocol = globalThis.location.protocol === "https:" ? "wss" : "ws";
|
|
19252
|
+
url.searchParams.set("epoch", "1");
|
|
19196
19253
|
const ws = new WebSocket(url);
|
|
19197
19254
|
wsRef.current = ws;
|
|
19198
19255
|
ws.addEventListener("open", () => {
|
|
@@ -19200,9 +19257,20 @@ function useSpansWebSocket(options) {
|
|
|
19200
19257
|
setConnected(true);
|
|
19201
19258
|
onOpenRef.current();
|
|
19202
19259
|
});
|
|
19203
|
-
ws.addEventListener("message", () => {
|
|
19260
|
+
ws.addEventListener("message", (event) => {
|
|
19204
19261
|
if (wsRef.current !== ws) return;
|
|
19205
|
-
|
|
19262
|
+
let message;
|
|
19263
|
+
try {
|
|
19264
|
+
message = parseSpansWebSocketMessage(event.data);
|
|
19265
|
+
} catch (error) {
|
|
19266
|
+
wsLogger.warn("Failed to parse WS message:", { error });
|
|
19267
|
+
return;
|
|
19268
|
+
}
|
|
19269
|
+
if (message === void 0) {
|
|
19270
|
+
wsLogger.warn("Received unrecognized WS message shape:", { data: String(event.data) });
|
|
19271
|
+
return;
|
|
19272
|
+
}
|
|
19273
|
+
onMessageRef.current(message);
|
|
19206
19274
|
});
|
|
19207
19275
|
ws.addEventListener("close", () => {
|
|
19208
19276
|
if (wsRef.current !== ws) return;
|
|
@@ -19246,7 +19314,7 @@ function useSpansWebSocket(options) {
|
|
|
19246
19314
|
var PAGE_SIZE = 100;
|
|
19247
19315
|
var useSpans = () => {
|
|
19248
19316
|
const api = useAppInspectorApi();
|
|
19249
|
-
const { onWsClose, onWsOpen, wsEnabled } = useAppInspectorStatus();
|
|
19317
|
+
const { onWsClose, onWsOpen, status, wsEnabled } = useAppInspectorStatus();
|
|
19250
19318
|
const [spans, setSpans] = useState11();
|
|
19251
19319
|
const [fetchError, setFetchError] = useState11();
|
|
19252
19320
|
const [fetchingForward, setFetchingForward] = useState11(false);
|
|
@@ -19269,6 +19337,33 @@ var useSpans = () => {
|
|
|
19269
19337
|
const fetchBackward = useCallback7(() => {
|
|
19270
19338
|
void paginationBufferRef.current?.fetchBackward();
|
|
19271
19339
|
}, []);
|
|
19340
|
+
const [epoch, setEpoch] = useState11();
|
|
19341
|
+
const onEpochSeen = useCallback7((observedEpoch) => {
|
|
19342
|
+
if (observedEpoch === null || observedEpoch === void 0) {
|
|
19343
|
+
return;
|
|
19344
|
+
}
|
|
19345
|
+
setEpoch(observedEpoch);
|
|
19346
|
+
}, []);
|
|
19347
|
+
const clearCache = useCallback7(() => {
|
|
19348
|
+
paginationBufferRef.current?.clear();
|
|
19349
|
+
setTotalCount(void 0);
|
|
19350
|
+
setErrorCount(void 0);
|
|
19351
|
+
setWarningCount(void 0);
|
|
19352
|
+
void paginationBufferRef.current?.fetchForward();
|
|
19353
|
+
}, []);
|
|
19354
|
+
const hasFetchedWithoutEpochRef = useRef10(false);
|
|
19355
|
+
const previousEpochRef = useRef10();
|
|
19356
|
+
useEffect16(() => {
|
|
19357
|
+
if (epoch === void 0) {
|
|
19358
|
+
return;
|
|
19359
|
+
}
|
|
19360
|
+
const isEpochChange = previousEpochRef.current !== void 0 && previousEpochRef.current !== epoch;
|
|
19361
|
+
const isFirstEpochAfterUnknownBackend = previousEpochRef.current === void 0 && hasFetchedWithoutEpochRef.current;
|
|
19362
|
+
if (isEpochChange || isFirstEpochAfterUnknownBackend) {
|
|
19363
|
+
clearCache();
|
|
19364
|
+
}
|
|
19365
|
+
previousEpochRef.current = epoch;
|
|
19366
|
+
}, [epoch, clearCache]);
|
|
19272
19367
|
const applySearch = useCallback7((term) => {
|
|
19273
19368
|
const normalized = term.trim();
|
|
19274
19369
|
if (normalized === searchRef.current) {
|
|
@@ -19288,23 +19383,37 @@ var useSpans = () => {
|
|
|
19288
19383
|
useEffect16(() => {
|
|
19289
19384
|
const buffer = createPaginationBuffer({
|
|
19290
19385
|
async fetchPage(token, signal) {
|
|
19291
|
-
const
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
|
|
19295
|
-
|
|
19296
|
-
|
|
19297
|
-
|
|
19298
|
-
|
|
19299
|
-
|
|
19300
|
-
|
|
19301
|
-
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
19386
|
+
const requestPage = async (paginationToken) => {
|
|
19387
|
+
const response = await api.getSpans({
|
|
19388
|
+
limit: PAGE_SIZE,
|
|
19389
|
+
pagination_token: paginationToken,
|
|
19390
|
+
search: searchRef.current || void 0
|
|
19391
|
+
}, { signal });
|
|
19392
|
+
if (!response.pagination.epoch) {
|
|
19393
|
+
hasFetchedWithoutEpochRef.current = true;
|
|
19394
|
+
}
|
|
19395
|
+
onEpochSeen(response.pagination.epoch);
|
|
19396
|
+
setTotalCount(response.pagination.total_count);
|
|
19397
|
+
setLicenseLimit(response.limits.span_count_limit_license);
|
|
19398
|
+
setSystemLimit(response.limits.span_count_limit_system);
|
|
19399
|
+
setErrorCount(response.error_count);
|
|
19400
|
+
setWarningCount(response.warning_count);
|
|
19401
|
+
return {
|
|
19402
|
+
hasMoreBackward: response.pagination.has_prev,
|
|
19403
|
+
hasMoreForward: response.pagination.has_next,
|
|
19404
|
+
items: response.spans,
|
|
19405
|
+
nextBackwardToken: response.pagination.prev_cursor ?? void 0,
|
|
19406
|
+
nextForwardToken: response.pagination.next_cursor ?? void 0
|
|
19407
|
+
};
|
|
19307
19408
|
};
|
|
19409
|
+
try {
|
|
19410
|
+
return await requestPage(token);
|
|
19411
|
+
} catch (error) {
|
|
19412
|
+
if (error instanceof StaleTokenError && token !== void 0) {
|
|
19413
|
+
return await requestPage();
|
|
19414
|
+
}
|
|
19415
|
+
throw error;
|
|
19416
|
+
}
|
|
19308
19417
|
},
|
|
19309
19418
|
onDataChange: ({ hasMoreBackward: hasMoreBackward2, hasMoreForward: hasMoreForward2, items }) => {
|
|
19310
19419
|
setFetchError(void 0);
|
|
@@ -19315,9 +19424,9 @@ var useSpans = () => {
|
|
|
19315
19424
|
onError: (error) => {
|
|
19316
19425
|
setFetchError(error);
|
|
19317
19426
|
},
|
|
19318
|
-
onStatusChange(
|
|
19319
|
-
setFetchingBackward(
|
|
19320
|
-
setFetchingForward(
|
|
19427
|
+
onStatusChange(status2) {
|
|
19428
|
+
setFetchingBackward(status2.fetchingBackward);
|
|
19429
|
+
setFetchingForward(status2.fetchingForward);
|
|
19321
19430
|
},
|
|
19322
19431
|
// The spans endpoint is sorted by span ingestion time, which might differ from
|
|
19323
19432
|
// the span timestamp. In order to accomodate for this, we will sort the spans
|
|
@@ -19328,7 +19437,7 @@ var useSpans = () => {
|
|
|
19328
19437
|
});
|
|
19329
19438
|
paginationBufferRef.current = buffer;
|
|
19330
19439
|
void buffer.fetchForward();
|
|
19331
|
-
}, [api]);
|
|
19440
|
+
}, [api, onEpochSeen]);
|
|
19332
19441
|
const [clearingSpans, setClearingSpans] = useState11(false);
|
|
19333
19442
|
const clearSpans = useCallback7(async () => {
|
|
19334
19443
|
if (clearingSpans) {
|
|
@@ -19360,10 +19469,19 @@ var useSpans = () => {
|
|
|
19360
19469
|
void paginationBufferRef.current?.fetchBackward();
|
|
19361
19470
|
}
|
|
19362
19471
|
}, [hasMoreBackward, fetchingBackward, fetchingForward]);
|
|
19472
|
+
const [previousStatusEpoch, setPreviousStatusEpoch] = useState11();
|
|
19473
|
+
if (status !== void 0 && status.epoch !== previousStatusEpoch) {
|
|
19474
|
+
setPreviousStatusEpoch(status.epoch);
|
|
19475
|
+
onEpochSeen(status.epoch);
|
|
19476
|
+
}
|
|
19363
19477
|
useSpansWebSocket({
|
|
19364
19478
|
enabled: wsEnabled,
|
|
19365
19479
|
onClose: onWsClose,
|
|
19366
|
-
onMessage: () => {
|
|
19480
|
+
onMessage: (message) => {
|
|
19481
|
+
onEpochSeen(message.epoch);
|
|
19482
|
+
if (message.type !== "span") {
|
|
19483
|
+
return;
|
|
19484
|
+
}
|
|
19367
19485
|
if (clearingSpans) {
|
|
19368
19486
|
return;
|
|
19369
19487
|
}
|