issue-sticker 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/{createLucideIcon-C0Ldhp96.js → createLucideIcon-BzRMP02h.js} +12 -12
- package/dist/{externalUserPayload-BVNl_35M.js → externalUserPayload-zu5IXBjn.js} +437 -182
- package/dist/issue-sticker-headless-react.js +2 -2
- package/dist/issue-sticker-headless.js +124 -15
- package/dist/issue-sticker-react.js +1690 -892
- package/dist/{tooltip-CEc_RGfI.js → tooltip-B6UPO-U8.js} +1 -1
- package/headless.d.ts +27 -2
- package/package.json +3 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
2
|
import { memo, useRef, useState, useCallback, useEffect } from "react";
|
|
3
|
-
import { c as createLucideIcon, u as ungzip_1, a as cn, R as Replayer } from "./createLucideIcon-
|
|
4
|
-
import { T as TooltipProvider, a as Tooltip, b as TooltipTrigger, c as TooltipContent } from "./tooltip-
|
|
3
|
+
import { c as createLucideIcon, u as ungzip_1, a as cn, R as Replayer } from "./createLucideIcon-BzRMP02h.js";
|
|
4
|
+
import { T as TooltipProvider, a as Tooltip, b as TooltipTrigger, c as TooltipContent } from "./tooltip-B6UPO-U8.js";
|
|
5
5
|
/**
|
|
6
6
|
* @license lucide-react v0.468.0 - ISC
|
|
7
7
|
*
|
|
@@ -1,7 +1,34 @@
|
|
|
1
|
-
import { u as uploadScreenshotDataUrls, a as uploadSignedMediaWithApi, d as directUploadToSignedUrl, p as pristineFetch, n as normalizeExposedJiraFields, b as normalizeJiraFieldId, g as getElementPosition, c as captureWithModernScreenshot, e as buildJiraCreateFields, f as buildNotionCreateFields, h as buildGuestOwnerQuery, i as buildIssueIntegrationsPayload, j as devLogger, C as ConsoleCollector, N as NetworkCollector, s as selectNetworkLogsForSubmit, k as filterExcludeServiceRelatedLogs, l as filterExcludeServiceRelatedConsoleLogs, m as compactNetworkLogsForTransport, o as CONSOLE_LOG_SUBMIT_LIMIT, q as buildExternalUserPayload, r as shrinkNetworkLogsToFit, t as measureJsonBytes, v as sanitizeString, w as getCurrentPageUrl, S as ScreenRecordingController, x as SessionReplayCaptureController, y as preloadModernScreenshot, z as isIssueStickerExtensionRuntimeActive } from "./externalUserPayload-
|
|
1
|
+
import { u as uploadScreenshotDataUrls, a as uploadSignedMediaWithApi, d as directUploadToSignedUrl, p as pristineFetch, n as normalizeExposedJiraFields, b as normalizeJiraFieldId, g as getElementPosition, c as captureWithModernScreenshot, e as buildJiraCreateFields, f as buildNotionCreateFields, h as buildGuestOwnerQuery, i as buildIssueIntegrationsPayload, j as devLogger, C as ConsoleCollector, N as NetworkCollector, s as selectNetworkLogsForSubmit, k as filterExcludeServiceRelatedLogs, l as filterExcludeServiceRelatedConsoleLogs, m as compactNetworkLogsForTransport, o as CONSOLE_LOG_SUBMIT_LIMIT, q as buildExternalUserPayload, r as shrinkNetworkLogsToFit, t as measureJsonBytes, v as sanitizeString, w as getCurrentPageUrl, S as ScreenRecordingController, x as SessionReplayCaptureController, y as preloadModernScreenshot, I as ISSUE_STICKER_INTERNAL_LOG_PREFIX, z as isIssueStickerExtensionRuntimeActive } from "./externalUserPayload-zu5IXBjn.js";
|
|
2
2
|
import "react/jsx-runtime";
|
|
3
3
|
import "react";
|
|
4
|
-
import "./createLucideIcon-
|
|
4
|
+
import "./createLucideIcon-BzRMP02h.js";
|
|
5
|
+
const WINDOW_MS = 6e4;
|
|
6
|
+
const MAX_PER_WINDOW = 5;
|
|
7
|
+
class AutoIssueRateLimit {
|
|
8
|
+
constructor(max = MAX_PER_WINDOW, windowMs = WINDOW_MS) {
|
|
9
|
+
this.max = max;
|
|
10
|
+
this.windowMs = windowMs;
|
|
11
|
+
this.accepted = [];
|
|
12
|
+
}
|
|
13
|
+
get label() {
|
|
14
|
+
return `${this.windowMs / 1e3}초에 ${this.max}건`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 자리가 있으면 차지하고 true 를 돌려준다. 실패한 호출도 서버 왕복을 이미 썼으므로
|
|
18
|
+
* 자리를 돌려주지 않는다.
|
|
19
|
+
*/
|
|
20
|
+
claim(now = Date.now()) {
|
|
21
|
+
this.accepted = this.accepted.filter((at) => now - at < this.windowMs);
|
|
22
|
+
if (this.accepted.length >= this.max) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
this.accepted.push(now);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
reset() {
|
|
29
|
+
this.accepted = [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
5
32
|
class IssueStickerHeadlessError extends Error {
|
|
6
33
|
constructor(code, message, cause) {
|
|
7
34
|
super(message);
|
|
@@ -604,7 +631,7 @@ function composeIssueFilterGroups(integrations, customFields) {
|
|
|
604
631
|
function buildHeadlessCaptureContext({
|
|
605
632
|
target,
|
|
606
633
|
event,
|
|
607
|
-
videoFile,
|
|
634
|
+
videoFile = null,
|
|
608
635
|
createVideoPreviewUrl,
|
|
609
636
|
sessionReplayEnabled
|
|
610
637
|
}) {
|
|
@@ -614,7 +641,7 @@ function buildHeadlessCaptureContext({
|
|
|
614
641
|
mediaMode,
|
|
615
642
|
position,
|
|
616
643
|
screenshotPreviewUrls: [],
|
|
617
|
-
videoPreviewUrl: mediaMode === "video" && videoFile ? createVideoPreviewUrl(videoFile) : null,
|
|
644
|
+
videoPreviewUrl: mediaMode === "video" && videoFile && createVideoPreviewUrl ? createVideoPreviewUrl(videoFile) : null,
|
|
618
645
|
targetSelector: position.selector,
|
|
619
646
|
sessionReplayEnabled,
|
|
620
647
|
sessionReplayPreview: null
|
|
@@ -624,6 +651,7 @@ async function captureHeadlessScreenshots({
|
|
|
624
651
|
target,
|
|
625
652
|
excludeSelectors
|
|
626
653
|
}) {
|
|
654
|
+
const targetIsDocumentRoot = target === document.body || target === document.documentElement;
|
|
627
655
|
const screenshots = await Promise.all([
|
|
628
656
|
captureWithModernScreenshot({
|
|
629
657
|
element: document.documentElement,
|
|
@@ -631,10 +659,12 @@ async function captureHeadlessScreenshots({
|
|
|
631
659
|
width: window.innerWidth,
|
|
632
660
|
height: window.innerHeight
|
|
633
661
|
}).catch(() => void 0),
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
662
|
+
...targetIsDocumentRoot ? [] : [
|
|
663
|
+
captureWithModernScreenshot({
|
|
664
|
+
element: target,
|
|
665
|
+
excludeSelectors
|
|
666
|
+
}).catch(() => void 0)
|
|
667
|
+
]
|
|
638
668
|
]);
|
|
639
669
|
return screenshots.filter(
|
|
640
670
|
(dataUrl) => typeof dataUrl === "string" && dataUrl.length > 0
|
|
@@ -1238,10 +1268,15 @@ function buildIssueCreatePayload({
|
|
|
1238
1268
|
currentUrl,
|
|
1239
1269
|
userAgent
|
|
1240
1270
|
}) {
|
|
1271
|
+
var _a, _b, _c;
|
|
1272
|
+
const title = (_a = draft.title) == null ? void 0 : _a.trim();
|
|
1273
|
+
const description = (_b = draft.description) == null ? void 0 : _b.trim();
|
|
1274
|
+
const intent = (_c = draft.intent) == null ? void 0 : _c.trim();
|
|
1241
1275
|
const base = {
|
|
1242
1276
|
url: currentUrl,
|
|
1243
|
-
title:
|
|
1244
|
-
description:
|
|
1277
|
+
...title ? { title } : {},
|
|
1278
|
+
...description ? { description } : {},
|
|
1279
|
+
...intent ? { intent } : {},
|
|
1245
1280
|
position: draft.position,
|
|
1246
1281
|
...uploads.screenshotUploadIds.length > 0 ? { screenshot_upload_ids: uploads.screenshotUploadIds } : {},
|
|
1247
1282
|
...uploads.videoUpload ? { video_upload_id: uploads.videoUpload.id } : {},
|
|
@@ -1402,6 +1437,7 @@ class HeadlessClientImpl {
|
|
|
1402
1437
|
var _a;
|
|
1403
1438
|
this.config = config;
|
|
1404
1439
|
this.state = new HeadlessClientState();
|
|
1440
|
+
this.autoIssueRateLimit = new AutoIssueRateLimit();
|
|
1405
1441
|
this.pendingMedia = new HeadlessPendingMedia();
|
|
1406
1442
|
this.initializePromise = null;
|
|
1407
1443
|
this.captureCleanup = null;
|
|
@@ -1605,14 +1641,15 @@ class HeadlessClientImpl {
|
|
|
1605
1641
|
this.state.setSnapshot({ phase: "idle", draft: null, formSchema: null, error: null });
|
|
1606
1642
|
}
|
|
1607
1643
|
async submitDraft(input) {
|
|
1644
|
+
var _a, _b, _c;
|
|
1608
1645
|
await this.ensureInitialized();
|
|
1609
1646
|
this.state.assertPhase(["draftingIssue"], "submitDraft");
|
|
1610
1647
|
this.state.requireDraft();
|
|
1611
|
-
if (!input.title.trim()) {
|
|
1612
|
-
throw new IssueStickerHeadlessError(
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1648
|
+
if (!((_a = input.title) == null ? void 0 : _a.trim()) && !((_b = input.description) == null ? void 0 : _b.trim()) && !((_c = input.intent) == null ? void 0 : _c.trim())) {
|
|
1649
|
+
throw new IssueStickerHeadlessError(
|
|
1650
|
+
"VALIDATION_ERROR",
|
|
1651
|
+
"제목·설명 또는 기대한 동작(intent)을 입력해주세요."
|
|
1652
|
+
);
|
|
1616
1653
|
}
|
|
1617
1654
|
this.state.setSnapshot({ phase: "submittingIssue", error: null });
|
|
1618
1655
|
try {
|
|
@@ -1624,6 +1661,7 @@ class HeadlessClientImpl {
|
|
|
1624
1661
|
screenshotPreviewUrls,
|
|
1625
1662
|
title: input.title,
|
|
1626
1663
|
description: input.description,
|
|
1664
|
+
intent: input.intent,
|
|
1627
1665
|
dynamicFields: input.dynamicFields ?? {}
|
|
1628
1666
|
};
|
|
1629
1667
|
const createdIssue = await submitHeadlessIssueDraft({
|
|
@@ -1656,6 +1694,76 @@ class HeadlessClientImpl {
|
|
|
1656
1694
|
throw nextError;
|
|
1657
1695
|
}
|
|
1658
1696
|
}
|
|
1697
|
+
async createIssue(input) {
|
|
1698
|
+
var _a, _b;
|
|
1699
|
+
try {
|
|
1700
|
+
if (!((_a = input.title) == null ? void 0 : _a.trim()) || !((_b = input.description) == null ? void 0 : _b.trim())) {
|
|
1701
|
+
return this.skipCreateIssue("제목과 설명이 모두 필요합니다.");
|
|
1702
|
+
}
|
|
1703
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1704
|
+
return this.skipCreateIssue("브라우저 DOM 에서만 이슈를 만들 수 있습니다.");
|
|
1705
|
+
}
|
|
1706
|
+
await this.ensureInitialized();
|
|
1707
|
+
if (this.snapshot.extensionActive) {
|
|
1708
|
+
return this.skipCreateIssue("이슈스티커 익스텐션이 이 페이지를 사용 중입니다.");
|
|
1709
|
+
}
|
|
1710
|
+
if (!this.autoIssueRateLimit.claim()) {
|
|
1711
|
+
return this.skipCreateIssue(
|
|
1712
|
+
`${this.autoIssueRateLimit.label}까지 호출할 수 있습니다. 호출 조건을 다시 확인해주세요.`
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
const target = input.target ?? document.body;
|
|
1716
|
+
const sessionReplay = this.sessionReplayController.getSnapshot();
|
|
1717
|
+
const draft = buildHeadlessCaptureContext({
|
|
1718
|
+
target,
|
|
1719
|
+
sessionReplayEnabled: Boolean(sessionReplay.enabled && sessionReplay.canAttach)
|
|
1720
|
+
});
|
|
1721
|
+
const logs = this.logCollectors.collect();
|
|
1722
|
+
const screenshotPreviewUrls = input.media === "none" ? [] : await this.logCollectors.pauseNetworkWhile(
|
|
1723
|
+
() => captureHeadlessScreenshots({ target, excludeSelectors: this.getExcludeSelectors() })
|
|
1724
|
+
);
|
|
1725
|
+
const formSchema = this.snapshot.auth.mode === "member" && Object.keys(input.dynamicFields ?? {}).length > 0 ? await this.loadFormSchema({}) : null;
|
|
1726
|
+
const createdIssue = await submitHeadlessIssueDraft({
|
|
1727
|
+
draft: {
|
|
1728
|
+
...draft,
|
|
1729
|
+
screenshotPreviewUrls,
|
|
1730
|
+
title: input.title,
|
|
1731
|
+
description: input.description,
|
|
1732
|
+
dynamicFields: input.dynamicFields ?? {}
|
|
1733
|
+
},
|
|
1734
|
+
pendingVideoFile: null,
|
|
1735
|
+
transport: this.transport,
|
|
1736
|
+
issueRepository: this.issueRepository,
|
|
1737
|
+
formSchema,
|
|
1738
|
+
auth: this.snapshot.auth,
|
|
1739
|
+
config: this.config,
|
|
1740
|
+
projectId: this.state.requireProjectId(),
|
|
1741
|
+
logs,
|
|
1742
|
+
currentUrl: getCurrentPageUrl(),
|
|
1743
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
|
|
1744
|
+
uploadSessionReplaySnapshot: () => this.uploadSessionReplaySnapshot()
|
|
1745
|
+
});
|
|
1746
|
+
this.state.setSnapshot({
|
|
1747
|
+
issues: [createdIssue, ...this.snapshot.issues]
|
|
1748
|
+
});
|
|
1749
|
+
return createdIssue;
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
return this.skipCreateIssue("이슈를 생성하지 못했습니다.", error);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* createIssue 가 이슈를 만들지 않고 끝난 이유를 남긴다.
|
|
1756
|
+
*
|
|
1757
|
+
* devLogger 는 프로덕션 빌드에서 침묵하는데, 이 실패는 예외로 드러나지 않으므로
|
|
1758
|
+
* 통합한 쪽이 프로덕션에서도 알아야 한다. 접두어가 붙어 있어 우리 수집기가
|
|
1759
|
+
* 자기 로그로 걸러내므로 고객 이슈의 콘솔 로그를 오염시키지 않는다.
|
|
1760
|
+
*/
|
|
1761
|
+
skipCreateIssue(reason, cause) {
|
|
1762
|
+
const args = [ISSUE_STICKER_INTERNAL_LOG_PREFIX, "[headless] createIssue:", reason];
|
|
1763
|
+
if (cause !== void 0) args.push(cause);
|
|
1764
|
+
console.warn(...args);
|
|
1765
|
+
return null;
|
|
1766
|
+
}
|
|
1659
1767
|
async loginAsMember() {
|
|
1660
1768
|
await this.ensureInitialized();
|
|
1661
1769
|
if (!this.authBridge.canUseBridge) {
|
|
@@ -1694,6 +1802,7 @@ class HeadlessClientImpl {
|
|
|
1694
1802
|
this.sessionReplayController.destroy();
|
|
1695
1803
|
this.clearPendingMedia();
|
|
1696
1804
|
this.logCollectors.stop();
|
|
1805
|
+
this.autoIssueRateLimit.reset();
|
|
1697
1806
|
this.authBridge.destroy();
|
|
1698
1807
|
this.state.reset();
|
|
1699
1808
|
}
|