issue-sticker 0.9.0 → 0.10.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,584 +2,17 @@ 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();
5
+ const sources = /* @__PURE__ */ new WeakMap();
568
6
  const registrationListeners = /* @__PURE__ */ new Set();
569
- function onThreeRendererRegistered(listener) {
7
+ function onCanvasSourceRegistered(listener) {
570
8
  registrationListeners.add(listener);
571
9
  return () => {
572
10
  registrationListeners.delete(listener);
573
11
  };
574
12
  }
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);
13
+ function registerCanvasSource(canvas, source) {
14
+ const isNew = !sources.has(canvas);
15
+ sources.set(canvas, source);
583
16
  if (isNew) {
584
17
  registrationListeners.forEach((listener) => {
585
18
  try {
@@ -589,184 +22,20 @@ function registerThreeRenderer(handle) {
589
22
  });
590
23
  }
591
24
  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
- }
25
+ if (sources.get(canvas) === source) {
26
+ sources.delete(canvas);
688
27
  }
689
28
  };
690
29
  }
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
- }
30
+ function getCanvasSource(canvas) {
31
+ return sources.get(canvas);
769
32
  }
33
+ const REPLAY_EVENT_TYPE = {
34
+ FullSnapshot: 2,
35
+ IncrementalSnapshot: 3,
36
+ Meta: 4,
37
+ Custom: 5
38
+ };
770
39
  const CANVAS_INTERACTION_TAG = "issue-sticker:canvas-interaction";
771
40
  /*! pako 2.2.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */
772
41
  const Z_FIXED$1 = 4;
@@ -2659,9 +1928,9 @@ const _has = (obj, key) => {
2659
1928
  return Object.prototype.hasOwnProperty.call(obj, key);
2660
1929
  };
2661
1930
  var assign = function(obj) {
2662
- const sources = Array.prototype.slice.call(arguments, 1);
2663
- while (sources.length) {
2664
- const source = sources.shift();
1931
+ const sources2 = Array.prototype.slice.call(arguments, 1);
1932
+ while (sources2.length) {
1933
+ const source = sources2.shift();
2665
1934
  if (!source) {
2666
1935
  continue;
2667
1936
  }
@@ -22434,6 +21703,205 @@ const rrweb = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
22434
21703
  takeFullSnapshot,
22435
21704
  utils
22436
21705
  }, Symbol.toStringTag, { value: "Module" }));
