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.
@@ -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-C0Ldhp96.js";
4
- import { T as TooltipProvider, a as Tooltip, b as TooltipTrigger, c as TooltipContent } from "./tooltip-CEc_RGfI.js";
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-BVNl_35M.js";
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-C0Ldhp96.js";
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
- captureWithModernScreenshot({
635
- element: target,
636
- excludeSelectors
637
- }).catch(() => void 0)
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: draft.title.trim(),
1244
- description: draft.description.trim(),
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("VALIDATION_ERROR", "제목을 입력해주세요.");
1613
- }
1614
- if (!input.description.trim()) {
1615
- throw new IssueStickerHeadlessError("VALIDATION_ERROR", "설명을 입력해주세요.");
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
  }