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.
@@ -1,15 +1,91 @@
1
- import { q as gzip_1, r as record, a as cn, P as Primitive, l as useCallbackRef$1, j as useComposedRefs, 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, d as OVERLAY_LAYER, s as stopEscapePropagation, k as useLayoutEffect2, V as VISUALLY_HIDDEN_STYLES, c as createLucideIcon, x as clsx, S as Slot$2, O as OVERLAY_LAYER_INDEX } from "./createLucideIcon-CmSNiW63.js";
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 DEFAULT_PRIVACY_CONFIG_V1 = {
208
- version: "v1",
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: `.${SESSION_REPLAY_MASK_CLASS}`,
211
- blockSelector: `.${SESSION_REPLAY_BLOCK_CLASS}`,
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 DEFAULT_PRIVACY_CONFIG_V1;
216
- return { ...DEFAULT_PRIVACY_CONFIG_V1, ...override };
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.options.now ?? (() => Date.now());
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: this.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) : new RingBufferRecorder({ 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
- var NetworkLogType = /* @__PURE__ */ ((NetworkLogType2) => {
2592
- NetworkLogType2["MAIN_FRAME"] = "main_frame";
2593
- NetworkLogType2["SUB_FRAME"] = "sub_frame";
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
- const JIRA_FIELD_ALIASES = {
2620
- fix_version: "fixVersions",
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 normalizeExposedJiraFields(exposedFields) {
2656
- if (!(exposedFields == null ? void 0 : exposedFields.length)) {
2657
- return [];
3525
+ function getCurrentPageUrl() {
3526
+ if (typeof window === "undefined") {
3527
+ return "";
2658
3528
  }
2659
- return exposedFields.filter((field) => typeof field === "string" && field.trim().length > 0).map((field) => normalizeJiraFieldId(field.trim())).filter((field, index, fields) => fields.indexOf(field) === index);
2660
- }
2661
- function getExposedJiraFields(config) {
2662
- const exposed = config == null ? void 0 : config.exposed_fields;
2663
- return normalizeExposedJiraFields(Array.isArray(exposed) ? exposed : void 0);
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
- function isJiraFieldExposed(exposedFields, fieldId) {
2666
- if (!exposedFields) {
2667
- return false;
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
- function getJiraMetadata(mapping) {
2673
- const raw = mapping == null ? void 0 : mapping.jira_issue_metadata;
2674
- if (Array.isArray(raw)) {
2675
- return raw[0] ?? null;
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 getPrimaryJiraMapping(issue) {
2680
- var _a, _b;
2681
- return ((_a = issue.issue_external_mappings) == null ? void 0 : _a.find((mapping) => {
2682
- const hasFields = normalizeExposedJiraFields(mapping.exposed_fields).length > 0;
2683
- return hasFields && getJiraMetadata(mapping);
2684
- })) ?? ((_b = issue.issue_external_mappings) == null ? void 0 : _b[0]) ?? null;
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 stringifyJiraValue(value) {
2687
- if (value === null || value === void 0 || value === "") {
2688
- return "";
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
- if (Array.isArray(value)) {
2691
- return value.map((item) => {
2692
- if (typeof item === "object" && item !== null) {
2693
- return String(
2694
- item.name ?? item.key ?? item.value ?? item.id ?? ""
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
- if (typeof value === "object") {
2701
- return String(
2702
- value.name ?? value.key ?? value.value ?? value.id ?? JSON.stringify(value)
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 String(value);
3642
+ return lines.join("\n");
2706
3643
  }
2707
- function getJiraIssueKey(value) {
2708
- if (value === null || value === void 0 || value === "") {
2709
- return "";
2710
- }
2711
- if (typeof value === "object" && !Array.isArray(value)) {
2712
- const key = value.key ?? value.id;
2713
- return typeof key === "string" ? key : "";
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 String(value);
3652
+ return sourceStack ?? errorStack;
2716
3653
  }
2717
- function getJiraMetadataValue(meta, fieldId) {
2718
- var _a, _b;
2719
- if (!meta) {
2720
- return null;
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
- switch (normalizeJiraFieldId(fieldId)) {
2723
- case "status":
2724
- return meta.status;
2725
- case "assignee":
2726
- return meta.assignee_external_user_id;
2727
- case "reporter":
2728
- return meta.reporter_external_user_id;
2729
- case "fixVersions":
2730
- return meta.fix_version;
2731
- case "parent":
2732
- return (_a = meta.custom_fields) == null ? void 0 : _a.parent;
2733
- case "issuetype":
2734
- return meta.issue_type;
2735
- case "priority":
2736
- return meta.priority;
2737
- case "project_key":
2738
- return meta.project_key;
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
- function findFieldDefinition(fieldId, fieldDefinitions) {
2756
- const normalized = normalizeJiraFieldId(fieldId);
2757
- return fieldDefinitions.find((field) => normalizeJiraFieldId(field.id) === normalized);
2758
- }
2759
- function getFieldLabel(fieldId, fieldDefinition) {
2760
- return (fieldDefinition == null ? void 0 : fieldDefinition.name) ?? JIRA_FIELD_LABELS[normalizeJiraFieldId(fieldId)] ?? fieldId;
2761
- }
2762
- function getOptionLabel(option) {
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
- if (typeof value === "object" && !Array.isArray(value)) {
2773
- const id = value.id;
2774
- return id === null || id === void 0 || id === "" ? "" : String(id);
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
- return String(value);
2777
- }
2778
- function findMatchingOption(value, options) {
2779
- if (!options.length) {
2780
- return void 0;
3694
+ getLogs() {
3695
+ return [...this.logs];
2781
3696
  }
2782
- const valueId = getStoredValueId(value);
2783
- return valueId ? options.find((option) => option.id === valueId) : void 0;
2784
- }
2785
- function findDisplayLabel(fieldId, value, displayLabels) {
2786
- const valueId = getStoredValueId(value);
2787
- if (!valueId) {
2788
- return void 0;
3697
+ /**
3698
+ * 우리 로그를 걷어낸 보관 로그 전체. NetworkCollector 와 같은 계약이다.
3699
+ *
3700
+ * 내부 디버그 로그는 `addLog` 에서 이미 떨어져 나가므로 여기서 더 걸러낼 것이 없다.
3701
+ * **건수를 여기서 제출 상한까지 자르면 안 된다** — 소비자가 서비스 관련 로그를 한 번 더
3702
+ * 골라낸 뒤 마지막에 잘라야, 그 노이즈가 차지한 자리 때문에 진짜 로그가 사라지지 않는다.
3703
+ */
3704
+ getFilteredLogs() {
3705
+ return this.getLogs();
2789
3706
  }
2790
- return displayLabels.get(getJiraFieldDisplayLabelKey(fieldId, valueId));
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
- const values = Array.isArray(value) ? value : [value];
2798
- const displayValues = values.map((item) => {
2799
- const matched = findMatchingOption(item, options);
2800
- if (matched) {
2801
- return getOptionLabel(matched);
3710
+ addLog(log) {
3711
+ var _a;
3712
+ if (isIssueStickerInternalConsoleLog$1(log)) {
3713
+ return;
2802
3714
  }
2803
- return findDisplayLabel(fieldId, item, displayLabels) ?? (DYNAMIC_USER_FIELD_IDS.has(normalizedFieldId) ? "" : stringifyJiraValue(item));
2804
- }).filter(Boolean);
2805
- return displayValues.join(", ") || "-";
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 getEditValue(fieldId, value, options) {
2808
- if (normalizeJiraFieldId(fieldId) === "parent") {
2809
- return getJiraIssueKey(value);
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 getInputType(fieldId, fieldDefinition, options) {
2822
- var _a, _b;
2823
- const normalizedFieldId = normalizeJiraFieldId(fieldId);
2824
- if (READONLY_FIELD_IDS.has(normalizedFieldId)) {
2825
- return "readonly";
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
- function resolveFieldValue(fieldId, metadata, values) {
2853
- if (values && Object.prototype.hasOwnProperty.call(values, fieldId)) {
2854
- return values[fieldId];
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 getJiraMetadataValue(metadata, fieldId);
3772
+ return bytes;
2857
3773
  }
2858
- function buildJiraFieldViews({
2859
- exposedFields,
2860
- fieldDefinitions = [],
2861
- metadata = null,
2862
- displayLabels = /* @__PURE__ */ new Map(),
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 buildJiraIssueFields(mapping, fieldDefinitions = [], displayLabels = /* @__PURE__ */ new Map()) {
2888
- return buildJiraFieldViews({
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 buildJiraCreateFields(exposedFields, fieldDefinitions = [], values = {}) {
2896
- return buildJiraFieldViews({
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
- const STABLE_ATTR_KEYS = [
2904
- "data-testid",
2905
- "data-test",
2906
- "data-qa",
2907
- "data-cy",
2908
- "name",
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
- return false;
2921
- }
2922
- function isUnstableToken(token) {
2923
- if (!token) return true;
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 isStableClassName(cls) {
2935
- return !isUnstableToken(cls);
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 isUnstableId(id) {
2938
- if (!id) return true;
2939
- if (id.includes(":")) return true;
2940
- if (/^[0-9]/.test(id)) return true;
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 cssAttrValue(value) {
2946
- return value.replace(/(["\\])/g, "\\$1");
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
- function escapeXPathString(value) {
2949
- if (!value.includes('"')) return `"${value}"`;
2950
- if (!value.includes("'")) return `'${value}'`;
2951
- const parts = value.split('"').map((part) => `"${part}"`).join(`,'"',`);
2952
- return `concat(${parts})`;
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 getScrollPosition() {
2955
- return {
2956
- x: window.scrollX || window.pageXOffset || 0,
2957
- y: window.scrollY || window.pageYOffset || 0
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 getViewportSize() {
2961
- const scroll = getScrollPosition();
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 matchesUniquely(selector, element) {
3886
+ function isSelfTrafficOrigin(url, apiOrigins) {
3887
+ if (apiOrigins.length === 0) return false;
3888
+ let origin;
3889
+ let hostname;
2970
3890
  try {
2971
- const found = document.querySelectorAll(selector);
2972
- return found.length === 1 && found[0] === element;
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
- function nthOfType(element) {
2978
- let count2 = 1;
2979
- let sibling = element.previousElementSibling;
2980
- while (sibling) {
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 stableAnchorSelector(element) {
2987
- if (element.id && !isUnstableId(element.id)) return `#${CSS.escape(element.id)}`;
2988
- const tag = element.tagName.toLowerCase();
2989
- for (const key of STABLE_ATTR_KEYS) {
2990
- const value = element.getAttribute(key);
2991
- if (value) return `${tag}[${key}="${cssAttrValue(value)}"]`;
3912
+ function clearPristineFetch() {
3913
+ registeredPristineFetch = null;
3914
+ }
3915
+ function pristineFetch(input, init) {
3916
+ if (registeredPristineFetch) {
3917
+ return registeredPristineFetch(input, init);
2992
3918
  }
2993
- return null;
3919
+ return typeof window !== "undefined" ? window.fetch(input, init) : fetch(input, init);
2994
3920
  }
2995
- function structuralSegment(element) {
2996
- const tag = element.tagName.toLowerCase();
2997
- const classes = typeof element.className === "string" ? element.className.trim().split(/\s+/).filter(Boolean).filter(isStableClassName).map((c2) => CSS.escape(c2)) : [];
2998
- const classPart = classes.length ? `.${classes.join(".")}` : "";
2999
- return `${tag}${classPart}:nth-of-type(${nthOfType(element)})`;
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 getElementSelector(element) {
3002
- if (element.id && !isUnstableId(element.id)) {
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 getXPath(element) {
3034
- if (element.id && !isUnstableId(element.id)) {
3035
- return `//*[@id=${escapeXPathString(element.id)}]`;
3036
- }
3037
- const parts = [];
3038
- let current = element;
3039
- while (current && current.nodeType === Node.ELEMENT_NODE && current !== document.body) {
3040
- if (current !== element && current.id && !isUnstableId(current.id)) {
3041
- return `//*[@id=${escapeXPathString(current.id)}]/${parts.join("/")}`;
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
- let index = 1;
3044
- let sibling = current.previousElementSibling;
3045
- while (sibling) {
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 parts.length ? `/html/body/${parts.join("/")}` : "/html/body";
3960
+ return Object.keys(entries).length > 0 ? redactSensitiveHeaders(entries) : void 0;
3053
3961
  }
3054
- function getElementPosition(element, event) {
3055
- const rect = element.getBoundingClientRect();
3056
- const scroll = getScrollPosition();
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
- x: event ? event.clientX + scroll.x : rect.left + scroll.x,
3059
- y: event ? event.clientY + scroll.y : rect.top + scroll.y,
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 findElementByXPath(xpath) {
3072
- if (!xpath || typeof xpath !== "string") {
3073
- return null;
3074
- }
3993
+ function toAbsoluteUrl(url) {
3075
3994
  try {
3076
- const result = document.evaluate(
3077
- xpath,
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 null;
3998
+ return url;
3087
3999
  }
3088
4000
  }
3089
- function findElementBySelector(selector) {
3090
- try {
3091
- const element = document.querySelector(selector);
3092
- if (element instanceof HTMLElement) {
3093
- return element;
3094
- }
3095
- } catch {
3096
- }
3097
- if (selector.startsWith("//") || selector.startsWith("/")) {
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 null;
4011
+ return { requestBody: redactSensitiveBody(requestBody) };
3101
4012
  }
3102
- const NETWORK_LOG_BODY_CAPTURE_LIMIT_BYTES = 256 * 1024;
3103
- const NETWORK_LOG_TRANSPORT_BODY_BUDGET_BYTES = 2 * 1024 * 1024;
3104
- function getUtf8ByteLength(value) {
3105
- let bytes = 0;
3106
- for (let index = 0; index < value.length; index += 1) {
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 estimateBase64DecodedBytes(value) {
3124
- const compact = value.replace(/^data:[^,]*,/i, "").replace(/\s/g, "");
3125
- if (!compact) return 0;
3126
- const padding = compact.endsWith("==") ? 2 : compact.endsWith("=") ? 1 : 0;
3127
- return Math.max(0, Math.floor(compact.length * 3 / 4) - padding);
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 getCapturedBodySizeBytes(body, encoding) {
3130
- return encoding === "base64" ? estimateBase64DecodedBytes(body) : getUtf8ByteLength(body);
4028
+ async function arrayBufferToBase64(arrayBuffer) {
4029
+ return bytesToBase64(new Uint8Array(arrayBuffer));
3131
4030
  }
3132
- function isBodyWithinCaptureLimit(sizeBytes) {
3133
- return sizeBytes <= NETWORK_LOG_BODY_CAPTURE_LIMIT_BYTES;
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 setOmittedBodyMetadata(log, field, reason, sizeBytes) {
3136
- if (field === "requestBody") {
3137
- delete log.requestBody;
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
- delete log.responseBody;
3143
- delete log.responseBodyEncoding;
3144
- log.responseBodyOmittedReason = reason;
3145
- log.responseBodySizeBytes = sizeBytes;
3146
- }
3147
- function omitOversizedIndividualBodies(logs) {
3148
- return logs.map((log) => {
3149
- const nextLog = { ...log };
3150
- if (nextLog.requestBody != null) {
3151
- const sizeBytes = getUtf8ByteLength(nextLog.requestBody);
3152
- if (sizeBytes > NETWORK_LOG_BODY_CAPTURE_LIMIT_BYTES) {
3153
- setOmittedBodyMetadata(nextLog, "requestBody", "too-large", sizeBytes);
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
- if (nextLog.responseBody != null) {
3157
- const sizeBytes = getCapturedBodySizeBytes(
3158
- nextLog.responseBody,
3159
- nextLog.responseBodyEncoding
3160
- );
3161
- if (sizeBytes > NETWORK_LOG_BODY_CAPTURE_LIMIT_BYTES) {
3162
- setOmittedBodyMetadata(nextLog, "responseBody", "too-large", sizeBytes);
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
- return nextLog;
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 compactNetworkLogsForTransport(logs, budgetBytes = NETWORK_LOG_TRANSPORT_BODY_BUDGET_BYTES) {
3169
- if (!logs) return void 0;
3170
- const compacted = omitOversizedIndividualBodies(logs);
3171
- const candidates = [];
3172
- compacted.forEach((log, index) => {
3173
- if (log.requestBody != null) {
3174
- candidates.push({
3175
- index,
3176
- field: "requestBody",
3177
- timestamp: log.timestamp,
3178
- sizeBytes: getUtf8ByteLength(log.requestBody)
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
- if (log.responseBody != null) {
3182
- candidates.push({
3183
- index,
3184
- field: "responseBody",
3185
- timestamp: log.timestamp,
3186
- sizeBytes: getUtf8ByteLength(log.responseBody)
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
- candidates.sort((a2, b) => {
3191
- const timestampDiff = b.timestamp - a2.timestamp;
3192
- if (timestampDiff !== 0) return timestampDiff;
3193
- const indexDiff = b.index - a2.index;
3194
- if (indexDiff !== 0) return indexDiff;
3195
- return a2.field === "responseBody" ? -1 : 1;
3196
- });
3197
- let usedBytes = 0;
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
- if (usedBytes + candidate.sizeBytes > budgetBytes) {
3200
- const log = compacted[candidate.index];
3201
- if (!log) continue;
3202
- setOmittedBodyMetadata(log, candidate.field, "transport-budget", candidate.sizeBytes);
3203
- continue;
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 compacted;
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
- Label.displayName = Root$1.displayName;
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 tracking-[-0.01em]",
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 tracking-tight text-text-primary",
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 = 8;
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(CUTOUT_RADIUS, cutoutRect.width / 2, cutoutRect.height / 2) : 0;
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 storageInput = React.useMemo(
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) && hasSeenOnboarding(storageInput)) return;
11406
- if (options == null ? void 0 : options.force) resetOnboardingStorage(storageInput);
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, storageInput]
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
- writeOnboardingStorage(storageInput, "completed");
13180
+ storage.write("completed");
11426
13181
  closeFlow();
11427
- }, [closeFlow, storageInput]);
13182
+ }, [closeFlow, storage]);
11428
13183
  const dismiss2 = React.useCallback(() => {
11429
- writeOnboardingStorage(storageInput, "dismissed");
13184
+ storage.write("dismissed");
11430
13185
  closeFlow();
11431
- }, [closeFlow, storageInput]);
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 || hasSeenOnboarding(storageInput)) return;
13231
+ if (!autoStart || steps.length === 0 || storage.hasSeen()) return;
11477
13232
  openAtStep(0);
11478
- }, [autoStart, openAtStep, steps.length, storageInput]);
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 shouldRenderOverlay = isOpen && activeStep && isActiveRoute && !activeStep.silent && (!stepHasTarget || targetRect);
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
- [activeStep, advanceFromTargetClick, complete, dismiss2, isOpen, registerTarget, start]
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 isIssueStickerServiceConsoleLog(log) {
11880
- var _a;
11881
- if (isIssueStickerInternalConsoleLog(log)) {
11882
- return false;
11883
- }
11884
- if (!log.url) {
11885
- return false;
11886
- }
11887
- const logDomain = extractDomain(log.url);
11888
- if (!logDomain) {
11889
- return false;
11890
- }
11891
- if (isIssueStickerDomain(logDomain)) {
11892
- return true;
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
- function filterExcludeServiceRelatedLogs(networkLogs) {
11902
- if (!networkLogs || networkLogs.length === 0) {
11903
- return [];
11904
- }
11905
- return networkLogs.filter((log) => {
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 filterExcludeServiceRelatedConsoleLogs(consoleLogs) {
11910
- if (!consoleLogs || consoleLogs.length === 0) {
11911
- return [];
11912
- }
11913
- return consoleLogs.filter((log) => {
11914
- return !isIssueStickerInternalConsoleLog(log) && !isIssueStickerServiceConsoleLog(log);
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
- const MAX_BUFFER$1 = 200;
11918
- const CONSOLE_METHODS = ["log", "warn", "error", "info", "debug"];
11919
- function methodToLogType(method) {
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 serializeArgs(args) {
11924
- return args.map((a2) => {
11925
- if (a2 instanceof Error) return a2.stack || a2.message;
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 extractStackTrace(args) {
11937
- for (const arg of args) {
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
- function captureSourceStackTrace() {
11945
- const stack = new Error().stack;
11946
- if (!stack) {
11947
- return void 0;
11948
- }
11949
- const lines = stack.split("\n").slice(1).filter((line) => {
11950
- return !line.includes("captureConsoleStackTrace") && !line.includes("ConsoleCollector");
11951
- });
11952
- if (lines.length === 0) {
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 captureConsoleStackTrace(args) {
11958
- const sourceStack = captureSourceStackTrace();
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
- class ConsoleCollector {
11968
- constructor() {
11969
- this.logs = [];
11970
- this.originals = {};
11971
- this.running = false;
11972
- }
11973
- start() {
11974
- if (this.running) return;
11975
- this.running = true;
11976
- for (const method of CONSOLE_METHODS) {
11977
- this.originals[method] = console[method];
11978
- console[method] = (...args) => {
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
- addLog(log) {
12011
- if (isIssueStickerInternalConsoleLog(log)) {
12012
- return;
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 MAX_BUFFER = 200;
12021
- function headersToRecord(headers) {
12022
- if (!headers || typeof headers.forEach !== "function") return void 0;
12023
- const entries = {};
12024
- headers.forEach((value, key) => {
12025
- entries[key] = value;
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 isImageMimeType(mimeType) {
12055
- return Boolean(mimeType == null ? void 0 : mimeType.startsWith("image/"));
13487
+ function isInternalDebugLog(message2) {
13488
+ return Boolean(message2 == null ? void 0 : message2.includes(ISSUE_STICKER_INTERNAL_LOG_PREFIX));
12056
13489
  }
12057
- function captureRequestBody(body) {
12058
- if (body == null) return {};
12059
- const requestBody = String(body);
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
- return { requestBody };
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
- function bytesToBase64(bytes) {
12076
- let binary = "";
12077
- const chunkSize = 32768;
12078
- for (let offset = 0; offset < bytes.length; offset += chunkSize) {
12079
- const chunk = bytes.subarray(offset, offset + chunkSize);
12080
- binary += String.fromCharCode(...chunk);
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
- return btoa(binary);
12083
- }
12084
- async function arrayBufferToBase64(arrayBuffer) {
12085
- return bytesToBase64(new Uint8Array(arrayBuffer));
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
- async function readFetchResponseBody(response, responseMimeType, contentLength) {
12088
- if (contentLength != null && contentLength > NETWORK_LOG_BODY_CAPTURE_LIMIT_BYTES) {
12089
- return omitTooLargeResponseBody(contentLength);
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
- return {
12099
- responseBody: await arrayBufferToBase64(arrayBuffer),
12100
- responseBodyEncoding: "base64"
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
- async function readXHRResponseBody(xhr, responseMimeType, contentLength) {
12113
- if (contentLength != null && contentLength > NETWORK_LOG_BODY_CAPTURE_LIMIT_BYTES) {
12114
- return omitTooLargeResponseBody(contentLength);
13531
+ function matchWildcardDomain(pattern, domain) {
13532
+ if (!pattern.includes("*")) {
13533
+ return pattern === domain;
12115
13534
  }
12116
- if (isImageMimeType(responseMimeType)) {
12117
- if (xhr.response instanceof Blob && typeof xhr.response.arrayBuffer === "function") {
12118
- const arrayBuffer = await xhr.response.arrayBuffer();
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 responseBody = xhr.responseText;
12140
- const sizeBytes = getCapturedBodySizeBytes(responseBody);
12141
- if (!isBodyWithinCaptureLimit(sizeBytes)) {
12142
- return omitTooLargeResponseBody(sizeBytes);
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
- class NetworkCollector {
12149
- constructor(apiBaseUrl) {
12150
- this.logs = [];
12151
- this.originalFetch = null;
12152
- this.originalXHROpen = null;
12153
- this.originalXHRSend = null;
12154
- this.running = false;
12155
- const normalizedApiBaseUrl = typeof apiBaseUrl === "string" && apiBaseUrl.trim().length > 0 ? apiBaseUrl.trim() : null;
12156
- this.apiBaseUrl = normalizedApiBaseUrl;
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
- start() {
12159
- if (this.running) return;
12160
- this.running = true;
12161
- this.interceptFetch();
12162
- this.interceptXHR();
13556
+ try {
13557
+ return new URL(baseUrl).origin;
13558
+ } catch {
13559
+ return null;
12163
13560
  }
12164
- stop() {
12165
- if (!this.running) return;
12166
- this.running = false;
12167
- this.restoreFetch();
12168
- this.restoreXHR();
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
- getLogs() {
12171
- return [...this.logs];
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
- getFilteredLogs() {
12174
- return this.logs.filter((log) => !this.isServiceRequest(log.url)).slice(-50);
13588
+ if (isIssueStickerApiUrl(log.url)) {
13589
+ return true;
12175
13590
  }
12176
- clear() {
12177
- this.logs = [];
13591
+ return false;
13592
+ }
13593
+ function isIssueStickerServiceConsoleLog(log) {
13594
+ var _a;
13595
+ if (isIssueStickerInternalConsoleLog(log)) {
13596
+ return false;
12178
13597
  }
12179
- addLog(log) {
12180
- if (this.isServiceRequest(log.url)) return;
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
- isServiceRequest(url) {
12187
- const matchesConfiguredApi = this.apiBaseUrl ? url.includes(this.apiBaseUrl) : false;
12188
- return matchesConfiguredApi || url.includes("issuesticker");
13601
+ const logDomain = extractDomain(log.url);
13602
+ if (!logDomain) {
13603
+ return false;
12189
13604
  }
12190
- interceptFetch() {
12191
- this.originalFetch = window.fetch;
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
- interceptXHR() {
12236
- this.originalXHROpen = XMLHttpRequest.prototype.open;
12237
- this.originalXHRSend = XMLHttpRequest.prototype.send;
12238
- const originalOpen = this.originalXHROpen;
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
- restoreFetch() {
12287
- if (this.originalFetch) {
12288
- window.fetch = this.originalFetch;
12289
- this.originalFetch = null;
12290
- }
13613
+ return false;
13614
+ }
13615
+ function filterExcludeServiceRelatedLogs(networkLogs) {
13616
+ if (!networkLogs || networkLogs.length === 0) {
13617
+ return [];
12291
13618
  }
12292
- restoreXHR() {
12293
- if (this.originalXHROpen) {
12294
- XMLHttpRequest.prototype.open = this.originalXHROpen;
12295
- this.originalXHROpen = null;
12296
- }
12297
- if (this.originalXHRSend) {
12298
- XMLHttpRequest.prototype.send = this.originalXHRSend;
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
- getPrimaryJiraMapping as $,
12312
- getJiraMetadata as A,
12313
- buildJiraIssueFields as B,
13639
+ DatePicker as $,
13640
+ trimLogBackupPayload as A,
13641
+ createSafeLogBackup as B,
12314
13642
  ConsoleCollector as C,
12315
- Button$1 as D,
12316
- ChevronDown as E,
12317
- Portal as F,
12318
- Content2 as G,
12319
- Check as H,
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
- DatePicker as J,
12322
- getNotionPageMetadata as K,
13649
+ SelectTrigger as J,
13650
+ SelectValue as K,
12323
13651
  Label as L,
12324
- buildNotionFieldViews as M,
13652
+ SelectContent as M,
12325
13653
  NetworkCollector as N,
12326
- DirectUploadError as O,
13654
+ SelectItem as O,
12327
13655
  Popover as P,
12328
- getRegisteredUploadId as Q,
12329
- Root2 as R,
13656
+ getJiraMetadata as Q,
13657
+ buildJiraIssueFields as R,
12330
13658
  ScreenRecordingController as S,
12331
- Trigger as T,
12332
- subscribe as U,
12333
- getSnapshot as V,
12334
- toast as W,
12335
- X,
12336
- getExposedJiraFields as Y,
12337
- isJiraFieldExposed as Z,
12338
- getIntegrationFailureMessage as _,
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
- createCollection as a0,
12341
- useDirection as a1,
12342
- ReactRemoveScroll as a2,
12343
- hideOthers as a3,
12344
- useFocusGuards as a4,
12345
- FocusScope as a5,
12346
- getPrimaryNotionMapping as a6,
12347
- buttonVariants as a7,
12348
- findElementBySelector as a8,
12349
- findElementByXPath as a9,
12350
- useOnboardingFlow as aa,
12351
- useOnboardingTarget as ab,
12352
- ONBOARDING_POPOVER_DATA_ATTR as ac,
12353
- OnboardingProvider as ad,
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
- devLogger as i,
12362
- filterExcludeServiceRelatedLogs as j,
12363
- filterExcludeServiceRelatedConsoleLogs as k,
12364
- compactNetworkLogsForTransport as l,
12365
- sanitizeString as m,
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
- buildExternalUserPayload as o,
12368
- SessionReplayCaptureController as p,
12369
- preloadModernScreenshot as q,
12370
- Select$1 as r,
12371
- syncExternalIssueIntegrations as s,
12372
- SelectTrigger as t,
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
- SelectValue as v,
12375
- SelectContent as w,
12376
- SelectItem as x,
12377
- PopoverTrigger as y,
12378
- PopoverContent as z
13718
+ sanitizeString as v,
13719
+ getCurrentPageUrl as w,
13720
+ SessionReplayCaptureController as x,
13721
+ preloadModernScreenshot as y,
13722
+ isIssueStickerExtensionRuntimeActive as z
12379
13723
  };