issue-sticker 0.3.0 → 0.4.0
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/CHANGELOG.md +39 -0
- package/README.md +111 -131
- package/dist/{createLucideIcon-CmSNiW63.js → createLucideIcon-C0Ldhp96.js} +90 -87
- package/dist/{externalUserPayload-hJjuUx6q.js → externalUserPayload-BVNl_35M.js} +2659 -1315
- package/dist/issue-sticker-headless-react.js +129 -72
- package/dist/issue-sticker-headless.js +97 -78
- package/dist/issue-sticker-react.js +2534 -2071
- package/dist/{tooltip-DciWyUWs.js → tooltip-CEc_RGfI.js} +1 -1
- package/headless.d.ts +142 -11
- package/package.json +1 -4
- package/react.d.ts +9 -1
|
@@ -1,15 +1,91 @@
|
|
|
1
|
-
import { q as gzip_1, r as record, a as cn,
|
|
1
|
+
import { d as OVERLAY_LAYER_INDEX, q as gzip_1, r as record, a as cn, l as useCallbackRef$1, j as useComposedRefs, P as Primitive, h as Presence, n as Portal$1, g as composeEventHandlers, i as createContextScope, o as createSlot, D as DismissableLayer, C as Content, t as createPopperScope, e as useControllableState, v as Root2$1, f as useId, A as Anchor, w as Arrow, b as usePortalContainer, O as OVERLAY_LAYER, s as stopEscapePropagation, k as useLayoutEffect2, V as VISUALLY_HIDDEN_STYLES, c as createLucideIcon, x as clsx, S as Slot$2 } from "./createLucideIcon-C0Ldhp96.js";
|
|
2
2
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import React__default, { useState, createContext, useContext, useCallback, useRef, useLayoutEffect, useEffect, useMemo } from "react";
|
|
5
5
|
import * as ReactDOM from "react-dom";
|
|
6
|
+
const ISSUE_STICKER_HOST_ELEMENT_ID = "issue-sticker-host";
|
|
7
|
+
const ISSUE_STICKER_RUNTIME_OWNER_ATTRIBUTE = "data-issue-sticker-runtime";
|
|
8
|
+
const ISSUE_STICKER_EXTENSION_RUNTIME_READY_EVENT = "issue-sticker:extension-runtime-ready";
|
|
9
|
+
const ISSUE_STICKER_RUNTIME_OWNER = {
|
|
10
|
+
extension: "extension",
|
|
11
|
+
sdk: "sdk"
|
|
12
|
+
};
|
|
13
|
+
function resolveDocument(doc) {
|
|
14
|
+
return typeof document === "undefined" ? null : document;
|
|
15
|
+
}
|
|
16
|
+
function getIssueStickerRuntimeHost(doc) {
|
|
17
|
+
var _a;
|
|
18
|
+
return ((_a = resolveDocument()) == null ? void 0 : _a.getElementById(ISSUE_STICKER_HOST_ELEMENT_ID)) ?? null;
|
|
19
|
+
}
|
|
20
|
+
function getIssueStickerRuntimeOwner(element) {
|
|
21
|
+
const owner = element == null ? void 0 : element.getAttribute(ISSUE_STICKER_RUNTIME_OWNER_ATTRIBUTE);
|
|
22
|
+
if (owner === ISSUE_STICKER_RUNTIME_OWNER.extension || owner === ISSUE_STICKER_RUNTIME_OWNER.sdk) {
|
|
23
|
+
return owner;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function markIssueStickerRuntimeHost(element, owner) {
|
|
28
|
+
element.setAttribute(ISSUE_STICKER_RUNTIME_OWNER_ATTRIBUTE, owner);
|
|
29
|
+
}
|
|
30
|
+
function createRuntimeHostElement(owner, doc) {
|
|
31
|
+
const targetDocument = resolveDocument();
|
|
32
|
+
if (!targetDocument) {
|
|
33
|
+
throw new Error("createRuntimeHostElement: document 를 찾을 수 없습니다.");
|
|
34
|
+
}
|
|
35
|
+
const hostElement = targetDocument.createElement("div");
|
|
36
|
+
hostElement.id = ISSUE_STICKER_HOST_ELEMENT_ID;
|
|
37
|
+
markIssueStickerRuntimeHost(hostElement, owner);
|
|
38
|
+
hostElement.style.all = "initial";
|
|
39
|
+
hostElement.style.position = "fixed";
|
|
40
|
+
hostElement.style.top = "0";
|
|
41
|
+
hostElement.style.left = "0";
|
|
42
|
+
hostElement.style.width = "0";
|
|
43
|
+
hostElement.style.height = "0";
|
|
44
|
+
hostElement.style.overflow = "visible";
|
|
45
|
+
hostElement.style.background = "transparent";
|
|
46
|
+
hostElement.style.pointerEvents = "none";
|
|
47
|
+
hostElement.style.zIndex = String(OVERLAY_LAYER_INDEX.runtimeHost);
|
|
48
|
+
hostElement.style.display = "block";
|
|
49
|
+
hostElement.style.isolation = "isolate";
|
|
50
|
+
return hostElement;
|
|
51
|
+
}
|
|
52
|
+
function isEditableTarget(target) {
|
|
53
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
54
|
+
return target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable === true;
|
|
55
|
+
}
|
|
56
|
+
function attachHostTypingGuard(shadowRoot) {
|
|
57
|
+
const handleKeyDown = (event) => {
|
|
58
|
+
const keyboardEvent = event;
|
|
59
|
+
if (keyboardEvent.ctrlKey || keyboardEvent.metaKey || keyboardEvent.altKey) return;
|
|
60
|
+
if (keyboardEvent.key === "Escape") return;
|
|
61
|
+
if (!isEditableTarget(keyboardEvent.composedPath()[0])) return;
|
|
62
|
+
keyboardEvent.stopPropagation();
|
|
63
|
+
};
|
|
64
|
+
shadowRoot.addEventListener("keydown", handleKeyDown);
|
|
65
|
+
return () => shadowRoot.removeEventListener("keydown", handleKeyDown);
|
|
66
|
+
}
|
|
67
|
+
const RUNTIME_OWNER_MARKER_KEY = "__issueStickerRuntimeOwner";
|
|
68
|
+
function markerHost() {
|
|
69
|
+
return typeof window === "undefined" ? null : window;
|
|
70
|
+
}
|
|
71
|
+
function getClaimedIssueStickerRuntimeOwner() {
|
|
72
|
+
var _a;
|
|
73
|
+
return ((_a = markerHost()) == null ? void 0 : _a[RUNTIME_OWNER_MARKER_KEY]) ?? null;
|
|
74
|
+
}
|
|
75
|
+
function isIssueStickerExtensionRuntimeActive(doc) {
|
|
76
|
+
if (getClaimedIssueStickerRuntimeOwner() === ISSUE_STICKER_RUNTIME_OWNER.extension) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return getIssueStickerRuntimeOwner(getIssueStickerRuntimeHost()) === ISSUE_STICKER_RUNTIME_OWNER.extension;
|
|
80
|
+
}
|
|
6
81
|
const DEFAULT_MAX_DURATION_MS = 12e4;
|
|
7
82
|
const MIME_TYPE_CANDIDATES = ["video/webm;codecs=vp9", "video/webm"];
|
|
8
83
|
function createInitialSnapshot$1() {
|
|
9
84
|
return {
|
|
10
85
|
isRecording: false,
|
|
11
86
|
recordedFile: null,
|
|
12
|
-
error: null
|
|
87
|
+
error: null,
|
|
88
|
+
startedAt: null
|
|
13
89
|
};
|
|
14
90
|
}
|
|
15
91
|
function getDefaultDisplayMedia() {
|
|
@@ -55,6 +131,7 @@ class ScreenRecordingController {
|
|
|
55
131
|
this.createMediaRecorder = options.createMediaRecorder ?? createDefaultMediaRecorder;
|
|
56
132
|
this.isTypeSupported = options.isTypeSupported ?? isDefaultTypeSupported;
|
|
57
133
|
this.createFileName = options.createFileName ?? (() => `recording-${Date.now()}.webm`);
|
|
134
|
+
this.now = options.now ?? (() => Date.now());
|
|
58
135
|
}
|
|
59
136
|
getSnapshot() {
|
|
60
137
|
return this.snapshot;
|
|
@@ -97,7 +174,7 @@ class ScreenRecordingController {
|
|
|
97
174
|
}
|
|
98
175
|
});
|
|
99
176
|
mediaRecorder.start(1e3);
|
|
100
|
-
this.setSnapshot({ isRecording: true });
|
|
177
|
+
this.setSnapshot({ isRecording: true, startedAt: this.now() });
|
|
101
178
|
(_c = (_b = this.options).onStarted) == null ? void 0 : _c.call(_b);
|
|
102
179
|
this.maxDurationTimer = setTimeout(() => {
|
|
103
180
|
var _a2;
|
|
@@ -112,6 +189,7 @@ class ScreenRecordingController {
|
|
|
112
189
|
this.onStoppedCallback = null;
|
|
113
190
|
this.setSnapshot({
|
|
114
191
|
isRecording: false,
|
|
192
|
+
startedAt: null,
|
|
115
193
|
error: readErrorMessage$1(error)
|
|
116
194
|
});
|
|
117
195
|
(_e = (_d = this.options).onFailed) == null ? void 0 : _e.call(_d, error);
|
|
@@ -121,7 +199,7 @@ class ScreenRecordingController {
|
|
|
121
199
|
async stopRecording() {
|
|
122
200
|
const recorder = this.mediaRecorder;
|
|
123
201
|
if (!recorder || recorder.state !== "recording") {
|
|
124
|
-
this.setSnapshot({ isRecording: false });
|
|
202
|
+
this.setSnapshot({ isRecording: false, startedAt: null });
|
|
125
203
|
return null;
|
|
126
204
|
}
|
|
127
205
|
if (this.pendingStopPromise) {
|
|
@@ -148,7 +226,7 @@ class ScreenRecordingController {
|
|
|
148
226
|
}
|
|
149
227
|
this.cleanupAfterStop();
|
|
150
228
|
this.resolvePendingStop(null);
|
|
151
|
-
this.setSnapshot({ isRecording: false, recordedFile: null });
|
|
229
|
+
this.setSnapshot({ isRecording: false, startedAt: null, recordedFile: null });
|
|
152
230
|
}
|
|
153
231
|
destroy() {
|
|
154
232
|
this.discard();
|
|
@@ -160,13 +238,13 @@ class ScreenRecordingController {
|
|
|
160
238
|
this.cleanupAfterStop();
|
|
161
239
|
if (this.suppressNextStopFile) {
|
|
162
240
|
this.suppressNextStopFile = false;
|
|
163
|
-
this.setSnapshot({ isRecording: false, recordedFile: null });
|
|
241
|
+
this.setSnapshot({ isRecording: false, startedAt: null, recordedFile: null });
|
|
164
242
|
this.resolvePendingStop(null);
|
|
165
243
|
return;
|
|
166
244
|
}
|
|
167
245
|
const blob = new Blob(chunks, { type: mimeType });
|
|
168
246
|
const file = new File([blob], this.createFileName(), { type: mimeType });
|
|
169
|
-
this.setSnapshot({ isRecording: false, recordedFile: file });
|
|
247
|
+
this.setSnapshot({ isRecording: false, startedAt: null, recordedFile: file });
|
|
170
248
|
this.resolvePendingStop(file);
|
|
171
249
|
(_b = (_a = this.options).onStopped) == null ? void 0 : _b.call(_a, file);
|
|
172
250
|
(_c = this.onStoppedCallback) == null ? void 0 : _c.call(this, file);
|
|
@@ -204,16 +282,33 @@ class ScreenRecordingController {
|
|
|
204
282
|
}
|
|
205
283
|
const SESSION_REPLAY_BLOCK_CLASS = "rr-block";
|
|
206
284
|
const SESSION_REPLAY_MASK_CLASS = "rr-mask";
|
|
207
|
-
const
|
|
208
|
-
|
|
285
|
+
const FOREIGN_BLOCK_SELECTORS = [
|
|
286
|
+
".fs-exclude",
|
|
287
|
+
".sentry-block",
|
|
288
|
+
".sentry-ignore",
|
|
289
|
+
".ph-no-capture",
|
|
290
|
+
"[data-hj-suppress]"
|
|
291
|
+
];
|
|
292
|
+
const FOREIGN_MASK_SELECTORS = [".fs-mask", ".sentry-mask"];
|
|
293
|
+
const SESSION_REPLAY_FULL_MASK_SELECTOR = [
|
|
294
|
+
`.${SESSION_REPLAY_MASK_CLASS}`,
|
|
295
|
+
...FOREIGN_MASK_SELECTORS
|
|
296
|
+
].join(",");
|
|
297
|
+
const SESSION_REPLAY_BLOCK_SELECTOR = [
|
|
298
|
+
`.${SESSION_REPLAY_BLOCK_CLASS}`,
|
|
299
|
+
...FOREIGN_BLOCK_SELECTORS
|
|
300
|
+
].join(",");
|
|
301
|
+
const DEFAULT_PRIVACY_CONFIG_V2 = {
|
|
302
|
+
version: "v2",
|
|
209
303
|
maskAllInputs: true,
|
|
210
|
-
maskTextSelector:
|
|
211
|
-
|
|
304
|
+
maskTextSelector: "*",
|
|
305
|
+
fullMaskSelector: SESSION_REPLAY_FULL_MASK_SELECTOR,
|
|
306
|
+
blockSelector: SESSION_REPLAY_BLOCK_SELECTOR,
|
|
212
307
|
allowCanvasRecording: false
|
|
213
308
|
};
|
|
214
309
|
function resolvePrivacyConfig(override) {
|
|
215
|
-
if (!override) return
|
|
216
|
-
return { ...
|
|
310
|
+
if (!override) return DEFAULT_PRIVACY_CONFIG_V2;
|
|
311
|
+
return { ...DEFAULT_PRIVACY_CONFIG_V2, ...override };
|
|
217
312
|
}
|
|
218
313
|
function createSegments() {
|
|
219
314
|
return [{ events: [] }];
|
|
@@ -244,12 +339,15 @@ function trimSegments(segments, windowMs, now) {
|
|
|
244
339
|
function flattenEvents(segments) {
|
|
245
340
|
return segments.flatMap((s2) => s2.events);
|
|
246
341
|
}
|
|
342
|
+
function dropEventsAfter(segments, cutoff) {
|
|
343
|
+
return segments.map((s2) => ({ events: s2.events.filter((e2) => e2.timestamp <= cutoff) })).filter((s2) => s2.events.length > 0);
|
|
344
|
+
}
|
|
247
345
|
async function sha256Hex(bytes) {
|
|
248
346
|
const digest = await crypto.subtle.digest("SHA-256", bytes.buffer);
|
|
249
347
|
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
250
348
|
}
|
|
251
349
|
async function serializeSnapshot(input) {
|
|
252
|
-
const events = flattenEvents(input.segments);
|
|
350
|
+
const events = (input.redactEvents ?? ((e2) => e2))(flattenEvents(input.segments));
|
|
253
351
|
const json = JSON.stringify(events);
|
|
254
352
|
const payload = gzip_1(json);
|
|
255
353
|
const checksumSha256 = await sha256Hex(payload);
|
|
@@ -263,57 +361,876 @@ async function serializeSnapshot(input) {
|
|
|
263
361
|
privacyConfigVersion: input.privacyConfigVersion
|
|
264
362
|
};
|
|
265
363
|
}
|
|
364
|
+
const REDACTED_HEADER_VALUE = "[redacted]";
|
|
365
|
+
const SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
366
|
+
"authorization",
|
|
367
|
+
"proxy-authorization",
|
|
368
|
+
"cookie",
|
|
369
|
+
"set-cookie",
|
|
370
|
+
"x-api-key",
|
|
371
|
+
"api-key",
|
|
372
|
+
"apikey",
|
|
373
|
+
"x-auth-token",
|
|
374
|
+
"x-access-token",
|
|
375
|
+
"x-refresh-token",
|
|
376
|
+
"x-session-token",
|
|
377
|
+
"x-csrf-token",
|
|
378
|
+
"x-xsrf-token",
|
|
379
|
+
"x-amz-security-token",
|
|
380
|
+
"x-goog-api-key"
|
|
381
|
+
]);
|
|
382
|
+
function isSensitiveHeaderName(headerName) {
|
|
383
|
+
return SENSITIVE_HEADER_NAMES.has(headerName.toLowerCase());
|
|
384
|
+
}
|
|
385
|
+
function redactSensitiveHeaders(headers) {
|
|
386
|
+
if (!headers) return headers;
|
|
387
|
+
let changed = false;
|
|
388
|
+
const redacted = {};
|
|
389
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
390
|
+
if (value && isSensitiveHeaderName(name)) {
|
|
391
|
+
redacted[name] = REDACTED_HEADER_VALUE;
|
|
392
|
+
changed = true;
|
|
393
|
+
} else {
|
|
394
|
+
redacted[name] = value;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return changed ? redacted : headers;
|
|
398
|
+
}
|
|
399
|
+
const REDACTED_BODY_VALUE = "[redacted]";
|
|
400
|
+
const SENSITIVE_KEY_MARKERS = [
|
|
401
|
+
"password",
|
|
402
|
+
"passwd",
|
|
403
|
+
"pwd",
|
|
404
|
+
"secret",
|
|
405
|
+
"token",
|
|
406
|
+
"apikey",
|
|
407
|
+
"privatekey",
|
|
408
|
+
"credential",
|
|
409
|
+
// 결제·본인확인 계열. 토스페이먼츠 공식 문서의 필드명을 그대로 딴다.
|
|
410
|
+
// cardNumber / customerIdentityNumber / businessNumber / accountNumber(가상계좌)
|
|
411
|
+
"cardnumber",
|
|
412
|
+
"identitynumber",
|
|
413
|
+
"businessnumber",
|
|
414
|
+
"registrationnumber",
|
|
415
|
+
"accountnumber"
|
|
416
|
+
];
|
|
417
|
+
const SENSITIVE_KEY_EXACT = /* @__PURE__ */ new Set([
|
|
418
|
+
"otp",
|
|
419
|
+
"auth",
|
|
420
|
+
"authorization",
|
|
421
|
+
"bearer",
|
|
422
|
+
// 토스페이먼츠. billingKey + customerKey 조합이면 결제를 낼 수 있고, authKey 는 일회성
|
|
423
|
+
// 인증 키다(우리 admin 결제 성공 URL 의 쿼리 파라미터로 실제로 실린다).
|
|
424
|
+
// paymentKey 는 넣지 않는다 — 시크릿 키 없이는 아무것도 못 하는 식별자이고, 결제 장애
|
|
425
|
+
// 진단의 출발점이라 가리면 로그가 쓸모를 잃는다.
|
|
426
|
+
"billingkey",
|
|
427
|
+
"customerkey",
|
|
428
|
+
"authkey"
|
|
429
|
+
]);
|
|
430
|
+
const MAX_WALK_DEPTH = 20;
|
|
431
|
+
function isSensitiveFieldName(name) {
|
|
432
|
+
const normalized = name.toLowerCase().replace(/[-_\s]/g, "");
|
|
433
|
+
if (SENSITIVE_KEY_EXACT.has(normalized)) {
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
return SENSITIVE_KEY_MARKERS.some((marker) => normalized.includes(marker));
|
|
437
|
+
}
|
|
438
|
+
function redactJsonValue(value, depth) {
|
|
439
|
+
if (typeof value === "string") {
|
|
440
|
+
const redacted = redactSensitiveValues(value);
|
|
441
|
+
return { value: redacted, changed: redacted !== value };
|
|
442
|
+
}
|
|
443
|
+
if (depth > MAX_WALK_DEPTH || value === null || typeof value !== "object") {
|
|
444
|
+
return { value, changed: false };
|
|
445
|
+
}
|
|
446
|
+
if (Array.isArray(value)) {
|
|
447
|
+
let changed2 = false;
|
|
448
|
+
const next2 = value.map((item) => {
|
|
449
|
+
const result = redactJsonValue(item, depth + 1);
|
|
450
|
+
changed2 || (changed2 = result.changed);
|
|
451
|
+
return result.value;
|
|
452
|
+
});
|
|
453
|
+
return changed2 ? { value: next2, changed: changed2 } : { value, changed: changed2 };
|
|
454
|
+
}
|
|
455
|
+
let changed = false;
|
|
456
|
+
const next = {};
|
|
457
|
+
for (const [key, item] of Object.entries(value)) {
|
|
458
|
+
if (item !== "" && item != null && typeof item !== "boolean" && isSensitiveFieldName(key)) {
|
|
459
|
+
next[key] = REDACTED_BODY_VALUE;
|
|
460
|
+
changed = true;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
const result = redactJsonValue(item, depth + 1);
|
|
464
|
+
changed || (changed = result.changed);
|
|
465
|
+
next[key] = result.value;
|
|
466
|
+
}
|
|
467
|
+
return changed ? { value: next, changed } : { value, changed };
|
|
468
|
+
}
|
|
469
|
+
function redactKeyValuePairs(body) {
|
|
470
|
+
let changed = false;
|
|
471
|
+
const redacted = body.split("&").map((pair) => {
|
|
472
|
+
const separator = pair.indexOf("=");
|
|
473
|
+
if (separator <= 0) {
|
|
474
|
+
return pair;
|
|
475
|
+
}
|
|
476
|
+
const rawKey = pair.slice(0, separator);
|
|
477
|
+
const rawValue = pair.slice(separator + 1);
|
|
478
|
+
if (!rawValue) {
|
|
479
|
+
return pair;
|
|
480
|
+
}
|
|
481
|
+
if (isSensitiveFieldName(decodeURIComponent(rawKey))) {
|
|
482
|
+
changed = true;
|
|
483
|
+
return `${rawKey}=${encodeURIComponent(REDACTED_BODY_VALUE)}`;
|
|
484
|
+
}
|
|
485
|
+
const redactedValue = redactSensitiveValues(decodeURIComponent(rawValue));
|
|
486
|
+
if (redactedValue === decodeURIComponent(rawValue)) {
|
|
487
|
+
return pair;
|
|
488
|
+
}
|
|
489
|
+
changed = true;
|
|
490
|
+
return `${rawKey}=${encodeURIComponent(redactedValue)}`;
|
|
491
|
+
}).join("&");
|
|
492
|
+
return changed ? redacted : body;
|
|
493
|
+
}
|
|
494
|
+
function looksLikeFormUrlEncoded(body) {
|
|
495
|
+
return /^[^=&\s]+=[^&]*(&[^=&\s]+=[^&]*)*$/.test(body);
|
|
496
|
+
}
|
|
497
|
+
function redactSensitiveBody(body) {
|
|
498
|
+
if (!body) return body;
|
|
499
|
+
const trimmed = body.trim();
|
|
500
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
501
|
+
try {
|
|
502
|
+
const parsed = JSON.parse(trimmed);
|
|
503
|
+
const { value, changed } = redactJsonValue(parsed, 0);
|
|
504
|
+
return changed ? JSON.stringify(value) : body;
|
|
505
|
+
} catch {
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (looksLikeFormUrlEncoded(trimmed)) {
|
|
509
|
+
const redacted = redactKeyValuePairs(trimmed);
|
|
510
|
+
return redacted === trimmed ? body : redacted;
|
|
511
|
+
}
|
|
512
|
+
return body;
|
|
513
|
+
}
|
|
514
|
+
const JWT_PATTERN = /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]*/g;
|
|
515
|
+
const BEARER_PATTERN = /(Bearer\s+)[A-Za-z0-9._~+/-]{12,}=*/gi;
|
|
516
|
+
const CARD_CANDIDATE_PATTERN = /\b[2-6](?:[ -]?\d){13,18}\b/g;
|
|
517
|
+
const RRN_CANDIDATE_PATTERN = /\b(\d{2})(\d{2})(\d{2})-?([1-8])(\d{6})\b/g;
|
|
518
|
+
const BRN_CANDIDATE_PATTERN = /\b(\d{3})-?(\d{2})-?(\d{5})\b/g;
|
|
519
|
+
const BRN_WEIGHTS = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
|
520
|
+
const KR_MOBILE_PATTERN = /\b01[016789]-?\d{3,4}-?\d{4}\b/g;
|
|
521
|
+
function passesBrnChecksum(digits) {
|
|
522
|
+
let sum = 0;
|
|
523
|
+
for (let i2 = 0; i2 < 9; i2 += 1) {
|
|
524
|
+
sum += (digits.charCodeAt(i2) - 48) * BRN_WEIGHTS[i2];
|
|
525
|
+
}
|
|
526
|
+
sum += Math.floor((digits.charCodeAt(8) - 48) * 5 / 10);
|
|
527
|
+
return (10 - sum % 10) % 10 === digits.charCodeAt(9) - 48;
|
|
528
|
+
}
|
|
529
|
+
function passesLuhn(digits) {
|
|
530
|
+
let sum = 0;
|
|
531
|
+
let double = false;
|
|
532
|
+
for (let i2 = digits.length - 1; i2 >= 0; i2 -= 1) {
|
|
533
|
+
let digit = digits.charCodeAt(i2) - 48;
|
|
534
|
+
if (double) {
|
|
535
|
+
digit *= 2;
|
|
536
|
+
if (digit > 9) digit -= 9;
|
|
537
|
+
}
|
|
538
|
+
sum += digit;
|
|
539
|
+
double = !double;
|
|
540
|
+
}
|
|
541
|
+
return sum % 10 === 0;
|
|
542
|
+
}
|
|
543
|
+
function looksLikeBirthDate(month, day) {
|
|
544
|
+
const monthValue = Number(month);
|
|
545
|
+
const dayValue = Number(day);
|
|
546
|
+
return monthValue >= 1 && monthValue <= 12 && dayValue >= 1 && dayValue <= 31;
|
|
547
|
+
}
|
|
548
|
+
function redactSensitiveValues(text) {
|
|
549
|
+
if (!text) return text;
|
|
550
|
+
let out = text;
|
|
551
|
+
out = out.replace(JWT_PATTERN, REDACTED_BODY_VALUE);
|
|
552
|
+
out = out.replace(BEARER_PATTERN, `$1${REDACTED_BODY_VALUE}`);
|
|
553
|
+
out = out.replace(CARD_CANDIDATE_PATTERN, (match2) => {
|
|
554
|
+
const digits = match2.replace(/[ -]/g, "");
|
|
555
|
+
return digits.length >= 14 && digits.length <= 19 && passesLuhn(digits) ? REDACTED_BODY_VALUE : match2;
|
|
556
|
+
});
|
|
557
|
+
out = out.replace(
|
|
558
|
+
RRN_CANDIDATE_PATTERN,
|
|
559
|
+
(match2, _year, month, day) => looksLikeBirthDate(month, day) ? REDACTED_BODY_VALUE : match2
|
|
560
|
+
);
|
|
561
|
+
out = out.replace(BRN_CANDIDATE_PATTERN, (match2) => {
|
|
562
|
+
const digits = match2.replace(/-/g, "");
|
|
563
|
+
return passesBrnChecksum(digits) ? REDACTED_BODY_VALUE : match2;
|
|
564
|
+
});
|
|
565
|
+
out = out.replace(KR_MOBILE_PATTERN, REDACTED_BODY_VALUE);
|
|
566
|
+
return out === text ? text : out;
|
|
567
|
+
}
|
|
568
|
+
function redactUrlSecrets(url) {
|
|
569
|
+
if (!url) return url;
|
|
570
|
+
const queryStart = url.indexOf("?");
|
|
571
|
+
const fragmentStart = url.indexOf("#");
|
|
572
|
+
if (queryStart < 0 && fragmentStart < 0) {
|
|
573
|
+
return redactSensitiveValues(url);
|
|
574
|
+
}
|
|
575
|
+
const hasQuery = queryStart >= 0 && (fragmentStart < 0 || queryStart < fragmentStart);
|
|
576
|
+
const base = url.slice(0, hasQuery ? queryStart : fragmentStart);
|
|
577
|
+
const query = hasQuery ? url.slice(queryStart + 1, fragmentStart >= 0 ? fragmentStart : void 0) : "";
|
|
578
|
+
const fragment = fragmentStart >= 0 ? url.slice(fragmentStart + 1) : "";
|
|
579
|
+
const redactedQuery = redactKeyValuePairs(query);
|
|
580
|
+
const redactedFragment = fragment.includes("=") ? redactKeyValuePairs(fragment) : fragment;
|
|
581
|
+
const redactedBase = redactSensitiveValues(base);
|
|
582
|
+
if (redactedBase === base && redactedQuery === query && redactedFragment === fragment) {
|
|
583
|
+
return url;
|
|
584
|
+
}
|
|
585
|
+
return redactedBase + (hasQuery ? `?${redactedQuery}` : "") + (fragmentStart >= 0 ? `#${redactedFragment}` : "");
|
|
586
|
+
}
|
|
587
|
+
function redactSensitiveText(text) {
|
|
588
|
+
if (!text) return text;
|
|
589
|
+
return redactSensitiveValues(redactUrlSecrets(redactSensitiveBody(text)));
|
|
590
|
+
}
|
|
591
|
+
const META_EVENT_TYPE = 4;
|
|
592
|
+
const FULL_SNAPSHOT_EVENT_TYPE = 2;
|
|
593
|
+
const INCREMENTAL_SNAPSHOT_EVENT_TYPE = 3;
|
|
594
|
+
const MUTATION_SOURCE = 0;
|
|
595
|
+
const URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "poster", "data", "cite"]);
|
|
596
|
+
function isRecord(value) {
|
|
597
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
598
|
+
}
|
|
599
|
+
function redactAttributeValue(name, value) {
|
|
600
|
+
if (typeof value !== "string" || !value) return value;
|
|
601
|
+
return URL_ATTRIBUTES.has(name.toLowerCase()) ? redactUrlSecrets(value) : redactSensitiveValues(value);
|
|
602
|
+
}
|
|
603
|
+
function redactAttributes(attributes) {
|
|
604
|
+
if (!isRecord(attributes)) return;
|
|
605
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
606
|
+
const redacted = redactAttributeValue(name, value);
|
|
607
|
+
if (redacted !== value) {
|
|
608
|
+
attributes[name] = redacted;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function redactSerializedNode(node) {
|
|
613
|
+
if (!isRecord(node)) return;
|
|
614
|
+
redactAttributes(node.attributes);
|
|
615
|
+
if (typeof node.textContent === "string" && node.textContent) {
|
|
616
|
+
const redacted = redactSensitiveValues(node.textContent);
|
|
617
|
+
if (redacted !== node.textContent) {
|
|
618
|
+
node.textContent = redacted;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (Array.isArray(node.childNodes)) {
|
|
622
|
+
for (const child of node.childNodes) {
|
|
623
|
+
redactSerializedNode(child);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
function redactMutationData(data) {
|
|
628
|
+
if (Array.isArray(data.adds)) {
|
|
629
|
+
for (const add of data.adds) {
|
|
630
|
+
if (isRecord(add)) redactSerializedNode(add.node);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (Array.isArray(data.attributes)) {
|
|
634
|
+
for (const mutation of data.attributes) {
|
|
635
|
+
if (isRecord(mutation)) redactAttributes(mutation.attributes);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (Array.isArray(data.texts)) {
|
|
639
|
+
for (const mutation of data.texts) {
|
|
640
|
+
if (isRecord(mutation) && typeof mutation.value === "string" && mutation.value) {
|
|
641
|
+
const redacted = redactSensitiveValues(mutation.value);
|
|
642
|
+
if (redacted !== mutation.value) {
|
|
643
|
+
mutation.value = redacted;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function redactReplayEvents(events) {
|
|
650
|
+
for (const event of events) {
|
|
651
|
+
const data = event.data;
|
|
652
|
+
if (!isRecord(data)) continue;
|
|
653
|
+
if (event.type === META_EVENT_TYPE && typeof data.href === "string") {
|
|
654
|
+
data.href = redactUrlSecrets(data.href);
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (event.type === FULL_SNAPSHOT_EVENT_TYPE) {
|
|
658
|
+
redactSerializedNode(data.node);
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (event.type === INCREMENTAL_SNAPSHOT_EVENT_TYPE && data.source === MUTATION_SOURCE) {
|
|
662
|
+
redactMutationData(data);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return events;
|
|
666
|
+
}
|
|
266
667
|
const RRWEB_VERSION = "2.0.0";
|
|
267
668
|
const SESSION_REPLAY_WINDOW_MS = 12e4;
|
|
268
669
|
const SESSION_REPLAY_CHECKOUT_MS = 6e4;
|
|
670
|
+
const REPLAY_MASK_VALUE = "[redacted]";
|
|
671
|
+
function maskReplayInput(text) {
|
|
672
|
+
return text ? REPLAY_MASK_VALUE : text;
|
|
673
|
+
}
|
|
674
|
+
function createMaskTextFn(fullMaskSelector) {
|
|
675
|
+
return (text, element) => {
|
|
676
|
+
try {
|
|
677
|
+
if (fullMaskSelector && (element == null ? void 0 : element.closest(fullMaskSelector))) {
|
|
678
|
+
return REPLAY_MASK_VALUE;
|
|
679
|
+
}
|
|
680
|
+
return redactSensitiveValues(text);
|
|
681
|
+
} catch {
|
|
682
|
+
return REPLAY_MASK_VALUE;
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
const IDLE_CHECKOUT_POLL_MS = 5e3;
|
|
269
687
|
class RingBufferRecorder {
|
|
270
688
|
constructor(options = {}) {
|
|
271
689
|
this.options = options;
|
|
272
690
|
this.segments = createSegments();
|
|
273
691
|
this.stopFn = null;
|
|
692
|
+
this.idleCheckoutTimer = null;
|
|
693
|
+
this.tailCutoff = null;
|
|
274
694
|
}
|
|
275
695
|
get windowMs() {
|
|
276
696
|
return this.options.windowMs ?? SESSION_REPLAY_WINDOW_MS;
|
|
277
697
|
}
|
|
698
|
+
get nowFn() {
|
|
699
|
+
return this.options.now ?? (() => Date.now());
|
|
700
|
+
}
|
|
701
|
+
get checkoutEveryMs() {
|
|
702
|
+
return this.options.checkoutEveryNms ?? SESSION_REPLAY_CHECKOUT_MS;
|
|
703
|
+
}
|
|
704
|
+
/** 마지막 segment 가 시작한 시각. 비어 있으면 null. */
|
|
705
|
+
get lastSegmentStart() {
|
|
706
|
+
var _a;
|
|
707
|
+
const last = this.segments[this.segments.length - 1];
|
|
708
|
+
return ((_a = last == null ? void 0 : last.events[0]) == null ? void 0 : _a.timestamp) ?? null;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* 유휴가 길어 경계가 없으면 FullSnapshot 을 강제로 찍는다.
|
|
712
|
+
*
|
|
713
|
+
* 숨은 탭에서는 건너뛴다 — 보이지도 않는 페이지의 DOM 을 주기적으로 직렬화할 이유가 없고,
|
|
714
|
+
* 다시 보이게 되면 그때 다음 검사에서 찍힌다.
|
|
715
|
+
*/
|
|
716
|
+
forceCheckoutIfIdle() {
|
|
717
|
+
var _a;
|
|
718
|
+
if (typeof document !== "undefined" && document.hidden) return;
|
|
719
|
+
const start = this.lastSegmentStart;
|
|
720
|
+
if (start === null) return;
|
|
721
|
+
if (this.nowFn() - start < this.checkoutEveryMs) return;
|
|
722
|
+
const recordFn = this.options.recordFn ?? record;
|
|
723
|
+
(_a = recordFn.takeFullSnapshot) == null ? void 0 : _a.call(recordFn, true);
|
|
724
|
+
}
|
|
278
725
|
start() {
|
|
279
726
|
if (this.stopFn) return;
|
|
280
727
|
const cfg = resolvePrivacyConfig(this.options.privacyConfig);
|
|
281
728
|
const recordFn = this.options.recordFn ?? record;
|
|
282
|
-
const now = this.
|
|
729
|
+
const now = this.nowFn;
|
|
283
730
|
this.segments = createSegments();
|
|
284
731
|
const stop = recordFn({
|
|
285
732
|
emit: (event, isCheckout) => {
|
|
733
|
+
var _a, _b;
|
|
286
734
|
this.segments = appendEvent(this.segments, event, Boolean(isCheckout));
|
|
287
735
|
this.segments = trimSegments(this.segments, this.windowMs, now());
|
|
736
|
+
(_b = (_a = this.options).onChange) == null ? void 0 : _b.call(_a);
|
|
288
737
|
},
|
|
289
738
|
checkoutEveryNms: this.options.checkoutEveryNms ?? SESSION_REPLAY_CHECKOUT_MS,
|
|
290
739
|
maskAllInputs: cfg.maskAllInputs,
|
|
291
740
|
blockSelector: cfg.blockSelector,
|
|
292
741
|
maskTextSelector: cfg.maskTextSelector,
|
|
742
|
+
maskTextFn: createMaskTextFn(cfg.fullMaskSelector),
|
|
743
|
+
maskInputFn: maskReplayInput,
|
|
293
744
|
recordCanvas: cfg.allowCanvasRecording
|
|
294
745
|
// rrweb 옵션 타입이 버전별로 달라 캐스팅한다.
|
|
295
746
|
});
|
|
296
747
|
this.stopFn = typeof stop === "function" ? stop : null;
|
|
748
|
+
this.idleCheckoutTimer = setInterval(() => this.forceCheckoutIfIdle(), IDLE_CHECKOUT_POLL_MS);
|
|
297
749
|
}
|
|
298
750
|
stop() {
|
|
299
751
|
var _a;
|
|
300
752
|
(_a = this.stopFn) == null ? void 0 : _a.call(this);
|
|
301
753
|
this.stopFn = null;
|
|
754
|
+
if (this.idleCheckoutTimer !== null) {
|
|
755
|
+
clearInterval(this.idleCheckoutTimer);
|
|
756
|
+
this.idleCheckoutTimer = null;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* 이슈 생성 단계의 시작을 표시한다. 이 시각 이후는 스냅샷에서 빠진다.
|
|
761
|
+
*
|
|
762
|
+
* 녹화를 멈추지 않고 스냅샷에서 잘라내는 이유는, 멈췄다 다시 켜면 그 사이 mutation 이
|
|
763
|
+
* 빠져 이후 이벤트가 어긋난 DOM 에 적용되기 때문이다. 폼을 닫고 계속 쓰는 경우가 있으므로
|
|
764
|
+
* 되돌릴 수 있어야 한다.
|
|
765
|
+
*/
|
|
766
|
+
markTailCutoff(timestamp = this.nowFn()) {
|
|
767
|
+
this.tailCutoff = timestamp;
|
|
768
|
+
}
|
|
769
|
+
/** 이슈 폼이 제출 없이 닫혔을 때. 다시 전체 구간을 담는다. */
|
|
770
|
+
clearTailCutoff() {
|
|
771
|
+
this.tailCutoff = null;
|
|
302
772
|
}
|
|
303
773
|
isRecording() {
|
|
304
774
|
return this.stopFn !== null;
|
|
305
775
|
}
|
|
776
|
+
/** 백업이 읽어갈 현재 버퍼. */
|
|
777
|
+
getSegments() {
|
|
778
|
+
return this.segments;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* 이전 문서의 segment 를 버퍼 앞에 되돌린다.
|
|
782
|
+
*
|
|
783
|
+
* **`start()` 뒤에 불러야 한다** — `start()` 는 버퍼를 새로 만들기 때문에 순서가 바뀌면
|
|
784
|
+
* 되돌린 것이 그대로 지워진다.
|
|
785
|
+
*
|
|
786
|
+
* 되돌린 뒤에도 윈도 규칙은 그대로 적용된다. "최근 120초" 라는 정의는 바뀌지 않고,
|
|
787
|
+
* 문서 경계에서 시계가 0 으로 되돌아가지 않게 되는 것뿐이다. 이전 문서에 오래 머물렀다면
|
|
788
|
+
* 그 앞부분은 여기서 잘려 나가는 게 맞다.
|
|
789
|
+
*/
|
|
790
|
+
restore(segments) {
|
|
791
|
+
var _a, _b;
|
|
792
|
+
if (segments.length === 0) return;
|
|
793
|
+
this.segments = trimSegments([...segments, ...this.segments], this.windowMs, this.nowFn());
|
|
794
|
+
(_b = (_a = this.options).onChange) == null ? void 0 : _b.call(_a);
|
|
795
|
+
}
|
|
306
796
|
/** 현재 버퍼를 업로드 가능한 스냅샷으로 직렬화한다. */
|
|
307
797
|
async snapshot() {
|
|
308
798
|
const cfg = resolvePrivacyConfig(this.options.privacyConfig);
|
|
799
|
+
const segments = this.tailCutoff === null ? this.segments : dropEventsAfter(this.segments, this.tailCutoff);
|
|
309
800
|
return serializeSnapshot({
|
|
310
|
-
segments
|
|
801
|
+
segments,
|
|
802
|
+
redactEvents: redactReplayEvents,
|
|
311
803
|
windowSeconds: Math.round(this.windowMs / 1e3),
|
|
312
804
|
rrwebVersion: RRWEB_VERSION,
|
|
313
805
|
privacyConfigVersion: cfg.version
|
|
314
806
|
});
|
|
315
807
|
}
|
|
316
808
|
}
|
|
809
|
+
const LOG_BACKUP_SESSION_KEY = "__issueStickerLogSession";
|
|
810
|
+
const DB_NAME$1 = "issue-sticker-logs";
|
|
811
|
+
const DB_VERSION$1 = 1;
|
|
812
|
+
const STORE_NAME$1 = "sessions";
|
|
813
|
+
const LOG_BACKUP_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
814
|
+
const LOG_BACKUP_BUDGET_BYTES = 8 * 1024 * 1024;
|
|
815
|
+
function payloadSizeBytes(payload) {
|
|
816
|
+
try {
|
|
817
|
+
return JSON.stringify(payload).length;
|
|
818
|
+
} catch {
|
|
819
|
+
return Number.POSITIVE_INFINITY;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function withoutBody(log) {
|
|
823
|
+
if (log.responseBody === void 0 && log.requestBody === void 0) return log;
|
|
824
|
+
const stripped = { ...log };
|
|
825
|
+
if (stripped.responseBody !== void 0) {
|
|
826
|
+
delete stripped.responseBody;
|
|
827
|
+
delete stripped.responseBodyEncoding;
|
|
828
|
+
stripped.responseBodyOmittedReason = "transport-budget";
|
|
829
|
+
}
|
|
830
|
+
if (stripped.requestBody !== void 0) {
|
|
831
|
+
delete stripped.requestBody;
|
|
832
|
+
stripped.requestBodyOmittedReason = "transport-budget";
|
|
833
|
+
}
|
|
834
|
+
return stripped;
|
|
835
|
+
}
|
|
836
|
+
function trimLogBackupPayload(payload, budgetBytes = LOG_BACKUP_BUDGET_BYTES) {
|
|
837
|
+
if (payloadSizeBytes(payload) <= budgetBytes) return payload;
|
|
838
|
+
let trimmed = {
|
|
839
|
+
network: payload.network.map(withoutBody),
|
|
840
|
+
console: payload.console
|
|
841
|
+
};
|
|
842
|
+
while (payloadSizeBytes(trimmed) > budgetBytes && trimmed.network.length > 0) {
|
|
843
|
+
trimmed = { network: trimmed.network.slice(1), console: trimmed.console };
|
|
844
|
+
}
|
|
845
|
+
while (payloadSizeBytes(trimmed) > budgetBytes && trimmed.console.length > 0) {
|
|
846
|
+
trimmed = { network: trimmed.network, console: trimmed.console.slice(1) };
|
|
847
|
+
}
|
|
848
|
+
return trimmed;
|
|
849
|
+
}
|
|
850
|
+
function requestToPromise$1(request) {
|
|
851
|
+
return new Promise((resolve, reject) => {
|
|
852
|
+
request.onsuccess = () => resolve(request.result);
|
|
853
|
+
request.onerror = () => reject(request.error);
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
function openDatabase$1() {
|
|
857
|
+
return new Promise((resolve, reject) => {
|
|
858
|
+
const request = indexedDB.open(DB_NAME$1, DB_VERSION$1);
|
|
859
|
+
request.onupgradeneeded = () => {
|
|
860
|
+
if (!request.result.objectStoreNames.contains(STORE_NAME$1)) {
|
|
861
|
+
request.result.createObjectStore(STORE_NAME$1, { keyPath: "sessionId" });
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
request.onsuccess = () => {
|
|
865
|
+
const db = request.result;
|
|
866
|
+
db.onversionchange = () => db.close();
|
|
867
|
+
resolve(db);
|
|
868
|
+
};
|
|
869
|
+
request.onerror = () => reject(request.error);
|
|
870
|
+
request.onblocked = () => reject(new Error("indexedDB open blocked"));
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
function createIndexedDbLogBackup() {
|
|
874
|
+
return {
|
|
875
|
+
async load(sessionId) {
|
|
876
|
+
const db = await openDatabase$1();
|
|
877
|
+
try {
|
|
878
|
+
const record2 = await requestToPromise$1(
|
|
879
|
+
db.transaction(STORE_NAME$1, "readonly").objectStore(STORE_NAME$1).get(sessionId)
|
|
880
|
+
);
|
|
881
|
+
if (!record2) return null;
|
|
882
|
+
if (record2.expiresAt <= Date.now()) return null;
|
|
883
|
+
return record2.payload;
|
|
884
|
+
} finally {
|
|
885
|
+
db.close();
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
async save(sessionId, payload) {
|
|
889
|
+
const db = await openDatabase$1();
|
|
890
|
+
try {
|
|
891
|
+
const record2 = {
|
|
892
|
+
sessionId,
|
|
893
|
+
payload,
|
|
894
|
+
expiresAt: Date.now() + LOG_BACKUP_TTL_MS
|
|
895
|
+
};
|
|
896
|
+
await requestToPromise$1(
|
|
897
|
+
db.transaction(STORE_NAME$1, "readwrite").objectStore(STORE_NAME$1).put(record2)
|
|
898
|
+
);
|
|
899
|
+
} finally {
|
|
900
|
+
db.close();
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
async prune() {
|
|
904
|
+
const db = await openDatabase$1();
|
|
905
|
+
try {
|
|
906
|
+
const store = db.transaction(STORE_NAME$1, "readwrite").objectStore(STORE_NAME$1);
|
|
907
|
+
const records = await requestToPromise$1(store.getAll());
|
|
908
|
+
const now = Date.now();
|
|
909
|
+
const expired = records.filter((record2) => record2.expiresAt <= now);
|
|
910
|
+
await Promise.all(
|
|
911
|
+
expired.map((record2) => requestToPromise$1(store.delete(record2.sessionId)))
|
|
912
|
+
);
|
|
913
|
+
return expired.length;
|
|
914
|
+
} finally {
|
|
915
|
+
db.close();
|
|
916
|
+
}
|
|
917
|
+
},
|
|
918
|
+
async clear(sessionId) {
|
|
919
|
+
const db = await openDatabase$1();
|
|
920
|
+
try {
|
|
921
|
+
await requestToPromise$1(
|
|
922
|
+
db.transaction(STORE_NAME$1, "readwrite").objectStore(STORE_NAME$1).delete(sessionId)
|
|
923
|
+
);
|
|
924
|
+
} finally {
|
|
925
|
+
db.close();
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
function createSessionId() {
|
|
931
|
+
var _a, _b;
|
|
932
|
+
const randomUUID = (_b = (_a = globalThis.crypto) == null ? void 0 : _a.randomUUID) == null ? void 0 : _b.bind(globalThis.crypto);
|
|
933
|
+
if (randomUUID) return randomUUID();
|
|
934
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
935
|
+
}
|
|
936
|
+
let fallbackSessionId = null;
|
|
937
|
+
function getLogBackupSessionId() {
|
|
938
|
+
try {
|
|
939
|
+
const stored = window.sessionStorage.getItem(LOG_BACKUP_SESSION_KEY);
|
|
940
|
+
if (stored) return stored;
|
|
941
|
+
const created = fallbackSessionId ?? createSessionId();
|
|
942
|
+
window.sessionStorage.setItem(LOG_BACKUP_SESSION_KEY, created);
|
|
943
|
+
return created;
|
|
944
|
+
} catch {
|
|
945
|
+
fallbackSessionId ?? (fallbackSessionId = createSessionId());
|
|
946
|
+
return fallbackSessionId;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function createSafeLogBackup(store) {
|
|
950
|
+
let disabled = false;
|
|
951
|
+
function disable() {
|
|
952
|
+
disabled = true;
|
|
953
|
+
}
|
|
954
|
+
return {
|
|
955
|
+
async load(sessionId) {
|
|
956
|
+
if (disabled) return null;
|
|
957
|
+
try {
|
|
958
|
+
return await store.load(sessionId);
|
|
959
|
+
} catch {
|
|
960
|
+
disable();
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
async save(sessionId, payload) {
|
|
965
|
+
if (disabled) return;
|
|
966
|
+
try {
|
|
967
|
+
await store.save(sessionId, payload);
|
|
968
|
+
} catch {
|
|
969
|
+
disable();
|
|
970
|
+
}
|
|
971
|
+
},
|
|
972
|
+
async clear(sessionId) {
|
|
973
|
+
if (disabled) return;
|
|
974
|
+
try {
|
|
975
|
+
await store.clear(sessionId);
|
|
976
|
+
} catch {
|
|
977
|
+
disable();
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
async prune() {
|
|
981
|
+
if (disabled) return 0;
|
|
982
|
+
try {
|
|
983
|
+
return await store.prune();
|
|
984
|
+
} catch {
|
|
985
|
+
disable();
|
|
986
|
+
return 0;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
const DB_NAME = "issue-sticker-replay";
|
|
992
|
+
const DB_VERSION = 1;
|
|
993
|
+
const STORE_NAME = "segments";
|
|
994
|
+
const REPLAY_BACKUP_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
995
|
+
const REPLAY_BACKUP_EVENT_BUDGET = 12e3;
|
|
996
|
+
function countEvents(segments) {
|
|
997
|
+
return segments.reduce((total, segment) => total + segment.events.length, 0);
|
|
998
|
+
}
|
|
999
|
+
function trimReplayBackup(segments, eventBudget = REPLAY_BACKUP_EVENT_BUDGET) {
|
|
1000
|
+
let trimmed = segments;
|
|
1001
|
+
while (trimmed.length > 1 && countEvents(trimmed) > eventBudget) {
|
|
1002
|
+
trimmed = trimmed.slice(1);
|
|
1003
|
+
}
|
|
1004
|
+
return trimmed;
|
|
1005
|
+
}
|
|
1006
|
+
function keepPlayableSegments(segments) {
|
|
1007
|
+
if (!Array.isArray(segments)) return [];
|
|
1008
|
+
return segments.filter((segment) => {
|
|
1009
|
+
if (!segment || typeof segment !== "object") return false;
|
|
1010
|
+
const events = segment.events;
|
|
1011
|
+
if (!Array.isArray(events) || events.length === 0) return false;
|
|
1012
|
+
return events.every(isReplayEvent) && hasFullSnapshot(events);
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
const FULL_SNAPSHOT_TYPE = 2;
|
|
1016
|
+
function isReplayEvent(value) {
|
|
1017
|
+
if (!value || typeof value !== "object") return false;
|
|
1018
|
+
const event = value;
|
|
1019
|
+
return typeof event.type === "number" && typeof event.timestamp === "number";
|
|
1020
|
+
}
|
|
1021
|
+
function hasFullSnapshot(events) {
|
|
1022
|
+
return events.some((event) => event.type === FULL_SNAPSHOT_TYPE);
|
|
1023
|
+
}
|
|
1024
|
+
function requestToPromise(request) {
|
|
1025
|
+
return new Promise((resolve, reject) => {
|
|
1026
|
+
request.onsuccess = () => resolve(request.result);
|
|
1027
|
+
request.onerror = () => reject(request.error);
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
function openDatabase() {
|
|
1031
|
+
return new Promise((resolve, reject) => {
|
|
1032
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
1033
|
+
request.onupgradeneeded = () => {
|
|
1034
|
+
if (!request.result.objectStoreNames.contains(STORE_NAME)) {
|
|
1035
|
+
request.result.createObjectStore(STORE_NAME, { keyPath: "sessionId" });
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
request.onsuccess = () => {
|
|
1039
|
+
const db = request.result;
|
|
1040
|
+
db.onversionchange = () => db.close();
|
|
1041
|
+
resolve(db);
|
|
1042
|
+
};
|
|
1043
|
+
request.onerror = () => reject(request.error);
|
|
1044
|
+
request.onblocked = () => reject(new Error("indexedDB open blocked"));
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
function createIndexedDbReplayBackup() {
|
|
1048
|
+
return {
|
|
1049
|
+
async load(sessionId) {
|
|
1050
|
+
const db = await openDatabase();
|
|
1051
|
+
try {
|
|
1052
|
+
const record2 = await requestToPromise(
|
|
1053
|
+
db.transaction(STORE_NAME, "readonly").objectStore(STORE_NAME).get(sessionId)
|
|
1054
|
+
);
|
|
1055
|
+
if (!record2) return null;
|
|
1056
|
+
if (record2.expiresAt <= Date.now()) return null;
|
|
1057
|
+
return keepPlayableSegments(record2.segments);
|
|
1058
|
+
} finally {
|
|
1059
|
+
db.close();
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
async save(sessionId, segments) {
|
|
1063
|
+
const db = await openDatabase();
|
|
1064
|
+
try {
|
|
1065
|
+
const record2 = {
|
|
1066
|
+
sessionId,
|
|
1067
|
+
segments: trimReplayBackup(segments),
|
|
1068
|
+
expiresAt: Date.now() + REPLAY_BACKUP_TTL_MS
|
|
1069
|
+
};
|
|
1070
|
+
await requestToPromise(
|
|
1071
|
+
db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).put(record2)
|
|
1072
|
+
);
|
|
1073
|
+
} finally {
|
|
1074
|
+
db.close();
|
|
1075
|
+
}
|
|
1076
|
+
},
|
|
1077
|
+
async prune() {
|
|
1078
|
+
const db = await openDatabase();
|
|
1079
|
+
try {
|
|
1080
|
+
const store = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME);
|
|
1081
|
+
const records = await requestToPromise(store.getAll());
|
|
1082
|
+
const now = Date.now();
|
|
1083
|
+
const expired = records.filter((record2) => record2.expiresAt <= now);
|
|
1084
|
+
await Promise.all(
|
|
1085
|
+
expired.map((record2) => requestToPromise(store.delete(record2.sessionId)))
|
|
1086
|
+
);
|
|
1087
|
+
return expired.length;
|
|
1088
|
+
} finally {
|
|
1089
|
+
db.close();
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
async clear(sessionId) {
|
|
1093
|
+
const db = await openDatabase();
|
|
1094
|
+
try {
|
|
1095
|
+
await requestToPromise(
|
|
1096
|
+
db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).delete(sessionId)
|
|
1097
|
+
);
|
|
1098
|
+
} finally {
|
|
1099
|
+
db.close();
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
function createSafeReplayBackup(store) {
|
|
1105
|
+
let disabled = false;
|
|
1106
|
+
return {
|
|
1107
|
+
async load(sessionId) {
|
|
1108
|
+
if (disabled) return null;
|
|
1109
|
+
try {
|
|
1110
|
+
return await store.load(sessionId);
|
|
1111
|
+
} catch {
|
|
1112
|
+
disabled = true;
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
},
|
|
1116
|
+
async save(sessionId, segments) {
|
|
1117
|
+
if (disabled) return;
|
|
1118
|
+
try {
|
|
1119
|
+
await store.save(sessionId, segments);
|
|
1120
|
+
} catch {
|
|
1121
|
+
disabled = true;
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
async clear(sessionId) {
|
|
1125
|
+
if (disabled) return;
|
|
1126
|
+
try {
|
|
1127
|
+
await store.clear(sessionId);
|
|
1128
|
+
} catch {
|
|
1129
|
+
disabled = true;
|
|
1130
|
+
}
|
|
1131
|
+
},
|
|
1132
|
+
async prune() {
|
|
1133
|
+
if (disabled) return 0;
|
|
1134
|
+
try {
|
|
1135
|
+
return await store.prune();
|
|
1136
|
+
} catch {
|
|
1137
|
+
disabled = true;
|
|
1138
|
+
return 0;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
const DEFAULT_DEBOUNCE_MS = 3e3;
|
|
1144
|
+
function bindReplayBackup(options) {
|
|
1145
|
+
const { store, sessionId, snapshot, restoreInto } = options;
|
|
1146
|
+
const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
1147
|
+
let timer = null;
|
|
1148
|
+
let stopped = false;
|
|
1149
|
+
function clearTimer() {
|
|
1150
|
+
if (timer !== null) {
|
|
1151
|
+
clearTimeout(timer);
|
|
1152
|
+
timer = null;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
async function flush() {
|
|
1156
|
+
clearTimer();
|
|
1157
|
+
if (stopped) return;
|
|
1158
|
+
await store.save(sessionId, snapshot());
|
|
1159
|
+
}
|
|
1160
|
+
function handlePageHiding() {
|
|
1161
|
+
if (typeof document !== "undefined" && document.visibilityState === "visible") return;
|
|
1162
|
+
void flush();
|
|
1163
|
+
}
|
|
1164
|
+
if (typeof document !== "undefined") {
|
|
1165
|
+
document.addEventListener("visibilitychange", handlePageHiding);
|
|
1166
|
+
}
|
|
1167
|
+
if (typeof window !== "undefined") {
|
|
1168
|
+
window.addEventListener("pagehide", handlePageHiding);
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
async restore() {
|
|
1172
|
+
const stored = await store.load(sessionId);
|
|
1173
|
+
if (stored && stored.length > 0) restoreInto(stored);
|
|
1174
|
+
},
|
|
1175
|
+
schedule() {
|
|
1176
|
+
if (stopped || timer !== null) return;
|
|
1177
|
+
timer = setTimeout(() => {
|
|
1178
|
+
timer = null;
|
|
1179
|
+
void flush();
|
|
1180
|
+
}, debounceMs);
|
|
1181
|
+
},
|
|
1182
|
+
flush,
|
|
1183
|
+
stop() {
|
|
1184
|
+
stopped = true;
|
|
1185
|
+
clearTimer();
|
|
1186
|
+
if (typeof document !== "undefined") {
|
|
1187
|
+
document.removeEventListener("visibilitychange", handlePageHiding);
|
|
1188
|
+
}
|
|
1189
|
+
if (typeof window !== "undefined") {
|
|
1190
|
+
window.removeEventListener("pagehide", handlePageHiding);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
function createReplaySession(options = {}) {
|
|
1196
|
+
const store = options.store ?? createSafeReplayBackup(createIndexedDbReplayBackup());
|
|
1197
|
+
const sessionId = options.sessionId ?? getLogBackupSessionId();
|
|
1198
|
+
const scheduleBackup = () => {
|
|
1199
|
+
backup.schedule();
|
|
1200
|
+
};
|
|
1201
|
+
const recorder = options.recorder ?? new RingBufferRecorder({
|
|
1202
|
+
privacyConfig: options.privacyConfig,
|
|
1203
|
+
onChange: scheduleBackup
|
|
1204
|
+
});
|
|
1205
|
+
const backup = bindReplayBackup({
|
|
1206
|
+
store,
|
|
1207
|
+
sessionId,
|
|
1208
|
+
snapshot: () => recorder.getSegments(),
|
|
1209
|
+
restoreInto: (segments) => recorder.restore(segments)
|
|
1210
|
+
});
|
|
1211
|
+
return {
|
|
1212
|
+
recorder,
|
|
1213
|
+
backup,
|
|
1214
|
+
start() {
|
|
1215
|
+
recorder.start();
|
|
1216
|
+
void backup.restore();
|
|
1217
|
+
void store.prune();
|
|
1218
|
+
},
|
|
1219
|
+
stop() {
|
|
1220
|
+
recorder.stop();
|
|
1221
|
+
backup.stop();
|
|
1222
|
+
},
|
|
1223
|
+
snapshot() {
|
|
1224
|
+
return recorder.snapshot();
|
|
1225
|
+
},
|
|
1226
|
+
markReportStart() {
|
|
1227
|
+
recorder.markTailCutoff();
|
|
1228
|
+
},
|
|
1229
|
+
clearReportStart() {
|
|
1230
|
+
recorder.clearTailCutoff();
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
317
1234
|
const STORAGE_KEY = "issue-sticker-session-replay-attach:v1";
|
|
318
1235
|
function getReplayAttachPreference() {
|
|
319
1236
|
try {
|
|
@@ -398,6 +1315,16 @@ class SessionReplayCaptureController {
|
|
|
398
1315
|
setReplayAttachPreference(enabled);
|
|
399
1316
|
this.setSnapshot({ enabled });
|
|
400
1317
|
}
|
|
1318
|
+
/** 이슈 폼이 열렸다. 이후 구간은 리플레이에서 빠진다. */
|
|
1319
|
+
markReportStart() {
|
|
1320
|
+
var _a;
|
|
1321
|
+
(_a = this.recorder) == null ? void 0 : _a.markReportStart();
|
|
1322
|
+
}
|
|
1323
|
+
/** 이슈 폼이 제출 없이 닫혔다. */
|
|
1324
|
+
clearReportStart() {
|
|
1325
|
+
var _a;
|
|
1326
|
+
(_a = this.recorder) == null ? void 0 : _a.clearReportStart();
|
|
1327
|
+
}
|
|
401
1328
|
async takeSnapshot() {
|
|
402
1329
|
if (!this.recorder) return null;
|
|
403
1330
|
return this.recorder.snapshot();
|
|
@@ -408,7 +1335,7 @@ class SessionReplayCaptureController {
|
|
|
408
1335
|
this.listeners.clear();
|
|
409
1336
|
}
|
|
410
1337
|
createRecorder(privacyConfig) {
|
|
411
|
-
return this.options.createRecorder ? this.options.createRecorder(privacyConfig) :
|
|
1338
|
+
return this.options.createRecorder ? this.options.createRecorder(privacyConfig) : createReplaySession({ privacyConfig });
|
|
412
1339
|
}
|
|
413
1340
|
stopRecorder() {
|
|
414
1341
|
var _a;
|
|
@@ -2588,623 +3515,1427 @@ async function uploadScreenshotDataUrls(screenshotDataUrls, uploadSignedMedia, i
|
|
|
2588
3515
|
}
|
|
2589
3516
|
throw new Error("스크린샷 업로드 응답에 URL이 없습니다.");
|
|
2590
3517
|
}
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
NetworkLogType2["STYLESHEET"] = "stylesheet";
|
|
2595
|
-
NetworkLogType2["SCRIPT"] = "script";
|
|
2596
|
-
NetworkLogType2["IMAGE"] = "image";
|
|
2597
|
-
NetworkLogType2["FONT"] = "font";
|
|
2598
|
-
NetworkLogType2["XMLHTTPREQUEST"] = "xmlhttprequest";
|
|
2599
|
-
NetworkLogType2["FETCH"] = "fetch";
|
|
2600
|
-
NetworkLogType2["WEBSOCKET"] = "websocket";
|
|
2601
|
-
NetworkLogType2["MEDIA"] = "media";
|
|
2602
|
-
NetworkLogType2["OTHER"] = "other";
|
|
2603
|
-
return NetworkLogType2;
|
|
2604
|
-
})(NetworkLogType || {});
|
|
2605
|
-
const EXTERNAL_INTEGRATION_SERVICES = [
|
|
2606
|
-
"jira",
|
|
2607
|
-
"notion",
|
|
2608
|
-
"custom-field"
|
|
2609
|
-
];
|
|
2610
|
-
const FAILED_ACTION_TEXT = {
|
|
2611
|
-
jira: "Jira 이슈 생성에 실패했어요.",
|
|
2612
|
-
notion: "Notion 페이지 생성에 실패했어요.",
|
|
2613
|
-
"custom-field": "커스텀 필드 저장에 실패했어요."
|
|
2614
|
-
};
|
|
2615
|
-
function getIntegrationFailureMessage(service) {
|
|
2616
|
-
const action = FAILED_ACTION_TEXT[service] ?? "연동 작업에 실패했어요.";
|
|
2617
|
-
return `이슈는 저장됐어요. 다만 ${action}`;
|
|
3518
|
+
const ISSUE_STICKER_INTERNAL_LOG_PREFIX = "__ISSUE_STICKER_INTERNAL_DEBUG__";
|
|
3519
|
+
function isInternalDebugLog$1(message2) {
|
|
3520
|
+
return Boolean(message2 == null ? void 0 : message2.includes(ISSUE_STICKER_INTERNAL_LOG_PREFIX));
|
|
2618
3521
|
}
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
fixVersion: "fixVersions",
|
|
2622
|
-
assignee_external_user_id: "assignee",
|
|
2623
|
-
reporter_external_user_id: "reporter",
|
|
2624
|
-
issue_type: "issuetype"
|
|
2625
|
-
};
|
|
2626
|
-
const JIRA_FIELD_LABELS = {
|
|
2627
|
-
status: "상태",
|
|
2628
|
-
priority: "우선순위",
|
|
2629
|
-
assignee: "담당자",
|
|
2630
|
-
reporter: "보고자",
|
|
2631
|
-
fixVersions: "수정 버전",
|
|
2632
|
-
parent: "상위 항목",
|
|
2633
|
-
issuetype: "이슈 유형",
|
|
2634
|
-
components: "컴포넌트",
|
|
2635
|
-
labels: "라벨",
|
|
2636
|
-
sprint_id: "스프린트 ID",
|
|
2637
|
-
sprint_name: "스프린트",
|
|
2638
|
-
epic_link: "에픽",
|
|
2639
|
-
story_points: "스토리 포인트"
|
|
2640
|
-
};
|
|
2641
|
-
const READONLY_SCHEMA_TYPES = /* @__PURE__ */ new Set(["array", "object"]);
|
|
2642
|
-
const READONLY_FIELD_IDS = /* @__PURE__ */ new Set(["description", "project_key", "reporter"]);
|
|
2643
|
-
const DYNAMIC_USER_FIELD_IDS = /* @__PURE__ */ new Set(["assignee", "reporter"]);
|
|
2644
|
-
const CREATE_UNSUPPORTED_FIELD_IDS = /* @__PURE__ */ new Set(["status"]);
|
|
2645
|
-
const OPTION_SCHEMA_CUSTOM_TYPES = [
|
|
2646
|
-
":select",
|
|
2647
|
-
":radiobuttons",
|
|
2648
|
-
":multiselect",
|
|
2649
|
-
":checkboxes",
|
|
2650
|
-
":multicheckboxes"
|
|
2651
|
-
];
|
|
2652
|
-
function normalizeJiraFieldId(fieldId) {
|
|
2653
|
-
return JIRA_FIELD_ALIASES[fieldId] || fieldId;
|
|
3522
|
+
function isIssueStickerInternalConsoleLog$1(log) {
|
|
3523
|
+
return isInternalDebugLog$1(log.message);
|
|
2654
3524
|
}
|
|
2655
|
-
function
|
|
2656
|
-
if (
|
|
2657
|
-
return
|
|
3525
|
+
function getCurrentPageUrl() {
|
|
3526
|
+
if (typeof window === "undefined") {
|
|
3527
|
+
return "";
|
|
2658
3528
|
}
|
|
2659
|
-
return
|
|
2660
|
-
}
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
3529
|
+
return redactUrlSecrets(window.location.href);
|
|
3530
|
+
}
|
|
3531
|
+
const NETWORK_LOG_CLIENT_BUFFER_SIZE = 200;
|
|
3532
|
+
const NETWORK_LOG_SUBMIT_LIMIT = 50;
|
|
3533
|
+
const CONSOLE_LOG_SUBMIT_LIMIT = 50;
|
|
3534
|
+
const CONSOLE_LOG_CLIENT_BUFFER_SIZE = 200;
|
|
3535
|
+
const NETWORK_LOG_TIMING_SUBMIT_CAP = 15;
|
|
3536
|
+
const RESOURCE_REQUEST_TYPES = /* @__PURE__ */ new Set([
|
|
3537
|
+
"img",
|
|
3538
|
+
"image",
|
|
3539
|
+
"script",
|
|
3540
|
+
"link",
|
|
3541
|
+
"stylesheet",
|
|
3542
|
+
"css",
|
|
3543
|
+
"iframe",
|
|
3544
|
+
"sub_frame",
|
|
3545
|
+
"font",
|
|
3546
|
+
"media",
|
|
3547
|
+
"ping",
|
|
3548
|
+
"beacon",
|
|
3549
|
+
"csp_report",
|
|
3550
|
+
"resource"
|
|
3551
|
+
]);
|
|
3552
|
+
function isAppRequest(log) {
|
|
3553
|
+
if (log.detailLevel === "timing-only") return false;
|
|
3554
|
+
return !RESOURCE_REQUEST_TYPES.has((log.type ?? "").toLowerCase());
|
|
3555
|
+
}
|
|
3556
|
+
function selectNetworkLogsForSubmit(logs) {
|
|
3557
|
+
const detailed = logs.filter(isAppRequest);
|
|
3558
|
+
const timing = logs.filter((log) => !isAppRequest(log));
|
|
3559
|
+
const keptDetailed = detailed.slice(-NETWORK_LOG_SUBMIT_LIMIT);
|
|
3560
|
+
const timingQuota = Math.min(
|
|
3561
|
+
NETWORK_LOG_TIMING_SUBMIT_CAP,
|
|
3562
|
+
NETWORK_LOG_SUBMIT_LIMIT - keptDetailed.length
|
|
3563
|
+
);
|
|
3564
|
+
const failedTiming = timing.filter((log) => log.statusCode >= 400);
|
|
3565
|
+
const restTiming = timing.filter((log) => log.statusCode < 400);
|
|
3566
|
+
const keptTiming = [
|
|
3567
|
+
...failedTiming.slice(-timingQuota),
|
|
3568
|
+
...restTiming.slice(-Math.max(0, timingQuota - failedTiming.length))
|
|
3569
|
+
];
|
|
3570
|
+
return [...keptDetailed, ...keptTiming].sort((a2, b) => a2.timestamp - b.timestamp);
|
|
2664
3571
|
}
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
const normalized = normalizeJiraFieldId(fieldId);
|
|
2670
|
-
return normalizeExposedJiraFields(exposedFields).includes(normalized);
|
|
3572
|
+
const CONSOLE_METHODS = ["log", "warn", "error", "info", "debug"];
|
|
3573
|
+
function methodToLogType(method) {
|
|
3574
|
+
if (method === "warn") return "warning";
|
|
3575
|
+
return method;
|
|
2671
3576
|
}
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
3577
|
+
const MAX_SERIALIZE_DEPTH = 4;
|
|
3578
|
+
function isSensitiveValue(key, value) {
|
|
3579
|
+
return value !== "" && value != null && typeof value !== "boolean" && isSensitiveFieldName(key);
|
|
3580
|
+
}
|
|
3581
|
+
function serializeValue(value, depth, seen) {
|
|
3582
|
+
if (value === null) return "null";
|
|
3583
|
+
if (typeof value === "string") return JSON.stringify(redactSensitiveText(value));
|
|
3584
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
3585
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
3586
|
+
if (typeof value === "undefined") return "undefined";
|
|
3587
|
+
if (typeof value === "function") return "[Function]";
|
|
3588
|
+
if (typeof value === "symbol") return value.toString();
|
|
3589
|
+
if (value instanceof Error) return value.stack || value.message;
|
|
3590
|
+
const object = value;
|
|
3591
|
+
if (seen.has(object)) return '"[Circular]"';
|
|
3592
|
+
if (depth >= MAX_SERIALIZE_DEPTH) return '"[Depth]"';
|
|
3593
|
+
seen.add(object);
|
|
3594
|
+
try {
|
|
3595
|
+
if (Array.isArray(value)) {
|
|
3596
|
+
return `[${value.map((item) => serializeValue(item, depth + 1, seen)).join(",")}]`;
|
|
3597
|
+
}
|
|
3598
|
+
const entries = [];
|
|
3599
|
+
for (const key of Object.keys(object)) {
|
|
3600
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
3601
|
+
if (!descriptor) continue;
|
|
3602
|
+
const serialized = descriptor.get ? '"[Getter]"' : isSensitiveValue(key, descriptor.value) ? `"${REDACTED_BODY_VALUE}"` : serializeValue(descriptor.value, depth + 1, seen);
|
|
3603
|
+
entries.push(`${JSON.stringify(key)}:${serialized}`);
|
|
3604
|
+
}
|
|
3605
|
+
return `{${entries.join(",")}}`;
|
|
3606
|
+
} finally {
|
|
3607
|
+
seen.delete(object);
|
|
2676
3608
|
}
|
|
2677
|
-
return raw ?? null;
|
|
2678
3609
|
}
|
|
2679
|
-
function
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
3610
|
+
function serializeArgs(args) {
|
|
3611
|
+
return args.map((a2) => {
|
|
3612
|
+
if (a2 instanceof Error) return redactSensitiveText(a2.stack || a2.message);
|
|
3613
|
+
if (typeof a2 === "object" && a2 !== null) {
|
|
3614
|
+
try {
|
|
3615
|
+
return serializeValue(a2, 0, /* @__PURE__ */ new WeakSet());
|
|
3616
|
+
} catch {
|
|
3617
|
+
return redactSensitiveText(String(a2));
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
return redactSensitiveText(String(a2));
|
|
3621
|
+
}).join(" ");
|
|
2685
3622
|
}
|
|
2686
|
-
function
|
|
2687
|
-
|
|
2688
|
-
|
|
3623
|
+
function extractStackTrace(args) {
|
|
3624
|
+
for (const arg of args) {
|
|
3625
|
+
if (arg instanceof Error && arg.stack) {
|
|
3626
|
+
return arg.stack;
|
|
3627
|
+
}
|
|
2689
3628
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
}
|
|
2697
|
-
return String(item);
|
|
2698
|
-
}).filter(Boolean).join(", ");
|
|
3629
|
+
return void 0;
|
|
3630
|
+
}
|
|
3631
|
+
function captureSourceStackTrace() {
|
|
3632
|
+
const stack = new Error().stack;
|
|
3633
|
+
if (!stack) {
|
|
3634
|
+
return void 0;
|
|
2699
3635
|
}
|
|
2700
|
-
|
|
2701
|
-
return
|
|
2702
|
-
|
|
2703
|
-
|
|
3636
|
+
const lines = stack.split("\n").slice(1).filter((line) => {
|
|
3637
|
+
return !line.includes("captureConsoleStackTrace") && !line.includes("ConsoleCollector");
|
|
3638
|
+
});
|
|
3639
|
+
if (lines.length === 0) {
|
|
3640
|
+
return void 0;
|
|
2704
3641
|
}
|
|
2705
|
-
return
|
|
3642
|
+
return lines.join("\n");
|
|
2706
3643
|
}
|
|
2707
|
-
function
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
3644
|
+
function captureConsoleStackTrace(args) {
|
|
3645
|
+
const sourceStack = captureSourceStackTrace();
|
|
3646
|
+
const errorStack = extractStackTrace(args);
|
|
3647
|
+
if (sourceStack && errorStack && !sourceStack.includes(errorStack)) {
|
|
3648
|
+
return `${sourceStack}
|
|
3649
|
+
Caused by:
|
|
3650
|
+
${errorStack}`;
|
|
2714
3651
|
}
|
|
2715
|
-
return
|
|
3652
|
+
return sourceStack ?? errorStack;
|
|
2716
3653
|
}
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
3654
|
+
class ConsoleCollector {
|
|
3655
|
+
constructor(options) {
|
|
3656
|
+
this.logs = [];
|
|
3657
|
+
this.originals = {};
|
|
3658
|
+
this.running = false;
|
|
3659
|
+
this.onChange = (options == null ? void 0 : options.onChange) ?? null;
|
|
2721
3660
|
}
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
case "components":
|
|
2740
|
-
return meta.components;
|
|
2741
|
-
case "labels":
|
|
2742
|
-
return meta.labels;
|
|
2743
|
-
case "sprint_id":
|
|
2744
|
-
return meta.sprint_id;
|
|
2745
|
-
case "sprint_name":
|
|
2746
|
-
return meta.sprint_name;
|
|
2747
|
-
case "epic_link":
|
|
2748
|
-
return meta.epic_link;
|
|
2749
|
-
case "story_points":
|
|
2750
|
-
return meta.story_points;
|
|
2751
|
-
default:
|
|
2752
|
-
return (_b = meta.custom_fields) == null ? void 0 : _b[fieldId];
|
|
3661
|
+
start() {
|
|
3662
|
+
if (this.running) return;
|
|
3663
|
+
this.running = true;
|
|
3664
|
+
for (const method of CONSOLE_METHODS) {
|
|
3665
|
+
this.originals[method] = console[method];
|
|
3666
|
+
console[method] = (...args) => {
|
|
3667
|
+
var _a;
|
|
3668
|
+
this.addLog({
|
|
3669
|
+
type: methodToLogType(method),
|
|
3670
|
+
message: serializeArgs(args),
|
|
3671
|
+
timestamp: Date.now(),
|
|
3672
|
+
url: getCurrentPageUrl() || void 0,
|
|
3673
|
+
stackTrace: redactSensitiveText(captureConsoleStackTrace(args))
|
|
3674
|
+
});
|
|
3675
|
+
(_a = this.originals[method]) == null ? void 0 : _a.apply(console, args);
|
|
3676
|
+
};
|
|
3677
|
+
}
|
|
2753
3678
|
}
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
}
|
|
2762
|
-
|
|
2763
|
-
return option.name || option.value || option.id;
|
|
2764
|
-
}
|
|
2765
|
-
function getJiraFieldDisplayLabelKey(fieldId, value) {
|
|
2766
|
-
return `${normalizeJiraFieldId(fieldId)}:${value}`;
|
|
2767
|
-
}
|
|
2768
|
-
function getStoredValueId(value) {
|
|
2769
|
-
if (value === null || value === void 0 || value === "") {
|
|
2770
|
-
return "";
|
|
3679
|
+
stop() {
|
|
3680
|
+
if (!this.running) return;
|
|
3681
|
+
this.running = false;
|
|
3682
|
+
for (const method of CONSOLE_METHODS) {
|
|
3683
|
+
if (this.originals[method]) {
|
|
3684
|
+
console[method] = this.originals[method];
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
this.originals = {};
|
|
2771
3688
|
}
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
3689
|
+
/** 리로드 이전 로그를 버퍼 앞에 되돌린다. */
|
|
3690
|
+
restore(logs) {
|
|
3691
|
+
if (logs.length === 0) return;
|
|
3692
|
+
this.logs = [...logs, ...this.logs].slice(-CONSOLE_LOG_CLIENT_BUFFER_SIZE);
|
|
2775
3693
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
function findMatchingOption(value, options) {
|
|
2779
|
-
if (!options.length) {
|
|
2780
|
-
return void 0;
|
|
3694
|
+
getLogs() {
|
|
3695
|
+
return [...this.logs];
|
|
2781
3696
|
}
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
3697
|
+
/**
|
|
3698
|
+
* 우리 로그를 걷어낸 보관 로그 전체. NetworkCollector 와 같은 계약이다.
|
|
3699
|
+
*
|
|
3700
|
+
* 내부 디버그 로그는 `addLog` 에서 이미 떨어져 나가므로 여기서 더 걸러낼 것이 없다.
|
|
3701
|
+
* **건수를 여기서 제출 상한까지 자르면 안 된다** — 소비자가 서비스 관련 로그를 한 번 더
|
|
3702
|
+
* 골라낸 뒤 마지막에 잘라야, 그 노이즈가 차지한 자리 때문에 진짜 로그가 사라지지 않는다.
|
|
3703
|
+
*/
|
|
3704
|
+
getFilteredLogs() {
|
|
3705
|
+
return this.getLogs();
|
|
2789
3706
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
function getDisplayValue(fieldId, value, options, displayLabels) {
|
|
2793
|
-
const normalizedFieldId = normalizeJiraFieldId(fieldId);
|
|
2794
|
-
if (normalizedFieldId === "parent") {
|
|
2795
|
-
return getJiraIssueKey(value) || "-";
|
|
3707
|
+
clear() {
|
|
3708
|
+
this.logs = [];
|
|
2796
3709
|
}
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
return getOptionLabel(matched);
|
|
3710
|
+
addLog(log) {
|
|
3711
|
+
var _a;
|
|
3712
|
+
if (isIssueStickerInternalConsoleLog$1(log)) {
|
|
3713
|
+
return;
|
|
2802
3714
|
}
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
3715
|
+
this.logs.push(log);
|
|
3716
|
+
if (this.logs.length > CONSOLE_LOG_CLIENT_BUFFER_SIZE) {
|
|
3717
|
+
this.logs = this.logs.slice(-CONSOLE_LOG_CLIENT_BUFFER_SIZE);
|
|
3718
|
+
}
|
|
3719
|
+
(_a = this.onChange) == null ? void 0 : _a.call(this);
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
const NETWORK_LOG_IMAGE_BODY_LIMIT_BYTES = 64 * 1024;
|
|
3723
|
+
const BODY_DENIED_MIME_PREFIXES = ["font/", "video/", "audio/", "model/", "multipart/"];
|
|
3724
|
+
const STREAMING_MIME_TYPES = /* @__PURE__ */ new Set(["text/event-stream", "application/x-ndjson"]);
|
|
3725
|
+
const BODY_DENIED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
3726
|
+
"application/wasm",
|
|
3727
|
+
"application/font-woff",
|
|
3728
|
+
"application/font-woff2",
|
|
3729
|
+
"application/x-font-ttf",
|
|
3730
|
+
"application/x-font-otf",
|
|
3731
|
+
"application/vnd.ms-fontobject",
|
|
3732
|
+
"application/pdf",
|
|
3733
|
+
"application/zip",
|
|
3734
|
+
"application/gzip",
|
|
3735
|
+
"application/x-gzip",
|
|
3736
|
+
"application/x-tar"
|
|
3737
|
+
]);
|
|
3738
|
+
const OCTET_STREAM_MIME = "application/octet-stream";
|
|
3739
|
+
const OCTET_STREAM_BODY_LIMIT_BYTES = 32 * 1024;
|
|
3740
|
+
function isImageMimeType(mimeType) {
|
|
3741
|
+
return Boolean(mimeType == null ? void 0 : mimeType.startsWith("image/"));
|
|
2806
3742
|
}
|
|
2807
|
-
function
|
|
2808
|
-
if (
|
|
2809
|
-
|
|
2810
|
-
}
|
|
2811
|
-
const stringValue = stringifyJiraValue(value);
|
|
2812
|
-
if (!options.length) {
|
|
2813
|
-
return stringValue;
|
|
2814
|
-
}
|
|
2815
|
-
const matched = findMatchingOption(value, options);
|
|
2816
|
-
if (!matched) {
|
|
2817
|
-
return "";
|
|
2818
|
-
}
|
|
2819
|
-
return matched.id;
|
|
3743
|
+
function isBodyDeniedMimeType(mimeType) {
|
|
3744
|
+
if (!mimeType) return false;
|
|
3745
|
+
return BODY_DENIED_MIME_PREFIXES.some((prefix) => mimeType.startsWith(prefix)) || BODY_DENIED_MIME_TYPES.has(mimeType) || STREAMING_MIME_TYPES.has(mimeType);
|
|
2820
3746
|
}
|
|
2821
|
-
function
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
if (
|
|
2825
|
-
|
|
2826
|
-
}
|
|
2827
|
-
if (normalizedFieldId === "assignee") {
|
|
2828
|
-
return "user";
|
|
2829
|
-
}
|
|
2830
|
-
if (normalizedFieldId === "parent") {
|
|
2831
|
-
return "issue";
|
|
2832
|
-
}
|
|
2833
|
-
if (normalizedFieldId === "fixVersions") {
|
|
2834
|
-
return "select";
|
|
2835
|
-
}
|
|
2836
|
-
if (options.length > 0) {
|
|
2837
|
-
return "select";
|
|
2838
|
-
}
|
|
2839
|
-
const schemaType = (_a = fieldDefinition == null ? void 0 : fieldDefinition.schema) == null ? void 0 : _a.type;
|
|
2840
|
-
const schemaCustom = (_b = fieldDefinition == null ? void 0 : fieldDefinition.schema) == null ? void 0 : _b.custom;
|
|
2841
|
-
if (schemaType === "option" || OPTION_SCHEMA_CUSTOM_TYPES.some((marker) => schemaCustom == null ? void 0 : schemaCustom.includes(marker))) {
|
|
2842
|
-
return "readonly";
|
|
2843
|
-
}
|
|
2844
|
-
if (schemaType && READONLY_SCHEMA_TYPES.has(schemaType)) {
|
|
2845
|
-
return "readonly";
|
|
2846
|
-
}
|
|
2847
|
-
if (schemaType === "number") {
|
|
2848
|
-
return "number";
|
|
2849
|
-
}
|
|
2850
|
-
return "text";
|
|
3747
|
+
function getResponseBodyLimitBytes(mimeType) {
|
|
3748
|
+
if (isBodyDeniedMimeType(mimeType)) return 0;
|
|
3749
|
+
if (isImageMimeType(mimeType)) return NETWORK_LOG_IMAGE_BODY_LIMIT_BYTES;
|
|
3750
|
+
if (mimeType === OCTET_STREAM_MIME) return OCTET_STREAM_BODY_LIMIT_BYTES;
|
|
3751
|
+
return NETWORK_LOG_BODY_CAPTURE_LIMIT_BYTES;
|
|
2851
3752
|
}
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
3753
|
+
const NETWORK_LOG_BODY_CAPTURE_LIMIT_BYTES = 256 * 1024;
|
|
3754
|
+
const NETWORK_LOG_TRANSPORT_BODY_BUDGET_BYTES = 2 * 1024 * 1024;
|
|
3755
|
+
function getUtf8ByteLength(value) {
|
|
3756
|
+
let bytes = 0;
|
|
3757
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
3758
|
+
const codePoint = value.codePointAt(index) ?? 0;
|
|
3759
|
+
if (codePoint > 65535) {
|
|
3760
|
+
index += 1;
|
|
3761
|
+
}
|
|
3762
|
+
if (codePoint <= 127) {
|
|
3763
|
+
bytes += 1;
|
|
3764
|
+
} else if (codePoint <= 2047) {
|
|
3765
|
+
bytes += 2;
|
|
3766
|
+
} else if (codePoint <= 65535) {
|
|
3767
|
+
bytes += 3;
|
|
3768
|
+
} else {
|
|
3769
|
+
bytes += 4;
|
|
3770
|
+
}
|
|
2855
3771
|
}
|
|
2856
|
-
return
|
|
3772
|
+
return bytes;
|
|
2857
3773
|
}
|
|
2858
|
-
function
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
values,
|
|
2864
|
-
includeReadonly = true
|
|
2865
|
-
}) {
|
|
2866
|
-
const fields = normalizeExposedJiraFields(exposedFields).map((fieldId) => {
|
|
2867
|
-
const fieldDefinition = findFieldDefinition(fieldId, fieldDefinitions);
|
|
2868
|
-
const value = resolveFieldValue(fieldId, metadata, values);
|
|
2869
|
-
const options = (fieldDefinition == null ? void 0 : fieldDefinition.options) ?? [];
|
|
2870
|
-
const inputType = getInputType(fieldId, fieldDefinition, options);
|
|
2871
|
-
return {
|
|
2872
|
-
id: fieldId,
|
|
2873
|
-
label: getFieldLabel(fieldId, fieldDefinition),
|
|
2874
|
-
value,
|
|
2875
|
-
displayValue: getDisplayValue(fieldId, value, options, displayLabels),
|
|
2876
|
-
editValue: getEditValue(fieldId, value, options),
|
|
2877
|
-
options,
|
|
2878
|
-
editable: inputType !== "readonly",
|
|
2879
|
-
inputType
|
|
2880
|
-
};
|
|
2881
|
-
});
|
|
2882
|
-
if (includeReadonly) {
|
|
2883
|
-
return fields;
|
|
2884
|
-
}
|
|
2885
|
-
return fields.filter((field) => field.editable);
|
|
3774
|
+
function estimateBase64DecodedBytes(value) {
|
|
3775
|
+
const compact = value.replace(/^data:[^,]*,/i, "").replace(/\s/g, "");
|
|
3776
|
+
if (!compact) return 0;
|
|
3777
|
+
const padding = compact.endsWith("==") ? 2 : compact.endsWith("=") ? 1 : 0;
|
|
3778
|
+
return Math.max(0, Math.floor(compact.length * 3 / 4) - padding);
|
|
2886
3779
|
}
|
|
2887
|
-
function
|
|
2888
|
-
return
|
|
2889
|
-
exposedFields: mapping == null ? void 0 : mapping.exposed_fields,
|
|
2890
|
-
fieldDefinitions,
|
|
2891
|
-
metadata: getJiraMetadata(mapping),
|
|
2892
|
-
displayLabels
|
|
2893
|
-
});
|
|
3780
|
+
function getCapturedBodySizeBytes(body, encoding) {
|
|
3781
|
+
return encoding === "base64" ? estimateBase64DecodedBytes(body) : getUtf8ByteLength(body);
|
|
2894
3782
|
}
|
|
2895
|
-
function
|
|
2896
|
-
return
|
|
2897
|
-
exposedFields,
|
|
2898
|
-
fieldDefinitions,
|
|
2899
|
-
values,
|
|
2900
|
-
includeReadonly: false
|
|
2901
|
-
}).filter((field) => !CREATE_UNSUPPORTED_FIELD_IDS.has(normalizeJiraFieldId(field.id)));
|
|
3783
|
+
function isBodyWithinCaptureLimit(sizeBytes) {
|
|
3784
|
+
return sizeBytes <= NETWORK_LOG_BODY_CAPTURE_LIMIT_BYTES;
|
|
2902
3785
|
}
|
|
2903
|
-
|
|
2904
|
-
"
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
"aria-label"
|
|
2910
|
-
];
|
|
2911
|
-
const STATE_CLASS_RE = /^(is-|has-|js-)|^(active|open|selected|disabled|focus|hover|show|hidden|loading|dragging)$/i;
|
|
2912
|
-
function looksHashed(segment) {
|
|
2913
|
-
if (segment.includes("-")) return false;
|
|
2914
|
-
if (/^[a-f0-9]{6,}$/i.test(segment)) return true;
|
|
2915
|
-
if (segment.length >= 6 && /[a-z]/i.test(segment) && /[0-9]/.test(segment)) return true;
|
|
2916
|
-
if (segment.length >= 5 && !/[_-]/.test(segment) && /[a-z]/.test(segment) && /[A-Z]/.test(segment)) {
|
|
2917
|
-
const vowels = (segment.match(/[aeiou]/gi) ?? []).length;
|
|
2918
|
-
if (vowels / segment.length < 0.25) return true;
|
|
3786
|
+
function setOmittedBodyMetadata(log, field, reason, sizeBytes) {
|
|
3787
|
+
if (field === "requestBody") {
|
|
3788
|
+
delete log.requestBody;
|
|
3789
|
+
log.requestBodyOmittedReason = reason;
|
|
3790
|
+
log.requestBodySizeBytes = sizeBytes;
|
|
3791
|
+
return;
|
|
2919
3792
|
}
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
if (token.includes(":") || token.includes("/")) return true;
|
|
2925
|
-
if (token.includes("--")) return true;
|
|
2926
|
-
if (/^css-[a-z0-9]+$/i.test(token)) return true;
|
|
2927
|
-
if (/^sc-[a-z0-9]+$/i.test(token)) return true;
|
|
2928
|
-
if (STATE_CLASS_RE.test(token)) return true;
|
|
2929
|
-
const segments = token.split("__");
|
|
2930
|
-
if (segments.length > 1 && looksHashed(segments[segments.length - 1] ?? "")) return true;
|
|
2931
|
-
if (looksHashed(token)) return true;
|
|
2932
|
-
return false;
|
|
3793
|
+
delete log.responseBody;
|
|
3794
|
+
delete log.responseBodyEncoding;
|
|
3795
|
+
log.responseBodyOmittedReason = reason;
|
|
3796
|
+
log.responseBodySizeBytes = sizeBytes;
|
|
2933
3797
|
}
|
|
2934
|
-
function
|
|
2935
|
-
return
|
|
3798
|
+
function omitOversizedIndividualBodies(logs) {
|
|
3799
|
+
return logs.map((log) => {
|
|
3800
|
+
const nextLog = { ...log };
|
|
3801
|
+
if (nextLog.requestBody != null) {
|
|
3802
|
+
const sizeBytes = getUtf8ByteLength(nextLog.requestBody);
|
|
3803
|
+
if (sizeBytes > NETWORK_LOG_BODY_CAPTURE_LIMIT_BYTES) {
|
|
3804
|
+
setOmittedBodyMetadata(nextLog, "requestBody", "too-large", sizeBytes);
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
if (nextLog.responseBody != null) {
|
|
3808
|
+
const sizeBytes = getCapturedBodySizeBytes(
|
|
3809
|
+
nextLog.responseBody,
|
|
3810
|
+
nextLog.responseBodyEncoding
|
|
3811
|
+
);
|
|
3812
|
+
const limitBytes = getResponseBodyLimitBytes(nextLog.responseMimeType);
|
|
3813
|
+
if (limitBytes === 0) {
|
|
3814
|
+
setOmittedBodyMetadata(nextLog, "responseBody", "mime-type", sizeBytes);
|
|
3815
|
+
} else if (sizeBytes > limitBytes) {
|
|
3816
|
+
setOmittedBodyMetadata(nextLog, "responseBody", "too-large", sizeBytes);
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
return nextLog;
|
|
3820
|
+
});
|
|
2936
3821
|
}
|
|
2937
|
-
function
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
if (/^[a-f0-9]{8,}$/i.test(id)) return true;
|
|
2942
|
-
if (looksHashed(id)) return true;
|
|
2943
|
-
return false;
|
|
3822
|
+
function omitNetworkLogBody(log, field, reason, sizeBytes) {
|
|
3823
|
+
const nextLog = { ...log };
|
|
3824
|
+
setOmittedBodyMetadata(nextLog, field, reason, sizeBytes);
|
|
3825
|
+
return nextLog;
|
|
2944
3826
|
}
|
|
2945
|
-
function
|
|
2946
|
-
|
|
3827
|
+
function compactNetworkLogsForTransport(logs, budgetBytes = NETWORK_LOG_TRANSPORT_BODY_BUDGET_BYTES) {
|
|
3828
|
+
if (!logs) return void 0;
|
|
3829
|
+
const compacted = omitOversizedIndividualBodies(logs);
|
|
3830
|
+
const candidates = [];
|
|
3831
|
+
compacted.forEach((log, index) => {
|
|
3832
|
+
if (log.requestBody != null) {
|
|
3833
|
+
candidates.push({
|
|
3834
|
+
index,
|
|
3835
|
+
field: "requestBody",
|
|
3836
|
+
timestamp: log.timestamp,
|
|
3837
|
+
sizeBytes: getUtf8ByteLength(log.requestBody)
|
|
3838
|
+
});
|
|
3839
|
+
}
|
|
3840
|
+
if (log.responseBody != null) {
|
|
3841
|
+
candidates.push({
|
|
3842
|
+
index,
|
|
3843
|
+
field: "responseBody",
|
|
3844
|
+
timestamp: log.timestamp,
|
|
3845
|
+
sizeBytes: getUtf8ByteLength(log.responseBody)
|
|
3846
|
+
});
|
|
3847
|
+
}
|
|
3848
|
+
});
|
|
3849
|
+
candidates.sort((a2, b) => {
|
|
3850
|
+
const timestampDiff = b.timestamp - a2.timestamp;
|
|
3851
|
+
if (timestampDiff !== 0) return timestampDiff;
|
|
3852
|
+
const indexDiff = b.index - a2.index;
|
|
3853
|
+
if (indexDiff !== 0) return indexDiff;
|
|
3854
|
+
return a2.field === "responseBody" ? -1 : 1;
|
|
3855
|
+
});
|
|
3856
|
+
let usedBytes = 0;
|
|
3857
|
+
for (const candidate of candidates) {
|
|
3858
|
+
if (usedBytes + candidate.sizeBytes > budgetBytes) {
|
|
3859
|
+
const log = compacted[candidate.index];
|
|
3860
|
+
if (!log) continue;
|
|
3861
|
+
setOmittedBodyMetadata(log, candidate.field, "transport-budget", candidate.sizeBytes);
|
|
3862
|
+
continue;
|
|
3863
|
+
}
|
|
3864
|
+
usedBytes += candidate.sizeBytes;
|
|
3865
|
+
}
|
|
3866
|
+
return compacted;
|
|
2947
3867
|
}
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
return
|
|
3868
|
+
const SELF_TRAFFIC_CLIENT_HEADER = "x-client-info";
|
|
3869
|
+
const SELF_TRAFFIC_CLIENT_VALUES = ["issue-sticker-sdk", "issue-sticker-extension"];
|
|
3870
|
+
const SELF_TRAFFIC_CLIENT_VALUE_SET = new Set(SELF_TRAFFIC_CLIENT_VALUES);
|
|
3871
|
+
function isSharedLocalHost(hostname) {
|
|
3872
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "0.0.0.0" || hostname.endsWith(".local") || hostname.endsWith(".localhost");
|
|
2953
3873
|
}
|
|
2954
|
-
function
|
|
2955
|
-
return
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
3874
|
+
function readHeader(headers, headerName) {
|
|
3875
|
+
if (!headers) return void 0;
|
|
3876
|
+
const target = headerName.toLowerCase();
|
|
3877
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
3878
|
+
if (key.toLowerCase() === target) return value;
|
|
3879
|
+
}
|
|
3880
|
+
return void 0;
|
|
2959
3881
|
}
|
|
2960
|
-
function
|
|
2961
|
-
const
|
|
2962
|
-
return
|
|
2963
|
-
width: window.innerWidth,
|
|
2964
|
-
height: window.innerHeight,
|
|
2965
|
-
scrollX: scroll.x,
|
|
2966
|
-
scrollY: scroll.y
|
|
2967
|
-
};
|
|
3882
|
+
function hasSelfTrafficHeader(headers) {
|
|
3883
|
+
const clientInfo = readHeader(headers, SELF_TRAFFIC_CLIENT_HEADER);
|
|
3884
|
+
return Boolean(clientInfo && SELF_TRAFFIC_CLIENT_VALUE_SET.has(clientInfo));
|
|
2968
3885
|
}
|
|
2969
|
-
function
|
|
3886
|
+
function isSelfTrafficOrigin(url, apiOrigins) {
|
|
3887
|
+
if (apiOrigins.length === 0) return false;
|
|
3888
|
+
let origin;
|
|
3889
|
+
let hostname;
|
|
2970
3890
|
try {
|
|
2971
|
-
const
|
|
2972
|
-
|
|
3891
|
+
const parsed = new URL(url);
|
|
3892
|
+
origin = parsed.origin;
|
|
3893
|
+
hostname = parsed.hostname;
|
|
2973
3894
|
} catch {
|
|
2974
3895
|
return false;
|
|
2975
3896
|
}
|
|
3897
|
+
if (isSharedLocalHost(hostname)) return false;
|
|
3898
|
+
return apiOrigins.some((candidate) => {
|
|
3899
|
+
try {
|
|
3900
|
+
return new URL(candidate).origin === origin;
|
|
3901
|
+
} catch {
|
|
3902
|
+
return false;
|
|
3903
|
+
}
|
|
3904
|
+
});
|
|
2976
3905
|
}
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
if (sibling.tagName === element.tagName) count2 += 1;
|
|
2982
|
-
sibling = sibling.previousElementSibling;
|
|
3906
|
+
let registeredPristineFetch = null;
|
|
3907
|
+
function registerPristineFetch(fn) {
|
|
3908
|
+
if (!registeredPristineFetch) {
|
|
3909
|
+
registeredPristineFetch = fn;
|
|
2983
3910
|
}
|
|
2984
|
-
return count2;
|
|
2985
3911
|
}
|
|
2986
|
-
function
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
3912
|
+
function clearPristineFetch() {
|
|
3913
|
+
registeredPristineFetch = null;
|
|
3914
|
+
}
|
|
3915
|
+
function pristineFetch(input, init) {
|
|
3916
|
+
if (registeredPristineFetch) {
|
|
3917
|
+
return registeredPristineFetch(input, init);
|
|
2992
3918
|
}
|
|
2993
|
-
return
|
|
3919
|
+
return typeof window !== "undefined" ? window.fetch(input, init) : fetch(input, init);
|
|
2994
3920
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3921
|
+
const INSTRUMENTATION_FAILURE_LIMIT = 5;
|
|
3922
|
+
const REPATCH_LIMIT = 2;
|
|
3923
|
+
function setInternalField(target, key, value) {
|
|
3924
|
+
Object.defineProperty(target, key, {
|
|
3925
|
+
value,
|
|
3926
|
+
enumerable: false,
|
|
3927
|
+
writable: true,
|
|
3928
|
+
configurable: true
|
|
3929
|
+
});
|
|
3000
3930
|
}
|
|
3001
|
-
function
|
|
3002
|
-
|
|
3003
|
-
const idSelector = `#${CSS.escape(element.id)}`;
|
|
3004
|
-
if (matchesUniquely(idSelector, element)) return idSelector;
|
|
3005
|
-
}
|
|
3006
|
-
const attrAnchor = stableAnchorSelector(element);
|
|
3007
|
-
if (attrAnchor && !attrAnchor.startsWith("#") && matchesUniquely(attrAnchor, element)) {
|
|
3008
|
-
return attrAnchor;
|
|
3009
|
-
}
|
|
3010
|
-
const segments = [];
|
|
3011
|
-
let current = element;
|
|
3012
|
-
let depth = 0;
|
|
3013
|
-
const MAX_DEPTH = 5;
|
|
3014
|
-
while (current && current.nodeType === Node.ELEMENT_NODE && depth < MAX_DEPTH) {
|
|
3015
|
-
segments.unshift(structuralSegment(current));
|
|
3016
|
-
const candidate = segments.join(" > ");
|
|
3017
|
-
if (matchesUniquely(candidate, element)) return candidate;
|
|
3018
|
-
const parent = current.parentElement;
|
|
3019
|
-
if (parent) {
|
|
3020
|
-
const parentAnchor = stableAnchorSelector(parent);
|
|
3021
|
-
if (parentAnchor) {
|
|
3022
|
-
const anchored = `${parentAnchor} > ${segments.join(" > ")}`;
|
|
3023
|
-
if (matchesUniquely(anchored, element)) return anchored;
|
|
3024
|
-
return anchored;
|
|
3025
|
-
}
|
|
3026
|
-
if (parent.tagName === "BODY") break;
|
|
3027
|
-
}
|
|
3028
|
-
current = parent;
|
|
3029
|
-
depth += 1;
|
|
3030
|
-
}
|
|
3031
|
-
return segments.join(" > ");
|
|
3931
|
+
function toHeaderValue(value) {
|
|
3932
|
+
return typeof value === "string" ? value : String(value ?? "");
|
|
3032
3933
|
}
|
|
3033
|
-
function
|
|
3034
|
-
if (
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3934
|
+
function headersToRecord(headers) {
|
|
3935
|
+
if (!headers || typeof headers.forEach !== "function") return void 0;
|
|
3936
|
+
const entries = {};
|
|
3937
|
+
headers.forEach((value, key) => {
|
|
3938
|
+
entries[key] = value;
|
|
3939
|
+
});
|
|
3940
|
+
return Object.keys(entries).length > 0 ? redactSensitiveHeaders(entries) : void 0;
|
|
3941
|
+
}
|
|
3942
|
+
function requestHeadersToRecord(input, init) {
|
|
3943
|
+
const source = (init == null ? void 0 : init.headers) ?? (typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0);
|
|
3944
|
+
if (!source) return void 0;
|
|
3945
|
+
const entries = {};
|
|
3946
|
+
if (typeof Headers !== "undefined" && source instanceof Headers) {
|
|
3947
|
+
source.forEach((value, key) => {
|
|
3948
|
+
entries[key] = value;
|
|
3949
|
+
});
|
|
3950
|
+
} else if (Array.isArray(source)) {
|
|
3951
|
+
for (const pair of source) {
|
|
3952
|
+
const [key, value] = pair;
|
|
3953
|
+
if (key) entries[key] = toHeaderValue(value);
|
|
3042
3954
|
}
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
if (sibling.tagName === current.tagName) index += 1;
|
|
3047
|
-
sibling = sibling.previousElementSibling;
|
|
3955
|
+
} else {
|
|
3956
|
+
for (const [key, value] of Object.entries(source)) {
|
|
3957
|
+
entries[key] = toHeaderValue(value);
|
|
3048
3958
|
}
|
|
3049
|
-
parts.unshift(`${current.tagName.toLowerCase()}[${index}]`);
|
|
3050
|
-
current = current.parentElement;
|
|
3051
3959
|
}
|
|
3052
|
-
return
|
|
3960
|
+
return Object.keys(entries).length > 0 ? redactSensitiveHeaders(entries) : void 0;
|
|
3053
3961
|
}
|
|
3054
|
-
function
|
|
3055
|
-
const
|
|
3056
|
-
|
|
3962
|
+
function parseXHRHeaders(rawHeaders) {
|
|
3963
|
+
const entries = rawHeaders.trim().split(/[\r\n]+/).map((line) => {
|
|
3964
|
+
const separatorIndex = line.indexOf(":");
|
|
3965
|
+
if (separatorIndex === -1) return null;
|
|
3966
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
3967
|
+
const value = line.slice(separatorIndex + 1).trim();
|
|
3968
|
+
return key ? [key, value] : null;
|
|
3969
|
+
}).filter((entry) => entry !== null);
|
|
3970
|
+
return entries.length > 0 ? redactSensitiveHeaders(Object.fromEntries(entries)) : void 0;
|
|
3971
|
+
}
|
|
3972
|
+
function getHeaderValue(headers, headerName) {
|
|
3973
|
+
if (!headers) return void 0;
|
|
3974
|
+
const normalizedHeaderName = headerName.toLowerCase();
|
|
3975
|
+
const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === normalizedHeaderName);
|
|
3976
|
+
return entry == null ? void 0 : entry[1];
|
|
3977
|
+
}
|
|
3978
|
+
function normalizeMimeType(contentType) {
|
|
3979
|
+
var _a;
|
|
3980
|
+
return ((_a = contentType == null ? void 0 : contentType.split(";", 1)[0]) == null ? void 0 : _a.trim().toLowerCase()) || void 0;
|
|
3981
|
+
}
|
|
3982
|
+
function parseContentLength(value) {
|
|
3983
|
+
if (!value) return void 0;
|
|
3984
|
+
const parsed = Number.parseInt(value, 10);
|
|
3985
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
3986
|
+
}
|
|
3987
|
+
function omitByMimeType(sizeBytes) {
|
|
3057
3988
|
return {
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
selector: getElementSelector(element),
|
|
3061
|
-
xpath: getXPath(element),
|
|
3062
|
-
viewport: getViewportSize(),
|
|
3063
|
-
elementRect: {
|
|
3064
|
-
left: rect.left + scroll.x,
|
|
3065
|
-
top: rect.top + scroll.y,
|
|
3066
|
-
width: rect.width,
|
|
3067
|
-
height: rect.height
|
|
3068
|
-
}
|
|
3989
|
+
responseBodyOmittedReason: "mime-type",
|
|
3990
|
+
responseBodySizeBytes: sizeBytes
|
|
3069
3991
|
};
|
|
3070
3992
|
}
|
|
3071
|
-
function
|
|
3072
|
-
if (!xpath || typeof xpath !== "string") {
|
|
3073
|
-
return null;
|
|
3074
|
-
}
|
|
3993
|
+
function toAbsoluteUrl(url) {
|
|
3075
3994
|
try {
|
|
3076
|
-
const
|
|
3077
|
-
|
|
3078
|
-
document,
|
|
3079
|
-
null,
|
|
3080
|
-
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
3081
|
-
null
|
|
3082
|
-
);
|
|
3083
|
-
const node = result.singleNodeValue;
|
|
3084
|
-
return node instanceof HTMLElement ? node : null;
|
|
3995
|
+
const base = typeof document !== "undefined" && document.baseURI ? document.baseURI : typeof location === "undefined" ? void 0 : location.href;
|
|
3996
|
+
return new URL(url, base).href;
|
|
3085
3997
|
} catch {
|
|
3086
|
-
return
|
|
3998
|
+
return url;
|
|
3087
3999
|
}
|
|
3088
4000
|
}
|
|
3089
|
-
function
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
return findElementByXPath(selector);
|
|
4001
|
+
function captureRequestBody(body) {
|
|
4002
|
+
if (body == null) return {};
|
|
4003
|
+
const requestBody = String(body);
|
|
4004
|
+
const sizeBytes = getUtf8ByteLength(requestBody);
|
|
4005
|
+
if (!isBodyWithinCaptureLimit(sizeBytes)) {
|
|
4006
|
+
return {
|
|
4007
|
+
requestBodyOmittedReason: "too-large",
|
|
4008
|
+
requestBodySizeBytes: sizeBytes
|
|
4009
|
+
};
|
|
3099
4010
|
}
|
|
3100
|
-
return
|
|
4011
|
+
return { requestBody: redactSensitiveBody(requestBody) };
|
|
3101
4012
|
}
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
const codePoint = value.codePointAt(index) ?? 0;
|
|
3108
|
-
if (codePoint > 65535) {
|
|
3109
|
-
index += 1;
|
|
3110
|
-
}
|
|
3111
|
-
if (codePoint <= 127) {
|
|
3112
|
-
bytes += 1;
|
|
3113
|
-
} else if (codePoint <= 2047) {
|
|
3114
|
-
bytes += 2;
|
|
3115
|
-
} else if (codePoint <= 65535) {
|
|
3116
|
-
bytes += 3;
|
|
3117
|
-
} else {
|
|
3118
|
-
bytes += 4;
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
return bytes;
|
|
4013
|
+
function omitTooLargeResponseBody(sizeBytes) {
|
|
4014
|
+
return {
|
|
4015
|
+
responseBodyOmittedReason: "too-large",
|
|
4016
|
+
responseBodySizeBytes: sizeBytes
|
|
4017
|
+
};
|
|
3122
4018
|
}
|
|
3123
|
-
function
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
4019
|
+
function bytesToBase64(bytes) {
|
|
4020
|
+
let binary = "";
|
|
4021
|
+
const chunkSize = 32768;
|
|
4022
|
+
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
|
|
4023
|
+
const chunk = bytes.subarray(offset, offset + chunkSize);
|
|
4024
|
+
binary += String.fromCharCode(...chunk);
|
|
4025
|
+
}
|
|
4026
|
+
return btoa(binary);
|
|
3128
4027
|
}
|
|
3129
|
-
function
|
|
3130
|
-
return
|
|
4028
|
+
async function arrayBufferToBase64(arrayBuffer) {
|
|
4029
|
+
return bytesToBase64(new Uint8Array(arrayBuffer));
|
|
3131
4030
|
}
|
|
3132
|
-
function
|
|
3133
|
-
|
|
4031
|
+
function getResponseDetailLevel(response) {
|
|
4032
|
+
if (response.type === "opaque" || response.type === "opaqueredirect") return "opaque";
|
|
4033
|
+
if (response.type === "cors") return "cors";
|
|
4034
|
+
return void 0;
|
|
3134
4035
|
}
|
|
3135
|
-
function
|
|
3136
|
-
if (
|
|
3137
|
-
|
|
3138
|
-
log.requestBodyOmittedReason = reason;
|
|
3139
|
-
log.requestBodySizeBytes = sizeBytes;
|
|
3140
|
-
return;
|
|
4036
|
+
async function readFetchResponseBody(response, responseMimeType, contentLength) {
|
|
4037
|
+
if (response.type === "opaque" || response.type === "opaqueredirect") {
|
|
4038
|
+
return { responseBodyOmittedReason: "unavailable" };
|
|
3141
4039
|
}
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
4040
|
+
const mimeLimitBytes = getResponseBodyLimitBytes(responseMimeType);
|
|
4041
|
+
if (mimeLimitBytes === 0) {
|
|
4042
|
+
return omitByMimeType(contentLength ?? 0);
|
|
4043
|
+
}
|
|
4044
|
+
if (contentLength != null && contentLength > mimeLimitBytes) {
|
|
4045
|
+
return omitTooLargeResponseBody(contentLength);
|
|
4046
|
+
}
|
|
4047
|
+
const cloned = response.clone();
|
|
4048
|
+
if (isImageMimeType(responseMimeType) && typeof cloned.arrayBuffer === "function") {
|
|
4049
|
+
const arrayBuffer = await cloned.arrayBuffer();
|
|
4050
|
+
const sizeBytes2 = arrayBuffer.byteLength;
|
|
4051
|
+
if (sizeBytes2 > mimeLimitBytes) {
|
|
4052
|
+
return omitTooLargeResponseBody(sizeBytes2);
|
|
3155
4053
|
}
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
4054
|
+
return {
|
|
4055
|
+
responseBody: await arrayBufferToBase64(arrayBuffer),
|
|
4056
|
+
responseBodyEncoding: "base64"
|
|
4057
|
+
};
|
|
4058
|
+
}
|
|
4059
|
+
const responseBody = await cloned.text();
|
|
4060
|
+
const sizeBytes = getCapturedBodySizeBytes(responseBody);
|
|
4061
|
+
if (sizeBytes > mimeLimitBytes) {
|
|
4062
|
+
return omitTooLargeResponseBody(sizeBytes);
|
|
4063
|
+
}
|
|
4064
|
+
return {
|
|
4065
|
+
responseBody: redactSensitiveBody(responseBody)
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
4068
|
+
async function readXHRResponseBody(xhr, responseMimeType, contentLength) {
|
|
4069
|
+
const mimeLimitBytes = getResponseBodyLimitBytes(responseMimeType);
|
|
4070
|
+
if (mimeLimitBytes === 0) {
|
|
4071
|
+
return omitByMimeType(contentLength ?? 0);
|
|
4072
|
+
}
|
|
4073
|
+
if (contentLength != null && contentLength > mimeLimitBytes) {
|
|
4074
|
+
return omitTooLargeResponseBody(contentLength);
|
|
4075
|
+
}
|
|
4076
|
+
if (isImageMimeType(responseMimeType)) {
|
|
4077
|
+
if (xhr.response instanceof Blob && typeof xhr.response.arrayBuffer === "function") {
|
|
4078
|
+
const arrayBuffer = await xhr.response.arrayBuffer();
|
|
4079
|
+
const sizeBytes2 = arrayBuffer.byteLength;
|
|
4080
|
+
if (sizeBytes2 > mimeLimitBytes) {
|
|
4081
|
+
return omitTooLargeResponseBody(sizeBytes2);
|
|
3163
4082
|
}
|
|
4083
|
+
return {
|
|
4084
|
+
responseBody: await arrayBufferToBase64(arrayBuffer),
|
|
4085
|
+
responseBodyEncoding: "base64"
|
|
4086
|
+
};
|
|
3164
4087
|
}
|
|
3165
|
-
|
|
4088
|
+
if (xhr.response instanceof ArrayBuffer) {
|
|
4089
|
+
const sizeBytes2 = xhr.response.byteLength;
|
|
4090
|
+
if (sizeBytes2 > mimeLimitBytes) {
|
|
4091
|
+
return omitTooLargeResponseBody(sizeBytes2);
|
|
4092
|
+
}
|
|
4093
|
+
return {
|
|
4094
|
+
responseBody: await arrayBufferToBase64(xhr.response),
|
|
4095
|
+
responseBodyEncoding: "base64"
|
|
4096
|
+
};
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
const responseBody = xhr.responseText;
|
|
4100
|
+
const sizeBytes = getCapturedBodySizeBytes(responseBody);
|
|
4101
|
+
if (sizeBytes > mimeLimitBytes) {
|
|
4102
|
+
return omitTooLargeResponseBody(sizeBytes);
|
|
4103
|
+
}
|
|
4104
|
+
return {
|
|
4105
|
+
responseBody: redactSensitiveBody(responseBody)
|
|
4106
|
+
};
|
|
4107
|
+
}
|
|
4108
|
+
class NetworkCollector {
|
|
4109
|
+
constructor(apiBaseUrl, options) {
|
|
4110
|
+
this.logs = [];
|
|
4111
|
+
this.originalFetch = null;
|
|
4112
|
+
this.patchedFetch = null;
|
|
4113
|
+
this.originalXHROpen = null;
|
|
4114
|
+
this.originalXHRSend = null;
|
|
4115
|
+
this.originalXHRSetRequestHeader = null;
|
|
4116
|
+
this.patchedXHR = null;
|
|
4117
|
+
this.running = false;
|
|
4118
|
+
this.instrumentationFailures = 0;
|
|
4119
|
+
this.repatchCount = 0;
|
|
4120
|
+
const normalizedApiBaseUrl = typeof apiBaseUrl === "string" && apiBaseUrl.trim().length > 0 ? apiBaseUrl.trim() : null;
|
|
4121
|
+
this.apiBaseUrl = normalizedApiBaseUrl;
|
|
4122
|
+
this.onChange = (options == null ? void 0 : options.onChange) ?? null;
|
|
4123
|
+
}
|
|
4124
|
+
start() {
|
|
4125
|
+
if (this.running) return;
|
|
4126
|
+
this.running = true;
|
|
4127
|
+
this.instrumentationFailures = 0;
|
|
4128
|
+
this.interceptFetch();
|
|
4129
|
+
this.interceptXHR();
|
|
4130
|
+
}
|
|
4131
|
+
stop() {
|
|
4132
|
+
if (!this.running) return;
|
|
4133
|
+
this.running = false;
|
|
4134
|
+
this.restoreFetch();
|
|
4135
|
+
this.restoreXHR();
|
|
4136
|
+
}
|
|
4137
|
+
/**
|
|
4138
|
+
* 우리 래퍼가 아직 걸려 있는지 확인하고, 아니면 다시 붙인다.
|
|
4139
|
+
*
|
|
4140
|
+
* 폴리필이나 다른 계측이 `window.fetch` 를 통째로 갈아치우면 우리 수집은 조용히 멈춘다.
|
|
4141
|
+
* 조용한 실패가 제일 나쁘다 — 로그를 읽어가는 시점에 한 번 확인한다.
|
|
4142
|
+
*/
|
|
4143
|
+
ensurePatched() {
|
|
4144
|
+
if (!this.running) return;
|
|
4145
|
+
if (window.fetch === this.patchedFetch) return;
|
|
4146
|
+
if (this.repatchCount >= REPATCH_LIMIT) return;
|
|
4147
|
+
this.repatchCount += 1;
|
|
4148
|
+
this.interceptFetch();
|
|
4149
|
+
}
|
|
4150
|
+
getLogs() {
|
|
4151
|
+
return [...this.logs];
|
|
4152
|
+
}
|
|
4153
|
+
/**
|
|
4154
|
+
* 패치가 볼 수 없는 요청(문서·리소스 로딩)을 밖에서 넣는다.
|
|
4155
|
+
*
|
|
4156
|
+
* 자기 트래픽 판정과 버퍼 상한은 다른 로그와 똑같이 적용된다.
|
|
4157
|
+
*/
|
|
4158
|
+
ingest(log) {
|
|
4159
|
+
this.addLog(log);
|
|
4160
|
+
}
|
|
4161
|
+
/**
|
|
4162
|
+
* 리로드 이전 로그를 버퍼 앞에 되돌린다.
|
|
4163
|
+
*
|
|
4164
|
+
* MPA 는 클릭마다 문서가 바뀐다. 이전 문서에서 잡은 요청이 사라지면 "무엇을 하다 터졌는지"
|
|
4165
|
+
* 가 통째로 없어진다.
|
|
4166
|
+
*/
|
|
4167
|
+
restore(logs) {
|
|
4168
|
+
if (logs.length === 0) return;
|
|
4169
|
+
this.logs = [...logs, ...this.logs].slice(-NETWORK_LOG_CLIENT_BUFFER_SIZE);
|
|
4170
|
+
}
|
|
4171
|
+
/**
|
|
4172
|
+
* 자기 트래픽을 걷어낸 보관 로그 전체.
|
|
4173
|
+
*
|
|
4174
|
+
* 여기서 제출 상한(50)까지 잘라내면 안 된다. 소비자가 헤더 기반 판정을 한 번 더 돌린 뒤
|
|
4175
|
+
* 마지막에 잘라야, 노이즈가 채운 자리 때문에 진짜 로그가 사라지지 않는다.
|
|
4176
|
+
*/
|
|
4177
|
+
getFilteredLogs() {
|
|
4178
|
+
return this.logs.filter((log) => !this.isServiceRequest(log.url));
|
|
4179
|
+
}
|
|
4180
|
+
clear() {
|
|
4181
|
+
this.logs = [];
|
|
4182
|
+
}
|
|
4183
|
+
/**
|
|
4184
|
+
* 계측이 스스로 꺼지는 차단기.
|
|
4185
|
+
*
|
|
4186
|
+
* 계측에서 난 예외가 반복된다는 건 우리가 이해하지 못하는 환경에 들어와 있다는 뜻이다.
|
|
4187
|
+
* 무슨 일이 나는지 모르는 채로 고객 페이지에 계속 손대는 것보다, 패치를 걷어내고 로그를
|
|
4188
|
+
* 포기하는 쪽이 안전하다. 자동 재시도는 하지 않는다.
|
|
4189
|
+
*/
|
|
4190
|
+
recordInstrumentationFailure() {
|
|
4191
|
+
this.instrumentationFailures += 1;
|
|
4192
|
+
if (this.instrumentationFailures >= INSTRUMENTATION_FAILURE_LIMIT) {
|
|
4193
|
+
this.stop();
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
addLog(log) {
|
|
4197
|
+
var _a;
|
|
4198
|
+
if (!this.running) return;
|
|
4199
|
+
if (this.isServiceRequest(log.url)) return;
|
|
4200
|
+
this.logs.push(log);
|
|
4201
|
+
if (this.logs.length > NETWORK_LOG_CLIENT_BUFFER_SIZE) {
|
|
4202
|
+
this.logs = this.logs.slice(-NETWORK_LOG_CLIENT_BUFFER_SIZE);
|
|
4203
|
+
}
|
|
4204
|
+
(_a = this.onChange) == null ? void 0 : _a.call(this);
|
|
4205
|
+
}
|
|
4206
|
+
/**
|
|
4207
|
+
* 우리 백엔드 호출인가.
|
|
4208
|
+
*
|
|
4209
|
+
* 예전에는 `url.includes(apiBaseUrl)` 과 `url.includes('issuesticker')` 를 썼는데 둘 다
|
|
4210
|
+
* 오탐이 난다 — 앞은 localdev 백엔드가 `localhost:3001` 이라 같은 포트를 쓰는 고객 앱의
|
|
4211
|
+
* API 로그를 통째로 삼키고, 뒤는 고객 URL 에 그 문자열이 들어가기만 해도 걸린다.
|
|
4212
|
+
* 지금은 공인 도메인에 한해 origin 을 정확히 비교한다(`isSelfTrafficOrigin`).
|
|
4213
|
+
*
|
|
4214
|
+
* 캡처 중 우리가 일으키는 서드파티 요청(스크린샷 폰트 인라인 등)은 이 판정으로는 못
|
|
4215
|
+
* 잡는다. 그건 `pauseWhile()` 로 수집 자체를 멈춰서 막는다.
|
|
4216
|
+
*/
|
|
4217
|
+
isServiceRequest(url) {
|
|
4218
|
+
return isSelfTrafficOrigin(url, this.apiBaseUrl ? [this.apiBaseUrl] : []);
|
|
4219
|
+
}
|
|
4220
|
+
/**
|
|
4221
|
+
* 콜백이 도는 동안 수집을 멈춘다.
|
|
4222
|
+
*
|
|
4223
|
+
* 스크린샷 캡처는 웹폰트·이미지를 `fetch()` 로 끌어와 인라인한다. 그 요청들은 남의
|
|
4224
|
+
* 도메인이라 자기 트래픽 판정에 걸리지 않으면서 최근 50건을 통째로 채워버린다 — payload 가
|
|
4225
|
+
* 터지는 것도 문제지만 진짜 앱 요청이 링버퍼 밖으로 밀려나는 게 더 크다.
|
|
4226
|
+
*
|
|
4227
|
+
* 대가로 캡처가 도는 수백 ms 동안의 앱 요청이 수집되지 않는다. 화면을 얼려 찍는 순간이라
|
|
4228
|
+
* 받아들일 만한 손실로 본다.
|
|
4229
|
+
*/
|
|
4230
|
+
async pauseWhile(task) {
|
|
4231
|
+
if (!this.running) return task();
|
|
4232
|
+
this.stop();
|
|
4233
|
+
try {
|
|
4234
|
+
return await task();
|
|
4235
|
+
} finally {
|
|
4236
|
+
this.start();
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
interceptFetch() {
|
|
4240
|
+
this.originalFetch = window.fetch;
|
|
4241
|
+
const originalFetch = this.originalFetch;
|
|
4242
|
+
registerPristineFetch(originalFetch.bind(window));
|
|
4243
|
+
const addLog = this.addLog.bind(this);
|
|
4244
|
+
const onInstrumentationFailure = this.recordInstrumentationFailure.bind(this);
|
|
4245
|
+
const patchedFetch = async function(input, init) {
|
|
4246
|
+
const startTime = Date.now();
|
|
4247
|
+
const url = redactUrlSecrets(
|
|
4248
|
+
toAbsoluteUrl(
|
|
4249
|
+
typeof input === "string" ? input : input instanceof URL ? input.href : input.url
|
|
4250
|
+
)
|
|
4251
|
+
);
|
|
4252
|
+
const method = (init == null ? void 0 : init.method) || (typeof Request !== "undefined" && input instanceof Request ? input.method : "GET");
|
|
4253
|
+
const requestHeaders = requestHeadersToRecord(input, init);
|
|
4254
|
+
const capturedRequestBody = captureRequestBody(init == null ? void 0 : init.body);
|
|
4255
|
+
function recordResponse(response) {
|
|
4256
|
+
const responseHeaders = headersToRecord(response.headers);
|
|
4257
|
+
const responseMimeType = normalizeMimeType(getHeaderValue(responseHeaders, "content-type"));
|
|
4258
|
+
const contentLength = parseContentLength(getHeaderValue(responseHeaders, "content-length"));
|
|
4259
|
+
const detailLevel = getResponseDetailLevel(response);
|
|
4260
|
+
const log = {
|
|
4261
|
+
url,
|
|
4262
|
+
method,
|
|
4263
|
+
statusCode: response.status,
|
|
4264
|
+
timestamp: startTime,
|
|
4265
|
+
type: "fetch",
|
|
4266
|
+
requestHeaders,
|
|
4267
|
+
...capturedRequestBody,
|
|
4268
|
+
responseHeaders,
|
|
4269
|
+
responseMimeType,
|
|
4270
|
+
...detailLevel ? { detailLevel } : {}
|
|
4271
|
+
};
|
|
4272
|
+
addLog(log);
|
|
4273
|
+
void readFetchResponseBody(response, responseMimeType, contentLength).then((capturedBody) => {
|
|
4274
|
+
Object.assign(log, capturedBody);
|
|
4275
|
+
}).catch(() => {
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
try {
|
|
4279
|
+
const response = await originalFetch.call(window, input, init);
|
|
4280
|
+
try {
|
|
4281
|
+
recordResponse(response);
|
|
4282
|
+
} catch {
|
|
4283
|
+
onInstrumentationFailure();
|
|
4284
|
+
}
|
|
4285
|
+
return response;
|
|
4286
|
+
} catch (error) {
|
|
4287
|
+
try {
|
|
4288
|
+
addLog({
|
|
4289
|
+
url,
|
|
4290
|
+
method,
|
|
4291
|
+
statusCode: 0,
|
|
4292
|
+
timestamp: startTime,
|
|
4293
|
+
type: "fetch",
|
|
4294
|
+
requestHeaders,
|
|
4295
|
+
...capturedRequestBody
|
|
4296
|
+
});
|
|
4297
|
+
} catch {
|
|
4298
|
+
}
|
|
4299
|
+
throw error;
|
|
4300
|
+
}
|
|
4301
|
+
};
|
|
4302
|
+
window.fetch = patchedFetch;
|
|
4303
|
+
this.patchedFetch = patchedFetch;
|
|
4304
|
+
}
|
|
4305
|
+
interceptXHR() {
|
|
4306
|
+
this.originalXHROpen = XMLHttpRequest.prototype.open;
|
|
4307
|
+
this.originalXHRSend = XMLHttpRequest.prototype.send;
|
|
4308
|
+
const originalOpen = this.originalXHROpen;
|
|
4309
|
+
const originalSend = this.originalXHRSend;
|
|
4310
|
+
const addLog = this.addLog.bind(this);
|
|
4311
|
+
function interceptedOpen(method, url, async, username, password) {
|
|
4312
|
+
setInternalField(this, "_isSDK_method", method);
|
|
4313
|
+
setInternalField(
|
|
4314
|
+
this,
|
|
4315
|
+
"_isSDK_url",
|
|
4316
|
+
redactUrlSecrets(toAbsoluteUrl(typeof url === "string" ? url : url.href))
|
|
4317
|
+
);
|
|
4318
|
+
setInternalField(this, "_isSDK_requestHeaders", void 0);
|
|
4319
|
+
if (async === void 0) {
|
|
4320
|
+
return originalOpen.call(this, method, url, true);
|
|
4321
|
+
}
|
|
4322
|
+
return originalOpen.call(this, method, url, async, username, password);
|
|
4323
|
+
}
|
|
4324
|
+
XMLHttpRequest.prototype.open = interceptedOpen;
|
|
4325
|
+
this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
4326
|
+
const originalSetRequestHeader = this.originalXHRSetRequestHeader;
|
|
4327
|
+
XMLHttpRequest.prototype.setRequestHeader = function(name, value) {
|
|
4328
|
+
setInternalField(this, "_isSDK_requestHeaders", {
|
|
4329
|
+
...this._isSDK_requestHeaders ?? {},
|
|
4330
|
+
[name]: toHeaderValue(value)
|
|
4331
|
+
});
|
|
4332
|
+
return originalSetRequestHeader.call(this, name, value);
|
|
4333
|
+
};
|
|
4334
|
+
XMLHttpRequest.prototype.send = function(body) {
|
|
4335
|
+
const startTime = Date.now();
|
|
4336
|
+
const xhrUrl = this._isSDK_url || "";
|
|
4337
|
+
const xhrMethod = this._isSDK_method || "GET";
|
|
4338
|
+
const capturedRequestBody = captureRequestBody(body);
|
|
4339
|
+
const requestHeaders = redactSensitiveHeaders(this._isSDK_requestHeaders);
|
|
4340
|
+
this.addEventListener("loadend", () => {
|
|
4341
|
+
void (async () => {
|
|
4342
|
+
const responseHeaders = parseXHRHeaders(this.getAllResponseHeaders());
|
|
4343
|
+
const responseMimeType = normalizeMimeType(
|
|
4344
|
+
getHeaderValue(responseHeaders, "content-type")
|
|
4345
|
+
);
|
|
4346
|
+
const contentLength = parseContentLength(
|
|
4347
|
+
getHeaderValue(responseHeaders, "content-length")
|
|
4348
|
+
);
|
|
4349
|
+
const blocked = this.status === 0 && !responseHeaders;
|
|
4350
|
+
let capturedBody = blocked ? { responseBodyOmittedReason: "unavailable" } : {};
|
|
4351
|
+
if (!blocked) {
|
|
4352
|
+
try {
|
|
4353
|
+
capturedBody = await readXHRResponseBody(this, responseMimeType, contentLength);
|
|
4354
|
+
} catch {
|
|
4355
|
+
capturedBody = { responseBodyOmittedReason: "unavailable" };
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
addLog({
|
|
4359
|
+
url: xhrUrl,
|
|
4360
|
+
method: xhrMethod,
|
|
4361
|
+
statusCode: this.status,
|
|
4362
|
+
timestamp: startTime,
|
|
4363
|
+
type: "xmlhttprequest",
|
|
4364
|
+
requestHeaders,
|
|
4365
|
+
...capturedRequestBody,
|
|
4366
|
+
responseHeaders,
|
|
4367
|
+
responseMimeType,
|
|
4368
|
+
...capturedBody,
|
|
4369
|
+
...blocked ? { detailLevel: "opaque" } : {}
|
|
4370
|
+
});
|
|
4371
|
+
})();
|
|
4372
|
+
});
|
|
4373
|
+
return originalSend.call(this, body);
|
|
4374
|
+
};
|
|
4375
|
+
this.patchedXHR = {
|
|
4376
|
+
open: XMLHttpRequest.prototype.open,
|
|
4377
|
+
send: XMLHttpRequest.prototype.send,
|
|
4378
|
+
setRequestHeader: XMLHttpRequest.prototype.setRequestHeader
|
|
4379
|
+
};
|
|
4380
|
+
}
|
|
4381
|
+
/**
|
|
4382
|
+
* 우리 래퍼가 아직 현재 값일 때만 되돌린다.
|
|
4383
|
+
*
|
|
4384
|
+
* 무조건 되돌리면 우리 뒤에 붙은 계측(Sentry 등)의 래퍼가 통째로 사라진다 — 고객의 에러
|
|
4385
|
+
* 추적이 조용히 죽는다. 남의 것이 위에 있으면 우리 래퍼는 체인에 남겨 두고 수집만 멈춘다.
|
|
4386
|
+
*/
|
|
4387
|
+
restoreFetch() {
|
|
4388
|
+
if (!this.originalFetch) return;
|
|
4389
|
+
if (window.fetch === this.patchedFetch) {
|
|
4390
|
+
window.fetch = this.originalFetch;
|
|
4391
|
+
}
|
|
4392
|
+
this.originalFetch = null;
|
|
4393
|
+
this.patchedFetch = null;
|
|
4394
|
+
clearPristineFetch();
|
|
4395
|
+
}
|
|
4396
|
+
/** fetch 와 같은 규칙 — 우리 래퍼가 아직 걸려 있을 때만 되돌린다. */
|
|
4397
|
+
restoreXHR() {
|
|
4398
|
+
const patched = this.patchedXHR;
|
|
4399
|
+
if (this.originalXHROpen && (!patched || XMLHttpRequest.prototype.open === patched.open)) {
|
|
4400
|
+
XMLHttpRequest.prototype.open = this.originalXHROpen;
|
|
4401
|
+
}
|
|
4402
|
+
if (this.originalXHRSend && (!patched || XMLHttpRequest.prototype.send === patched.send)) {
|
|
4403
|
+
XMLHttpRequest.prototype.send = this.originalXHRSend;
|
|
4404
|
+
}
|
|
4405
|
+
if (this.originalXHRSetRequestHeader && (!patched || XMLHttpRequest.prototype.setRequestHeader === patched.setRequestHeader)) {
|
|
4406
|
+
XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
|
|
4407
|
+
}
|
|
4408
|
+
this.originalXHROpen = null;
|
|
4409
|
+
this.originalXHRSend = null;
|
|
4410
|
+
this.originalXHRSetRequestHeader = null;
|
|
4411
|
+
this.patchedXHR = null;
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
var NetworkLogType = /* @__PURE__ */ ((NetworkLogType2) => {
|
|
4415
|
+
NetworkLogType2["MAIN_FRAME"] = "main_frame";
|
|
4416
|
+
NetworkLogType2["SUB_FRAME"] = "sub_frame";
|
|
4417
|
+
NetworkLogType2["STYLESHEET"] = "stylesheet";
|
|
4418
|
+
NetworkLogType2["SCRIPT"] = "script";
|
|
4419
|
+
NetworkLogType2["IMAGE"] = "image";
|
|
4420
|
+
NetworkLogType2["FONT"] = "font";
|
|
4421
|
+
NetworkLogType2["XMLHTTPREQUEST"] = "xmlhttprequest";
|
|
4422
|
+
NetworkLogType2["FETCH"] = "fetch";
|
|
4423
|
+
NetworkLogType2["WEBSOCKET"] = "websocket";
|
|
4424
|
+
NetworkLogType2["MEDIA"] = "media";
|
|
4425
|
+
NetworkLogType2["OTHER"] = "other";
|
|
4426
|
+
return NetworkLogType2;
|
|
4427
|
+
})(NetworkLogType || {});
|
|
4428
|
+
const JIRA_FIELD_ALIASES = {
|
|
4429
|
+
fix_version: "fixVersions",
|
|
4430
|
+
fixVersion: "fixVersions",
|
|
4431
|
+
assignee_external_user_id: "assignee",
|
|
4432
|
+
reporter_external_user_id: "reporter",
|
|
4433
|
+
issue_type: "issuetype"
|
|
4434
|
+
};
|
|
4435
|
+
const READONLY_SCHEMA_TYPES = /* @__PURE__ */ new Set(["array", "object"]);
|
|
4436
|
+
const READONLY_FIELD_IDS = /* @__PURE__ */ new Set(["description", "project_key", "reporter"]);
|
|
4437
|
+
const DYNAMIC_USER_FIELD_IDS = /* @__PURE__ */ new Set(["assignee", "reporter"]);
|
|
4438
|
+
const CREATE_UNSUPPORTED_FIELD_IDS = /* @__PURE__ */ new Set(["status"]);
|
|
4439
|
+
const OPTION_SCHEMA_CUSTOM_TYPES = [
|
|
4440
|
+
":select",
|
|
4441
|
+
":radiobuttons",
|
|
4442
|
+
":multiselect",
|
|
4443
|
+
":checkboxes",
|
|
4444
|
+
":multicheckboxes"
|
|
4445
|
+
];
|
|
4446
|
+
function normalizeJiraFieldId(fieldId) {
|
|
4447
|
+
return JIRA_FIELD_ALIASES[fieldId] || fieldId;
|
|
4448
|
+
}
|
|
4449
|
+
function normalizeExposedJiraFields(exposedFields) {
|
|
4450
|
+
if (!(exposedFields == null ? void 0 : exposedFields.length)) {
|
|
4451
|
+
return [];
|
|
4452
|
+
}
|
|
4453
|
+
return exposedFields.filter((field) => typeof field === "string" && field.trim().length > 0).map((field) => normalizeJiraFieldId(field.trim())).filter((field, index, fields) => fields.indexOf(field) === index);
|
|
4454
|
+
}
|
|
4455
|
+
function getExposedJiraFields(config) {
|
|
4456
|
+
const exposed = config == null ? void 0 : config.exposed_fields;
|
|
4457
|
+
return normalizeExposedJiraFields(Array.isArray(exposed) ? exposed : void 0);
|
|
4458
|
+
}
|
|
4459
|
+
function isJiraFieldExposed(exposedFields, fieldId) {
|
|
4460
|
+
if (!exposedFields) {
|
|
4461
|
+
return false;
|
|
4462
|
+
}
|
|
4463
|
+
const normalized = normalizeJiraFieldId(fieldId);
|
|
4464
|
+
return normalizeExposedJiraFields(exposedFields).includes(normalized);
|
|
4465
|
+
}
|
|
4466
|
+
function getJiraMetadata(mapping) {
|
|
4467
|
+
const raw = mapping == null ? void 0 : mapping.jira_issue_metadata;
|
|
4468
|
+
if (Array.isArray(raw)) {
|
|
4469
|
+
return raw[0] ?? null;
|
|
4470
|
+
}
|
|
4471
|
+
return raw ?? null;
|
|
4472
|
+
}
|
|
4473
|
+
function getPrimaryJiraMapping(issue) {
|
|
4474
|
+
var _a, _b;
|
|
4475
|
+
return ((_a = issue.issue_external_mappings) == null ? void 0 : _a.find((mapping) => {
|
|
4476
|
+
const hasFields = normalizeExposedJiraFields(mapping.exposed_fields).length > 0;
|
|
4477
|
+
return hasFields && getJiraMetadata(mapping);
|
|
4478
|
+
})) ?? ((_b = issue.issue_external_mappings) == null ? void 0 : _b[0]) ?? null;
|
|
4479
|
+
}
|
|
4480
|
+
function stringifyJiraValue(value) {
|
|
4481
|
+
if (value === null || value === void 0 || value === "") {
|
|
4482
|
+
return "";
|
|
4483
|
+
}
|
|
4484
|
+
if (Array.isArray(value)) {
|
|
4485
|
+
return value.map((item) => {
|
|
4486
|
+
if (typeof item === "object" && item !== null) {
|
|
4487
|
+
return String(
|
|
4488
|
+
item.name ?? item.key ?? item.value ?? item.id ?? ""
|
|
4489
|
+
);
|
|
4490
|
+
}
|
|
4491
|
+
return String(item);
|
|
4492
|
+
}).filter(Boolean).join(", ");
|
|
4493
|
+
}
|
|
4494
|
+
if (typeof value === "object") {
|
|
4495
|
+
return String(
|
|
4496
|
+
value.name ?? value.key ?? value.value ?? value.id ?? JSON.stringify(value)
|
|
4497
|
+
);
|
|
4498
|
+
}
|
|
4499
|
+
return String(value);
|
|
4500
|
+
}
|
|
4501
|
+
function getJiraIssueKey(value) {
|
|
4502
|
+
if (value === null || value === void 0 || value === "") {
|
|
4503
|
+
return "";
|
|
4504
|
+
}
|
|
4505
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
4506
|
+
const key = value.key ?? value.id;
|
|
4507
|
+
return typeof key === "string" ? key : "";
|
|
4508
|
+
}
|
|
4509
|
+
return String(value);
|
|
4510
|
+
}
|
|
4511
|
+
function getJiraMetadataValue(meta, fieldId) {
|
|
4512
|
+
var _a, _b;
|
|
4513
|
+
if (!meta) {
|
|
4514
|
+
return null;
|
|
4515
|
+
}
|
|
4516
|
+
switch (normalizeJiraFieldId(fieldId)) {
|
|
4517
|
+
case "status":
|
|
4518
|
+
return meta.status;
|
|
4519
|
+
case "assignee":
|
|
4520
|
+
return meta.assignee_external_user_id;
|
|
4521
|
+
case "reporter":
|
|
4522
|
+
return meta.reporter_external_user_id;
|
|
4523
|
+
case "fixVersions":
|
|
4524
|
+
return meta.fix_version;
|
|
4525
|
+
case "parent":
|
|
4526
|
+
return (_a = meta.custom_fields) == null ? void 0 : _a.parent;
|
|
4527
|
+
case "issuetype":
|
|
4528
|
+
return meta.issue_type;
|
|
4529
|
+
case "priority":
|
|
4530
|
+
return meta.priority;
|
|
4531
|
+
case "project_key":
|
|
4532
|
+
return meta.project_key;
|
|
4533
|
+
case "components":
|
|
4534
|
+
return meta.components;
|
|
4535
|
+
case "labels":
|
|
4536
|
+
return meta.labels;
|
|
4537
|
+
case "sprint_id":
|
|
4538
|
+
return meta.sprint_id;
|
|
4539
|
+
case "sprint_name":
|
|
4540
|
+
return meta.sprint_name;
|
|
4541
|
+
case "epic_link":
|
|
4542
|
+
return meta.epic_link;
|
|
4543
|
+
case "story_points":
|
|
4544
|
+
return meta.story_points;
|
|
4545
|
+
default:
|
|
4546
|
+
return (_b = meta.custom_fields) == null ? void 0 : _b[fieldId];
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
function findFieldDefinition(fieldId, fieldDefinitions) {
|
|
4550
|
+
const normalized = normalizeJiraFieldId(fieldId);
|
|
4551
|
+
return fieldDefinitions.find((field) => normalizeJiraFieldId(field.id) === normalized);
|
|
4552
|
+
}
|
|
4553
|
+
function getFieldLabel(fieldId, fieldDefinition) {
|
|
4554
|
+
return (fieldDefinition == null ? void 0 : fieldDefinition.name) ?? fieldId;
|
|
4555
|
+
}
|
|
4556
|
+
function getOptionLabel(option) {
|
|
4557
|
+
return option.name || option.value || option.id;
|
|
4558
|
+
}
|
|
4559
|
+
function getJiraFieldDisplayLabelKey(fieldId, value) {
|
|
4560
|
+
return `${normalizeJiraFieldId(fieldId)}:${value}`;
|
|
4561
|
+
}
|
|
4562
|
+
function getStoredValueId(value) {
|
|
4563
|
+
if (value === null || value === void 0 || value === "") {
|
|
4564
|
+
return "";
|
|
4565
|
+
}
|
|
4566
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
4567
|
+
const id = value.id;
|
|
4568
|
+
return id === null || id === void 0 || id === "" ? "" : String(id);
|
|
4569
|
+
}
|
|
4570
|
+
return String(value);
|
|
4571
|
+
}
|
|
4572
|
+
function findMatchingOption(value, options) {
|
|
4573
|
+
if (!options.length) {
|
|
4574
|
+
return void 0;
|
|
4575
|
+
}
|
|
4576
|
+
const valueId = getStoredValueId(value);
|
|
4577
|
+
return valueId ? options.find((option) => option.id === valueId) : void 0;
|
|
4578
|
+
}
|
|
4579
|
+
function findDisplayLabel(fieldId, value, displayLabels) {
|
|
4580
|
+
const valueId = getStoredValueId(value);
|
|
4581
|
+
if (!valueId) {
|
|
4582
|
+
return void 0;
|
|
4583
|
+
}
|
|
4584
|
+
return displayLabels.get(getJiraFieldDisplayLabelKey(fieldId, valueId));
|
|
4585
|
+
}
|
|
4586
|
+
function getDisplayValue(fieldId, value, options, displayLabels) {
|
|
4587
|
+
const normalizedFieldId = normalizeJiraFieldId(fieldId);
|
|
4588
|
+
if (normalizedFieldId === "parent") {
|
|
4589
|
+
return getJiraIssueKey(value) || "-";
|
|
4590
|
+
}
|
|
4591
|
+
const values = Array.isArray(value) ? value : [value];
|
|
4592
|
+
const displayValues = values.map((item) => {
|
|
4593
|
+
const matched = findMatchingOption(item, options);
|
|
4594
|
+
if (matched) {
|
|
4595
|
+
return getOptionLabel(matched);
|
|
4596
|
+
}
|
|
4597
|
+
return findDisplayLabel(fieldId, item, displayLabels) ?? (DYNAMIC_USER_FIELD_IDS.has(normalizedFieldId) ? "" : stringifyJiraValue(item));
|
|
4598
|
+
}).filter(Boolean);
|
|
4599
|
+
return displayValues.join(", ") || "-";
|
|
4600
|
+
}
|
|
4601
|
+
function getEditValue(fieldId, value, options) {
|
|
4602
|
+
if (normalizeJiraFieldId(fieldId) === "parent") {
|
|
4603
|
+
return getJiraIssueKey(value);
|
|
4604
|
+
}
|
|
4605
|
+
const stringValue = stringifyJiraValue(value);
|
|
4606
|
+
if (!options.length) {
|
|
4607
|
+
return stringValue;
|
|
4608
|
+
}
|
|
4609
|
+
const matched = findMatchingOption(value, options);
|
|
4610
|
+
if (!matched) {
|
|
4611
|
+
return "";
|
|
4612
|
+
}
|
|
4613
|
+
return matched.id;
|
|
4614
|
+
}
|
|
4615
|
+
function getInputType(fieldId, fieldDefinition, options) {
|
|
4616
|
+
var _a, _b;
|
|
4617
|
+
const normalizedFieldId = normalizeJiraFieldId(fieldId);
|
|
4618
|
+
if (READONLY_FIELD_IDS.has(normalizedFieldId)) {
|
|
4619
|
+
return "readonly";
|
|
4620
|
+
}
|
|
4621
|
+
if (normalizedFieldId === "assignee") {
|
|
4622
|
+
return "user";
|
|
4623
|
+
}
|
|
4624
|
+
if (normalizedFieldId === "parent") {
|
|
4625
|
+
return "issue";
|
|
4626
|
+
}
|
|
4627
|
+
if (normalizedFieldId === "fixVersions") {
|
|
4628
|
+
return "select";
|
|
4629
|
+
}
|
|
4630
|
+
if (options.length > 0) {
|
|
4631
|
+
return "select";
|
|
4632
|
+
}
|
|
4633
|
+
const schemaType = (_a = fieldDefinition == null ? void 0 : fieldDefinition.schema) == null ? void 0 : _a.type;
|
|
4634
|
+
const schemaCustom = (_b = fieldDefinition == null ? void 0 : fieldDefinition.schema) == null ? void 0 : _b.custom;
|
|
4635
|
+
if (schemaType === "option" || OPTION_SCHEMA_CUSTOM_TYPES.some((marker) => schemaCustom == null ? void 0 : schemaCustom.includes(marker))) {
|
|
4636
|
+
return "readonly";
|
|
4637
|
+
}
|
|
4638
|
+
if (schemaType && READONLY_SCHEMA_TYPES.has(schemaType)) {
|
|
4639
|
+
return "readonly";
|
|
4640
|
+
}
|
|
4641
|
+
if (schemaType === "number") {
|
|
4642
|
+
return "number";
|
|
4643
|
+
}
|
|
4644
|
+
return "text";
|
|
4645
|
+
}
|
|
4646
|
+
function resolveFieldValue(fieldId, metadata, values) {
|
|
4647
|
+
if (values && Object.prototype.hasOwnProperty.call(values, fieldId)) {
|
|
4648
|
+
return values[fieldId];
|
|
4649
|
+
}
|
|
4650
|
+
return getJiraMetadataValue(metadata, fieldId);
|
|
4651
|
+
}
|
|
4652
|
+
function buildJiraFieldViews({
|
|
4653
|
+
exposedFields,
|
|
4654
|
+
fieldDefinitions = [],
|
|
4655
|
+
metadata = null,
|
|
4656
|
+
displayLabels = /* @__PURE__ */ new Map(),
|
|
4657
|
+
values,
|
|
4658
|
+
includeReadonly = true
|
|
4659
|
+
}) {
|
|
4660
|
+
const fields = normalizeExposedJiraFields(exposedFields).map((fieldId) => {
|
|
4661
|
+
const fieldDefinition = findFieldDefinition(fieldId, fieldDefinitions);
|
|
4662
|
+
const value = resolveFieldValue(fieldId, metadata, values);
|
|
4663
|
+
const options = (fieldDefinition == null ? void 0 : fieldDefinition.options) ?? [];
|
|
4664
|
+
const inputType = getInputType(fieldId, fieldDefinition, options);
|
|
4665
|
+
return {
|
|
4666
|
+
id: fieldId,
|
|
4667
|
+
label: getFieldLabel(fieldId, fieldDefinition),
|
|
4668
|
+
value,
|
|
4669
|
+
displayValue: getDisplayValue(fieldId, value, options, displayLabels),
|
|
4670
|
+
editValue: getEditValue(fieldId, value, options),
|
|
4671
|
+
options,
|
|
4672
|
+
editable: inputType !== "readonly",
|
|
4673
|
+
inputType
|
|
4674
|
+
};
|
|
3166
4675
|
});
|
|
4676
|
+
if (includeReadonly) {
|
|
4677
|
+
return fields;
|
|
4678
|
+
}
|
|
4679
|
+
return fields.filter((field) => field.editable);
|
|
4680
|
+
}
|
|
4681
|
+
function buildJiraIssueFields(mapping, fieldDefinitions = [], displayLabels = /* @__PURE__ */ new Map()) {
|
|
4682
|
+
return buildJiraFieldViews({
|
|
4683
|
+
exposedFields: mapping == null ? void 0 : mapping.exposed_fields,
|
|
4684
|
+
fieldDefinitions,
|
|
4685
|
+
metadata: getJiraMetadata(mapping),
|
|
4686
|
+
displayLabels
|
|
4687
|
+
});
|
|
4688
|
+
}
|
|
4689
|
+
function buildJiraCreateFields(exposedFields, fieldDefinitions = [], values = {}) {
|
|
4690
|
+
const knownFieldIds = new Set(
|
|
4691
|
+
fieldDefinitions.map((definition) => normalizeJiraFieldId(definition.id))
|
|
4692
|
+
);
|
|
4693
|
+
return buildJiraFieldViews({
|
|
4694
|
+
exposedFields,
|
|
4695
|
+
fieldDefinitions,
|
|
4696
|
+
values,
|
|
4697
|
+
includeReadonly: false
|
|
4698
|
+
}).filter((field) => knownFieldIds.has(normalizeJiraFieldId(field.id))).filter((field) => !CREATE_UNSUPPORTED_FIELD_IDS.has(normalizeJiraFieldId(field.id)));
|
|
4699
|
+
}
|
|
4700
|
+
const STABLE_ATTR_KEYS = [
|
|
4701
|
+
"data-testid",
|
|
4702
|
+
"data-test",
|
|
4703
|
+
"data-qa",
|
|
4704
|
+
"data-cy",
|
|
4705
|
+
"name",
|
|
4706
|
+
"aria-label"
|
|
4707
|
+
];
|
|
4708
|
+
const STATE_CLASS_RE = /^(is-|has-|js-)|^(active|open|selected|disabled|focus|hover|show|hidden|loading|dragging)$/i;
|
|
4709
|
+
function looksHashed(segment) {
|
|
4710
|
+
if (segment.includes("-")) return false;
|
|
4711
|
+
if (/^[a-f0-9]{6,}$/i.test(segment)) return true;
|
|
4712
|
+
if (segment.length >= 6 && /[a-z]/i.test(segment) && /[0-9]/.test(segment)) return true;
|
|
4713
|
+
if (segment.length >= 5 && !/[_-]/.test(segment) && /[a-z]/.test(segment) && /[A-Z]/.test(segment)) {
|
|
4714
|
+
const vowels = (segment.match(/[aeiou]/gi) ?? []).length;
|
|
4715
|
+
if (vowels / segment.length < 0.25) return true;
|
|
4716
|
+
}
|
|
4717
|
+
return false;
|
|
4718
|
+
}
|
|
4719
|
+
function isUnstableToken(token) {
|
|
4720
|
+
if (!token) return true;
|
|
4721
|
+
if (token.includes(":") || token.includes("/")) return true;
|
|
4722
|
+
if (token.includes("--")) return true;
|
|
4723
|
+
if (/^css-[a-z0-9]+$/i.test(token)) return true;
|
|
4724
|
+
if (/^sc-[a-z0-9]+$/i.test(token)) return true;
|
|
4725
|
+
if (STATE_CLASS_RE.test(token)) return true;
|
|
4726
|
+
const segments = token.split("__");
|
|
4727
|
+
if (segments.length > 1 && looksHashed(segments[segments.length - 1] ?? "")) return true;
|
|
4728
|
+
if (looksHashed(token)) return true;
|
|
4729
|
+
return false;
|
|
3167
4730
|
}
|
|
3168
|
-
function
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
4731
|
+
function isStableClassName(cls) {
|
|
4732
|
+
return !isUnstableToken(cls);
|
|
4733
|
+
}
|
|
4734
|
+
function isUnstableId(id) {
|
|
4735
|
+
if (!id) return true;
|
|
4736
|
+
if (id.includes(":")) return true;
|
|
4737
|
+
if (/^[0-9]/.test(id)) return true;
|
|
4738
|
+
if (/^[a-f0-9]{8,}$/i.test(id)) return true;
|
|
4739
|
+
if (looksHashed(id)) return true;
|
|
4740
|
+
return false;
|
|
4741
|
+
}
|
|
4742
|
+
function cssAttrValue(value) {
|
|
4743
|
+
return value.replace(/(["\\])/g, "\\$1");
|
|
4744
|
+
}
|
|
4745
|
+
function escapeXPathString(value) {
|
|
4746
|
+
if (!value.includes('"')) return `"${value}"`;
|
|
4747
|
+
if (!value.includes("'")) return `'${value}'`;
|
|
4748
|
+
const parts = value.split('"').map((part) => `"${part}"`).join(`,'"',`);
|
|
4749
|
+
return `concat(${parts})`;
|
|
4750
|
+
}
|
|
4751
|
+
function getScrollPosition() {
|
|
4752
|
+
return {
|
|
4753
|
+
x: window.scrollX || window.pageXOffset || 0,
|
|
4754
|
+
y: window.scrollY || window.pageYOffset || 0
|
|
4755
|
+
};
|
|
4756
|
+
}
|
|
4757
|
+
function getViewportSize() {
|
|
4758
|
+
const scroll = getScrollPosition();
|
|
4759
|
+
return {
|
|
4760
|
+
width: window.innerWidth,
|
|
4761
|
+
height: window.innerHeight,
|
|
4762
|
+
scrollX: scroll.x,
|
|
4763
|
+
scrollY: scroll.y
|
|
4764
|
+
};
|
|
4765
|
+
}
|
|
4766
|
+
function matchesUniquely(selector, element) {
|
|
4767
|
+
try {
|
|
4768
|
+
const found = document.querySelectorAll(selector);
|
|
4769
|
+
return found.length === 1 && found[0] === element;
|
|
4770
|
+
} catch {
|
|
4771
|
+
return false;
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
function nthOfType(element) {
|
|
4775
|
+
let count2 = 1;
|
|
4776
|
+
let sibling = element.previousElementSibling;
|
|
4777
|
+
while (sibling) {
|
|
4778
|
+
if (sibling.tagName === element.tagName) count2 += 1;
|
|
4779
|
+
sibling = sibling.previousElementSibling;
|
|
4780
|
+
}
|
|
4781
|
+
return count2;
|
|
4782
|
+
}
|
|
4783
|
+
function stableAnchorSelector(element) {
|
|
4784
|
+
if (element.id && !isUnstableId(element.id)) return `#${CSS.escape(element.id)}`;
|
|
4785
|
+
const tag = element.tagName.toLowerCase();
|
|
4786
|
+
for (const key of STABLE_ATTR_KEYS) {
|
|
4787
|
+
const value = element.getAttribute(key);
|
|
4788
|
+
if (value) return `${tag}[${key}="${cssAttrValue(value)}"]`;
|
|
4789
|
+
}
|
|
4790
|
+
return null;
|
|
4791
|
+
}
|
|
4792
|
+
function structuralSegment(element) {
|
|
4793
|
+
const tag = element.tagName.toLowerCase();
|
|
4794
|
+
const classes = typeof element.className === "string" ? element.className.trim().split(/\s+/).filter(Boolean).filter(isStableClassName).map((c2) => CSS.escape(c2)) : [];
|
|
4795
|
+
const classPart = classes.length ? `.${classes.join(".")}` : "";
|
|
4796
|
+
return `${tag}${classPart}:nth-of-type(${nthOfType(element)})`;
|
|
4797
|
+
}
|
|
4798
|
+
function getElementSelector(element) {
|
|
4799
|
+
if (element.id && !isUnstableId(element.id)) {
|
|
4800
|
+
const idSelector = `#${CSS.escape(element.id)}`;
|
|
4801
|
+
if (matchesUniquely(idSelector, element)) return idSelector;
|
|
4802
|
+
}
|
|
4803
|
+
const attrAnchor = stableAnchorSelector(element);
|
|
4804
|
+
if (attrAnchor && !attrAnchor.startsWith("#") && matchesUniquely(attrAnchor, element)) {
|
|
4805
|
+
return attrAnchor;
|
|
4806
|
+
}
|
|
4807
|
+
const segments = [];
|
|
4808
|
+
let current = element;
|
|
4809
|
+
let depth = 0;
|
|
4810
|
+
const MAX_DEPTH = 5;
|
|
4811
|
+
while (current && current.nodeType === Node.ELEMENT_NODE && depth < MAX_DEPTH) {
|
|
4812
|
+
segments.unshift(structuralSegment(current));
|
|
4813
|
+
const candidate = segments.join(" > ");
|
|
4814
|
+
if (matchesUniquely(candidate, element)) return candidate;
|
|
4815
|
+
const parent = current.parentElement;
|
|
4816
|
+
if (parent) {
|
|
4817
|
+
const parentAnchor = stableAnchorSelector(parent);
|
|
4818
|
+
if (parentAnchor) {
|
|
4819
|
+
const anchored = `${parentAnchor} > ${segments.join(" > ")}`;
|
|
4820
|
+
if (matchesUniquely(anchored, element)) return anchored;
|
|
4821
|
+
return anchored;
|
|
4822
|
+
}
|
|
4823
|
+
if (parent.tagName === "BODY") break;
|
|
3180
4824
|
}
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
4825
|
+
current = parent;
|
|
4826
|
+
depth += 1;
|
|
4827
|
+
}
|
|
4828
|
+
return segments.join(" > ");
|
|
4829
|
+
}
|
|
4830
|
+
function getXPath(element) {
|
|
4831
|
+
if (element.id && !isUnstableId(element.id)) {
|
|
4832
|
+
return `//*[@id=${escapeXPathString(element.id)}]`;
|
|
4833
|
+
}
|
|
4834
|
+
const parts = [];
|
|
4835
|
+
let current = element;
|
|
4836
|
+
while (current && current.nodeType === Node.ELEMENT_NODE && current !== document.body) {
|
|
4837
|
+
if (current !== element && current.id && !isUnstableId(current.id)) {
|
|
4838
|
+
return `//*[@id=${escapeXPathString(current.id)}]/${parts.join("/")}`;
|
|
3188
4839
|
}
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
4840
|
+
let index = 1;
|
|
4841
|
+
let sibling = current.previousElementSibling;
|
|
4842
|
+
while (sibling) {
|
|
4843
|
+
if (sibling.tagName === current.tagName) index += 1;
|
|
4844
|
+
sibling = sibling.previousElementSibling;
|
|
4845
|
+
}
|
|
4846
|
+
parts.unshift(`${current.tagName.toLowerCase()}[${index}]`);
|
|
4847
|
+
current = current.parentElement;
|
|
4848
|
+
}
|
|
4849
|
+
return parts.length ? `/html/body/${parts.join("/")}` : "/html/body";
|
|
4850
|
+
}
|
|
4851
|
+
function getElementPosition(element, event) {
|
|
4852
|
+
const rect = element.getBoundingClientRect();
|
|
4853
|
+
const scroll = getScrollPosition();
|
|
4854
|
+
return {
|
|
4855
|
+
x: event ? event.clientX + scroll.x : rect.left + scroll.x,
|
|
4856
|
+
y: event ? event.clientY + scroll.y : rect.top + scroll.y,
|
|
4857
|
+
selector: getElementSelector(element),
|
|
4858
|
+
xpath: getXPath(element),
|
|
4859
|
+
viewport: getViewportSize(),
|
|
4860
|
+
elementRect: {
|
|
4861
|
+
left: rect.left + scroll.x,
|
|
4862
|
+
top: rect.top + scroll.y,
|
|
4863
|
+
width: rect.width,
|
|
4864
|
+
height: rect.height
|
|
4865
|
+
}
|
|
4866
|
+
};
|
|
4867
|
+
}
|
|
4868
|
+
function findElementByXPath(xpath) {
|
|
4869
|
+
if (!xpath || typeof xpath !== "string") {
|
|
4870
|
+
return null;
|
|
4871
|
+
}
|
|
4872
|
+
try {
|
|
4873
|
+
const result = document.evaluate(
|
|
4874
|
+
xpath,
|
|
4875
|
+
document,
|
|
4876
|
+
null,
|
|
4877
|
+
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
4878
|
+
null
|
|
4879
|
+
);
|
|
4880
|
+
const node = result.singleNodeValue;
|
|
4881
|
+
return node instanceof HTMLElement ? node : null;
|
|
4882
|
+
} catch {
|
|
4883
|
+
return null;
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
function findElementBySelector(selector) {
|
|
4887
|
+
try {
|
|
4888
|
+
const element = document.querySelector(selector);
|
|
4889
|
+
if (element instanceof HTMLElement) {
|
|
4890
|
+
return element;
|
|
4891
|
+
}
|
|
4892
|
+
} catch {
|
|
4893
|
+
}
|
|
4894
|
+
if (selector.startsWith("//") || selector.startsWith("/")) {
|
|
4895
|
+
return findElementByXPath(selector);
|
|
4896
|
+
}
|
|
4897
|
+
return null;
|
|
4898
|
+
}
|
|
4899
|
+
const ISSUE_PAYLOAD_LIMIT_BYTES = 3 * 1024 * 1024;
|
|
4900
|
+
const ISSUE_PAYLOAD_SAFETY_MARGIN_BYTES = 128 * 1024;
|
|
4901
|
+
const ISSUE_PAYLOAD_BUDGET_BYTES = ISSUE_PAYLOAD_LIMIT_BYTES - ISSUE_PAYLOAD_SAFETY_MARGIN_BYTES;
|
|
4902
|
+
function measureJsonBytes(value) {
|
|
4903
|
+
try {
|
|
4904
|
+
return getUtf8ByteLength(JSON.stringify(value) ?? "");
|
|
4905
|
+
} catch {
|
|
4906
|
+
return Number.POSITIVE_INFINITY;
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
function bodySize(log, field) {
|
|
4910
|
+
const body = log[field];
|
|
4911
|
+
if (body == null) return 0;
|
|
4912
|
+
return field === "responseBody" ? getCapturedBodySizeBytes(body, log.responseBodyEncoding) : getUtf8ByteLength(body);
|
|
4913
|
+
}
|
|
4914
|
+
function shrinkNetworkLogsToFit(networkLogs, otherPayloadBytes, budgetBytes = ISSUE_PAYLOAD_BUDGET_BYTES) {
|
|
4915
|
+
const logs = [...networkLogs];
|
|
4916
|
+
let droppedBodyCount = 0;
|
|
4917
|
+
const totalBytes = () => otherPayloadBytes + measureJsonBytes(logs);
|
|
4918
|
+
if (totalBytes() <= budgetBytes) {
|
|
4919
|
+
return { networkLogs: logs, droppedBodyCount: 0, stillOverBudget: false };
|
|
4920
|
+
}
|
|
4921
|
+
const candidates = logs.flatMap(
|
|
4922
|
+
(log, index) => ["responseBody", "requestBody"].filter((field) => log[field] != null).map((field) => ({ index, field, sizeBytes: bodySize(log, field) }))
|
|
4923
|
+
).sort((a2, b) => b.sizeBytes - a2.sizeBytes || a2.index - b.index);
|
|
3198
4924
|
for (const candidate of candidates) {
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
4925
|
+
const log = logs[candidate.index];
|
|
4926
|
+
if (!log) continue;
|
|
4927
|
+
logs[candidate.index] = omitNetworkLogBody(
|
|
4928
|
+
log,
|
|
4929
|
+
candidate.field,
|
|
4930
|
+
"transport-budget",
|
|
4931
|
+
candidate.sizeBytes
|
|
4932
|
+
);
|
|
4933
|
+
droppedBodyCount += 1;
|
|
4934
|
+
if (totalBytes() <= budgetBytes) {
|
|
4935
|
+
return { networkLogs: logs, droppedBodyCount, stillOverBudget: false };
|
|
3204
4936
|
}
|
|
3205
|
-
usedBytes += candidate.sizeBytes;
|
|
3206
4937
|
}
|
|
3207
|
-
return
|
|
4938
|
+
return { networkLogs: logs, droppedBodyCount, stillOverBudget: totalBytes() > budgetBytes };
|
|
3208
4939
|
}
|
|
3209
4940
|
const READONLY_NOTION_TYPES = /* @__PURE__ */ new Set([
|
|
3210
4941
|
"created_time",
|
|
@@ -3413,53 +5144,21 @@ const Input = React.forwardRef(
|
|
|
3413
5144
|
type,
|
|
3414
5145
|
ref,
|
|
3415
5146
|
className: cn(
|
|
3416
|
-
"flex h-11 w-full rounded-md border border-line-subtle bg-surface-1 px-4 text-sm text-text-primary",
|
|
3417
|
-
"transition-[border-color,outline-color,box-shadow,background-color,transform] duration-fast ease-out",
|
|
3418
|
-
"placeholder:text-text-tertiary",
|
|
3419
|
-
"outline outline-2 outline-offset-0 outline-transparent",
|
|
3420
|
-
"focus-visible:border-transparent focus-visible:outline-line-focus focus-visible:bg-surface-1",
|
|
3421
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
3422
|
-
"file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-text-primary",
|
|
3423
|
-
className
|
|
3424
|
-
),
|
|
3425
|
-
...props
|
|
3426
|
-
}
|
|
3427
|
-
);
|
|
3428
|
-
}
|
|
3429
|
-
);
|
|
3430
|
-
Input.displayName = "Input";
|
|
3431
|
-
var NAME = "Label";
|
|
3432
|
-
var Label$1 = React.forwardRef((props, forwardedRef) => {
|
|
3433
|
-
return /* @__PURE__ */ jsx(
|
|
3434
|
-
Primitive.label,
|
|
3435
|
-
{
|
|
3436
|
-
...props,
|
|
3437
|
-
ref: forwardedRef,
|
|
3438
|
-
onMouseDown: (event) => {
|
|
3439
|
-
var _a;
|
|
3440
|
-
const target = event.target;
|
|
3441
|
-
if (target.closest("button, input, select, textarea")) return;
|
|
3442
|
-
(_a = props.onMouseDown) == null ? void 0 : _a.call(props, event);
|
|
3443
|
-
if (!event.defaultPrevented && event.detail > 1) event.preventDefault();
|
|
3444
|
-
}
|
|
3445
|
-
}
|
|
3446
|
-
);
|
|
3447
|
-
});
|
|
3448
|
-
Label$1.displayName = NAME;
|
|
3449
|
-
var Root$1 = Label$1;
|
|
3450
|
-
const Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
3451
|
-
Root$1,
|
|
3452
|
-
{
|
|
3453
|
-
ref,
|
|
3454
|
-
className: cn(
|
|
3455
|
-
"text-xs font-medium leading-none text-text-secondary",
|
|
3456
|
-
"peer-disabled:cursor-not-allowed peer-disabled:opacity-60",
|
|
3457
|
-
className
|
|
3458
|
-
),
|
|
3459
|
-
...props
|
|
5147
|
+
"flex h-11 w-full rounded-md border border-line-subtle bg-surface-1 px-4 text-sm text-text-primary",
|
|
5148
|
+
"transition-[border-color,outline-color,box-shadow,background-color,transform] duration-fast ease-out",
|
|
5149
|
+
"placeholder:text-text-tertiary",
|
|
5150
|
+
"outline outline-2 outline-offset-0 outline-transparent",
|
|
5151
|
+
"focus-visible:border-transparent focus-visible:outline-line-focus focus-visible:bg-surface-1",
|
|
5152
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
5153
|
+
"file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-text-primary",
|
|
5154
|
+
className
|
|
5155
|
+
),
|
|
5156
|
+
...props
|
|
5157
|
+
}
|
|
5158
|
+
);
|
|
3460
5159
|
}
|
|
3461
|
-
)
|
|
3462
|
-
|
|
5160
|
+
);
|
|
5161
|
+
Input.displayName = "Input";
|
|
3463
5162
|
var count = 0;
|
|
3464
5163
|
var guards = null;
|
|
3465
5164
|
function useFocusGuards() {
|
|
@@ -4778,6 +6477,38 @@ const PopoverContent = React.forwardRef(({ className, align = "center", sideOffs
|
|
|
4778
6477
|
) });
|
|
4779
6478
|
});
|
|
4780
6479
|
PopoverContent.displayName = Content2.displayName;
|
|
6480
|
+
var NAME = "Label";
|
|
6481
|
+
var Label$1 = React.forwardRef((props, forwardedRef) => {
|
|
6482
|
+
return /* @__PURE__ */ jsx(
|
|
6483
|
+
Primitive.label,
|
|
6484
|
+
{
|
|
6485
|
+
...props,
|
|
6486
|
+
ref: forwardedRef,
|
|
6487
|
+
onMouseDown: (event) => {
|
|
6488
|
+
var _a;
|
|
6489
|
+
const target = event.target;
|
|
6490
|
+
if (target.closest("button, input, select, textarea")) return;
|
|
6491
|
+
(_a = props.onMouseDown) == null ? void 0 : _a.call(props, event);
|
|
6492
|
+
if (!event.defaultPrevented && event.detail > 1) event.preventDefault();
|
|
6493
|
+
}
|
|
6494
|
+
}
|
|
6495
|
+
);
|
|
6496
|
+
});
|
|
6497
|
+
Label$1.displayName = NAME;
|
|
6498
|
+
var Root$1 = Label$1;
|
|
6499
|
+
const Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
6500
|
+
Root$1,
|
|
6501
|
+
{
|
|
6502
|
+
ref,
|
|
6503
|
+
className: cn(
|
|
6504
|
+
"text-xs font-medium leading-none text-text-secondary",
|
|
6505
|
+
"peer-disabled:cursor-not-allowed peer-disabled:opacity-60",
|
|
6506
|
+
className
|
|
6507
|
+
),
|
|
6508
|
+
...props
|
|
6509
|
+
}
|
|
6510
|
+
));
|
|
6511
|
+
Label.displayName = Root$1.displayName;
|
|
4781
6512
|
function clamp$1(value, [min2, max2]) {
|
|
4782
6513
|
return Math.min(max2, Math.max(min2, value));
|
|
4783
6514
|
}
|
|
@@ -6204,10 +7935,7 @@ const SelectLabel = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
|
|
|
6204
7935
|
SelectLabel$1,
|
|
6205
7936
|
{
|
|
6206
7937
|
ref,
|
|
6207
|
-
className: cn(
|
|
6208
|
-
"px-3 py-2 font-mono text-2xs uppercase tracking-[0.14em] text-text-tertiary",
|
|
6209
|
-
className
|
|
6210
|
-
),
|
|
7938
|
+
className: cn("px-3 py-2 font-mono text-2xs uppercase text-text-tertiary", className),
|
|
6211
7939
|
...props
|
|
6212
7940
|
}
|
|
6213
7941
|
));
|
|
@@ -6284,7 +8012,7 @@ const cva = (base, config) => (props) => {
|
|
|
6284
8012
|
const buttonVariants = cva(
|
|
6285
8013
|
[
|
|
6286
8014
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap",
|
|
6287
|
-
"font-medium select-none
|
|
8015
|
+
"font-medium select-none",
|
|
6288
8016
|
"transition-[background-color,color,border-color,box-shadow,opacity]",
|
|
6289
8017
|
"duration-fast ease-out",
|
|
6290
8018
|
"focus-visible:outline-none focus-visible:shadow-focus",
|
|
@@ -10592,7 +12320,7 @@ function Calendar({
|
|
|
10592
12320
|
RDP_DEFAULTS.month_caption
|
|
10593
12321
|
),
|
|
10594
12322
|
caption_label: cn(
|
|
10595
|
-
"select-none text-sm font-semibold
|
|
12323
|
+
"select-none text-sm font-semibold text-text-primary",
|
|
10596
12324
|
RDP_DEFAULTS.caption_label
|
|
10597
12325
|
),
|
|
10598
12326
|
table: "mt-1 w-full border-collapse",
|
|
@@ -10791,7 +12519,7 @@ const DEFAULT_LABELS = {
|
|
|
10791
12519
|
skipConfirmTitle: "가이드를 건너뛸까요?",
|
|
10792
12520
|
// 표면마다 진입점이 다르다(admin=헤더 아이콘, extension=더보기 메뉴, sdk=FAB 메뉴).
|
|
10793
12521
|
// 위치를 특정하면 어느 한쪽에서는 틀린 안내가 되므로 라벨만 가리킨다.
|
|
10794
|
-
skipConfirmBody: "'가이드 다시 보기'를
|
|
12522
|
+
skipConfirmBody: "'가이드 다시 보기'를 클릭하면 언제든 다시 열 수 있습니다.",
|
|
10795
12523
|
skipConfirmCancel: "계속 보기",
|
|
10796
12524
|
skipConfirmConfirm: "건너뛰기",
|
|
10797
12525
|
previous: "이전",
|
|
@@ -10799,7 +12527,7 @@ const DEFAULT_LABELS = {
|
|
|
10799
12527
|
done: "완료",
|
|
10800
12528
|
clickHint: "강조된 곳을 클릭하세요"
|
|
10801
12529
|
};
|
|
10802
|
-
const MASK_PADDING =
|
|
12530
|
+
const MASK_PADDING = 6;
|
|
10803
12531
|
const CUTOUT_RADIUS = 12;
|
|
10804
12532
|
const POPOVER_WIDTH = 336;
|
|
10805
12533
|
const POPOVER_FALLBACK_HEIGHT = 190;
|
|
@@ -10835,6 +12563,19 @@ function getElementRect(element) {
|
|
|
10835
12563
|
height: rect.height
|
|
10836
12564
|
};
|
|
10837
12565
|
}
|
|
12566
|
+
function getElementRadius(element) {
|
|
12567
|
+
if (!element || typeof window === "undefined") return null;
|
|
12568
|
+
const style = window.getComputedStyle(element);
|
|
12569
|
+
const raw = style.borderTopLeftRadius || style.borderRadius.trim().split(/[\s/]+/)[0] || "";
|
|
12570
|
+
if (!raw) return null;
|
|
12571
|
+
const parsed = Number.parseFloat(raw);
|
|
12572
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return null;
|
|
12573
|
+
if (!raw.trim().endsWith("%")) return parsed;
|
|
12574
|
+
const rect = element.getBoundingClientRect();
|
|
12575
|
+
const shorterSide = Math.min(rect.width, rect.height);
|
|
12576
|
+
if (shorterSide <= 0) return null;
|
|
12577
|
+
return parsed / 100 * shorterSide;
|
|
12578
|
+
}
|
|
10838
12579
|
function padRect(rect, padding) {
|
|
10839
12580
|
const viewportWidth = window.innerWidth;
|
|
10840
12581
|
const viewportHeight = window.innerHeight;
|
|
@@ -11043,6 +12784,7 @@ function OnboardingPopoverContent({
|
|
|
11043
12784
|
function OnboardingOverlay({
|
|
11044
12785
|
step,
|
|
11045
12786
|
targetRect,
|
|
12787
|
+
targetRadius,
|
|
11046
12788
|
className,
|
|
11047
12789
|
labels,
|
|
11048
12790
|
isPopoverVisible,
|
|
@@ -11055,7 +12797,11 @@ function OnboardingOverlay({
|
|
|
11055
12797
|
const maskId = React.useId().replace(/:/g, "");
|
|
11056
12798
|
const [isSkipConfirmOpen, setIsSkipConfirmOpen] = React.useState(false);
|
|
11057
12799
|
const cutoutRect = targetRect ? padRect(targetRect, MASK_PADDING) : null;
|
|
11058
|
-
const cutoutRadius = cutoutRect ? Math.min(
|
|
12800
|
+
const cutoutRadius = cutoutRect ? Math.min(
|
|
12801
|
+
targetRadius === null || targetRadius === void 0 ? CUTOUT_RADIUS : targetRadius + MASK_PADDING,
|
|
12802
|
+
cutoutRect.width / 2,
|
|
12803
|
+
cutoutRect.height / 2
|
|
12804
|
+
) : 0;
|
|
11059
12805
|
const [popoverLeft, popoverTop] = getPopoverCoordinates(
|
|
11060
12806
|
step.position ?? (cutoutRect ? "bottom" : "center"),
|
|
11061
12807
|
cutoutRect ?? getViewportRect()
|
|
@@ -11210,6 +12956,13 @@ function OnboardingOverlay({
|
|
|
11210
12956
|
);
|
|
11211
12957
|
}
|
|
11212
12958
|
const DEFAULT_ONBOARDING_STORAGE_VERSION = 1;
|
|
12959
|
+
function createLocalStorageOnboardingAdapter(input) {
|
|
12960
|
+
return {
|
|
12961
|
+
hasSeen: () => hasSeenOnboarding(input),
|
|
12962
|
+
write: (status) => writeOnboardingStorage(input, status),
|
|
12963
|
+
reset: () => resetOnboardingStorage(input)
|
|
12964
|
+
};
|
|
12965
|
+
}
|
|
11213
12966
|
function buildOnboardingStorageKey({
|
|
11214
12967
|
surface,
|
|
11215
12968
|
flowId,
|
|
@@ -11287,12 +13040,14 @@ function OnboardingProvider({
|
|
|
11287
13040
|
currentRoute,
|
|
11288
13041
|
onNavigate,
|
|
11289
13042
|
className,
|
|
11290
|
-
labels: labelOverrides
|
|
13043
|
+
labels: labelOverrides,
|
|
13044
|
+
storage: storageOverride,
|
|
13045
|
+
suppressed = false
|
|
11291
13046
|
}) {
|
|
11292
13047
|
const labels = React.useMemo(() => ({ ...DEFAULT_LABELS, ...labelOverrides }), [labelOverrides]);
|
|
11293
|
-
const
|
|
11294
|
-
() => ({ surface, flowId, version }),
|
|
11295
|
-
[surface, flowId, version]
|
|
13048
|
+
const storage = React.useMemo(
|
|
13049
|
+
() => storageOverride ?? createLocalStorageOnboardingAdapter({ surface, flowId, version }),
|
|
13050
|
+
[storageOverride, surface, flowId, version]
|
|
11296
13051
|
);
|
|
11297
13052
|
const onBeforeStartRef = React.useRef(onBeforeStart);
|
|
11298
13053
|
onBeforeStartRef.current = onBeforeStart;
|
|
@@ -11402,11 +13157,11 @@ function OnboardingProvider({
|
|
|
11402
13157
|
const start = React.useCallback(
|
|
11403
13158
|
(options) => {
|
|
11404
13159
|
if (steps.length === 0) return;
|
|
11405
|
-
if (!(options == null ? void 0 : options.force) &&
|
|
11406
|
-
if (options == null ? void 0 : options.force)
|
|
13160
|
+
if (!(options == null ? void 0 : options.force) && storage.hasSeen()) return;
|
|
13161
|
+
if (options == null ? void 0 : options.force) storage.reset();
|
|
11407
13162
|
openAtStep(getStepIndexById(steps, options == null ? void 0 : options.stepId));
|
|
11408
13163
|
},
|
|
11409
|
-
[openAtStep, steps,
|
|
13164
|
+
[openAtStep, steps, storage]
|
|
11410
13165
|
);
|
|
11411
13166
|
const closeFlow = React.useCallback(
|
|
11412
13167
|
(afterClose) => {
|
|
@@ -11422,13 +13177,13 @@ function OnboardingProvider({
|
|
|
11422
13177
|
[clearCloseTimeout, clearStepTransitionTimeout]
|
|
11423
13178
|
);
|
|
11424
13179
|
const complete = React.useCallback(() => {
|
|
11425
|
-
|
|
13180
|
+
storage.write("completed");
|
|
11426
13181
|
closeFlow();
|
|
11427
|
-
}, [closeFlow,
|
|
13182
|
+
}, [closeFlow, storage]);
|
|
11428
13183
|
const dismiss2 = React.useCallback(() => {
|
|
11429
|
-
|
|
13184
|
+
storage.write("dismissed");
|
|
11430
13185
|
closeFlow();
|
|
11431
|
-
}, [closeFlow,
|
|
13186
|
+
}, [closeFlow, storage]);
|
|
11432
13187
|
const transitionToStep = React.useCallback(
|
|
11433
13188
|
(nextIndex) => {
|
|
11434
13189
|
if (!steps[nextIndex]) return;
|
|
@@ -11473,9 +13228,9 @@ function OnboardingProvider({
|
|
|
11473
13228
|
[steps, transitionToStep]
|
|
11474
13229
|
);
|
|
11475
13230
|
React.useEffect(() => {
|
|
11476
|
-
if (!autoStart || steps.length === 0 ||
|
|
13231
|
+
if (!autoStart || steps.length === 0 || storage.hasSeen()) return;
|
|
11477
13232
|
openAtStep(0);
|
|
11478
|
-
}, [autoStart, openAtStep, steps.length,
|
|
13233
|
+
}, [autoStart, openAtStep, steps.length, storage]);
|
|
11479
13234
|
React.useEffect(() => {
|
|
11480
13235
|
var _a, _b;
|
|
11481
13236
|
const previousStepId = previousStepIdRef.current;
|
|
@@ -11568,7 +13323,8 @@ function OnboardingProvider({
|
|
|
11568
13323
|
);
|
|
11569
13324
|
const allTargetsResolved = activeTargetIds.length > 0 && resolvedTargetRects.every((rect) => rect !== null);
|
|
11570
13325
|
const targetRect = isOpen && isActiveRoute && allTargetsResolved ? unionRect(resolvedTargetRects.filter((r2) => r2 !== null)) : null;
|
|
11571
|
-
const
|
|
13326
|
+
const targetRadius = activeTargetIds.length === 1 ? getElementRadius(targetMapRef.current.get(activeTargetIds[0]) ?? null) : null;
|
|
13327
|
+
const shouldRenderOverlay = isOpen && activeStep && isActiveRoute && !suppressed && !activeStep.silent && (!stepHasTarget || targetRect);
|
|
11572
13328
|
const isTargetReady = !stepHasTarget || targetRect !== null;
|
|
11573
13329
|
React.useEffect(() => {
|
|
11574
13330
|
setIsPopoverVisible(false);
|
|
@@ -11596,9 +13352,19 @@ function OnboardingProvider({
|
|
|
11596
13352
|
dismiss: dismiss2,
|
|
11597
13353
|
complete,
|
|
11598
13354
|
isOpen,
|
|
11599
|
-
currentStep: isOpen ? activeStep : null
|
|
13355
|
+
currentStep: isOpen ? activeStep : null,
|
|
13356
|
+
hasSteps: steps.length > 0
|
|
11600
13357
|
}),
|
|
11601
|
-
[
|
|
13358
|
+
[
|
|
13359
|
+
activeStep,
|
|
13360
|
+
advanceFromTargetClick,
|
|
13361
|
+
complete,
|
|
13362
|
+
dismiss2,
|
|
13363
|
+
isOpen,
|
|
13364
|
+
registerTarget,
|
|
13365
|
+
start,
|
|
13366
|
+
steps.length
|
|
13367
|
+
]
|
|
11602
13368
|
);
|
|
11603
13369
|
return /* @__PURE__ */ jsxs(OnboardingContext.Provider, { value: contextValue, children: [
|
|
11604
13370
|
children,
|
|
@@ -11608,6 +13374,7 @@ function OnboardingProvider({
|
|
|
11608
13374
|
{
|
|
11609
13375
|
step: activeStep,
|
|
11610
13376
|
targetRect,
|
|
13377
|
+
targetRadius,
|
|
11611
13378
|
className,
|
|
11612
13379
|
labels,
|
|
11613
13380
|
isPopoverVisible,
|
|
@@ -11627,678 +13394,239 @@ function useOnboardingTarget(id) {
|
|
|
11627
13394
|
(node) => {
|
|
11628
13395
|
registerTarget == null ? void 0 : registerTarget(id, node);
|
|
11629
13396
|
},
|
|
11630
|
-
[id, registerTarget]
|
|
11631
|
-
);
|
|
11632
|
-
}
|
|
11633
|
-
function useOnboardingFlow() {
|
|
11634
|
-
const context = React.useContext(OnboardingContext);
|
|
11635
|
-
return React.useMemo(
|
|
11636
|
-
() => ({
|
|
11637
|
-
start: (context == null ? void 0 : context.start) ?? (() => void 0),
|
|
11638
|
-
replay: (stepId) => context == null ? void 0 : context.start({ force: true, stepId }),
|
|
11639
|
-
advance: (context == null ? void 0 : context.advance) ?? (() => void 0),
|
|
11640
|
-
dismiss: (context == null ? void 0 : context.dismiss) ?? (() => void 0),
|
|
11641
|
-
complete: (context == null ? void 0 : context.complete) ?? (() => void 0),
|
|
11642
|
-
isOpen: (context == null ? void 0 : context.isOpen) ?? false,
|
|
11643
|
-
currentStep: (context == null ? void 0 : context.currentStep) ?? null
|
|
11644
|
-
}),
|
|
11645
|
-
[context]
|
|
11646
|
-
);
|
|
11647
|
-
}
|
|
11648
|
-
let nextId = 0;
|
|
11649
|
-
let toasts = [];
|
|
11650
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
11651
|
-
function emit() {
|
|
11652
|
-
listeners.forEach((l2) => l2());
|
|
11653
|
-
}
|
|
11654
|
-
function addToast(message2, type, options) {
|
|
11655
|
-
const id = `toast-${++nextId}`;
|
|
11656
|
-
const t2 = {
|
|
11657
|
-
id,
|
|
11658
|
-
message: message2,
|
|
11659
|
-
type,
|
|
11660
|
-
action: options == null ? void 0 : options.action,
|
|
11661
|
-
duration: (options == null ? void 0 : options.duration) ?? 2e3,
|
|
11662
|
-
createdAt: Date.now()
|
|
11663
|
-
};
|
|
11664
|
-
toasts = [...toasts, t2];
|
|
11665
|
-
emit();
|
|
11666
|
-
return id;
|
|
11667
|
-
}
|
|
11668
|
-
function dismiss(id) {
|
|
11669
|
-
toasts = toasts.filter((t2) => t2.id !== id);
|
|
11670
|
-
emit();
|
|
11671
|
-
}
|
|
11672
|
-
function dismissAll() {
|
|
11673
|
-
toasts = [];
|
|
11674
|
-
emit();
|
|
11675
|
-
}
|
|
11676
|
-
function toast(message2, options) {
|
|
11677
|
-
return addToast(message2, "default", options);
|
|
11678
|
-
}
|
|
11679
|
-
toast.success = (message2, options) => addToast(message2, "success", options);
|
|
11680
|
-
toast.warning = (message2, options) => addToast(message2, "warning", options);
|
|
11681
|
-
toast.error = (message2, options) => addToast(message2, "error", options);
|
|
11682
|
-
toast.info = (message2, options) => addToast(message2, "info", options);
|
|
11683
|
-
toast.dismiss = dismiss;
|
|
11684
|
-
toast.dismissAll = dismissAll;
|
|
11685
|
-
function subscribe(listener) {
|
|
11686
|
-
listeners.add(listener);
|
|
11687
|
-
return () => listeners.delete(listener);
|
|
11688
|
-
}
|
|
11689
|
-
function getSnapshot() {
|
|
11690
|
-
return toasts;
|
|
11691
|
-
}
|
|
11692
|
-
({
|
|
11693
|
-
type: [NetworkLogType.XMLHTTPREQUEST, NetworkLogType.FETCH]
|
|
11694
|
-
});
|
|
11695
|
-
function hasFilledValue(value) {
|
|
11696
|
-
return value !== void 0 && value !== null && value !== "";
|
|
11697
|
-
}
|
|
11698
|
-
function getFilledValues(values) {
|
|
11699
|
-
return Object.fromEntries(
|
|
11700
|
-
Object.entries(values ?? {}).filter(([, value]) => hasFilledValue(value))
|
|
11701
|
-
);
|
|
11702
|
-
}
|
|
11703
|
-
async function syncExternalIssueIntegrations({
|
|
11704
|
-
issueId,
|
|
11705
|
-
title,
|
|
11706
|
-
description,
|
|
11707
|
-
url,
|
|
11708
|
-
screenshotUrl,
|
|
11709
|
-
videoUrl,
|
|
11710
|
-
jira,
|
|
11711
|
-
notion,
|
|
11712
|
-
customFieldValues,
|
|
11713
|
-
upsertCustomFieldValue,
|
|
11714
|
-
onError
|
|
11715
|
-
}) {
|
|
11716
|
-
const failed = /* @__PURE__ */ new Set();
|
|
11717
|
-
const record2 = (service, error) => {
|
|
11718
|
-
failed.add(service);
|
|
11719
|
-
onError == null ? void 0 : onError(service, error);
|
|
11720
|
-
};
|
|
11721
|
-
if (jira == null ? void 0 : jira.enabled) {
|
|
11722
|
-
await jira.createIssue({
|
|
11723
|
-
issueId,
|
|
11724
|
-
title,
|
|
11725
|
-
description,
|
|
11726
|
-
url,
|
|
11727
|
-
screenshotUrl,
|
|
11728
|
-
videoUrl,
|
|
11729
|
-
fields: jira.fields ?? {}
|
|
11730
|
-
}).catch((error) => record2("jira", error));
|
|
11731
|
-
}
|
|
11732
|
-
if (notion == null ? void 0 : notion.enabled) {
|
|
11733
|
-
await notion.createPage({
|
|
11734
|
-
issueId,
|
|
11735
|
-
properties: notion.properties ?? {}
|
|
11736
|
-
}).catch((error) => record2("notion", error));
|
|
11737
|
-
}
|
|
11738
|
-
if (upsertCustomFieldValue) {
|
|
11739
|
-
await Promise.all(
|
|
11740
|
-
Object.entries(getFilledValues(customFieldValues)).map(
|
|
11741
|
-
([fieldDefinitionId, value]) => upsertCustomFieldValue(fieldDefinitionId, value).catch(
|
|
11742
|
-
(error) => record2("custom-field", error)
|
|
11743
|
-
)
|
|
11744
|
-
)
|
|
11745
|
-
);
|
|
11746
|
-
}
|
|
11747
|
-
return {
|
|
11748
|
-
failedServices: EXTERNAL_INTEGRATION_SERVICES.filter((service) => failed.has(service))
|
|
11749
|
-
};
|
|
11750
|
-
}
|
|
11751
|
-
const __vite_import_meta_env__ = { "DEV": false };
|
|
11752
|
-
const ISSUE_STICKER_INTERNAL_LOG_PREFIX = "__ISSUE_STICKER_INTERNAL_DEBUG__";
|
|
11753
|
-
function isDevLoggingEnabled() {
|
|
11754
|
-
const env = __vite_import_meta_env__;
|
|
11755
|
-
{
|
|
11756
|
-
return env.DEV;
|
|
11757
|
-
}
|
|
11758
|
-
}
|
|
11759
|
-
function isInternalDebugLog(message2) {
|
|
11760
|
-
return Boolean(message2 == null ? void 0 : message2.includes(ISSUE_STICKER_INTERNAL_LOG_PREFIX));
|
|
11761
|
-
}
|
|
11762
|
-
function write(method, scope, args) {
|
|
11763
|
-
if (!isDevLoggingEnabled()) {
|
|
11764
|
-
return;
|
|
11765
|
-
}
|
|
11766
|
-
const log = console[method];
|
|
11767
|
-
log(ISSUE_STICKER_INTERNAL_LOG_PREFIX, `[${scope}]`, ...args);
|
|
11768
|
-
}
|
|
11769
|
-
const devLogger = {
|
|
11770
|
-
log(scope, ...args) {
|
|
11771
|
-
write("log", scope, args);
|
|
11772
|
-
},
|
|
11773
|
-
info(scope, ...args) {
|
|
11774
|
-
write("info", scope, args);
|
|
11775
|
-
},
|
|
11776
|
-
warn(scope, ...args) {
|
|
11777
|
-
write("warn", scope, args);
|
|
11778
|
-
},
|
|
11779
|
-
error(scope, ...args) {
|
|
11780
|
-
write("error", scope, args);
|
|
11781
|
-
},
|
|
11782
|
-
debug(scope, ...args) {
|
|
11783
|
-
write("debug", scope, args);
|
|
11784
|
-
}
|
|
11785
|
-
};
|
|
11786
|
-
function buildGuestOwnerQuery(mode, externalUserId) {
|
|
11787
|
-
if (mode !== "public") return "";
|
|
11788
|
-
if (!externalUserId) return "";
|
|
11789
|
-
return `?external_user_id=${encodeURIComponent(externalUserId)}`;
|
|
11790
|
-
}
|
|
11791
|
-
function extractDomain(url) {
|
|
11792
|
-
try {
|
|
11793
|
-
if (!url.includes("://")) {
|
|
11794
|
-
return null;
|
|
11795
|
-
}
|
|
11796
|
-
const urlObj = new URL(url);
|
|
11797
|
-
return urlObj.hostname;
|
|
11798
|
-
} catch (error) {
|
|
11799
|
-
devLogger.error("domainChecker", "URL 파싱 실패:", url, error);
|
|
11800
|
-
return null;
|
|
11801
|
-
}
|
|
11802
|
-
}
|
|
11803
|
-
function matchWildcardDomain(pattern, domain) {
|
|
11804
|
-
if (!pattern.includes("*")) {
|
|
11805
|
-
return pattern === domain;
|
|
11806
|
-
}
|
|
11807
|
-
const patternParts = pattern.split("*");
|
|
11808
|
-
if (patternParts.length !== 2) {
|
|
11809
|
-
return pattern === domain;
|
|
11810
|
-
}
|
|
11811
|
-
const suffix = patternParts[1];
|
|
11812
|
-
if (suffix.startsWith(".")) {
|
|
11813
|
-
return domain.endsWith(suffix) || domain === suffix.slice(1);
|
|
11814
|
-
} else {
|
|
11815
|
-
return domain.endsWith(suffix) || domain === suffix;
|
|
11816
|
-
}
|
|
11817
|
-
}
|
|
11818
|
-
const ISSUE_STICKER_API_DOMAINS = ["api.issuesticker.com", "api.dev.issuesticker.com"];
|
|
11819
|
-
const ISSUE_STICKER_API_ORIGINS = [
|
|
11820
|
-
"https://api.issuesticker.com",
|
|
11821
|
-
"https://api.dev.issuesticker.com"
|
|
11822
|
-
];
|
|
11823
|
-
const ISSUE_STICKER_CLIENT_INFO_VALUES = /* @__PURE__ */ new Set(["issue-sticker-extension", "issue-sticker-sdk"]);
|
|
11824
|
-
function readConfiguredApiOrigin() {
|
|
11825
|
-
const baseUrl = "https://api.issuesticker.com";
|
|
11826
|
-
if (baseUrl.trim().length === 0) {
|
|
11827
|
-
return null;
|
|
11828
|
-
}
|
|
11829
|
-
try {
|
|
11830
|
-
return new URL(baseUrl).origin;
|
|
11831
|
-
} catch {
|
|
11832
|
-
return null;
|
|
11833
|
-
}
|
|
11834
|
-
}
|
|
11835
|
-
function isIssueStickerApiUrl(url) {
|
|
11836
|
-
try {
|
|
11837
|
-
const origin = new URL(url).origin;
|
|
11838
|
-
const configuredOrigin = readConfiguredApiOrigin();
|
|
11839
|
-
return ISSUE_STICKER_API_ORIGINS.includes(origin) || configuredOrigin !== null && origin === configuredOrigin;
|
|
11840
|
-
} catch {
|
|
11841
|
-
return false;
|
|
11842
|
-
}
|
|
11843
|
-
}
|
|
11844
|
-
function getHeaderValue$1(headers, headerName) {
|
|
11845
|
-
if (!headers) {
|
|
11846
|
-
return void 0;
|
|
11847
|
-
}
|
|
11848
|
-
const target = headerName.toLowerCase();
|
|
11849
|
-
const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === target);
|
|
11850
|
-
return entry == null ? void 0 : entry[1];
|
|
11851
|
-
}
|
|
11852
|
-
function hasIssueStickerClientInfo(headers) {
|
|
11853
|
-
const clientInfo = getHeaderValue$1(headers, "x-client-info");
|
|
11854
|
-
return Boolean(clientInfo && ISSUE_STICKER_CLIENT_INFO_VALUES.has(clientInfo));
|
|
11855
|
-
}
|
|
11856
|
-
function isInternalConsoleLog(log) {
|
|
11857
|
-
return isInternalDebugLog(log.message);
|
|
11858
|
-
}
|
|
11859
|
-
function isIssueStickerInternalConsoleLog(log) {
|
|
11860
|
-
return isInternalConsoleLog(log);
|
|
11861
|
-
}
|
|
11862
|
-
function isIssueStickerDomain(domain) {
|
|
11863
|
-
if (!domain) {
|
|
11864
|
-
return false;
|
|
11865
|
-
}
|
|
11866
|
-
return ISSUE_STICKER_API_DOMAINS.some((stickerDomain) => {
|
|
11867
|
-
return matchWildcardDomain(stickerDomain, domain);
|
|
11868
|
-
});
|
|
11869
|
-
}
|
|
11870
|
-
function isIssueStickerServiceLog(log) {
|
|
11871
|
-
if (hasIssueStickerClientInfo(log.requestHeaders)) {
|
|
11872
|
-
return true;
|
|
11873
|
-
}
|
|
11874
|
-
if (isIssueStickerApiUrl(log.url)) {
|
|
11875
|
-
return true;
|
|
11876
|
-
}
|
|
11877
|
-
return false;
|
|
13397
|
+
[id, registerTarget]
|
|
13398
|
+
);
|
|
11878
13399
|
}
|
|
11879
|
-
function
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
|
|
11894
|
-
const message2 = ((_a = log.message) == null ? void 0 : _a.toLowerCase()) || "";
|
|
11895
|
-
const serviceKeywords = ["issue-sticker", "qa-issue-tracker"];
|
|
11896
|
-
if (serviceKeywords.some((keyword) => message2.includes(keyword))) {
|
|
11897
|
-
return true;
|
|
11898
|
-
}
|
|
11899
|
-
return false;
|
|
13400
|
+
function useOnboardingFlow() {
|
|
13401
|
+
const context = React.useContext(OnboardingContext);
|
|
13402
|
+
return React.useMemo(
|
|
13403
|
+
() => ({
|
|
13404
|
+
start: (context == null ? void 0 : context.start) ?? (() => void 0),
|
|
13405
|
+
replay: (stepId) => context == null ? void 0 : context.start({ force: true, stepId }),
|
|
13406
|
+
advance: (context == null ? void 0 : context.advance) ?? (() => void 0),
|
|
13407
|
+
dismiss: (context == null ? void 0 : context.dismiss) ?? (() => void 0),
|
|
13408
|
+
complete: (context == null ? void 0 : context.complete) ?? (() => void 0),
|
|
13409
|
+
isOpen: (context == null ? void 0 : context.isOpen) ?? false,
|
|
13410
|
+
currentStep: (context == null ? void 0 : context.currentStep) ?? null,
|
|
13411
|
+
hasSteps: (context == null ? void 0 : context.hasSteps) ?? false
|
|
13412
|
+
}),
|
|
13413
|
+
[context]
|
|
13414
|
+
);
|
|
11900
13415
|
}
|
|
11901
|
-
|
|
11902
|
-
|
|
11903
|
-
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
return !isIssueStickerServiceLog(log);
|
|
11907
|
-
});
|
|
13416
|
+
let nextId = 0;
|
|
13417
|
+
let toasts = [];
|
|
13418
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
13419
|
+
function emit() {
|
|
13420
|
+
listeners.forEach((l2) => l2());
|
|
11908
13421
|
}
|
|
11909
|
-
function
|
|
11910
|
-
|
|
11911
|
-
|
|
11912
|
-
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
13422
|
+
function addToast(message2, type, options) {
|
|
13423
|
+
const id = `toast-${++nextId}`;
|
|
13424
|
+
const t2 = {
|
|
13425
|
+
id,
|
|
13426
|
+
message: message2,
|
|
13427
|
+
type,
|
|
13428
|
+
action: options == null ? void 0 : options.action,
|
|
13429
|
+
duration: (options == null ? void 0 : options.duration) ?? 2e3,
|
|
13430
|
+
createdAt: Date.now()
|
|
13431
|
+
};
|
|
13432
|
+
toasts = [...toasts, t2];
|
|
13433
|
+
emit();
|
|
13434
|
+
return id;
|
|
11916
13435
|
}
|
|
11917
|
-
|
|
11918
|
-
|
|
11919
|
-
|
|
11920
|
-
if (method === "warn") return "warning";
|
|
11921
|
-
return method;
|
|
13436
|
+
function dismiss(id) {
|
|
13437
|
+
toasts = toasts.filter((t2) => t2.id !== id);
|
|
13438
|
+
emit();
|
|
11922
13439
|
}
|
|
11923
|
-
function
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
if (typeof a2 === "object" && a2 !== null) {
|
|
11927
|
-
try {
|
|
11928
|
-
return JSON.stringify(a2);
|
|
11929
|
-
} catch {
|
|
11930
|
-
return String(a2);
|
|
11931
|
-
}
|
|
11932
|
-
}
|
|
11933
|
-
return String(a2);
|
|
11934
|
-
}).join(" ");
|
|
13440
|
+
function dismissAll() {
|
|
13441
|
+
toasts = [];
|
|
13442
|
+
emit();
|
|
11935
13443
|
}
|
|
11936
|
-
function
|
|
11937
|
-
|
|
11938
|
-
if (arg instanceof Error && arg.stack) {
|
|
11939
|
-
return arg.stack;
|
|
11940
|
-
}
|
|
11941
|
-
}
|
|
11942
|
-
return void 0;
|
|
13444
|
+
function toast(message2, options) {
|
|
13445
|
+
return addToast(message2, "default", options);
|
|
11943
13446
|
}
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
return void 0;
|
|
11954
|
-
}
|
|
11955
|
-
return lines.join("\n");
|
|
13447
|
+
toast.success = (message2, options) => addToast(message2, "success", options);
|
|
13448
|
+
toast.warning = (message2, options) => addToast(message2, "warning", options);
|
|
13449
|
+
toast.error = (message2, options) => addToast(message2, "error", options);
|
|
13450
|
+
toast.info = (message2, options) => addToast(message2, "info", options);
|
|
13451
|
+
toast.dismiss = dismiss;
|
|
13452
|
+
toast.dismissAll = dismissAll;
|
|
13453
|
+
function subscribe(listener) {
|
|
13454
|
+
listeners.add(listener);
|
|
13455
|
+
return () => listeners.delete(listener);
|
|
11956
13456
|
}
|
|
11957
|
-
function
|
|
11958
|
-
|
|
11959
|
-
const errorStack = extractStackTrace(args);
|
|
11960
|
-
if (sourceStack && errorStack && !sourceStack.includes(errorStack)) {
|
|
11961
|
-
return `${sourceStack}
|
|
11962
|
-
Caused by:
|
|
11963
|
-
${errorStack}`;
|
|
11964
|
-
}
|
|
11965
|
-
return sourceStack ?? errorStack;
|
|
13457
|
+
function getSnapshot() {
|
|
13458
|
+
return toasts;
|
|
11966
13459
|
}
|
|
11967
|
-
|
|
11968
|
-
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
var _a;
|
|
11980
|
-
this.addLog({
|
|
11981
|
-
type: methodToLogType(method),
|
|
11982
|
-
message: serializeArgs(args),
|
|
11983
|
-
timestamp: Date.now(),
|
|
11984
|
-
url: typeof window !== "undefined" ? window.location.href : void 0,
|
|
11985
|
-
stackTrace: captureConsoleStackTrace(args)
|
|
11986
|
-
});
|
|
11987
|
-
(_a = this.originals[method]) == null ? void 0 : _a.apply(console, args);
|
|
11988
|
-
};
|
|
11989
|
-
}
|
|
11990
|
-
}
|
|
11991
|
-
stop() {
|
|
11992
|
-
if (!this.running) return;
|
|
11993
|
-
this.running = false;
|
|
11994
|
-
for (const method of CONSOLE_METHODS) {
|
|
11995
|
-
if (this.originals[method]) {
|
|
11996
|
-
console[method] = this.originals[method];
|
|
11997
|
-
}
|
|
11998
|
-
}
|
|
11999
|
-
this.originals = {};
|
|
12000
|
-
}
|
|
12001
|
-
getLogs() {
|
|
12002
|
-
return [...this.logs];
|
|
12003
|
-
}
|
|
12004
|
-
getFilteredLogs() {
|
|
12005
|
-
return this.logs.slice(-50);
|
|
12006
|
-
}
|
|
12007
|
-
clear() {
|
|
12008
|
-
this.logs = [];
|
|
13460
|
+
({
|
|
13461
|
+
type: [NetworkLogType.XMLHTTPREQUEST, NetworkLogType.FETCH]
|
|
13462
|
+
});
|
|
13463
|
+
function buildIssueIntegrationsPayload({
|
|
13464
|
+
jiraEnabled,
|
|
13465
|
+
jiraFields,
|
|
13466
|
+
notionEnabled,
|
|
13467
|
+
notionProperties
|
|
13468
|
+
}) {
|
|
13469
|
+
const payload = {};
|
|
13470
|
+
if (jiraEnabled && jiraFields) {
|
|
13471
|
+
payload.jira = { fields: jiraFields };
|
|
12009
13472
|
}
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
}
|
|
12014
|
-
this.logs.push(log);
|
|
12015
|
-
if (this.logs.length > MAX_BUFFER$1) {
|
|
12016
|
-
this.logs = this.logs.slice(-MAX_BUFFER$1);
|
|
12017
|
-
}
|
|
13473
|
+
if (notionEnabled) {
|
|
13474
|
+
payload.notion = {
|
|
13475
|
+
properties: notionProperties ?? {}
|
|
13476
|
+
};
|
|
12018
13477
|
}
|
|
13478
|
+
return payload.jira || payload.notion ? payload : void 0;
|
|
12019
13479
|
}
|
|
12020
|
-
const
|
|
12021
|
-
function
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
});
|
|
12027
|
-
return Object.keys(entries).length > 0 ? entries : void 0;
|
|
12028
|
-
}
|
|
12029
|
-
function parseXHRHeaders(rawHeaders) {
|
|
12030
|
-
const entries = rawHeaders.trim().split(/[\r\n]+/).map((line) => {
|
|
12031
|
-
const separatorIndex = line.indexOf(":");
|
|
12032
|
-
if (separatorIndex === -1) return null;
|
|
12033
|
-
const key = line.slice(0, separatorIndex).trim();
|
|
12034
|
-
const value = line.slice(separatorIndex + 1).trim();
|
|
12035
|
-
return key ? [key, value] : null;
|
|
12036
|
-
}).filter((entry) => entry !== null);
|
|
12037
|
-
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
12038
|
-
}
|
|
12039
|
-
function getHeaderValue(headers, headerName) {
|
|
12040
|
-
if (!headers) return void 0;
|
|
12041
|
-
const normalizedHeaderName = headerName.toLowerCase();
|
|
12042
|
-
const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === normalizedHeaderName);
|
|
12043
|
-
return entry == null ? void 0 : entry[1];
|
|
12044
|
-
}
|
|
12045
|
-
function normalizeMimeType(contentType) {
|
|
12046
|
-
var _a;
|
|
12047
|
-
return ((_a = contentType == null ? void 0 : contentType.split(";", 1)[0]) == null ? void 0 : _a.trim().toLowerCase()) || void 0;
|
|
12048
|
-
}
|
|
12049
|
-
function parseContentLength(value) {
|
|
12050
|
-
if (!value) return void 0;
|
|
12051
|
-
const parsed = Number.parseInt(value, 10);
|
|
12052
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
13480
|
+
const __vite_import_meta_env__ = { "DEV": false };
|
|
13481
|
+
function isDevLoggingEnabled() {
|
|
13482
|
+
const env = __vite_import_meta_env__;
|
|
13483
|
+
{
|
|
13484
|
+
return env.DEV;
|
|
13485
|
+
}
|
|
12053
13486
|
}
|
|
12054
|
-
function
|
|
12055
|
-
return Boolean(
|
|
13487
|
+
function isInternalDebugLog(message2) {
|
|
13488
|
+
return Boolean(message2 == null ? void 0 : message2.includes(ISSUE_STICKER_INTERNAL_LOG_PREFIX));
|
|
12056
13489
|
}
|
|
12057
|
-
function
|
|
12058
|
-
if (
|
|
12059
|
-
|
|
12060
|
-
const sizeBytes = getUtf8ByteLength(requestBody);
|
|
12061
|
-
if (!isBodyWithinCaptureLimit(sizeBytes)) {
|
|
12062
|
-
return {
|
|
12063
|
-
requestBodyOmittedReason: "too-large",
|
|
12064
|
-
requestBodySizeBytes: sizeBytes
|
|
12065
|
-
};
|
|
13490
|
+
function write(method, scope, args) {
|
|
13491
|
+
if (!isDevLoggingEnabled()) {
|
|
13492
|
+
return;
|
|
12066
13493
|
}
|
|
12067
|
-
|
|
12068
|
-
}
|
|
12069
|
-
function omitTooLargeResponseBody(sizeBytes) {
|
|
12070
|
-
return {
|
|
12071
|
-
responseBodyOmittedReason: "too-large",
|
|
12072
|
-
responseBodySizeBytes: sizeBytes
|
|
12073
|
-
};
|
|
13494
|
+
const log = console[method];
|
|
13495
|
+
log(ISSUE_STICKER_INTERNAL_LOG_PREFIX, `[${scope}]`, ...args);
|
|
12074
13496
|
}
|
|
12075
|
-
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
|
|
13497
|
+
const devLogger = {
|
|
13498
|
+
log(scope, ...args) {
|
|
13499
|
+
write("log", scope, args);
|
|
13500
|
+
},
|
|
13501
|
+
info(scope, ...args) {
|
|
13502
|
+
write("info", scope, args);
|
|
13503
|
+
},
|
|
13504
|
+
warn(scope, ...args) {
|
|
13505
|
+
write("warn", scope, args);
|
|
13506
|
+
},
|
|
13507
|
+
error(scope, ...args) {
|
|
13508
|
+
write("error", scope, args);
|
|
13509
|
+
},
|
|
13510
|
+
debug(scope, ...args) {
|
|
13511
|
+
write("debug", scope, args);
|
|
12081
13512
|
}
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
13513
|
+
};
|
|
13514
|
+
function buildGuestOwnerQuery(mode, externalUserId) {
|
|
13515
|
+
if (mode !== "public") return "";
|
|
13516
|
+
if (!externalUserId) return "";
|
|
13517
|
+
return `?external_user_id=${encodeURIComponent(externalUserId)}`;
|
|
12086
13518
|
}
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
const cloned = response.clone();
|
|
12092
|
-
if (isImageMimeType(responseMimeType) && typeof cloned.arrayBuffer === "function") {
|
|
12093
|
-
const arrayBuffer = await cloned.arrayBuffer();
|
|
12094
|
-
const sizeBytes2 = arrayBuffer.byteLength;
|
|
12095
|
-
if (!isBodyWithinCaptureLimit(sizeBytes2)) {
|
|
12096
|
-
return omitTooLargeResponseBody(sizeBytes2);
|
|
13519
|
+
function extractDomain(url) {
|
|
13520
|
+
try {
|
|
13521
|
+
if (!url.includes("://")) {
|
|
13522
|
+
return null;
|
|
12097
13523
|
}
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
const responseBody = await cloned.text();
|
|
12104
|
-
const sizeBytes = getCapturedBodySizeBytes(responseBody);
|
|
12105
|
-
if (!isBodyWithinCaptureLimit(sizeBytes)) {
|
|
12106
|
-
return omitTooLargeResponseBody(sizeBytes);
|
|
13524
|
+
const urlObj = new URL(url);
|
|
13525
|
+
return urlObj.hostname;
|
|
13526
|
+
} catch (error) {
|
|
13527
|
+
devLogger.error("domainChecker", "URL 파싱 실패:", url, error);
|
|
13528
|
+
return null;
|
|
12107
13529
|
}
|
|
12108
|
-
return {
|
|
12109
|
-
responseBody
|
|
12110
|
-
};
|
|
12111
13530
|
}
|
|
12112
|
-
|
|
12113
|
-
if (
|
|
12114
|
-
return
|
|
13531
|
+
function matchWildcardDomain(pattern, domain) {
|
|
13532
|
+
if (!pattern.includes("*")) {
|
|
13533
|
+
return pattern === domain;
|
|
12115
13534
|
}
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
const sizeBytes2 = arrayBuffer.byteLength;
|
|
12120
|
-
if (!isBodyWithinCaptureLimit(sizeBytes2)) {
|
|
12121
|
-
return omitTooLargeResponseBody(sizeBytes2);
|
|
12122
|
-
}
|
|
12123
|
-
return {
|
|
12124
|
-
responseBody: await arrayBufferToBase64(arrayBuffer),
|
|
12125
|
-
responseBodyEncoding: "base64"
|
|
12126
|
-
};
|
|
12127
|
-
}
|
|
12128
|
-
if (xhr.response instanceof ArrayBuffer) {
|
|
12129
|
-
const sizeBytes2 = xhr.response.byteLength;
|
|
12130
|
-
if (!isBodyWithinCaptureLimit(sizeBytes2)) {
|
|
12131
|
-
return omitTooLargeResponseBody(sizeBytes2);
|
|
12132
|
-
}
|
|
12133
|
-
return {
|
|
12134
|
-
responseBody: await arrayBufferToBase64(xhr.response),
|
|
12135
|
-
responseBodyEncoding: "base64"
|
|
12136
|
-
};
|
|
12137
|
-
}
|
|
13535
|
+
const patternParts = pattern.split("*");
|
|
13536
|
+
if (patternParts.length !== 2) {
|
|
13537
|
+
return pattern === domain;
|
|
12138
13538
|
}
|
|
12139
|
-
const
|
|
12140
|
-
|
|
12141
|
-
|
|
12142
|
-
|
|
13539
|
+
const suffix = patternParts[1];
|
|
13540
|
+
if (suffix.startsWith(".")) {
|
|
13541
|
+
return domain.endsWith(suffix) || domain === suffix.slice(1);
|
|
13542
|
+
} else {
|
|
13543
|
+
return domain.endsWith(suffix) || domain === suffix;
|
|
12143
13544
|
}
|
|
12144
|
-
return {
|
|
12145
|
-
responseBody
|
|
12146
|
-
};
|
|
12147
13545
|
}
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
13546
|
+
const ISSUE_STICKER_API_DOMAINS = ["api.issuesticker.com", "api.dev.issuesticker.com"];
|
|
13547
|
+
const ISSUE_STICKER_API_ORIGINS = [
|
|
13548
|
+
"https://api.issuesticker.com",
|
|
13549
|
+
"https://api.dev.issuesticker.com"
|
|
13550
|
+
];
|
|
13551
|
+
function readConfiguredApiOrigin() {
|
|
13552
|
+
const baseUrl = "https://api.issuesticker.com";
|
|
13553
|
+
if (baseUrl.trim().length === 0) {
|
|
13554
|
+
return null;
|
|
12157
13555
|
}
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
this.interceptXHR();
|
|
13556
|
+
try {
|
|
13557
|
+
return new URL(baseUrl).origin;
|
|
13558
|
+
} catch {
|
|
13559
|
+
return null;
|
|
12163
13560
|
}
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
13561
|
+
}
|
|
13562
|
+
function isIssueStickerApiUrl(url) {
|
|
13563
|
+
const configuredOrigin = readConfiguredApiOrigin();
|
|
13564
|
+
const origins = configuredOrigin ? [...ISSUE_STICKER_API_ORIGINS, configuredOrigin] : ISSUE_STICKER_API_ORIGINS;
|
|
13565
|
+
return isSelfTrafficOrigin(url, origins);
|
|
13566
|
+
}
|
|
13567
|
+
function hasIssueStickerClientInfo(headers) {
|
|
13568
|
+
return hasSelfTrafficHeader(headers);
|
|
13569
|
+
}
|
|
13570
|
+
function isInternalConsoleLog(log) {
|
|
13571
|
+
return isInternalDebugLog(log.message);
|
|
13572
|
+
}
|
|
13573
|
+
function isIssueStickerInternalConsoleLog(log) {
|
|
13574
|
+
return isInternalConsoleLog(log);
|
|
13575
|
+
}
|
|
13576
|
+
function isIssueStickerDomain(domain) {
|
|
13577
|
+
if (!domain) {
|
|
13578
|
+
return false;
|
|
12169
13579
|
}
|
|
12170
|
-
|
|
12171
|
-
return
|
|
13580
|
+
return ISSUE_STICKER_API_DOMAINS.some((stickerDomain) => {
|
|
13581
|
+
return matchWildcardDomain(stickerDomain, domain);
|
|
13582
|
+
});
|
|
13583
|
+
}
|
|
13584
|
+
function isIssueStickerServiceLog(log) {
|
|
13585
|
+
if (hasIssueStickerClientInfo(log.requestHeaders)) {
|
|
13586
|
+
return true;
|
|
12172
13587
|
}
|
|
12173
|
-
|
|
12174
|
-
return
|
|
13588
|
+
if (isIssueStickerApiUrl(log.url)) {
|
|
13589
|
+
return true;
|
|
12175
13590
|
}
|
|
12176
|
-
|
|
12177
|
-
|
|
13591
|
+
return false;
|
|
13592
|
+
}
|
|
13593
|
+
function isIssueStickerServiceConsoleLog(log) {
|
|
13594
|
+
var _a;
|
|
13595
|
+
if (isIssueStickerInternalConsoleLog(log)) {
|
|
13596
|
+
return false;
|
|
12178
13597
|
}
|
|
12179
|
-
|
|
12180
|
-
|
|
12181
|
-
this.logs.push(log);
|
|
12182
|
-
if (this.logs.length > MAX_BUFFER) {
|
|
12183
|
-
this.logs = this.logs.slice(-MAX_BUFFER);
|
|
12184
|
-
}
|
|
13598
|
+
if (!log.url) {
|
|
13599
|
+
return false;
|
|
12185
13600
|
}
|
|
12186
|
-
|
|
12187
|
-
|
|
12188
|
-
return
|
|
13601
|
+
const logDomain = extractDomain(log.url);
|
|
13602
|
+
if (!logDomain) {
|
|
13603
|
+
return false;
|
|
12189
13604
|
}
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
const originalFetch = this.originalFetch;
|
|
12193
|
-
const addLog = this.addLog.bind(this);
|
|
12194
|
-
window.fetch = async function(input, init) {
|
|
12195
|
-
const startTime = Date.now();
|
|
12196
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
12197
|
-
const method = (init == null ? void 0 : init.method) || "GET";
|
|
12198
|
-
const capturedRequestBody = captureRequestBody(init == null ? void 0 : init.body);
|
|
12199
|
-
try {
|
|
12200
|
-
const response = await originalFetch.call(window, input, init);
|
|
12201
|
-
const responseHeaders = headersToRecord(response.headers);
|
|
12202
|
-
const responseMimeType = normalizeMimeType(getHeaderValue(responseHeaders, "content-type"));
|
|
12203
|
-
const contentLength = parseContentLength(getHeaderValue(responseHeaders, "content-length"));
|
|
12204
|
-
let capturedBody = {};
|
|
12205
|
-
try {
|
|
12206
|
-
capturedBody = await readFetchResponseBody(response, responseMimeType, contentLength);
|
|
12207
|
-
} catch {
|
|
12208
|
-
capturedBody = {};
|
|
12209
|
-
}
|
|
12210
|
-
addLog({
|
|
12211
|
-
url,
|
|
12212
|
-
method,
|
|
12213
|
-
statusCode: response.status,
|
|
12214
|
-
timestamp: startTime,
|
|
12215
|
-
type: "fetch",
|
|
12216
|
-
...capturedRequestBody,
|
|
12217
|
-
responseHeaders,
|
|
12218
|
-
responseMimeType,
|
|
12219
|
-
...capturedBody
|
|
12220
|
-
});
|
|
12221
|
-
return response;
|
|
12222
|
-
} catch (error) {
|
|
12223
|
-
addLog({
|
|
12224
|
-
url,
|
|
12225
|
-
method,
|
|
12226
|
-
statusCode: 0,
|
|
12227
|
-
timestamp: startTime,
|
|
12228
|
-
type: "fetch",
|
|
12229
|
-
...capturedRequestBody
|
|
12230
|
-
});
|
|
12231
|
-
throw error;
|
|
12232
|
-
}
|
|
12233
|
-
};
|
|
13605
|
+
if (isIssueStickerDomain(logDomain)) {
|
|
13606
|
+
return true;
|
|
12234
13607
|
}
|
|
12235
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
const originalSend = this.originalXHRSend;
|
|
12240
|
-
const addLog = this.addLog.bind(this);
|
|
12241
|
-
function interceptedOpen(method, url, async, username, password) {
|
|
12242
|
-
this._isSDK_method = method;
|
|
12243
|
-
this._isSDK_url = typeof url === "string" ? url : url.href;
|
|
12244
|
-
if (async === void 0) {
|
|
12245
|
-
return originalOpen.call(this, method, url, true);
|
|
12246
|
-
}
|
|
12247
|
-
return originalOpen.call(this, method, url, async, username, password);
|
|
12248
|
-
}
|
|
12249
|
-
XMLHttpRequest.prototype.open = interceptedOpen;
|
|
12250
|
-
XMLHttpRequest.prototype.send = function(body) {
|
|
12251
|
-
const startTime = Date.now();
|
|
12252
|
-
const xhrUrl = this._isSDK_url || "";
|
|
12253
|
-
const xhrMethod = this._isSDK_method || "GET";
|
|
12254
|
-
const capturedRequestBody = captureRequestBody(body);
|
|
12255
|
-
this.addEventListener("loadend", () => {
|
|
12256
|
-
void (async () => {
|
|
12257
|
-
const responseHeaders = parseXHRHeaders(this.getAllResponseHeaders());
|
|
12258
|
-
const responseMimeType = normalizeMimeType(
|
|
12259
|
-
getHeaderValue(responseHeaders, "content-type")
|
|
12260
|
-
);
|
|
12261
|
-
const contentLength = parseContentLength(
|
|
12262
|
-
getHeaderValue(responseHeaders, "content-length")
|
|
12263
|
-
);
|
|
12264
|
-
let capturedBody = {};
|
|
12265
|
-
try {
|
|
12266
|
-
capturedBody = await readXHRResponseBody(this, responseMimeType, contentLength);
|
|
12267
|
-
} catch {
|
|
12268
|
-
capturedBody = {};
|
|
12269
|
-
}
|
|
12270
|
-
addLog({
|
|
12271
|
-
url: xhrUrl,
|
|
12272
|
-
method: xhrMethod,
|
|
12273
|
-
statusCode: this.status,
|
|
12274
|
-
timestamp: startTime,
|
|
12275
|
-
type: "xmlhttprequest",
|
|
12276
|
-
...capturedRequestBody,
|
|
12277
|
-
responseHeaders,
|
|
12278
|
-
responseMimeType,
|
|
12279
|
-
...capturedBody
|
|
12280
|
-
});
|
|
12281
|
-
})();
|
|
12282
|
-
});
|
|
12283
|
-
return originalSend.call(this, body);
|
|
12284
|
-
};
|
|
13608
|
+
const message2 = ((_a = log.message) == null ? void 0 : _a.toLowerCase()) || "";
|
|
13609
|
+
const serviceKeywords = ["issue-sticker", "qa-issue-tracker"];
|
|
13610
|
+
if (serviceKeywords.some((keyword) => message2.includes(keyword))) {
|
|
13611
|
+
return true;
|
|
12285
13612
|
}
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
13613
|
+
return false;
|
|
13614
|
+
}
|
|
13615
|
+
function filterExcludeServiceRelatedLogs(networkLogs) {
|
|
13616
|
+
if (!networkLogs || networkLogs.length === 0) {
|
|
13617
|
+
return [];
|
|
12291
13618
|
}
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
this.originalXHRSend = null;
|
|
12300
|
-
}
|
|
13619
|
+
return networkLogs.filter((log) => {
|
|
13620
|
+
return !isIssueStickerServiceLog(log);
|
|
13621
|
+
});
|
|
13622
|
+
}
|
|
13623
|
+
function filterExcludeServiceRelatedConsoleLogs(consoleLogs) {
|
|
13624
|
+
if (!consoleLogs || consoleLogs.length === 0) {
|
|
13625
|
+
return [];
|
|
12301
13626
|
}
|
|
13627
|
+
return consoleLogs.filter((log) => {
|
|
13628
|
+
return !isIssueStickerInternalConsoleLog(log) && !isIssueStickerServiceConsoleLog(log);
|
|
13629
|
+
});
|
|
12302
13630
|
}
|
|
12303
13631
|
function buildExternalUserPayload(user) {
|
|
12304
13632
|
if (!user) return {};
|
|
@@ -12308,49 +13636,65 @@ function buildExternalUserPayload(user) {
|
|
|
12308
13636
|
};
|
|
12309
13637
|
}
|
|
12310
13638
|
export {
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
|
|
13639
|
+
DatePicker as $,
|
|
13640
|
+
trimLogBackupPayload as A,
|
|
13641
|
+
createSafeLogBackup as B,
|
|
12314
13642
|
ConsoleCollector as C,
|
|
12315
|
-
|
|
12316
|
-
|
|
12317
|
-
|
|
12318
|
-
|
|
12319
|
-
|
|
13643
|
+
getLogBackupSessionId as D,
|
|
13644
|
+
createIndexedDbLogBackup as E,
|
|
13645
|
+
PopoverTrigger as F,
|
|
13646
|
+
PopoverContent as G,
|
|
13647
|
+
Select$1 as H,
|
|
12320
13648
|
Input as I,
|
|
12321
|
-
|
|
12322
|
-
|
|
13649
|
+
SelectTrigger as J,
|
|
13650
|
+
SelectValue as K,
|
|
12323
13651
|
Label as L,
|
|
12324
|
-
|
|
13652
|
+
SelectContent as M,
|
|
12325
13653
|
NetworkCollector as N,
|
|
12326
|
-
|
|
13654
|
+
SelectItem as O,
|
|
12327
13655
|
Popover as P,
|
|
12328
|
-
|
|
12329
|
-
|
|
13656
|
+
getJiraMetadata as Q,
|
|
13657
|
+
buildJiraIssueFields as R,
|
|
12330
13658
|
ScreenRecordingController as S,
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
X,
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
13659
|
+
Button$1 as T,
|
|
13660
|
+
Root2 as U,
|
|
13661
|
+
Trigger as V,
|
|
13662
|
+
ChevronDown as W,
|
|
13663
|
+
Portal as X,
|
|
13664
|
+
Content2 as Y,
|
|
13665
|
+
Check as Z,
|
|
13666
|
+
X as _,
|
|
12339
13667
|
uploadSignedMediaWithApi as a,
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
13668
|
+
getNotionPageMetadata as a0,
|
|
13669
|
+
buildNotionFieldViews as a1,
|
|
13670
|
+
DirectUploadError as a2,
|
|
13671
|
+
getRegisteredUploadId as a3,
|
|
13672
|
+
subscribe as a4,
|
|
13673
|
+
getSnapshot as a5,
|
|
13674
|
+
toast as a6,
|
|
13675
|
+
ChevronRight as a7,
|
|
13676
|
+
getExposedJiraFields as a8,
|
|
13677
|
+
isJiraFieldExposed as a9,
|
|
13678
|
+
getPrimaryJiraMapping as aa,
|
|
13679
|
+
createCollection as ab,
|
|
13680
|
+
useDirection as ac,
|
|
13681
|
+
ReactRemoveScroll as ad,
|
|
13682
|
+
hideOthers as ae,
|
|
13683
|
+
useFocusGuards as af,
|
|
13684
|
+
FocusScope as ag,
|
|
13685
|
+
getPrimaryNotionMapping as ah,
|
|
13686
|
+
buttonVariants as ai,
|
|
13687
|
+
findElementBySelector as aj,
|
|
13688
|
+
findElementByXPath as ak,
|
|
13689
|
+
OnboardingProvider as al,
|
|
13690
|
+
useOnboardingTarget as am,
|
|
13691
|
+
useOnboardingFlow as an,
|
|
13692
|
+
ISSUE_STICKER_HOST_ELEMENT_ID as ao,
|
|
13693
|
+
getIssueStickerRuntimeOwner as ap,
|
|
13694
|
+
ISSUE_STICKER_RUNTIME_OWNER as aq,
|
|
13695
|
+
createRuntimeHostElement as ar,
|
|
13696
|
+
attachHostTypingGuard as as,
|
|
13697
|
+
ISSUE_STICKER_EXTENSION_RUNTIME_READY_EVENT as at,
|
|
12354
13698
|
normalizeJiraFieldId as b,
|
|
12355
13699
|
captureWithModernScreenshot as c,
|
|
12356
13700
|
directUploadToSignedUrl as d,
|
|
@@ -12358,22 +13702,22 @@ export {
|
|
|
12358
13702
|
buildNotionCreateFields as f,
|
|
12359
13703
|
getElementPosition as g,
|
|
12360
13704
|
buildGuestOwnerQuery as h,
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
13705
|
+
buildIssueIntegrationsPayload as i,
|
|
13706
|
+
devLogger as j,
|
|
13707
|
+
filterExcludeServiceRelatedLogs as k,
|
|
13708
|
+
filterExcludeServiceRelatedConsoleLogs as l,
|
|
13709
|
+
compactNetworkLogsForTransport as m,
|
|
12366
13710
|
normalizeExposedJiraFields as n,
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
13711
|
+
CONSOLE_LOG_SUBMIT_LIMIT as o,
|
|
13712
|
+
pristineFetch as p,
|
|
13713
|
+
buildExternalUserPayload as q,
|
|
13714
|
+
shrinkNetworkLogsToFit as r,
|
|
13715
|
+
selectNetworkLogsForSubmit as s,
|
|
13716
|
+
measureJsonBytes as t,
|
|
12373
13717
|
uploadScreenshotDataUrls as u,
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
|
|
12378
|
-
|
|
13718
|
+
sanitizeString as v,
|
|
13719
|
+
getCurrentPageUrl as w,
|
|
13720
|
+
SessionReplayCaptureController as x,
|
|
13721
|
+
preloadModernScreenshot as y,
|
|
13722
|
+
isIssueStickerExtensionRuntimeActive as z
|
|
12379
13723
|
};
|