issue-sticker 0.8.0 → 0.9.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.
@@ -2,6 +2,772 @@ import * as React from "react";
2
2
  import { useLayoutEffect, forwardRef, createElement } from "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import * as ReactDOM from "react-dom";
5
+ function forEachElementDeep(root2, callback) {
6
+ if (root2 instanceof Element) {
7
+ callback(root2);
8
+ if (root2.shadowRoot) {
9
+ forEachElementDeep(root2.shadowRoot, callback);
10
+ }
11
+ }
12
+ Array.from(root2.children).forEach((child) => {
13
+ forEachElementDeep(child, callback);
14
+ });
15
+ }
16
+ const SESSION_REPLAY_BLOCK_CLASS = "rr-block";
17
+ const SESSION_REPLAY_MASK_CLASS = "rr-mask";
18
+ const SESSION_REPLAY_UNMASK_CLASS = "rr-unmask";
19
+ const FOREIGN_BLOCK_SELECTORS = [
20
+ // FullStory
21
+ ".fs-exclude",
22
+ ".fs-block",
23
+ // Sentry — ignore 는 의미가 "입력 이벤트를 기록하지 않는다"라 차단과 어긋나지만,
24
+ // 빼면 이미 그것에 기대고 있는 고객에게 회귀다. 과잉 마스킹은 안전한 방향이라 둔다.
25
+ ".sentry-block",
26
+ ".sentry-ignore",
27
+ "[data-sentry-block]",
28
+ // PostHog
29
+ ".ph-no-capture",
30
+ // Hotjar
31
+ "[data-hj-suppress]",
32
+ // OpenReplay
33
+ "[data-openreplay-hidden]",
34
+ // Highlight.io
35
+ ".highlight-block",
36
+ ".highlight-ignore",
37
+ // Heap
38
+ "[data-heap-ignore]",
39
+ ".heap-ignore"
40
+ ];
41
+ const FOREIGN_MASK_SELECTORS = [
42
+ // FullStory
43
+ ".fs-mask",
44
+ // Sentry
45
+ ".sentry-mask",
46
+ "[data-sentry-mask]",
47
+ // LogRocket
48
+ "[data-private]",
49
+ // Microsoft Clarity
50
+ "[data-clarity-mask]",
51
+ // Hotjar
52
+ "[data-hj-masked]",
53
+ // OpenReplay
54
+ "[data-openreplay-obscured]",
55
+ // Highlight.io
56
+ ".highlight-mask",
57
+ // ContentSquare
58
+ "[data-cs-mask]",
59
+ "[data-cs-encrypt]",
60
+ // Heap
61
+ "[data-heap-redact-text]",
62
+ "[data-heap-redact-attributes]",
63
+ // Amplitude
64
+ "[data-amp-mask]",
65
+ // Matomo
66
+ "[data-matomo-mask]"
67
+ ];
68
+ const FOREIGN_UNMASK_SELECTORS = [
69
+ ".fs-unmask",
70
+ ".sentry-unmask",
71
+ "[data-clarity-unmask]",
72
+ "[data-amp-unmask]",
73
+ "[data-cs-capture]"
74
+ ];
75
+ const SESSION_REPLAY_FULL_MASK_SELECTOR = [
76
+ `.${SESSION_REPLAY_MASK_CLASS}`,
77
+ ...FOREIGN_MASK_SELECTORS
78
+ ].join(",");
79
+ const SESSION_REPLAY_BLOCK_SELECTOR = [
80
+ `.${SESSION_REPLAY_BLOCK_CLASS}`,
81
+ ...FOREIGN_BLOCK_SELECTORS
82
+ ].join(",");
83
+ const SESSION_REPLAY_UNMASK_SELECTOR = [
84
+ `.${SESSION_REPLAY_UNMASK_CLASS}`,
85
+ ...FOREIGN_UNMASK_SELECTORS
86
+ ].join(",");
87
+ function isElementFullyMasked(element, maskSelector = SESSION_REPLAY_FULL_MASK_SELECTOR, unmaskSelector = SESSION_REPLAY_UNMASK_SELECTOR) {
88
+ if (!element) return false;
89
+ try {
90
+ const nearest = element.closest(`${maskSelector},${unmaskSelector}`);
91
+ return nearest !== null && nearest.matches(maskSelector);
92
+ } catch {
93
+ return true;
94
+ }
95
+ }
96
+ const DEFAULT_PRIVACY_CONFIG_V2 = {
97
+ version: "v2",
98
+ maskAllInputs: true,
99
+ maskTextSelector: "*",
100
+ fullMaskSelector: SESSION_REPLAY_FULL_MASK_SELECTOR,
101
+ unmaskSelector: SESSION_REPLAY_UNMASK_SELECTOR,
102
+ blockSelector: SESSION_REPLAY_BLOCK_SELECTOR,
103
+ allowCanvasRecording: false
104
+ };
105
+ function resolvePrivacyConfig(override) {
106
+ if (!override) return DEFAULT_PRIVACY_CONFIG_V2;
107
+ return { ...DEFAULT_PRIVACY_CONFIG_V2, ...override };
108
+ }
109
+ const REDACTED_HEADER_VALUE = "[redacted]";
110
+ const SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
111
+ "authorization",
112
+ "proxy-authorization",
113
+ "cookie",
114
+ "set-cookie",
115
+ "x-api-key",
116
+ "api-key",
117
+ "apikey",
118
+ "x-auth-token",
119
+ "x-access-token",
120
+ "x-refresh-token",
121
+ "x-session-token",
122
+ "x-csrf-token",
123
+ "x-xsrf-token",
124
+ "x-amz-security-token",
125
+ "x-goog-api-key"
126
+ ]);
127
+ function isSensitiveHeaderName(headerName) {
128
+ return SENSITIVE_HEADER_NAMES.has(headerName.toLowerCase());
129
+ }
130
+ function redactSensitiveHeaders(headers) {
131
+ if (!headers) return headers;
132
+ let changed = false;
133
+ const redacted = {};
134
+ for (const [name, value] of Object.entries(headers)) {
135
+ if (value && isSensitiveHeaderName(name)) {
136
+ redacted[name] = REDACTED_HEADER_VALUE;
137
+ changed = true;
138
+ } else {
139
+ redacted[name] = value;
140
+ }
141
+ }
142
+ return changed ? redacted : headers;
143
+ }
144
+ const REDACTED_BODY_VALUE = "[redacted]";
145
+ const SENSITIVE_KEY_MARKERS = [
146
+ "password",
147
+ "passwd",
148
+ "pwd",
149
+ "secret",
150
+ "token",
151
+ "apikey",
152
+ "privatekey",
153
+ "credential",
154
+ // 결제·본인확인 계열. 토스페이먼츠 공식 문서의 필드명을 그대로 딴다.
155
+ // cardNumber / customerIdentityNumber / businessNumber / accountNumber(가상계좌)
156
+ "cardnumber",
157
+ "identitynumber",
158
+ "businessnumber",
159
+ "registrationnumber",
160
+ "accountnumber"
161
+ ];
162
+ const SENSITIVE_KEY_EXACT = /* @__PURE__ */ new Set([
163
+ "otp",
164
+ "auth",
165
+ "authorization",
166
+ "bearer",
167
+ // 토스페이먼츠. billingKey + customerKey 조합이면 결제를 낼 수 있고, authKey 는 일회성
168
+ // 인증 키다(우리 admin 결제 성공 URL 의 쿼리 파라미터로 실제로 실린다).
169
+ // paymentKey 는 넣지 않는다 — 시크릿 키 없이는 아무것도 못 하는 식별자이고, 결제 장애
170
+ // 진단의 출발점이라 가리면 로그가 쓸모를 잃는다.
171
+ "billingkey",
172
+ "customerkey",
173
+ "authkey"
174
+ ]);
175
+ const MAX_WALK_DEPTH = 20;
176
+ function isSensitiveFieldName(name) {
177
+ const normalized = name.toLowerCase().replace(/[-_\s]/g, "");
178
+ if (SENSITIVE_KEY_EXACT.has(normalized)) {
179
+ return true;
180
+ }
181
+ return SENSITIVE_KEY_MARKERS.some((marker) => normalized.includes(marker));
182
+ }
183
+ function redactJsonValue(value, depth) {
184
+ if (typeof value === "string") {
185
+ const redacted = redactSensitiveValues(value);
186
+ return { value: redacted, changed: redacted !== value };
187
+ }
188
+ if (depth > MAX_WALK_DEPTH || value === null || typeof value !== "object") {
189
+ return { value, changed: false };
190
+ }
191
+ if (Array.isArray(value)) {
192
+ let changed2 = false;
193
+ const next2 = value.map((item) => {
194
+ const result2 = redactJsonValue(item, depth + 1);
195
+ changed2 || (changed2 = result2.changed);
196
+ return result2.value;
197
+ });
198
+ return changed2 ? { value: next2, changed: changed2 } : { value, changed: changed2 };
199
+ }
200
+ let changed = false;
201
+ const next = {};
202
+ for (const [key, item] of Object.entries(value)) {
203
+ if (item !== "" && item != null && typeof item !== "boolean" && isSensitiveFieldName(key)) {
204
+ next[key] = REDACTED_BODY_VALUE;
205
+ changed = true;
206
+ continue;
207
+ }
208
+ const result2 = redactJsonValue(item, depth + 1);
209
+ changed || (changed = result2.changed);
210
+ next[key] = result2.value;
211
+ }
212
+ return changed ? { value: next, changed } : { value, changed };
213
+ }
214
+ function redactKeyValuePairs(body) {
215
+ let changed = false;
216
+ const redacted = body.split("&").map((pair) => {
217
+ const separator = pair.indexOf("=");
218
+ if (separator <= 0) {
219
+ return pair;
220
+ }
221
+ const rawKey = pair.slice(0, separator);
222
+ const rawValue = pair.slice(separator + 1);
223
+ if (!rawValue) {
224
+ return pair;
225
+ }
226
+ if (isSensitiveFieldName(decodeURIComponent(rawKey))) {
227
+ changed = true;
228
+ return `${rawKey}=${encodeURIComponent(REDACTED_BODY_VALUE)}`;
229
+ }
230
+ const redactedValue = redactSensitiveValues(decodeURIComponent(rawValue));
231
+ if (redactedValue === decodeURIComponent(rawValue)) {
232
+ return pair;
233
+ }
234
+ changed = true;
235
+ return `${rawKey}=${encodeURIComponent(redactedValue)}`;
236
+ }).join("&");
237
+ return changed ? redacted : body;
238
+ }
239
+ function looksLikeFormUrlEncoded(body) {
240
+ return /^[^=&\s]+=[^&]*(&[^=&\s]+=[^&]*)*$/.test(body);
241
+ }
242
+ function redactSensitiveBody(body) {
243
+ if (!body) return body;
244
+ const trimmed = body.trim();
245
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
246
+ try {
247
+ const parsed = JSON.parse(trimmed);
248
+ const { value, changed } = redactJsonValue(parsed, 0);
249
+ return changed ? JSON.stringify(value) : body;
250
+ } catch {
251
+ }
252
+ }
253
+ if (looksLikeFormUrlEncoded(trimmed)) {
254
+ const redacted = redactKeyValuePairs(trimmed);
255
+ return redacted === trimmed ? body : redacted;
256
+ }
257
+ return body;
258
+ }
259
+ const JWT_PATTERN = /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]*/g;
260
+ const BEARER_PATTERN = /(Bearer\s+)[A-Za-z0-9._~+/-]{12,}=*/gi;
261
+ const CARD_CANDIDATE_PATTERN = /\b[2-6](?:[ -]?\d){13,18}\b/g;
262
+ const RRN_CANDIDATE_PATTERN = /\b(\d{2})(\d{2})(\d{2})-?([1-8])(\d{6})\b/g;
263
+ const BRN_CANDIDATE_PATTERN = /\b(\d{3})-?(\d{2})-?(\d{5})\b/g;
264
+ const BRN_WEIGHTS = [1, 3, 7, 1, 3, 7, 1, 3, 5];
265
+ const KR_MOBILE_PATTERN = /\b01[016789]-?\d{3,4}-?\d{4}\b/g;
266
+ const EMAIL_PATTERN = /[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+/g;
267
+ function redactEmails(text) {
268
+ if (!text) return text;
269
+ const out = text.replace(EMAIL_PATTERN, REDACTED_BODY_VALUE);
270
+ return out === text ? text : out;
271
+ }
272
+ function passesBrnChecksum(digits) {
273
+ let sum = 0;
274
+ for (let i2 = 0; i2 < 9; i2 += 1) {
275
+ sum += (digits.charCodeAt(i2) - 48) * BRN_WEIGHTS[i2];
276
+ }
277
+ sum += Math.floor((digits.charCodeAt(8) - 48) * 5 / 10);
278
+ return (10 - sum % 10) % 10 === digits.charCodeAt(9) - 48;
279
+ }
280
+ function passesLuhn(digits) {
281
+ let sum = 0;
282
+ let double = false;
283
+ for (let i2 = digits.length - 1; i2 >= 0; i2 -= 1) {
284
+ let digit = digits.charCodeAt(i2) - 48;
285
+ if (double) {
286
+ digit *= 2;
287
+ if (digit > 9) digit -= 9;
288
+ }
289
+ sum += digit;
290
+ double = !double;
291
+ }
292
+ return sum % 10 === 0;
293
+ }
294
+ function looksLikeBirthDate(month, day) {
295
+ const monthValue = Number(month);
296
+ const dayValue = Number(day);
297
+ return monthValue >= 1 && monthValue <= 12 && dayValue >= 1 && dayValue <= 31;
298
+ }
299
+ function redactSensitiveValues(text) {
300
+ if (!text) return text;
301
+ let out = text;
302
+ out = out.replace(JWT_PATTERN, REDACTED_BODY_VALUE);
303
+ out = out.replace(BEARER_PATTERN, `$1${REDACTED_BODY_VALUE}`);
304
+ out = out.replace(CARD_CANDIDATE_PATTERN, (match) => {
305
+ const digits = match.replace(/[ -]/g, "");
306
+ return digits.length >= 14 && digits.length <= 19 && passesLuhn(digits) ? REDACTED_BODY_VALUE : match;
307
+ });
308
+ out = out.replace(
309
+ RRN_CANDIDATE_PATTERN,
310
+ (match, _year, month, day) => looksLikeBirthDate(month, day) ? REDACTED_BODY_VALUE : match
311
+ );
312
+ out = out.replace(BRN_CANDIDATE_PATTERN, (match) => {
313
+ const digits = match.replace(/-/g, "");
314
+ return passesBrnChecksum(digits) ? REDACTED_BODY_VALUE : match;
315
+ });
316
+ out = out.replace(KR_MOBILE_PATTERN, REDACTED_BODY_VALUE);
317
+ return out === text ? text : out;
318
+ }
319
+ function redactUrlSecrets(url) {
320
+ if (!url) return url;
321
+ const queryStart = url.indexOf("?");
322
+ const fragmentStart = url.indexOf("#");
323
+ if (queryStart < 0 && fragmentStart < 0) {
324
+ return redactSensitiveValues(url);
325
+ }
326
+ const hasQuery = queryStart >= 0 && (fragmentStart < 0 || queryStart < fragmentStart);
327
+ const base = url.slice(0, hasQuery ? queryStart : fragmentStart);
328
+ const query = hasQuery ? url.slice(queryStart + 1, fragmentStart >= 0 ? fragmentStart : void 0) : "";
329
+ const fragment = fragmentStart >= 0 ? url.slice(fragmentStart + 1) : "";
330
+ const redactedQuery = redactKeyValuePairs(query);
331
+ const redactedFragment = fragment.includes("=") ? redactKeyValuePairs(fragment) : fragment;
332
+ const redactedBase = redactSensitiveValues(base);
333
+ if (redactedBase === base && redactedQuery === query && redactedFragment === fragment) {
334
+ return url;
335
+ }
336
+ return redactedBase + (hasQuery ? `?${redactedQuery}` : "") + (fragmentStart >= 0 ? `#${redactedFragment}` : "");
337
+ }
338
+ function redactSensitiveText(text) {
339
+ if (!text) return text;
340
+ return redactSensitiveValues(redactUrlSecrets(redactSensitiveBody(text)));
341
+ }
342
+ const SCREENSHOT_FULL_MASK_SELECTOR = SESSION_REPLAY_FULL_MASK_SELECTOR;
343
+ const SCREENSHOT_BLOCK_SELECTOR = SESSION_REPLAY_BLOCK_SELECTOR;
344
+ const SCREENSHOT_MASK_BACKGROUND = "rgba(148, 148, 148, 0.28)";
345
+ const SCREENSHOT_BLOCK_BACKGROUND = "rgba(148, 148, 148, 0.85)";
346
+ const MASK_LABEL_WIDTH = 44;
347
+ const MASK_LABEL_HEIGHT = 16;
348
+ const MASK_LABEL_IMAGE = `url("data:image/svg+xml;charset=utf-8,${encodeURIComponent(
349
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${MASK_LABEL_WIDTH}" height="${MASK_LABEL_HEIGHT}" viewBox="0 0 ${MASK_LABEL_WIDTH} ${MASK_LABEL_HEIGHT}"><text x="${MASK_LABEL_WIDTH / 2}" y="12" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="#262626">가려짐</text></svg>`
350
+ )}")`;
351
+ const MASK_LABEL_MIN_WIDTH = MASK_LABEL_WIDTH + 8;
352
+ const MASK_LABEL_MIN_HEIGHT = MASK_LABEL_HEIGHT + 8;
353
+ const TEXT_HIDING_PROPERTIES = [
354
+ ["color", "transparent"],
355
+ ["-webkit-text-fill-color", "transparent"],
356
+ ["text-shadow", "none"],
357
+ ["fill", "transparent"]
358
+ ];
359
+ const REPLACED_MEDIA_SELECTOR = "img, svg, video, canvas, picture, iframe, object, embed";
360
+ const VALUE_ELEMENT_SELECTOR = "input, textarea, select";
361
+ const SENSITIVE_AUTOCOMPLETE_TOKENS = /* @__PURE__ */ new Set([
362
+ // 자격증명
363
+ "current-password",
364
+ "new-password",
365
+ "one-time-code",
366
+ // 결제
367
+ "cc-number",
368
+ "cc-exp",
369
+ "cc-exp-month",
370
+ "cc-exp-year",
371
+ "cc-csc",
372
+ "cc-name",
373
+ "cc-type",
374
+ "cc-given-name",
375
+ "cc-additional-name",
376
+ "cc-family-name",
377
+ // 신원
378
+ "name",
379
+ "given-name",
380
+ "additional-name",
381
+ "family-name",
382
+ "honorific-prefix",
383
+ "honorific-suffix",
384
+ "nickname",
385
+ "bday",
386
+ "bday-day",
387
+ "bday-month",
388
+ "bday-year",
389
+ "sex",
390
+ // 주소
391
+ "street-address",
392
+ "address-line1",
393
+ "address-line2",
394
+ "address-line3",
395
+ "postal-code",
396
+ // 연락처
397
+ "tel",
398
+ "tel-national",
399
+ "tel-local",
400
+ "tel-area-code",
401
+ "email",
402
+ "impp"
403
+ ]);
404
+ function redactScreenshotText(text) {
405
+ return redactEmails(redactSensitiveValues(text));
406
+ }
407
+ function matchesSafely(element, selector) {
408
+ try {
409
+ return element.matches(selector);
410
+ } catch {
411
+ return false;
412
+ }
413
+ }
414
+ function closestSafely(element, selector) {
415
+ if (!element) return null;
416
+ try {
417
+ return element.closest(selector);
418
+ } catch {
419
+ return null;
420
+ }
421
+ }
422
+ function hasSensitiveValueHint(element) {
423
+ var _a2;
424
+ if (((_a2 = element.getAttribute("type")) == null ? void 0 : _a2.toLowerCase()) === "password") {
425
+ return true;
426
+ }
427
+ const autocomplete = element.getAttribute("autocomplete");
428
+ if (!autocomplete) return false;
429
+ return autocomplete.toLowerCase().split(/\s+/).some((token) => SENSITIVE_AUTOCOMPLETE_TOKENS.has(token));
430
+ }
431
+ const overwriteProperty = (element, name, value) => {
432
+ const style = element.style;
433
+ style == null ? void 0 : style.setProperty(name, value, "important");
434
+ };
435
+ function coverMaskedRegion(region, setProperty) {
436
+ setProperty(region, "background-color", SCREENSHOT_MASK_BACKGROUND);
437
+ forEachElementDeep(region, (node2) => {
438
+ if (!isElementFullyMasked(node2)) return;
439
+ TEXT_HIDING_PROPERTIES.forEach(([name, value]) => setProperty(node2, name, value));
440
+ if (matchesSafely(node2, REPLACED_MEDIA_SELECTOR)) {
441
+ setProperty(node2, "visibility", "hidden");
442
+ }
443
+ });
444
+ }
445
+ function parsePixels(value) {
446
+ const parsed = Number.parseFloat(value ?? "");
447
+ return Number.isFinite(parsed) ? parsed : 0;
448
+ }
449
+ function fitsMaskLabel(region) {
450
+ const rect = region.getBoundingClientRect();
451
+ const style = region.style;
452
+ const width = rect.width || parsePixels(style == null ? void 0 : style.width);
453
+ const height = rect.height || parsePixels(style == null ? void 0 : style.height);
454
+ return width >= MASK_LABEL_MIN_WIDTH && height >= MASK_LABEL_MIN_HEIGHT;
455
+ }
456
+ function coverBlockedRegion(region, setProperty) {
457
+ const labeled = fitsMaskLabel(region);
458
+ setProperty(region, "visibility", "visible");
459
+ setProperty(region, "background-color", SCREENSHOT_BLOCK_BACKGROUND);
460
+ setProperty(region, "background-image", labeled ? MASK_LABEL_IMAGE : "none");
461
+ if (labeled) {
462
+ setProperty(region, "background-repeat", "no-repeat");
463
+ setProperty(region, "background-position", "center");
464
+ setProperty(region, "background-size", "auto");
465
+ }
466
+ TEXT_HIDING_PROPERTIES.forEach(([name, value]) => setProperty(region, name, value));
467
+ setProperty(region, "pointer-events", "none");
468
+ forEachElementDeep(region, (descendant) => {
469
+ if (descendant !== region) {
470
+ setProperty(descendant, "visibility", "hidden");
471
+ }
472
+ });
473
+ }
474
+ function coverPrivateRegions(root2, setProperty) {
475
+ const blocked = [];
476
+ const masked = [];
477
+ forEachElementDeep(root2, (element) => {
478
+ if (matchesSafely(element, SCREENSHOT_BLOCK_SELECTOR)) {
479
+ blocked.push(element);
480
+ return;
481
+ }
482
+ if (matchesSafely(element, SCREENSHOT_FULL_MASK_SELECTOR) && isElementFullyMasked(element)) {
483
+ masked.push(element);
484
+ }
485
+ });
486
+ masked.forEach((region) => coverMaskedRegion(region, setProperty));
487
+ blocked.forEach((region) => coverBlockedRegion(region, setProperty));
488
+ }
489
+ function applyScreenshotPrivacyMask(clonedRoot) {
490
+ coverPrivateRegions(clonedRoot, overwriteProperty);
491
+ const walker = clonedRoot.ownerDocument.createTreeWalker(clonedRoot, NodeFilter.SHOW_TEXT);
492
+ for (let node2 = walker.nextNode(); node2; node2 = walker.nextNode()) {
493
+ const text = node2.nodeValue;
494
+ if (!text) continue;
495
+ node2.nodeValue = redactScreenshotText(text);
496
+ }
497
+ clonedRoot.querySelectorAll(VALUE_ELEMENT_SELECTOR).forEach((element) => {
498
+ const value = element.getAttribute("value");
499
+ if (!value) return;
500
+ const hidden = closestSafely(element, SCREENSHOT_BLOCK_SELECTOR) !== null || isElementFullyMasked(element) || hasSensitiveValueHint(element);
501
+ element.setAttribute("value", hidden ? "" : redactScreenshotText(value));
502
+ });
503
+ }
504
+ const FRAME_GRAB_TIMEOUT_MS = 300;
505
+ const PROBE_SIZE = 8;
506
+ function matchesSelfOrAncestor(element, selector) {
507
+ try {
508
+ return element.closest(selector) !== null;
509
+ } catch {
510
+ return false;
511
+ }
512
+ }
513
+ function shouldOverlayCanvas(canvas, excludeSelectors) {
514
+ const rect = canvas.getBoundingClientRect();
515
+ if (rect.width < 1 || rect.height < 1) {
516
+ return false;
517
+ }
518
+ if (matchesSelfOrAncestor(canvas, SCREENSHOT_BLOCK_SELECTOR) || matchesSelfOrAncestor(canvas, SCREENSHOT_FULL_MASK_SELECTOR)) {
519
+ return false;
520
+ }
521
+ return !excludeSelectors.some((selector) => matchesSelfOrAncestor(canvas, selector));
522
+ }
523
+ function rendersBlank(canvas) {
524
+ const probe = document.createElement("canvas");
525
+ probe.width = PROBE_SIZE;
526
+ probe.height = PROBE_SIZE;
527
+ const context = probe.getContext("2d", { willReadFrequently: true });
528
+ if (!context) {
529
+ return false;
530
+ }
531
+ try {
532
+ context.drawImage(canvas, 0, 0, PROBE_SIZE, PROBE_SIZE);
533
+ const { data } = context.getImageData(0, 0, PROBE_SIZE, PROBE_SIZE);
534
+ for (let offset2 = 4; offset2 < data.length; offset2 += 4) {
535
+ if (data[offset2] !== data[0] || data[offset2 + 1] !== data[1] || data[offset2 + 2] !== data[2] || data[offset2 + 3] !== data[3]) {
536
+ return false;
537
+ }
538
+ }
539
+ return true;
540
+ } catch {
541
+ return false;
542
+ }
543
+ }
544
+ async function grabFrame(canvas) {
545
+ const FrameGrabber = globalThis.ImageCapture;
546
+ if (!FrameGrabber || typeof canvas.captureStream !== "function") {
547
+ return null;
548
+ }
549
+ let track;
550
+ try {
551
+ track = canvas.captureStream().getVideoTracks()[0];
552
+ if (!track) {
553
+ return null;
554
+ }
555
+ return await Promise.race([
556
+ new FrameGrabber(track).grabFrame(),
557
+ new Promise((resolve) => {
558
+ setTimeout(() => resolve(null), FRAME_GRAB_TIMEOUT_MS);
559
+ })
560
+ ]);
561
+ } catch {
562
+ return null;
563
+ } finally {
564
+ track == null ? void 0 : track.stop();
565
+ }
566
+ }
567
+ const threeHandles = /* @__PURE__ */ new Map();
568
+ const registrationListeners = /* @__PURE__ */ new Set();
569
+ function onThreeRendererRegistered(listener) {
570
+ registrationListeners.add(listener);
571
+ return () => {
572
+ registrationListeners.delete(listener);
573
+ };
574
+ }
575
+ function registerThreeRenderer(handle) {
576
+ var _a2;
577
+ const canvas = (_a2 = handle == null ? void 0 : handle.renderer) == null ? void 0 : _a2.domElement;
578
+ if (!(canvas instanceof HTMLCanvasElement)) {
579
+ return () => void 0;
580
+ }
581
+ const isNew = !threeHandles.has(canvas);
582
+ threeHandles.set(canvas, handle);
583
+ if (isNew) {
584
+ registrationListeners.forEach((listener) => {
585
+ try {
586
+ listener(canvas);
587
+ } catch {
588
+ }
589
+ });
590
+ }
591
+ return () => {
592
+ threeHandles.delete(canvas);
593
+ };
594
+ }
595
+ function getThreeHandle(canvas) {
596
+ return threeHandles.get(canvas);
597
+ }
598
+ async function renderAndGrab(handle) {
599
+ try {
600
+ handle.renderer.render(handle.scene, handle.camera);
601
+ return await createImageBitmap(handle.renderer.domElement);
602
+ } catch {
603
+ return null;
604
+ }
605
+ }
606
+ async function grabCanvasFrames(root2, excludeSelectors) {
607
+ const targets = [];
608
+ forEachElementDeep(root2, (element) => {
609
+ if (!(element instanceof HTMLCanvasElement) || !shouldOverlayCanvas(element, excludeSelectors)) {
610
+ return;
611
+ }
612
+ if (threeHandles.has(element) || rendersBlank(element)) {
613
+ targets.push(element);
614
+ }
615
+ });
616
+ if (targets.length === 0) {
617
+ return [];
618
+ }
619
+ const frames = await Promise.all(
620
+ targets.map(async (canvas) => {
621
+ const handle = threeHandles.get(canvas);
622
+ const bitmap = (handle ? await renderAndGrab(handle) : null) ?? await grabFrame(canvas);
623
+ return bitmap ? { bitmap, rect: canvas.getBoundingClientRect() } : null;
624
+ })
625
+ );
626
+ return frames.filter((frame) => frame !== null);
627
+ }
628
+ function installThreeAutoDetect() {
629
+ if (typeof window === "undefined") {
630
+ return () => void 0;
631
+ }
632
+ const holder = window;
633
+ const restorePatches = [];
634
+ const observe = (event) => {
635
+ const detail = event.detail;
636
+ if ((detail == null ? void 0 : detail.isWebGLRenderer) === true) {
637
+ observeRenderer(detail, restorePatches);
638
+ }
639
+ };
640
+ const wrap = (hook) => {
641
+ if (!hook) {
642
+ return {
643
+ dispatchEvent(event) {
644
+ observe(event);
645
+ return true;
646
+ }
647
+ };
648
+ }
649
+ const original = hook.dispatchEvent;
650
+ if (typeof original !== "function" || original.__probed === true) {
651
+ return hook;
652
+ }
653
+ const patched = function(event) {
654
+ observe(event);
655
+ return original.call(this, event);
656
+ };
657
+ patched.__probed = true;
658
+ hook.dispatchEvent = patched;
659
+ restorePatches.push(() => {
660
+ hook.dispatchEvent = original;
661
+ });
662
+ return hook;
663
+ };
664
+ const previousDescriptor = Object.getOwnPropertyDescriptor(window, "__THREE_DEVTOOLS__");
665
+ let current = wrap(holder.__THREE_DEVTOOLS__);
666
+ Object.defineProperty(window, "__THREE_DEVTOOLS__", {
667
+ configurable: true,
668
+ enumerable: true,
669
+ get: () => current,
670
+ set: (next) => {
671
+ current = wrap(next);
672
+ }
673
+ });
674
+ return () => {
675
+ restorePatches.forEach((restore) => restore());
676
+ restorePatches.length = 0;
677
+ const survivor = current;
678
+ delete window.__THREE_DEVTOOLS__;
679
+ if (previousDescriptor) {
680
+ Object.defineProperty(window, "__THREE_DEVTOOLS__", previousDescriptor);
681
+ return;
682
+ }
683
+ if (survivor && !holder.__THREE_DEVTOOLS__) {
684
+ const isOurs = Object.keys(survivor).length === 1 && "dispatchEvent" in survivor;
685
+ if (!isOurs) {
686
+ holder.__THREE_DEVTOOLS__ = survivor;
687
+ }
688
+ }
689
+ };
690
+ }
691
+ function isSceneLike(value) {
692
+ return (value == null ? void 0 : value.isScene) === true;
693
+ }
694
+ const lastDrawn = /* @__PURE__ */ new WeakMap();
695
+ function observeRenderer(renderer, restorePatches) {
696
+ queueMicrotask(() => {
697
+ const owner = Object.prototype.hasOwnProperty.call(renderer, "render") ? renderer : Object.getPrototypeOf(renderer);
698
+ const original = owner.render;
699
+ if (typeof original !== "function") {
700
+ return;
701
+ }
702
+ owner.render = function(scene, camera) {
703
+ const canvas = (this == null ? void 0 : this.domElement) ?? renderer.domElement;
704
+ if (canvas instanceof HTMLCanvasElement) {
705
+ lastDrawn.set(canvas, [scene, camera]);
706
+ if (isSceneLike(scene)) {
707
+ const existing = threeHandles.get(canvas);
708
+ if (existing) {
709
+ existing.scene = scene;
710
+ existing.camera = camera;
711
+ } else {
712
+ registerThreeRenderer({
713
+ // 강제 렌더는 **앱이 마지막으로 그린 그대로** 다시 그린다. 후처리를 쓰면 화면을
714
+ // 그리는 것은 풀스크린 쿼드 패스라, 앱의 씬으로 그리면 후처리가 빠진다.
715
+ // 원본을 부른다 — 우리 래퍼를 다시 타지 않게 한다.
716
+ renderer: {
717
+ domElement: canvas,
718
+ render: (s2, c2) => {
719
+ const last = lastDrawn.get(canvas);
720
+ return last ? original.call(this, last[0], last[1]) : original.call(this, s2, c2);
721
+ }
722
+ },
723
+ scene,
724
+ camera
725
+ });
726
+ }
727
+ }
728
+ }
729
+ return original.call(this, scene, camera);
730
+ };
731
+ restorePatches.push(() => {
732
+ owner.render = original;
733
+ });
734
+ });
735
+ }
736
+ function frameDestRect(rect, origin, scale) {
737
+ return [
738
+ (rect.left - origin.x) * scale,
739
+ (rect.top - origin.y) * scale,
740
+ rect.width * scale,
741
+ rect.height * scale
742
+ ];
743
+ }
744
+ async function overlayCanvasFrames(capture, frames, origin, cssWidth, quality) {
745
+ let captured = null;
746
+ try {
747
+ captured = await createImageBitmap(capture);
748
+ const output = document.createElement("canvas");
749
+ output.width = captured.width;
750
+ output.height = captured.height;
751
+ const context = output.getContext("2d");
752
+ if (!context) {
753
+ return capture;
754
+ }
755
+ context.drawImage(captured, 0, 0);
756
+ const scale = cssWidth > 0 ? captured.width / cssWidth : 1;
757
+ frames.forEach(({ bitmap, rect }) => {
758
+ context.drawImage(bitmap, ...frameDestRect(rect, origin, scale));
759
+ });
760
+ const blob2 = await new Promise((resolve) => {
761
+ output.toBlob(resolve, capture.type || "image/webp", quality);
762
+ });
763
+ return blob2 ?? capture;
764
+ } catch {
765
+ return capture;
766
+ } finally {
767
+ captured == null ? void 0 : captured.close();
768
+ }
769
+ }
770
+ const CANVAS_INTERACTION_TAG = "issue-sticker:canvas-interaction";
5
771
  /*! pako 2.2.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */
