@uniai-fe/uds-templates 0.6.10 → 0.6.11
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 +212 -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,30 @@ 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
|
+
);
|
|
96
289
|
};
|
|
97
290
|
|
|
98
291
|
/**
|
|
@@ -126,6 +319,8 @@ const getCctvDebugBuffer = (): CctvDebugBuffer | null => {
|
|
|
126
319
|
buffer.sequence = 0;
|
|
127
320
|
},
|
|
128
321
|
dump: () => [...buffer.events],
|
|
322
|
+
filter: keywords => filterCctvDebugEvents(buffer.events, keywords),
|
|
323
|
+
summary: () => summarizeCctvDebugEvents(buffer.events),
|
|
129
324
|
};
|
|
130
325
|
|
|
131
326
|
window.__UDS_CCTV_DEBUG__ = buffer;
|
|
@@ -166,6 +361,8 @@ export const logCctvDebugEvent = ({
|
|
|
166
361
|
buffer.events.splice(0, buffer.events.length - buffer.limit);
|
|
167
362
|
}
|
|
168
363
|
|
|
364
|
+
if (!isCctvDebugConsoleEnabled()) return;
|
|
365
|
+
|
|
169
366
|
const logger =
|
|
170
367
|
level === "error"
|
|
171
368
|
? console.error
|