21706
+ const threeHandles = /* @__PURE__ */ new WeakMap();
21707
+ function isThreeNamespace(value) {
21708
+ const candidate = value;
21709
+ return typeof (candidate == null ? void 0 : candidate.Raycaster) === "function" && typeof (candidate == null ? void 0 : candidate.Vector2) === "function";
21710
+ }
21711
+ function isHelper(object) {
21712
+ let current = object;
21713
+ while (current) {
21714
+ if (typeof current.type === "string" && current.type.endsWith("Helper")) {
21715
+ return true;
21716
+ }
21717
+ current = current.parent;
21718
+ }
21719
+ return false;
21720
+ }
21721
+ function nameOf(object) {
21722
+ let current = object;
21723
+ while (current) {
21724
+ if (current.name) {
21725
+ return current.name;
21726
+ }
21727
+ current = current.parent;
21728
+ }
21729
+ return (object == null ? void 0 : object.type) ?? null;
21730
+ }
21731
+ function pickTarget(handle, pointer) {
21732
+ var _a2;
21733
+ const three = handle.three;
21734
+ if (!isThreeNamespace(three)) {
21735
+ return null;
21736
+ }
21737
+ const children = (_a2 = handle.scene) == null ? void 0 : _a2.children;
21738
+ if (!Array.isArray(children)) {
21739
+ return null;
21740
+ }
21741
+ try {
21742
+ const raycaster = new three.Raycaster();
21743
+ raycaster.setFromCamera(new three.Vector2(pointer[0], pointer[1]), handle.camera);
21744
+ const hits = raycaster.intersectObjects(children, true);
21745
+ const hit = hits.find(
21746
+ (candidate) => {
21747
+ var _a3, _b;
21748
+ return ((_a3 = candidate.object) == null ? void 0 : _a3.isMesh) === true && ((_b = candidate.object) == null ? void 0 : _b.visible) !== false && !isHelper(candidate.object);
21749
+ }
21750
+ );
21751
+ return hit ? nameOf(hit.object) : null;
21752
+ } catch {
21753
+ return null;
21754
+ }
21755
+ }
21756
+ function cameraMatrixOf(handle) {
21757
+ var _a2, _b;
21758
+ const matrix = (_a2 = handle.camera) == null ? void 0 : _a2.matrixWorld;
21759
+ try {
21760
+ const values = (_b = matrix == null ? void 0 : matrix.toArray) == null ? void 0 : _b.call(matrix);
21761
+ return Array.isArray(values) ? values : null;
21762
+ } catch {
21763
+ return null;
21764
+ }
21765
+ }
21766
+ function threeSourceOf(canvas) {
21767
+ const current = () => threeHandles.get(canvas);
21768
+ return {
21769
+ redraw() {
21770
+ const handle = current();
21771
+ handle == null ? void 0 : handle.renderer.render(handle.scene, handle.camera);
21772
+ },
21773
+ pickTarget(pointer) {
21774
+ const handle = current();
21775
+ return handle ? pickTarget(handle, pointer) : null;
21776
+ },
21777
+ cameraMatrix() {
21778
+ const handle = current();
21779
+ return handle ? cameraMatrixOf(handle) : null;
21780
+ }
21781
+ };
21782
+ }
21783
+ function registerThreeRenderer(handle) {
21784
+ var _a2;
21785
+ const canvas = (_a2 = handle == null ? void 0 : handle.renderer) == null ? void 0 : _a2.domElement;
21786
+ if (!(canvas instanceof HTMLCanvasElement)) {
21787
+ return () => void 0;
21788
+ }
21789
+ threeHandles.set(canvas, handle);
21790
+ const unregisterSource = registerCanvasSource(canvas, threeSourceOf(canvas));
21791
+ return () => {
21792
+ if (threeHandles.get(canvas) !== handle) return;
21793
+ threeHandles.delete(canvas);
21794
+ unregisterSource();
21795
+ };
21796
+ }
21797
+ function installThreeAutoDetect() {
21798
+ if (typeof window === "undefined") {
21799
+ return () => void 0;
21800
+ }
21801
+ const holder = window;
21802
+ const restorePatches = [];
21803
+ const observe = (event) => {
21804
+ const detail = event.detail;
21805
+ if ((detail == null ? void 0 : detail.isWebGLRenderer) === true) {
21806
+ observeRenderer(detail, restorePatches);
21807
+ }
21808
+ };
21809
+ const wrap = (hook) => {
21810
+ if (!hook) {
21811
+ return {
21812
+ dispatchEvent(event) {
21813
+ observe(event);
21814
+ return true;
21815
+ }
21816
+ };
21817
+ }
21818
+ const original = hook.dispatchEvent;
21819
+ if (typeof original !== "function" || original.__probed === true) {
21820
+ return hook;
21821
+ }
21822
+ const patched = function(event) {
21823
+ observe(event);
21824
+ return original.call(this, event);
21825
+ };
21826
+ patched.__probed = true;
21827
+ hook.dispatchEvent = patched;
21828
+ restorePatches.push(() => {
21829
+ hook.dispatchEvent = original;
21830
+ });
21831
+ return hook;
21832
+ };
21833
+ const previousDescriptor = Object.getOwnPropertyDescriptor(window, "__THREE_DEVTOOLS__");
21834
+ let current = wrap(holder.__THREE_DEVTOOLS__);
21835
+ Object.defineProperty(window, "__THREE_DEVTOOLS__", {
21836
+ configurable: true,
21837
+ enumerable: true,
21838
+ get: () => current,
21839
+ set: (next) => {
21840
+ current = wrap(next);
21841
+ }
21842
+ });
21843
+ return () => {
21844
+ restorePatches.forEach((restore) => restore());
21845
+ restorePatches.length = 0;
21846
+ const survivor = current;
21847
+ delete window.__THREE_DEVTOOLS__;
21848
+ if (previousDescriptor) {
21849
+ Object.defineProperty(window, "__THREE_DEVTOOLS__", previousDescriptor);
21850
+ return;
21851
+ }
21852
+ if (survivor && !holder.__THREE_DEVTOOLS__) {
21853
+ const isOurs = Object.keys(survivor).length === 1 && "dispatchEvent" in survivor;
21854
+ if (!isOurs) {
21855
+ holder.__THREE_DEVTOOLS__ = survivor;
21856
+ }
21857
+ }
21858
+ };
21859
+ }
21860
+ function isSceneLike(value) {
21861
+ return (value == null ? void 0 : value.isScene) === true;
21862
+ }
21863
+ const lastDrawn = /* @__PURE__ */ new WeakMap();
21864
+ function observeRenderer(renderer, restorePatches) {
21865
+ queueMicrotask(() => {
21866
+ const owner = Object.prototype.hasOwnProperty.call(renderer, "render") ? renderer : Object.getPrototypeOf(renderer);
21867
+ const original = owner.render;
21868
+ if (typeof original !== "function") {
21869
+ return;
21870
+ }
21871
+ owner.render = function(scene, camera) {
21872
+ const canvas = (this == null ? void 0 : this.domElement) ?? renderer.domElement;
21873
+ if (canvas instanceof HTMLCanvasElement) {
21874
+ lastDrawn.set(canvas, [scene, camera]);
21875
+ if (isSceneLike(scene)) {
21876
+ const existing = threeHandles.get(canvas);
21877
+ if (existing) {
21878
+ existing.scene = scene;
21879
+ existing.camera = camera;
21880
+ } else {
21881
+ registerThreeRenderer({
21882
+ // 강제 렌더는 **앱이 마지막으로 그린 그대로** 다시 그린다. 후처리를 쓰면 화면을
21883
+ // 그리는 것은 풀스크린 쿼드 패스라, 앱의 씬으로 그리면 후처리가 빠진다.
21884
+ // 원본을 부른다 — 우리 래퍼를 다시 타지 않게 한다.
21885
+ renderer: {
21886
+ domElement: canvas,
21887
+ render: (s2, c2) => {
21888
+ const last = lastDrawn.get(canvas);
21889
+ return last ? original.call(this, last[0], last[1]) : original.call(this, s2, c2);
21890
+ }
21891
+ },
21892
+ scene,
21893
+ camera
21894
+ });
21895
+ }
21896
+ }
21897
+ }
21898
+ return original.call(this, scene, camera);
21899
+ };
21900
+ restorePatches.push(() => {
21901
+ owner.render = original;
21902
+ });
21903
+ });
21904
+ }
22437
21905
  const OVERLAY_LAYER_INDEX = {
22438
21906
  // 익스텐션·SDK 위젯의 shadow host. isolation:isolate 로 stacking context 를 만들기 때문에
22439
21907
  // 아래 레이어 값들은 호스트 내부 순서만 정한다. 호스트 페이지의 오버레이와 겨루는 것은
@@ -28221,59 +27689,45 @@ function registerThree(handle) {
28221
27689
  }
28222
27690
  installThreeAutoDetect();
28223
27691
  export {
28224
- createSlottable as $,
28225
- record as A,
28226
- resolvePrivacyConfig as B,
27692
+ Root2 as A,
27693
+ Anchor as B,
28227
27694
  CANVAS_INTERACTION_TAG as C,
28228
27695
  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,
27696
+ Arrow as E,
27697
+ clsx as F,
27698
+ Root as G,
27699
+ createSlottable as H,
27700
+ rrweb as I,
28237
27701
  MousePointer2 as M,
28238
- redactSensitiveHeaders as N,
28239
27702
  OVERLAY_LAYER_INDEX as O,
28240
27703
  Presence as P,
28241
- redactSensitiveBody as Q,
28242
- Replayer as R,
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 _,
27704
+ REPLAY_EVENT_TYPE as R,
27705
+ Slot as S,
27706
+ VISUALLY_HIDDEN_STYLES as V,
28252
27707
  cn as a,
28253
- rrweb as a0,
28254
- usePortalContainer as b,
27708
+ Replayer as b,
28255
27709
  createLucideIcon as c,
28256
- OVERLAY_LAYER as d,
28257
- Primitive as e,
28258
- composeEventHandlers as f,
28259
- useDismissableLayerSurface as g,
28260
- useComposedRefs as h,
28261
- Portal as i,
28262
- createContextScope as j,
28263
- createSlot as k,
28264
- useControllableState as l,
28265
- useId as m,
28266
- useLayoutEffect2 as n,
28267
- useCallbackRef as o,
28268
- PortalContainerProvider as p,
28269
- onThreeRendererRegistered as q,
27710
+ usePortalContainer as d,
27711
+ OVERLAY_LAYER as e,
27712
+ Primitive as f,
27713
+ composeEventHandlers as g,
27714
+ useDismissableLayerSurface as h,
27715
+ useComposedRefs as i,
27716
+ Portal as j,
27717
+ createContextScope as k,
27718
+ createSlot as l,
27719
+ useControllableState as m,
27720
+ useId as n,
27721
+ useLayoutEffect2 as o,
27722
+ useCallbackRef as p,
27723
+ PortalContainerProvider as q,
28270
27724
  registerThree as r,
28271
27725
  stopEscapePropagation as s,
28272
- shouldOverlayCanvas as t,
27726
+ getCanvasSource as t,
28273
27727
  ungzip_1 as u,
28274
- getThreeHandle as v,
28275
- rendersBlank as w,
28276
- gzip_1 as x,
28277
- redactUrlSecrets as y,
28278
- redactSensitiveValues as z
27728
+ onCanvasSourceRegistered as v,
27729
+ gzip_1 as w,
27730
+ record as x,
27731
+ Content as y,
27732
+ createPopperScope as z
28279
27733
  };