6
772
  const Z_FIXED$1 = 4;
7
773
  const Z_BINARY = 0;
@@ -15433,6 +16199,42 @@ function inDom(n2) {
15433
16199
  if (!doc) return false;
15434
16200
  return index$2.contains(doc, n2) || shadowHostInDom(n2);
15435
16201
  }
16202
+ const utils = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
16203
+ __proto__: null,
16204
+ StyleSheetMirror,
16205
+ get _mirror() {
16206
+ return _mirror;
16207
+ },
16208
+ closestElementOfNode,
16209
+ getBaseDimension,
16210
+ getNestedRule,
16211
+ getPositionsAndIndex,
16212
+ getRootShadowHost,
16213
+ getShadowHost,
16214
+ getWindowHeight,
16215
+ getWindowScroll,
16216
+ getWindowWidth,
16217
+ hasShadowRoot,
16218
+ hookSetter,
16219
+ inDom,
16220
+ isAncestorRemoved,
16221
+ isBlocked,
16222
+ isIgnored,
16223
+ isSerialized,
16224
+ isSerializedIframe,
16225
+ isSerializedStylesheet,
16226
+ iterateResolveTree,
16227
+ legacy_isTouchEvent,
16228
+ get nowTimestamp() {
16229
+ return nowTimestamp;
16230
+ },
16231
+ on,
16232
+ polyfill: polyfill$1,
16233
+ queueToResolveTrees,
16234
+ shadowHostInDom,
16235
+ throttle,
16236
+ uniqueTextMutations
16237
+ }, Symbol.toStringTag, { value: "Module" }));
15436
16238
  var EventType = /* @__PURE__ */ ((EventType2) => {
15437
16239
  EventType2[EventType2["DomContentLoaded"] = 0] = "DomContentLoaded";
15438
16240
  EventType2[EventType2["Load"] = 1] = "Load";
@@ -21615,6 +22417,23 @@ class Replayer {
21615
22417
  const { addCustomEvent } = record;
21616
22418
  const { freezePage } = record;
21617
22419
  const { takeFullSnapshot } = record;
22420
+ const rrweb = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
22421
+ __proto__: null,
22422
+ EventType,
22423
+ IncrementalSource,
22424
+ MouseInteractions,
22425
+ Replayer,
22426
+ ReplayerEvents,
22427
+ addCustomEvent,
22428
+ canvasMutation,
22429
+ freezePage,
22430
+ get mirror() {
22431
+ return _mirror;
22432
+ },
22433
+ record,
22434
+ takeFullSnapshot,
22435
+ utils
22436
+ }, Symbol.toStringTag, { value: "Module" }));
21618
22437
  const OVERLAY_LAYER_INDEX = {
21619
22438
  // 익스텐션·SDK 위젯의 shadow host. isolation:isolate 로 stacking context 를 만들기 때문에
21620
22439
  // 아래 레이어 값들은 호스트 내부 순서만 정한다. 호스트 페이지의 오버레이와 겨루는 것은
@@ -27397,17 +28216,41 @@ const MousePointer2 = createLucideIcon("MousePointer2", [
27397
28216
  }
27398
28217
  ]
27399
28218
  ]);
