@uniai-fe/uds-templates 0.6.10 → 0.6.12
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/package.json +1 -1
- package/src/cctv/utils/debug.ts +213 -15
package/package.json
CHANGED
package/src/cctv/utils/debug.ts
CHANGED
|
@@ -11,13 +11,36 @@ export interface CctvDebugEvent {
|
|
|
11
11
|
source: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export interface CctvDebugReconnectStartSummary {
|
|
15
|
+
at: string;
|
|
16
|
+
caller: string | null;
|
|
17
|
+
camId: string | null;
|
|
18
|
+
connectionState: string | null;
|
|
19
|
+
isPostConnectedReconnectReady: boolean | null;
|
|
20
|
+
reconnectReason: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CctvDebugSummary {
|
|
24
|
+
connectionStateCounts: Record<string, number>;
|
|
25
|
+
eventCounts: Record<string, number>;
|
|
26
|
+
first: string | null;
|
|
27
|
+
generatedAt: string;
|
|
28
|
+
last: string | null;
|
|
29
|
+
reconnectCallers: Record<string, number>;
|
|
30
|
+
reconnectReasons: Record<string, number>;
|
|
31
|
+
reconnectStarts: CctvDebugReconnectStartSummary[];
|
|
32
|
+
total: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
14
35
|
export interface CctvDebugBuffer {
|
|
15
36
|
clear: () => void;
|
|
16
37
|
dump: () => CctvDebugEvent[];
|
|
17
38
|
enabled: true;
|
|
18
39
|
events: CctvDebugEvent[];
|
|
40
|
+
filter: (keywords?: readonly string[]) => CctvDebugEvent[];
|
|
19
41
|
limit: number;
|
|
20
42
|
sequence: number;
|
|
43
|
+
summary: () => CctvDebugSummary;
|
|
21
44
|
}
|
|
22
45
|
|
|
23
46
|
declare global {
|
|
@@ -28,15 +51,175 @@ declare global {
|
|
|
28
51
|
|
|
29
52
|
const DEBUG_QUERY_KEYS = ["udsCctvDebug", "cctvDebug"] as const;
|
|
30
53
|
const DEBUG_STORAGE_KEYS = ["uds:cctv:debug", "UDS_CCTV_DEBUG"] as const;
|
|
54
|
+
const DEBUG_CONSOLE_QUERY_KEYS = [
|
|
55
|
+
"udsCctvDebugConsole",
|
|
56
|
+
"cctvDebugConsole",
|
|
57
|
+
] as const;
|
|
58
|
+
const DEBUG_CONSOLE_STORAGE_KEYS = [
|
|
59
|
+
"uds:cctv:debug:console",
|
|
60
|
+
"UDS_CCTV_DEBUG_CONSOLE",
|
|
61
|
+
] as const;
|
|
31
62
|
const DEBUG_ENABLED_VALUES = new Set(["1", "true", "yes", "on", "debug"]);
|
|
63
|
+
const DEBUG_DISABLED_VALUES = new Set(["0", "false", "no", "off"]);
|
|
32
64
|
const DEBUG_BUFFER_LIMIT = 1000;
|
|
65
|
+
const DEBUG_DEFAULT_FILTER_KEYWORDS = [
|
|
66
|
+
"reconnect",
|
|
67
|
+
"close",
|
|
68
|
+
"rejected",
|
|
69
|
+
"connection-state:change",
|
|
70
|
+
"track:received",
|
|
71
|
+
] as const;
|
|
33
72
|
|
|
34
73
|
const isBrowser = (): boolean =>
|
|
35
74
|
typeof window !== "undefined" && typeof document !== "undefined";
|
|
36
75
|
|
|
37
|
-
const
|
|
38
|
-
value === ""
|
|
39
|
-
(typeof value
|
|
76
|
+
const getDebugValueOverride = (value: string | null): boolean | null => {
|
|
77
|
+
if (value === "") return true;
|
|
78
|
+
if (typeof value !== "string") return null;
|
|
79
|
+
|
|
80
|
+
const normalizedValue = value.toLowerCase();
|
|
81
|
+
if (DEBUG_ENABLED_VALUES.has(normalizedValue)) return true;
|
|
82
|
+
if (DEBUG_DISABLED_VALUES.has(normalizedValue)) return false;
|
|
83
|
+
return null;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const getDebugQueryOverride = (keys: readonly string[]): boolean | null => {
|
|
87
|
+
const query = new URLSearchParams(window.location.search);
|
|
88
|
+
for (const key of keys) {
|
|
89
|
+
if (!query.has(key)) continue;
|
|
90
|
+
|
|
91
|
+
const override = getDebugValueOverride(query.get(key));
|
|
92
|
+
if (override !== null) return override;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return null;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const getDebugStorageOverride = (keys: readonly string[]): boolean | null => {
|
|
99
|
+
for (const key of keys) {
|
|
100
|
+
try {
|
|
101
|
+
const override = getDebugValueOverride(window.localStorage.getItem(key));
|
|
102
|
+
if (override !== null) return override;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return null;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const isLocalhostDebugDefaultEnabled = (): boolean => {
|
|
112
|
+
const { hostname } = window.location;
|
|
113
|
+
return (
|
|
114
|
+
hostname === "localhost" ||
|
|
115
|
+
hostname === "127.0.0.1" ||
|
|
116
|
+
hostname === "[::1]" ||
|
|
117
|
+
hostname === "::1" ||
|
|
118
|
+
hostname.endsWith(".localhost")
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const getPayloadString = (
|
|
123
|
+
payload: Record<string, unknown> | undefined,
|
|
124
|
+
key: string,
|
|
125
|
+
): string | null => {
|
|
126
|
+
const value = payload?.[key];
|
|
127
|
+
return typeof value === "string" ? value : null;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const getPayloadBoolean = (
|
|
131
|
+
payload: Record<string, unknown> | undefined,
|
|
132
|
+
key: string,
|
|
133
|
+
): boolean | null => {
|
|
134
|
+
const value = payload?.[key];
|
|
135
|
+
return typeof value === "boolean" ? value : null;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const getReconnectCaller = (
|
|
139
|
+
payload: Record<string, unknown> | undefined,
|
|
140
|
+
): string | null => {
|
|
141
|
+
const callerStack = payload?.callerStack;
|
|
142
|
+
const stackLines = Array.isArray(callerStack)
|
|
143
|
+
? callerStack.filter((line): line is string => typeof line === "string")
|
|
144
|
+
: typeof callerStack === "string"
|
|
145
|
+
? callerStack.split("\n")
|
|
146
|
+
: [];
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
stackLines
|
|
150
|
+
.find(
|
|
151
|
+
line =>
|
|
152
|
+
line.includes("CCTVManagerVideoOverlay") ||
|
|
153
|
+
line.includes("reconnectOnFocus"),
|
|
154
|
+
)
|
|
155
|
+
?.trim() ?? null
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const countBy = <T extends string>(
|
|
160
|
+
target: Record<T, number>,
|
|
161
|
+
key: T | null,
|
|
162
|
+
): void => {
|
|
163
|
+
if (!key) return;
|
|
164
|
+
target[key] = (target[key] ?? 0) + 1;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const filterCctvDebugEvents = (
|
|
168
|
+
events: readonly CctvDebugEvent[],
|
|
169
|
+
keywords: readonly string[] = DEBUG_DEFAULT_FILTER_KEYWORDS,
|
|
170
|
+
): CctvDebugEvent[] =>
|
|
171
|
+
events.filter(entry =>
|
|
172
|
+
keywords.some(keyword => entry.event.includes(keyword)),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const summarizeCctvDebugEvents = (
|
|
176
|
+
events: readonly CctvDebugEvent[],
|
|
177
|
+
): CctvDebugSummary => {
|
|
178
|
+
const eventCounts: Record<string, number> = {};
|
|
179
|
+
const connectionStateCounts: Record<string, number> = {};
|
|
180
|
+
const reconnectReasons: Record<string, number> = {};
|
|
181
|
+
const reconnectCallers: Record<string, number> = {};
|
|
182
|
+
const reconnectStarts: CctvDebugReconnectStartSummary[] = [];
|
|
183
|
+
|
|
184
|
+
for (const entry of events) {
|
|
185
|
+
countBy(eventCounts, entry.event);
|
|
186
|
+
|
|
187
|
+
if (entry.event === "connection-state:change") {
|
|
188
|
+
countBy(connectionStateCounts, getPayloadString(entry.payload, "state"));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (entry.event !== "reconnect-stream:start") continue;
|
|
192
|
+
|
|
193
|
+
const reconnectReason = getPayloadString(entry.payload, "reconnectReason");
|
|
194
|
+
const caller = getReconnectCaller(entry.payload);
|
|
195
|
+
|
|
196
|
+
countBy(reconnectReasons, reconnectReason ?? "none");
|
|
197
|
+
countBy(reconnectCallers, caller ?? "unknown");
|
|
198
|
+
reconnectStarts.push({
|
|
199
|
+
at: entry.at,
|
|
200
|
+
caller,
|
|
201
|
+
camId: getPayloadString(entry.payload, "camId"),
|
|
202
|
+
connectionState: getPayloadString(entry.payload, "connectionState"),
|
|
203
|
+
isPostConnectedReconnectReady: getPayloadBoolean(
|
|
204
|
+
entry.payload,
|
|
205
|
+
"isPostConnectedReconnectReady",
|
|
206
|
+
),
|
|
207
|
+
reconnectReason,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
connectionStateCounts,
|
|
213
|
+
eventCounts,
|
|
214
|
+
first: events[0]?.at ?? null,
|
|
215
|
+
generatedAt: new Date().toISOString(),
|
|
216
|
+
last: events[events.length - 1]?.at ?? null,
|
|
217
|
+
reconnectCallers,
|
|
218
|
+
reconnectReasons,
|
|
219
|
+
reconnectStarts,
|
|
220
|
+
total: events.length,
|
|
221
|
+
};
|
|
222
|
+
};
|
|
40
223
|
|
|
41
224
|
/**
|
|
42
225
|
* identity/stream key를 원문 대신 추적 가능한 hash label로 바꾼다.
|
|
@@ -79,20 +262,31 @@ export const getCctvDebugUrlLabel = (
|
|
|
79
262
|
export const isCctvDebugEnabled = (): boolean => {
|
|
80
263
|
if (!isBrowser()) return false;
|
|
81
264
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
if (query.has(key) && isDebugValueEnabled(query.get(key))) return true;
|
|
85
|
-
}
|
|
265
|
+
const queryOverride = getDebugQueryOverride(DEBUG_QUERY_KEYS);
|
|
266
|
+
if (queryOverride !== null) return queryOverride;
|
|
86
267
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (isDebugValueEnabled(window.localStorage.getItem(key))) return true;
|
|
90
|
-
} catch {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
268
|
+
const storageOverride = getDebugStorageOverride(DEBUG_STORAGE_KEYS);
|
|
269
|
+
if (storageOverride !== null) return storageOverride;
|
|
94
270
|
|
|
95
|
-
return
|
|
271
|
+
return isLocalhostDebugDefaultEnabled();
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const isCctvDebugConsoleEnabled = (): boolean => {
|
|
275
|
+
if (!isBrowser()) return false;
|
|
276
|
+
|
|
277
|
+
const consoleQueryOverride = getDebugQueryOverride(DEBUG_CONSOLE_QUERY_KEYS);
|
|
278
|
+
if (consoleQueryOverride !== null) return consoleQueryOverride;
|
|
279
|
+
|
|
280
|
+
const consoleStorageOverride = getDebugStorageOverride(
|
|
281
|
+
DEBUG_CONSOLE_STORAGE_KEYS,
|
|
282
|
+
);
|
|
283
|
+
if (consoleStorageOverride !== null) return consoleStorageOverride;
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
getDebugQueryOverride(DEBUG_QUERY_KEYS) === true ||
|
|
287
|
+
getDebugStorageOverride(DEBUG_STORAGE_KEYS) === true ||
|
|
288
|
+
isLocalhostDebugDefaultEnabled()
|
|
289
|
+
);
|
|
96
290
|
};
|
|
97
291
|
|
|
98
292
|
/**
|
|
@@ -126,6 +320,8 @@ const getCctvDebugBuffer = (): CctvDebugBuffer | null => {
|
|
|
126
320
|
buffer.sequence = 0;
|
|
127
321
|
},
|
|
128
322
|
dump: () => [...buffer.events],
|
|
323
|
+
filter: keywords => filterCctvDebugEvents(buffer.events, keywords),
|
|
324
|
+
summary: () => summarizeCctvDebugEvents(buffer.events),
|
|
129
325
|
};
|
|
130
326
|
|
|
131
327
|
window.__UDS_CCTV_DEBUG__ = buffer;
|
|
@@ -166,6 +362,8 @@ export const logCctvDebugEvent = ({
|
|
|
166
362
|
buffer.events.splice(0, buffer.events.length - buffer.limit);
|
|
167
363
|
}
|
|
168
364
|
|
|
365
|
+
if (!isCctvDebugConsoleEnabled()) return;
|
|
366
|
+
|
|
169
367
|
const logger =
|
|
170
368
|
level === "error"
|
|
171
369
|
? console.error
|