28219
+ function registerThree(handle) {
28220
+ return registerThreeRenderer(handle);
28221
+ }
28222
+ installThreeAutoDetect();
27400
28223
  export {
27401
- Anchor as A,
27402
- Content as C,
28224
+ createSlottable as $,
28225
+ record as A,
28226
+ resolvePrivacyConfig as B,
28227
+ CANVAS_INTERACTION_TAG as C,
27403
28228
  DismissableLayer as D,
28229
+ isElementFullyMasked as E,
28230
+ grabCanvasFrames as F,
28231
+ applyScreenshotPrivacyMask as G,
28232
+ overlayCanvasFrames as H,
28233
+ forEachElementDeep as I,
28234
+ redactSensitiveText as J,
28235
+ REDACTED_BODY_VALUE as K,
28236
+ isSensitiveFieldName as L,
27404
28237
  MousePointer2 as M,
28238
+ redactSensitiveHeaders as N,
27405
28239
  OVERLAY_LAYER_INDEX as O,
27406
28240
  Presence as P,
28241
+ redactSensitiveBody as Q,
27407
28242
  Replayer as R,
27408
- Slot as S,
27409
- VISUALLY_HIDDEN_STYLES as V,
28243
+ Content as S,
28244
+ createPopperScope as T,
28245
+ Root2 as U,
28246
+ Anchor as V,
28247
+ Arrow as W,
28248
+ VISUALLY_HIDDEN_STYLES as X,
28249
+ clsx as Y,
28250
+ Slot as Z,
28251
+ Root as _,
27410
28252
  cn as a,
28253
+ rrweb as a0,
27411
28254
  usePortalContainer as b,
27412
28255
  createLucideIcon as c,
27413
28256
  OVERLAY_LAYER as d,
@@ -27423,14 +28266,14 @@ export {
27423
28266
  useLayoutEffect2 as n,
27424
28267
  useCallbackRef as o,
27425
28268
  PortalContainerProvider as p,
27426
- gzip_1 as q,
27427
- record as r,
28269
+ onThreeRendererRegistered as q,
28270
+ registerThree as r,
27428
28271
  stopEscapePropagation as s,
27429
- createPopperScope as t,
28272
+ shouldOverlayCanvas as t,
27430
28273
  ungzip_1 as u,
27431
- Root2 as v,
27432
- Arrow as w,
27433
- clsx as x,
27434
- Root as y,
27435
- createSlottable as z
28274
+ getThreeHandle as v,
28275
+ rendersBlank as w,
28276
+ gzip_1 as x,
28277
+ redactUrlSecrets as y,
28278
+ redactSensitiveValues as z
27436
28279
  };