@solhun/feedback-kit-core 0.7.0 → 0.8.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/dist/index.cjs +153 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +123 -6
- package/dist/index.d.ts +123 -6
- package/dist/index.js +149 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
|
+
/** 궤적 상한. 넘으면 오래된 것부터 버린다. */
|
|
2
|
+
declare const ROUTE_TRAIL_LIMIT = 20;
|
|
3
|
+
/** 탭 저장소에 쓰는 키. */
|
|
4
|
+
declare const ROUTE_TRAIL_KEY = "feedback-kit:route-trail";
|
|
5
|
+
/** 궤적 한 줄. */
|
|
6
|
+
interface RouteTrailEntry {
|
|
7
|
+
/** 그 경로에 **처음 도착한** 시각(epoch ms 정수). 접힘(count)으로는 갱신하지 않는다. */
|
|
8
|
+
at: number;
|
|
9
|
+
/** 직전 경로. 버퍼의 첫 항목은 `null`. */
|
|
10
|
+
from: string | null;
|
|
11
|
+
/** 도착 경로. */
|
|
12
|
+
to: string;
|
|
13
|
+
/** 같은 경로로 연속 이동해 접힌 횟수. 기본 1. */
|
|
14
|
+
count: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 경로 감시자 주입 계약 — **구독 함수 하나**다.
|
|
18
|
+
*
|
|
19
|
+
* 구독하면 **현재 경로를 한 번 흘리고**(마운트 시점의 화면이 궤적의 첫 줄이 된다),
|
|
20
|
+
* 이후 경로가 바뀔 때마다 새 경로를 넘긴다. 돌려주는 함수를 부르면 구독이 끊긴다.
|
|
21
|
+
* 뒤로가기·앞으로가기도 이동이므로 함께 흘려야 한다.
|
|
22
|
+
*/
|
|
23
|
+
type RouteWatcher = (onPath: (route: string) => void) => () => void;
|
|
24
|
+
/**
|
|
25
|
+
* 궤적을 남길 **동기** 저장소. `sessionStorage` 와 같은 모양이라 웹은 그대로 넘기면 된다.
|
|
26
|
+
*
|
|
27
|
+
* 코어의 `FeedbackStorage`(Promise)를 쓰지 않는 이유: 이동 직후 새로고침이 일어나면
|
|
28
|
+
* await 가 끝나기 전에 페이지가 사라져 그 이동이 통째로 유실된다.
|
|
29
|
+
*/
|
|
30
|
+
interface RouteTrailStore {
|
|
31
|
+
getItem(key: string): string | null;
|
|
32
|
+
setItem(key: string, value: string): void;
|
|
33
|
+
}
|
|
34
|
+
interface RouteTrailOpts {
|
|
35
|
+
/** 탭 단위 저장소. 없으면 메모리로만 동작한다(새로고침에서 끊긴다). */
|
|
36
|
+
storage?: RouteTrailStore | null;
|
|
37
|
+
limit?: number;
|
|
38
|
+
key?: string;
|
|
39
|
+
now?: () => number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 경로에서 쿼리스트링과 해시를 뺀다.
|
|
43
|
+
*
|
|
44
|
+
* 값 하나하나를 가려내지 않고 **쿼리를 통째로 버린다** — 그래야 `?token=…` 같은
|
|
45
|
+
* 자격증명형 파라미터가 애초에 들어올 자리가 없다. 화면 식별에는 경로만으로 충분하다.
|
|
46
|
+
*
|
|
47
|
+
* 인자를 `route` 라 부르는 이유: 화면 경로를 가리키는 더 흔한 영어 단어는 라쏘런 봉투가
|
|
48
|
+
* **요소 선택자**를 부르는 필드명이기도 하다. 그 단어가 코어 본체에 등장하면 경계 검사
|
|
49
|
+
* (`adapters.test.ts` TC5)가 "코어가 대상 필드명을 안다"로 잡는다 — 검사를 느슨하게
|
|
50
|
+
* 하는 대신 이쪽 이름을 비켰다.
|
|
51
|
+
*/
|
|
52
|
+
declare function normalizeRoutePath(route: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* 화면 이동 궤적 버퍼.
|
|
55
|
+
*
|
|
56
|
+
* 진단 링버퍼(network·logs)와 다른 점은 **탭 저장소에 남는다**는 것 하나다.
|
|
57
|
+
* 메모리에만 두면 전체 새로고침 한 번에 궤적이 끊기는데, 정작 재현 절차에
|
|
58
|
+
* 새로고침이 끼어 있는 제보가 많다.
|
|
59
|
+
*/
|
|
60
|
+
declare class RouteTrail {
|
|
61
|
+
readonly limit: number;
|
|
62
|
+
private entries;
|
|
63
|
+
private storage;
|
|
64
|
+
private readonly key;
|
|
65
|
+
private readonly now;
|
|
66
|
+
private unwatch;
|
|
67
|
+
constructor(opts?: RouteTrailOpts);
|
|
68
|
+
/** 감시자를 붙인다. 이미 붙어 있으면 먼저 끊는다(감시자가 둘이면 이동이 두 번 기록된다). */
|
|
69
|
+
watch(watcher: RouteWatcher): void;
|
|
70
|
+
/** 감시자를 뗀다. 버퍼 내용은 그대로 남는다. */
|
|
71
|
+
unwatchRoutes(): void;
|
|
72
|
+
/**
|
|
73
|
+
* 이동 하나를 기록한다.
|
|
74
|
+
*
|
|
75
|
+
* 같은 경로로 연속 이동하면 새 줄을 만들지 않고 `count` 만 올린다. 판정은 **직전 항목의
|
|
76
|
+
* `to`** 와 비교한다 — 새로고침 직후 같은 화면에서 다시 시작해도 줄이 늘지 않는다.
|
|
77
|
+
*/
|
|
78
|
+
record(route: string): void;
|
|
79
|
+
/** 지금까지의 궤적. 사본이라 이후 이동이 이미 만든 제보를 바꾸지 않는다. */
|
|
80
|
+
snapshot(): RouteTrailEntry[];
|
|
81
|
+
get size(): number;
|
|
82
|
+
/** 버퍼와 저장분을 모두 비운다. */
|
|
83
|
+
clear(): void;
|
|
84
|
+
/** 저장소를 나중에 붙인다. 이미 저장돼 있던 궤적이 있으면 그것을 이어받는다. */
|
|
85
|
+
attachStorage(storage: RouteTrailStore | null): void;
|
|
86
|
+
private readRaw;
|
|
87
|
+
private restore;
|
|
88
|
+
private persist;
|
|
89
|
+
}
|
|
90
|
+
|
|
1
91
|
/**
|
|
2
92
|
* 힌트가 어디에 뜨는가.
|
|
3
93
|
* `screen`=일반 모달(화면 전체를 묻는 흐름), `element`=요소 지목 모달, `both`=둘 다.
|
|
@@ -264,14 +354,19 @@ interface DiagLogEntry {
|
|
|
264
354
|
/**
|
|
265
355
|
* 제보에 실리는 진단 스냅샷.
|
|
266
356
|
*
|
|
267
|
-
*
|
|
268
|
-
* (그 세션에
|
|
357
|
+
* 세 배열은 **수집기가 실제로 모은 결과**다. 제출 시점에 비어 있는 것은 정상이고
|
|
358
|
+
* (그 세션에 요청·경고·이동이 없었다는 뜻), 배선이 안 됐을 때는 배열이 아니라
|
|
269
359
|
* 컨텍스트의 `diagnostics` 자체가 `null`이 된다. 그래서 "빈 배열"과
|
|
270
360
|
* "수집 안 함"이 페이로드에서 구분된다.
|
|
271
361
|
*/
|
|
272
362
|
interface DiagnosticsPayload {
|
|
273
363
|
network: DiagNetworkEntry[];
|
|
274
364
|
logs: DiagLogEntry[];
|
|
365
|
+
/**
|
|
366
|
+
* 화면 이동 궤적. 감시자를 주입하지 않은 호스트에서는 이것만 비고 나머지는 그대로 모인다
|
|
367
|
+
* — 궤적이 없다고 진단 전체를 `null` 로 만들지 않는다.
|
|
368
|
+
*/
|
|
369
|
+
routes: RouteTrailEntry[];
|
|
275
370
|
}
|
|
276
371
|
/** 앱 빌드 정보. 어느 빌드에서 났는지 특정하는 데 쓴다. */
|
|
277
372
|
interface AppInfo {
|
|
@@ -711,6 +806,16 @@ interface DiagnosticsInstallOpts {
|
|
|
711
806
|
* 그게 제보를 덮어 정작 중요한 경고가 묻힌다. 그래서 프로젝트마다 정한다.
|
|
712
807
|
*/
|
|
713
808
|
logs?: "problems" | "all" | "off";
|
|
809
|
+
/**
|
|
810
|
+
* 화면 이동을 알려주는 감시자. 코어는 `window`·`navigation` 을 보지 않으므로 플랫폼이
|
|
811
|
+
* 주입한다. 주지 않으면 **궤적만 비고** 네트워크·로그 수집은 그대로 돌아간다.
|
|
812
|
+
*/
|
|
813
|
+
watchRoutes?: RouteWatcher | null;
|
|
814
|
+
/**
|
|
815
|
+
* 궤적을 남길 탭 단위 저장소(웹은 `sessionStorage`). 주지 않으면 메모리로만 동작한다 —
|
|
816
|
+
* 전체 새로고침에서 궤적이 끊길 뿐, 수집 자체는 계속된다.
|
|
817
|
+
*/
|
|
818
|
+
routeStorage?: RouteTrailStore | null;
|
|
714
819
|
/** 시각 주입(테스트용). */
|
|
715
820
|
now?: () => number;
|
|
716
821
|
iso?: (n: number) => string;
|
|
@@ -718,6 +823,11 @@ interface DiagnosticsInstallOpts {
|
|
|
718
823
|
declare class DiagnosticsCollector {
|
|
719
824
|
readonly network: RingBuffer<DiagNetworkEntry>;
|
|
720
825
|
readonly logs: RingBuffer<DiagLogEntry>;
|
|
826
|
+
/**
|
|
827
|
+
* 화면 이동 궤적. network·logs 와 **같은 스위치(`captureDiagnostics`)** 로 켜고 끈다 —
|
|
828
|
+
* 별도 설정을 만들지 않는다. 다른 점은 탭 저장소에 남는다는 것 하나다.
|
|
829
|
+
*/
|
|
830
|
+
readonly routes: RouteTrail;
|
|
721
831
|
private installed;
|
|
722
832
|
private originalFetch;
|
|
723
833
|
private originalXhrOpen;
|
|
@@ -742,6 +852,8 @@ declare class DiagnosticsCollector {
|
|
|
742
852
|
iso?: (n: number) => string;
|
|
743
853
|
networkLimit?: number;
|
|
744
854
|
logLimit?: number;
|
|
855
|
+
routeLimit?: number;
|
|
856
|
+
routeStorage?: RouteTrailStore | null;
|
|
745
857
|
});
|
|
746
858
|
/** 위젯이 자신의 로그를 캡처 없이 찍을 때 쓰는 원본 console. */
|
|
747
859
|
readonly originalConsole: {
|
|
@@ -758,7 +870,7 @@ declare class DiagnosticsCollector {
|
|
|
758
870
|
* 반환 배열은 사본이라 이후 수집이 제보 페이로드를 바꾸지 않는다.
|
|
759
871
|
*/
|
|
760
872
|
snapshot(): DiagnosticsPayload;
|
|
761
|
-
/**
|
|
873
|
+
/** 세 버퍼를 비운다. */
|
|
762
874
|
clear(): void;
|
|
763
875
|
/** 실패 사유 한 줄. 예외 객체엔 요청 본문이 들어 있을 수 있어 메시지만, 길이도 자른다. */
|
|
764
876
|
private recordNetwork;
|
|
@@ -919,7 +1031,7 @@ declare function isInternalUser(resolved: ResolvedConfig, env: VisibilityEnv): b
|
|
|
919
1031
|
declare function shouldShowWidget(resolved: ResolvedConfig, input?: Partial<VisibilityEnv>): boolean;
|
|
920
1032
|
/** `DiagnosticsCollector` 중 설정 배선에 필요한 최소 형태. */
|
|
921
1033
|
interface DiagnosticsSource {
|
|
922
|
-
install(): void;
|
|
1034
|
+
install(opts?: DiagnosticsInstallOpts): void;
|
|
923
1035
|
snapshot(): DiagnosticsPayload;
|
|
924
1036
|
}
|
|
925
1037
|
/**
|
|
@@ -929,7 +1041,7 @@ interface DiagnosticsSource {
|
|
|
929
1041
|
* 돌려주는 공급자를 만들면 "수집했는데 아무 일도 없었다"와 "애초에 수집하지 않았다"가
|
|
930
1042
|
* 구분되지 않는다. 공급자가 없으면 `buildContext` 가 `diagnostics: null` 을 쓴다.
|
|
931
1043
|
*/
|
|
932
|
-
declare function diagnosticsProviderFor(resolved: Pick<ResolvedConfig, "captureDiagnostics">, collector: DiagnosticsSource): (() => DiagnosticsPayload) | undefined;
|
|
1044
|
+
declare function diagnosticsProviderFor(resolved: Pick<ResolvedConfig, "captureDiagnostics">, collector: DiagnosticsSource, installOpts?: DiagnosticsInstallOpts): (() => DiagnosticsPayload) | undefined;
|
|
933
1045
|
|
|
934
1046
|
/** 빌드 플러그인이 심는 속성 이름. `data-` 라서 DOM 이 그대로 통과시킨다. */
|
|
935
1047
|
declare const SOURCE_ATTR = "data-fk-source";
|
|
@@ -1512,6 +1624,11 @@ interface LassoReportContext {
|
|
|
1512
1624
|
device: Record<string, unknown>;
|
|
1513
1625
|
display: Record<string, unknown>;
|
|
1514
1626
|
} | null;
|
|
1627
|
+
/**
|
|
1628
|
+
* 화면 이동 궤적(도착 순서). 서버가 `meta` 로 펼치므로 제보를 다시 열었을 때
|
|
1629
|
+
* "어디를 거쳐 여기까지 왔는지"를 읽을 수 있다. 감시자가 없으면 빈 배열이다.
|
|
1630
|
+
*/
|
|
1631
|
+
routes: RouteTrailEntry[];
|
|
1515
1632
|
/** 호스트가 넣은 자유 메타. 우리가 해석하지 않는다. */
|
|
1516
1633
|
extra: Record<string, unknown>;
|
|
1517
1634
|
}
|
|
@@ -1737,4 +1854,4 @@ declare function parseHintCatalog(raw: unknown): HintCatalog | null;
|
|
|
1737
1854
|
*/
|
|
1738
1855
|
declare function rankHints(hints: readonly FeedbackHint[], input: HintRankInput, limit: number): FeedbackHint[];
|
|
1739
1856
|
|
|
1740
|
-
export { type AdapterName, type AppInfo, BACKOFF_BASE_MS, BACKOFF_MAX_MS, type BuildContextOpts, type BuildReportOpts, COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, type CaptureWithinLimitOpts, type ConfigWarning, type ConfigWarningCode, type ContextProviders, type ContextUser, DEFAULT_ADAPTER, DEFAULT_INTERNAL_ROLES, DEFAULT_POSITION, type DeviceInfo, type DiagLogEntry, type DiagNetworkEntry, type DiagRequestRef, DiagnosticsCollector, type DiagnosticsExcludeMatcher, type DiagnosticsInstallOpts, type DiagnosticsPayload, type DiagnosticsSource, type DisplayInfo, ENDPOINT_FROM_ADAPTER, type ElementInfo, type ElementLike, FLOATING_BUTTON_ID, type FeedbackAdapter, type FeedbackConfig, type FeedbackContext, type FeedbackHint, type FeedbackKind, type FeedbackPin, type FeedbackPriority, FeedbackQueue, type FeedbackQueueOpts, type FeedbackReport, type FeedbackScreenshot, type FeedbackStorage, type FeedbackUser, FocusRing, type GetCurrentScreenFn, type GetUserFn, HINT_DISPLAY_DEFAULT, HINT_DISPLAY_MAX, HINT_DISPLAY_MIN, HINT_DRAFT_MAX_CHARS, HINT_FAIL_COOLDOWN_MS, HINT_LABEL_MAX_CHARS, HINT_MAX_ITEMS, HINT_TIMEOUT_MS, HINT_TTL_MS, type HintCacheEntry, type HintCatalog, type HintElementRule, type HintFetchResult, type HintPlatform, HintProvider, type HintProviderOpts, type HintRankInput, type HintScope, type HintSource, KEEPALIVE_BODY_LIMIT_BYTES, LASSO_DEFAULT_ENDPOINT, LOG_BUFFER_LIMIT, type LassoAdapterOpts, type LassoEnvelope, type LassoFetch, type LassoReportContext, type LassoRequestInit, type LassoResponseLike, type LinearIssueInput, MAX_LOG_MESSAGE_CHARS, MAX_QUEUE_AGE_MS, MAX_QUEUE_ITEMS, MODAL_ACTION_ATTACH, MODAL_ACTION_CANCEL, MODAL_ACTION_HINT_TOGGLE, MODAL_ACTION_PICK, MODAL_ACTION_PIN, MODAL_ACTION_REMOVE_SCREENSHOT, MODAL_ACTION_RETRY, MODAL_ACTION_SEND, MODAL_FIELD_COMMENT, MODAL_FIELD_PRIORITY, type ModalQueueLike, type ModalSubmitStatus, NETWORK_BUFFER_LIMIT, type NativeContext, type NotionPageInput, PinController, type PixelPoint, type PixelSize, type QueueStatus, type QueueStatusListener, RATE_LIMIT_MIN_DELAY_MS, REDACTED, ReportModalController, type ReportModalListener, type ReportModalOpts, type ReportModalState, type ReportParts, type ResolvedConfig, RingBuffer, SCORE_ELEMENT, SCORE_PATH_EXACT, SCORE_PATH_PATTERN, SCREENSHOT_FAILED_MESSAGE, SCREENSHOT_MAX_BASE64_BYTES, SCREENSHOT_QUALITY_STEPS, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_FAILED_MESSAGE, SUBMIT_PENDING_MESSAGE, type ScreenshotCapture, type ScreenshotOutcome, type ScreenshotReencode, type ScreenshotStatus, type SourceLocation, type SourceMapping, type SubmitOutcome, type SubmitResult, TITLE_MAX_CHARS, type Visibility, type VisibilityEnv, type VisibilityFn, type WebContext, type WebContextInput, WidgetController, type WidgetControllerOpts, type WidgetCorner, type WidgetListener, type WidgetPlatform, type WidgetPosition, type WidgetScreen, type WidgetState, appPathFromNav, buildContext, buildLassoContext, buildLassoEnvelope, buildLinearIssue, buildNotionPage, buildReport, buildTitle, byteLengthOf, canUseKeepalive, captureWithinLimit, createLassoAdapter, defaultBackoff, denormalizePin, detectDevBuild, diagnosticsProviderFor, getOrCreateGuestId, guestUser, hintCharCount, isInternalUser, matchElement, matchPath, normalizePin, normalizeUser, parseHintCatalog, parseSourceAttr, rankHints, renderReportBody, resolveConfig, resolveContextUser, sanitizeUrl, screenshotBytes, sharedDiagnostics, shouldShowWidget, sourceFromElement, uuidv4 };
|
|
1857
|
+
export { type AdapterName, type AppInfo, BACKOFF_BASE_MS, BACKOFF_MAX_MS, type BuildContextOpts, type BuildReportOpts, COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, type CaptureWithinLimitOpts, type ConfigWarning, type ConfigWarningCode, type ContextProviders, type ContextUser, DEFAULT_ADAPTER, DEFAULT_INTERNAL_ROLES, DEFAULT_POSITION, type DeviceInfo, type DiagLogEntry, type DiagNetworkEntry, type DiagRequestRef, DiagnosticsCollector, type DiagnosticsExcludeMatcher, type DiagnosticsInstallOpts, type DiagnosticsPayload, type DiagnosticsSource, type DisplayInfo, ENDPOINT_FROM_ADAPTER, type ElementInfo, type ElementLike, FLOATING_BUTTON_ID, type FeedbackAdapter, type FeedbackConfig, type FeedbackContext, type FeedbackHint, type FeedbackKind, type FeedbackPin, type FeedbackPriority, FeedbackQueue, type FeedbackQueueOpts, type FeedbackReport, type FeedbackScreenshot, type FeedbackStorage, type FeedbackUser, FocusRing, type GetCurrentScreenFn, type GetUserFn, HINT_DISPLAY_DEFAULT, HINT_DISPLAY_MAX, HINT_DISPLAY_MIN, HINT_DRAFT_MAX_CHARS, HINT_FAIL_COOLDOWN_MS, HINT_LABEL_MAX_CHARS, HINT_MAX_ITEMS, HINT_TIMEOUT_MS, HINT_TTL_MS, type HintCacheEntry, type HintCatalog, type HintElementRule, type HintFetchResult, type HintPlatform, HintProvider, type HintProviderOpts, type HintRankInput, type HintScope, type HintSource, KEEPALIVE_BODY_LIMIT_BYTES, LASSO_DEFAULT_ENDPOINT, LOG_BUFFER_LIMIT, type LassoAdapterOpts, type LassoEnvelope, type LassoFetch, type LassoReportContext, type LassoRequestInit, type LassoResponseLike, type LinearIssueInput, MAX_LOG_MESSAGE_CHARS, MAX_QUEUE_AGE_MS, MAX_QUEUE_ITEMS, MODAL_ACTION_ATTACH, MODAL_ACTION_CANCEL, MODAL_ACTION_HINT_TOGGLE, MODAL_ACTION_PICK, MODAL_ACTION_PIN, MODAL_ACTION_REMOVE_SCREENSHOT, MODAL_ACTION_RETRY, MODAL_ACTION_SEND, MODAL_FIELD_COMMENT, MODAL_FIELD_PRIORITY, type ModalQueueLike, type ModalSubmitStatus, NETWORK_BUFFER_LIMIT, type NativeContext, type NotionPageInput, PinController, type PixelPoint, type PixelSize, type QueueStatus, type QueueStatusListener, RATE_LIMIT_MIN_DELAY_MS, REDACTED, ROUTE_TRAIL_KEY, ROUTE_TRAIL_LIMIT, ReportModalController, type ReportModalListener, type ReportModalOpts, type ReportModalState, type ReportParts, type ResolvedConfig, RingBuffer, RouteTrail, type RouteTrailEntry, type RouteTrailOpts, type RouteTrailStore, type RouteWatcher, SCORE_ELEMENT, SCORE_PATH_EXACT, SCORE_PATH_PATTERN, SCREENSHOT_FAILED_MESSAGE, SCREENSHOT_MAX_BASE64_BYTES, SCREENSHOT_QUALITY_STEPS, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_FAILED_MESSAGE, SUBMIT_PENDING_MESSAGE, type ScreenshotCapture, type ScreenshotOutcome, type ScreenshotReencode, type ScreenshotStatus, type SourceLocation, type SourceMapping, type SubmitOutcome, type SubmitResult, TITLE_MAX_CHARS, type Visibility, type VisibilityEnv, type VisibilityFn, type WebContext, type WebContextInput, WidgetController, type WidgetControllerOpts, type WidgetCorner, type WidgetListener, type WidgetPlatform, type WidgetPosition, type WidgetScreen, type WidgetState, appPathFromNav, buildContext, buildLassoContext, buildLassoEnvelope, buildLinearIssue, buildNotionPage, buildReport, buildTitle, byteLengthOf, canUseKeepalive, captureWithinLimit, createLassoAdapter, defaultBackoff, denormalizePin, detectDevBuild, diagnosticsProviderFor, getOrCreateGuestId, guestUser, hintCharCount, isInternalUser, matchElement, matchPath, normalizePin, normalizeRoutePath, normalizeUser, parseHintCatalog, parseSourceAttr, rankHints, renderReportBody, resolveConfig, resolveContextUser, sanitizeUrl, screenshotBytes, sharedDiagnostics, shouldShowWidget, sourceFromElement, uuidv4 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
|
+
/** 궤적 상한. 넘으면 오래된 것부터 버린다. */
|
|
2
|
+
declare const ROUTE_TRAIL_LIMIT = 20;
|
|
3
|
+
/** 탭 저장소에 쓰는 키. */
|
|
4
|
+
declare const ROUTE_TRAIL_KEY = "feedback-kit:route-trail";
|
|
5
|
+
/** 궤적 한 줄. */
|
|
6
|
+
interface RouteTrailEntry {
|
|
7
|
+
/** 그 경로에 **처음 도착한** 시각(epoch ms 정수). 접힘(count)으로는 갱신하지 않는다. */
|
|
8
|
+
at: number;
|
|
9
|
+
/** 직전 경로. 버퍼의 첫 항목은 `null`. */
|
|
10
|
+
from: string | null;
|
|
11
|
+
/** 도착 경로. */
|
|
12
|
+
to: string;
|
|
13
|
+
/** 같은 경로로 연속 이동해 접힌 횟수. 기본 1. */
|
|
14
|
+
count: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 경로 감시자 주입 계약 — **구독 함수 하나**다.
|
|
18
|
+
*
|
|
19
|
+
* 구독하면 **현재 경로를 한 번 흘리고**(마운트 시점의 화면이 궤적의 첫 줄이 된다),
|
|
20
|
+
* 이후 경로가 바뀔 때마다 새 경로를 넘긴다. 돌려주는 함수를 부르면 구독이 끊긴다.
|
|
21
|
+
* 뒤로가기·앞으로가기도 이동이므로 함께 흘려야 한다.
|
|
22
|
+
*/
|
|
23
|
+
type RouteWatcher = (onPath: (route: string) => void) => () => void;
|
|
24
|
+
/**
|
|
25
|
+
* 궤적을 남길 **동기** 저장소. `sessionStorage` 와 같은 모양이라 웹은 그대로 넘기면 된다.
|
|
26
|
+
*
|
|
27
|
+
* 코어의 `FeedbackStorage`(Promise)를 쓰지 않는 이유: 이동 직후 새로고침이 일어나면
|
|
28
|
+
* await 가 끝나기 전에 페이지가 사라져 그 이동이 통째로 유실된다.
|
|
29
|
+
*/
|
|
30
|
+
interface RouteTrailStore {
|
|
31
|
+
getItem(key: string): string | null;
|
|
32
|
+
setItem(key: string, value: string): void;
|
|
33
|
+
}
|
|
34
|
+
interface RouteTrailOpts {
|
|
35
|
+
/** 탭 단위 저장소. 없으면 메모리로만 동작한다(새로고침에서 끊긴다). */
|
|
36
|
+
storage?: RouteTrailStore | null;
|
|
37
|
+
limit?: number;
|
|
38
|
+
key?: string;
|
|
39
|
+
now?: () => number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 경로에서 쿼리스트링과 해시를 뺀다.
|
|
43
|
+
*
|
|
44
|
+
* 값 하나하나를 가려내지 않고 **쿼리를 통째로 버린다** — 그래야 `?token=…` 같은
|
|
45
|
+
* 자격증명형 파라미터가 애초에 들어올 자리가 없다. 화면 식별에는 경로만으로 충분하다.
|
|
46
|
+
*
|
|
47
|
+
* 인자를 `route` 라 부르는 이유: 화면 경로를 가리키는 더 흔한 영어 단어는 라쏘런 봉투가
|
|
48
|
+
* **요소 선택자**를 부르는 필드명이기도 하다. 그 단어가 코어 본체에 등장하면 경계 검사
|
|
49
|
+
* (`adapters.test.ts` TC5)가 "코어가 대상 필드명을 안다"로 잡는다 — 검사를 느슨하게
|
|
50
|
+
* 하는 대신 이쪽 이름을 비켰다.
|
|
51
|
+
*/
|
|
52
|
+
declare function normalizeRoutePath(route: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* 화면 이동 궤적 버퍼.
|
|
55
|
+
*
|
|
56
|
+
* 진단 링버퍼(network·logs)와 다른 점은 **탭 저장소에 남는다**는 것 하나다.
|
|
57
|
+
* 메모리에만 두면 전체 새로고침 한 번에 궤적이 끊기는데, 정작 재현 절차에
|
|
58
|
+
* 새로고침이 끼어 있는 제보가 많다.
|
|
59
|
+
*/
|
|
60
|
+
declare class RouteTrail {
|
|
61
|
+
readonly limit: number;
|
|
62
|
+
private entries;
|
|
63
|
+
private storage;
|
|
64
|
+
private readonly key;
|
|
65
|
+
private readonly now;
|
|
66
|
+
private unwatch;
|
|
67
|
+
constructor(opts?: RouteTrailOpts);
|
|
68
|
+
/** 감시자를 붙인다. 이미 붙어 있으면 먼저 끊는다(감시자가 둘이면 이동이 두 번 기록된다). */
|
|
69
|
+
watch(watcher: RouteWatcher): void;
|
|
70
|
+
/** 감시자를 뗀다. 버퍼 내용은 그대로 남는다. */
|
|
71
|
+
unwatchRoutes(): void;
|
|
72
|
+
/**
|
|
73
|
+
* 이동 하나를 기록한다.
|
|
74
|
+
*
|
|
75
|
+
* 같은 경로로 연속 이동하면 새 줄을 만들지 않고 `count` 만 올린다. 판정은 **직전 항목의
|
|
76
|
+
* `to`** 와 비교한다 — 새로고침 직후 같은 화면에서 다시 시작해도 줄이 늘지 않는다.
|
|
77
|
+
*/
|
|
78
|
+
record(route: string): void;
|
|
79
|
+
/** 지금까지의 궤적. 사본이라 이후 이동이 이미 만든 제보를 바꾸지 않는다. */
|
|
80
|
+
snapshot(): RouteTrailEntry[];
|
|
81
|
+
get size(): number;
|
|
82
|
+
/** 버퍼와 저장분을 모두 비운다. */
|
|
83
|
+
clear(): void;
|
|
84
|
+
/** 저장소를 나중에 붙인다. 이미 저장돼 있던 궤적이 있으면 그것을 이어받는다. */
|
|
85
|
+
attachStorage(storage: RouteTrailStore | null): void;
|
|
86
|
+
private readRaw;
|
|
87
|
+
private restore;
|
|
88
|
+
private persist;
|
|
89
|
+
}
|
|
90
|
+
|
|
1
91
|
/**
|
|
2
92
|
* 힌트가 어디에 뜨는가.
|
|
3
93
|
* `screen`=일반 모달(화면 전체를 묻는 흐름), `element`=요소 지목 모달, `both`=둘 다.
|
|
@@ -264,14 +354,19 @@ interface DiagLogEntry {
|
|
|
264
354
|
/**
|
|
265
355
|
* 제보에 실리는 진단 스냅샷.
|
|
266
356
|
*
|
|
267
|
-
*
|
|
268
|
-
* (그 세션에
|
|
357
|
+
* 세 배열은 **수집기가 실제로 모은 결과**다. 제출 시점에 비어 있는 것은 정상이고
|
|
358
|
+
* (그 세션에 요청·경고·이동이 없었다는 뜻), 배선이 안 됐을 때는 배열이 아니라
|
|
269
359
|
* 컨텍스트의 `diagnostics` 자체가 `null`이 된다. 그래서 "빈 배열"과
|
|
270
360
|
* "수집 안 함"이 페이로드에서 구분된다.
|
|
271
361
|
*/
|
|
272
362
|
interface DiagnosticsPayload {
|
|
273
363
|
network: DiagNetworkEntry[];
|
|
274
364
|
logs: DiagLogEntry[];
|
|
365
|
+
/**
|
|
366
|
+
* 화면 이동 궤적. 감시자를 주입하지 않은 호스트에서는 이것만 비고 나머지는 그대로 모인다
|
|
367
|
+
* — 궤적이 없다고 진단 전체를 `null` 로 만들지 않는다.
|
|
368
|
+
*/
|
|
369
|
+
routes: RouteTrailEntry[];
|
|
275
370
|
}
|
|
276
371
|
/** 앱 빌드 정보. 어느 빌드에서 났는지 특정하는 데 쓴다. */
|
|
277
372
|
interface AppInfo {
|
|
@@ -711,6 +806,16 @@ interface DiagnosticsInstallOpts {
|
|
|
711
806
|
* 그게 제보를 덮어 정작 중요한 경고가 묻힌다. 그래서 프로젝트마다 정한다.
|
|
712
807
|
*/
|
|
713
808
|
logs?: "problems" | "all" | "off";
|
|
809
|
+
/**
|
|
810
|
+
* 화면 이동을 알려주는 감시자. 코어는 `window`·`navigation` 을 보지 않으므로 플랫폼이
|
|
811
|
+
* 주입한다. 주지 않으면 **궤적만 비고** 네트워크·로그 수집은 그대로 돌아간다.
|
|
812
|
+
*/
|
|
813
|
+
watchRoutes?: RouteWatcher | null;
|
|
814
|
+
/**
|
|
815
|
+
* 궤적을 남길 탭 단위 저장소(웹은 `sessionStorage`). 주지 않으면 메모리로만 동작한다 —
|
|
816
|
+
* 전체 새로고침에서 궤적이 끊길 뿐, 수집 자체는 계속된다.
|
|
817
|
+
*/
|
|
818
|
+
routeStorage?: RouteTrailStore | null;
|
|
714
819
|
/** 시각 주입(테스트용). */
|
|
715
820
|
now?: () => number;
|
|
716
821
|
iso?: (n: number) => string;
|
|
@@ -718,6 +823,11 @@ interface DiagnosticsInstallOpts {
|
|
|
718
823
|
declare class DiagnosticsCollector {
|
|
719
824
|
readonly network: RingBuffer<DiagNetworkEntry>;
|
|
720
825
|
readonly logs: RingBuffer<DiagLogEntry>;
|
|
826
|
+
/**
|
|
827
|
+
* 화면 이동 궤적. network·logs 와 **같은 스위치(`captureDiagnostics`)** 로 켜고 끈다 —
|
|
828
|
+
* 별도 설정을 만들지 않는다. 다른 점은 탭 저장소에 남는다는 것 하나다.
|
|
829
|
+
*/
|
|
830
|
+
readonly routes: RouteTrail;
|
|
721
831
|
private installed;
|
|
722
832
|
private originalFetch;
|
|
723
833
|
private originalXhrOpen;
|
|
@@ -742,6 +852,8 @@ declare class DiagnosticsCollector {
|
|
|
742
852
|
iso?: (n: number) => string;
|
|
743
853
|
networkLimit?: number;
|
|
744
854
|
logLimit?: number;
|
|
855
|
+
routeLimit?: number;
|
|
856
|
+
routeStorage?: RouteTrailStore | null;
|
|
745
857
|
});
|
|
746
858
|
/** 위젯이 자신의 로그를 캡처 없이 찍을 때 쓰는 원본 console. */
|
|
747
859
|
readonly originalConsole: {
|
|
@@ -758,7 +870,7 @@ declare class DiagnosticsCollector {
|
|
|
758
870
|
* 반환 배열은 사본이라 이후 수집이 제보 페이로드를 바꾸지 않는다.
|
|
759
871
|
*/
|
|
760
872
|
snapshot(): DiagnosticsPayload;
|
|
761
|
-
/**
|
|
873
|
+
/** 세 버퍼를 비운다. */
|
|
762
874
|
clear(): void;
|
|
763
875
|
/** 실패 사유 한 줄. 예외 객체엔 요청 본문이 들어 있을 수 있어 메시지만, 길이도 자른다. */
|
|
764
876
|
private recordNetwork;
|
|
@@ -919,7 +1031,7 @@ declare function isInternalUser(resolved: ResolvedConfig, env: VisibilityEnv): b
|
|
|
919
1031
|
declare function shouldShowWidget(resolved: ResolvedConfig, input?: Partial<VisibilityEnv>): boolean;
|
|
920
1032
|
/** `DiagnosticsCollector` 중 설정 배선에 필요한 최소 형태. */
|
|
921
1033
|
interface DiagnosticsSource {
|
|
922
|
-
install(): void;
|
|
1034
|
+
install(opts?: DiagnosticsInstallOpts): void;
|
|
923
1035
|
snapshot(): DiagnosticsPayload;
|
|
924
1036
|
}
|
|
925
1037
|
/**
|
|
@@ -929,7 +1041,7 @@ interface DiagnosticsSource {
|
|
|
929
1041
|
* 돌려주는 공급자를 만들면 "수집했는데 아무 일도 없었다"와 "애초에 수집하지 않았다"가
|
|
930
1042
|
* 구분되지 않는다. 공급자가 없으면 `buildContext` 가 `diagnostics: null` 을 쓴다.
|
|
931
1043
|
*/
|
|
932
|
-
declare function diagnosticsProviderFor(resolved: Pick<ResolvedConfig, "captureDiagnostics">, collector: DiagnosticsSource): (() => DiagnosticsPayload) | undefined;
|
|
1044
|
+
declare function diagnosticsProviderFor(resolved: Pick<ResolvedConfig, "captureDiagnostics">, collector: DiagnosticsSource, installOpts?: DiagnosticsInstallOpts): (() => DiagnosticsPayload) | undefined;
|
|
933
1045
|
|
|
934
1046
|
/** 빌드 플러그인이 심는 속성 이름. `data-` 라서 DOM 이 그대로 통과시킨다. */
|
|
935
1047
|
declare const SOURCE_ATTR = "data-fk-source";
|
|
@@ -1512,6 +1624,11 @@ interface LassoReportContext {
|
|
|
1512
1624
|
device: Record<string, unknown>;
|
|
1513
1625
|
display: Record<string, unknown>;
|
|
1514
1626
|
} | null;
|
|
1627
|
+
/**
|
|
1628
|
+
* 화면 이동 궤적(도착 순서). 서버가 `meta` 로 펼치므로 제보를 다시 열었을 때
|
|
1629
|
+
* "어디를 거쳐 여기까지 왔는지"를 읽을 수 있다. 감시자가 없으면 빈 배열이다.
|
|
1630
|
+
*/
|
|
1631
|
+
routes: RouteTrailEntry[];
|
|
1515
1632
|
/** 호스트가 넣은 자유 메타. 우리가 해석하지 않는다. */
|
|
1516
1633
|
extra: Record<string, unknown>;
|
|
1517
1634
|
}
|
|
@@ -1737,4 +1854,4 @@ declare function parseHintCatalog(raw: unknown): HintCatalog | null;
|
|
|
1737
1854
|
*/
|
|
1738
1855
|
declare function rankHints(hints: readonly FeedbackHint[], input: HintRankInput, limit: number): FeedbackHint[];
|
|
1739
1856
|
|
|
1740
|
-
export { type AdapterName, type AppInfo, BACKOFF_BASE_MS, BACKOFF_MAX_MS, type BuildContextOpts, type BuildReportOpts, COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, type CaptureWithinLimitOpts, type ConfigWarning, type ConfigWarningCode, type ContextProviders, type ContextUser, DEFAULT_ADAPTER, DEFAULT_INTERNAL_ROLES, DEFAULT_POSITION, type DeviceInfo, type DiagLogEntry, type DiagNetworkEntry, type DiagRequestRef, DiagnosticsCollector, type DiagnosticsExcludeMatcher, type DiagnosticsInstallOpts, type DiagnosticsPayload, type DiagnosticsSource, type DisplayInfo, ENDPOINT_FROM_ADAPTER, type ElementInfo, type ElementLike, FLOATING_BUTTON_ID, type FeedbackAdapter, type FeedbackConfig, type FeedbackContext, type FeedbackHint, type FeedbackKind, type FeedbackPin, type FeedbackPriority, FeedbackQueue, type FeedbackQueueOpts, type FeedbackReport, type FeedbackScreenshot, type FeedbackStorage, type FeedbackUser, FocusRing, type GetCurrentScreenFn, type GetUserFn, HINT_DISPLAY_DEFAULT, HINT_DISPLAY_MAX, HINT_DISPLAY_MIN, HINT_DRAFT_MAX_CHARS, HINT_FAIL_COOLDOWN_MS, HINT_LABEL_MAX_CHARS, HINT_MAX_ITEMS, HINT_TIMEOUT_MS, HINT_TTL_MS, type HintCacheEntry, type HintCatalog, type HintElementRule, type HintFetchResult, type HintPlatform, HintProvider, type HintProviderOpts, type HintRankInput, type HintScope, type HintSource, KEEPALIVE_BODY_LIMIT_BYTES, LASSO_DEFAULT_ENDPOINT, LOG_BUFFER_LIMIT, type LassoAdapterOpts, type LassoEnvelope, type LassoFetch, type LassoReportContext, type LassoRequestInit, type LassoResponseLike, type LinearIssueInput, MAX_LOG_MESSAGE_CHARS, MAX_QUEUE_AGE_MS, MAX_QUEUE_ITEMS, MODAL_ACTION_ATTACH, MODAL_ACTION_CANCEL, MODAL_ACTION_HINT_TOGGLE, MODAL_ACTION_PICK, MODAL_ACTION_PIN, MODAL_ACTION_REMOVE_SCREENSHOT, MODAL_ACTION_RETRY, MODAL_ACTION_SEND, MODAL_FIELD_COMMENT, MODAL_FIELD_PRIORITY, type ModalQueueLike, type ModalSubmitStatus, NETWORK_BUFFER_LIMIT, type NativeContext, type NotionPageInput, PinController, type PixelPoint, type PixelSize, type QueueStatus, type QueueStatusListener, RATE_LIMIT_MIN_DELAY_MS, REDACTED, ReportModalController, type ReportModalListener, type ReportModalOpts, type ReportModalState, type ReportParts, type ResolvedConfig, RingBuffer, SCORE_ELEMENT, SCORE_PATH_EXACT, SCORE_PATH_PATTERN, SCREENSHOT_FAILED_MESSAGE, SCREENSHOT_MAX_BASE64_BYTES, SCREENSHOT_QUALITY_STEPS, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_FAILED_MESSAGE, SUBMIT_PENDING_MESSAGE, type ScreenshotCapture, type ScreenshotOutcome, type ScreenshotReencode, type ScreenshotStatus, type SourceLocation, type SourceMapping, type SubmitOutcome, type SubmitResult, TITLE_MAX_CHARS, type Visibility, type VisibilityEnv, type VisibilityFn, type WebContext, type WebContextInput, WidgetController, type WidgetControllerOpts, type WidgetCorner, type WidgetListener, type WidgetPlatform, type WidgetPosition, type WidgetScreen, type WidgetState, appPathFromNav, buildContext, buildLassoContext, buildLassoEnvelope, buildLinearIssue, buildNotionPage, buildReport, buildTitle, byteLengthOf, canUseKeepalive, captureWithinLimit, createLassoAdapter, defaultBackoff, denormalizePin, detectDevBuild, diagnosticsProviderFor, getOrCreateGuestId, guestUser, hintCharCount, isInternalUser, matchElement, matchPath, normalizePin, normalizeUser, parseHintCatalog, parseSourceAttr, rankHints, renderReportBody, resolveConfig, resolveContextUser, sanitizeUrl, screenshotBytes, sharedDiagnostics, shouldShowWidget, sourceFromElement, uuidv4 };
|
|
1857
|
+
export { type AdapterName, type AppInfo, BACKOFF_BASE_MS, BACKOFF_MAX_MS, type BuildContextOpts, type BuildReportOpts, COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, type CaptureWithinLimitOpts, type ConfigWarning, type ConfigWarningCode, type ContextProviders, type ContextUser, DEFAULT_ADAPTER, DEFAULT_INTERNAL_ROLES, DEFAULT_POSITION, type DeviceInfo, type DiagLogEntry, type DiagNetworkEntry, type DiagRequestRef, DiagnosticsCollector, type DiagnosticsExcludeMatcher, type DiagnosticsInstallOpts, type DiagnosticsPayload, type DiagnosticsSource, type DisplayInfo, ENDPOINT_FROM_ADAPTER, type ElementInfo, type ElementLike, FLOATING_BUTTON_ID, type FeedbackAdapter, type FeedbackConfig, type FeedbackContext, type FeedbackHint, type FeedbackKind, type FeedbackPin, type FeedbackPriority, FeedbackQueue, type FeedbackQueueOpts, type FeedbackReport, type FeedbackScreenshot, type FeedbackStorage, type FeedbackUser, FocusRing, type GetCurrentScreenFn, type GetUserFn, HINT_DISPLAY_DEFAULT, HINT_DISPLAY_MAX, HINT_DISPLAY_MIN, HINT_DRAFT_MAX_CHARS, HINT_FAIL_COOLDOWN_MS, HINT_LABEL_MAX_CHARS, HINT_MAX_ITEMS, HINT_TIMEOUT_MS, HINT_TTL_MS, type HintCacheEntry, type HintCatalog, type HintElementRule, type HintFetchResult, type HintPlatform, HintProvider, type HintProviderOpts, type HintRankInput, type HintScope, type HintSource, KEEPALIVE_BODY_LIMIT_BYTES, LASSO_DEFAULT_ENDPOINT, LOG_BUFFER_LIMIT, type LassoAdapterOpts, type LassoEnvelope, type LassoFetch, type LassoReportContext, type LassoRequestInit, type LassoResponseLike, type LinearIssueInput, MAX_LOG_MESSAGE_CHARS, MAX_QUEUE_AGE_MS, MAX_QUEUE_ITEMS, MODAL_ACTION_ATTACH, MODAL_ACTION_CANCEL, MODAL_ACTION_HINT_TOGGLE, MODAL_ACTION_PICK, MODAL_ACTION_PIN, MODAL_ACTION_REMOVE_SCREENSHOT, MODAL_ACTION_RETRY, MODAL_ACTION_SEND, MODAL_FIELD_COMMENT, MODAL_FIELD_PRIORITY, type ModalQueueLike, type ModalSubmitStatus, NETWORK_BUFFER_LIMIT, type NativeContext, type NotionPageInput, PinController, type PixelPoint, type PixelSize, type QueueStatus, type QueueStatusListener, RATE_LIMIT_MIN_DELAY_MS, REDACTED, ROUTE_TRAIL_KEY, ROUTE_TRAIL_LIMIT, ReportModalController, type ReportModalListener, type ReportModalOpts, type ReportModalState, type ReportParts, type ResolvedConfig, RingBuffer, RouteTrail, type RouteTrailEntry, type RouteTrailOpts, type RouteTrailStore, type RouteWatcher, SCORE_ELEMENT, SCORE_PATH_EXACT, SCORE_PATH_PATTERN, SCREENSHOT_FAILED_MESSAGE, SCREENSHOT_MAX_BASE64_BYTES, SCREENSHOT_QUALITY_STEPS, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_FAILED_MESSAGE, SUBMIT_PENDING_MESSAGE, type ScreenshotCapture, type ScreenshotOutcome, type ScreenshotReencode, type ScreenshotStatus, type SourceLocation, type SourceMapping, type SubmitOutcome, type SubmitResult, TITLE_MAX_CHARS, type Visibility, type VisibilityEnv, type VisibilityFn, type WebContext, type WebContextInput, WidgetController, type WidgetControllerOpts, type WidgetCorner, type WidgetListener, type WidgetPlatform, type WidgetPosition, type WidgetScreen, type WidgetState, appPathFromNav, buildContext, buildLassoContext, buildLassoEnvelope, buildLinearIssue, buildNotionPage, buildReport, buildTitle, byteLengthOf, canUseKeepalive, captureWithinLimit, createLassoAdapter, defaultBackoff, denormalizePin, detectDevBuild, diagnosticsProviderFor, getOrCreateGuestId, guestUser, hintCharCount, isInternalUser, matchElement, matchPath, normalizePin, normalizeRoutePath, normalizeUser, parseHintCatalog, parseSourceAttr, rankHints, renderReportBody, resolveConfig, resolveContextUser, sanitizeUrl, screenshotBytes, sharedDiagnostics, shouldShowWidget, sourceFromElement, uuidv4 };
|
package/dist/index.js
CHANGED
|
@@ -151,6 +151,7 @@ function normalizeDiagnostics(snap) {
|
|
|
151
151
|
if (!snap || !Array.isArray(snap.network) || !Array.isArray(snap.logs)) {
|
|
152
152
|
return null;
|
|
153
153
|
}
|
|
154
|
+
if (!Array.isArray(snap.routes)) return { ...snap, routes: [] };
|
|
154
155
|
return snap;
|
|
155
156
|
}
|
|
156
157
|
function safeCall(fn, fallback) {
|
|
@@ -625,6 +626,130 @@ var RingBuffer = class {
|
|
|
625
626
|
}
|
|
626
627
|
};
|
|
627
628
|
|
|
629
|
+
// src/route-trail.ts
|
|
630
|
+
var ROUTE_TRAIL_LIMIT = 20;
|
|
631
|
+
var ROUTE_TRAIL_KEY = "feedback-kit:route-trail";
|
|
632
|
+
function normalizeRoutePath(route) {
|
|
633
|
+
const raw = typeof route === "string" ? route : "";
|
|
634
|
+
const cut = [raw.indexOf("?"), raw.indexOf("#")].filter((i) => i >= 0);
|
|
635
|
+
const trimmed = (cut.length > 0 ? raw.slice(0, Math.min(...cut)) : raw).trim();
|
|
636
|
+
return trimmed.length > 0 ? trimmed : "/";
|
|
637
|
+
}
|
|
638
|
+
function parseEntries(raw) {
|
|
639
|
+
if (!raw) return [];
|
|
640
|
+
let parsed;
|
|
641
|
+
try {
|
|
642
|
+
parsed = JSON.parse(raw);
|
|
643
|
+
} catch {
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
if (!Array.isArray(parsed)) return [];
|
|
647
|
+
const out = [];
|
|
648
|
+
for (const item of parsed) {
|
|
649
|
+
if (typeof item !== "object" || item === null) continue;
|
|
650
|
+
const e = item;
|
|
651
|
+
if (typeof e.to !== "string" || typeof e.at !== "number") continue;
|
|
652
|
+
out.push({
|
|
653
|
+
at: e.at,
|
|
654
|
+
from: typeof e.from === "string" ? e.from : null,
|
|
655
|
+
to: e.to,
|
|
656
|
+
count: typeof e.count === "number" && e.count > 0 ? Math.floor(e.count) : 1
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
return out;
|
|
660
|
+
}
|
|
661
|
+
var RouteTrail = class {
|
|
662
|
+
constructor(opts = {}) {
|
|
663
|
+
this.entries = [];
|
|
664
|
+
this.unwatch = null;
|
|
665
|
+
this.limit = opts.limit !== void 0 && opts.limit > 0 ? Math.floor(opts.limit) : ROUTE_TRAIL_LIMIT;
|
|
666
|
+
this.key = opts.key ?? ROUTE_TRAIL_KEY;
|
|
667
|
+
this.now = opts.now ?? (() => Date.now());
|
|
668
|
+
this.storage = opts.storage ?? null;
|
|
669
|
+
this.restore();
|
|
670
|
+
}
|
|
671
|
+
/** 감시자를 붙인다. 이미 붙어 있으면 먼저 끊는다(감시자가 둘이면 이동이 두 번 기록된다). */
|
|
672
|
+
watch(watcher) {
|
|
673
|
+
this.unwatchRoutes();
|
|
674
|
+
try {
|
|
675
|
+
this.unwatch = watcher((route) => this.record(route));
|
|
676
|
+
} catch {
|
|
677
|
+
this.unwatch = null;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/** 감시자를 뗀다. 버퍼 내용은 그대로 남는다. */
|
|
681
|
+
unwatchRoutes() {
|
|
682
|
+
if (!this.unwatch) return;
|
|
683
|
+
const off = this.unwatch;
|
|
684
|
+
this.unwatch = null;
|
|
685
|
+
try {
|
|
686
|
+
off();
|
|
687
|
+
} catch {
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* 이동 하나를 기록한다.
|
|
692
|
+
*
|
|
693
|
+
* 같은 경로로 연속 이동하면 새 줄을 만들지 않고 `count` 만 올린다. 판정은 **직전 항목의
|
|
694
|
+
* `to`** 와 비교한다 — 새로고침 직후 같은 화면에서 다시 시작해도 줄이 늘지 않는다.
|
|
695
|
+
*/
|
|
696
|
+
record(route) {
|
|
697
|
+
const to = normalizeRoutePath(route);
|
|
698
|
+
const last = this.entries[this.entries.length - 1];
|
|
699
|
+
if (last && last.to === to) {
|
|
700
|
+
last.count += 1;
|
|
701
|
+
} else {
|
|
702
|
+
this.entries.push({ at: this.now(), from: last ? last.to : null, to, count: 1 });
|
|
703
|
+
const overflow = this.entries.length - this.limit;
|
|
704
|
+
if (overflow > 0) this.entries.splice(0, overflow);
|
|
705
|
+
}
|
|
706
|
+
this.persist();
|
|
707
|
+
}
|
|
708
|
+
/** 지금까지의 궤적. 사본이라 이후 이동이 이미 만든 제보를 바꾸지 않는다. */
|
|
709
|
+
snapshot() {
|
|
710
|
+
return this.entries.map((e) => ({ ...e }));
|
|
711
|
+
}
|
|
712
|
+
get size() {
|
|
713
|
+
return this.entries.length;
|
|
714
|
+
}
|
|
715
|
+
/** 버퍼와 저장분을 모두 비운다. */
|
|
716
|
+
clear() {
|
|
717
|
+
this.entries = [];
|
|
718
|
+
this.persist();
|
|
719
|
+
}
|
|
720
|
+
/** 저장소를 나중에 붙인다. 이미 저장돼 있던 궤적이 있으면 그것을 이어받는다. */
|
|
721
|
+
attachStorage(storage) {
|
|
722
|
+
this.storage = storage;
|
|
723
|
+
if (!storage) return;
|
|
724
|
+
const restored = parseEntries(this.readRaw());
|
|
725
|
+
if (restored.length > 0) {
|
|
726
|
+
this.entries = [...restored, ...this.entries].slice(-this.limit);
|
|
727
|
+
}
|
|
728
|
+
this.persist();
|
|
729
|
+
}
|
|
730
|
+
// ── 내부: 저장소 ──────────────────────────────────────────
|
|
731
|
+
// 저장은 **거들 뿐**이다. 던지면 이동 한 번에 앱이 죽는다(프라이빗 모드 등에서
|
|
732
|
+
// getItem/setItem 이 실제로 던진다). 그래서 전부 삼키고 메모리로 계속 간다.
|
|
733
|
+
readRaw() {
|
|
734
|
+
if (!this.storage) return null;
|
|
735
|
+
try {
|
|
736
|
+
return this.storage.getItem(this.key);
|
|
737
|
+
} catch {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
restore() {
|
|
742
|
+
this.entries = parseEntries(this.readRaw()).slice(-this.limit);
|
|
743
|
+
}
|
|
744
|
+
persist() {
|
|
745
|
+
if (!this.storage) return;
|
|
746
|
+
try {
|
|
747
|
+
this.storage.setItem(this.key, JSON.stringify(this.entries));
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
|
|
628
753
|
// src/diagnostics.ts
|
|
629
754
|
var NETWORK_BUFFER_LIMIT = 30;
|
|
630
755
|
var LOG_BUFFER_LIMIT = 50;
|
|
@@ -671,6 +796,11 @@ var DiagnosticsCollector = class {
|
|
|
671
796
|
opts.networkLimit ?? NETWORK_BUFFER_LIMIT
|
|
672
797
|
);
|
|
673
798
|
this.logs = new RingBuffer(opts.logLimit ?? LOG_BUFFER_LIMIT);
|
|
799
|
+
this.routes = new RouteTrail({
|
|
800
|
+
limit: opts.routeLimit ?? ROUTE_TRAIL_LIMIT,
|
|
801
|
+
storage: opts.routeStorage ?? null,
|
|
802
|
+
now: () => this.now()
|
|
803
|
+
});
|
|
674
804
|
}
|
|
675
805
|
/** 글로벌 fetch/XHR/console 을 패치한다. 이미 설치했으면 no-op. */
|
|
676
806
|
install(opts = {}) {
|
|
@@ -683,6 +813,8 @@ var DiagnosticsCollector = class {
|
|
|
683
813
|
this.patchFetch();
|
|
684
814
|
this.patchXhr();
|
|
685
815
|
this.patchConsole();
|
|
816
|
+
if (opts.routeStorage !== void 0) this.routes.attachStorage(opts.routeStorage);
|
|
817
|
+
if (opts.watchRoutes) this.routes.watch(opts.watchRoutes);
|
|
686
818
|
}
|
|
687
819
|
/** 글로벌 패치를 모두 원복한다. 버퍼 내용은 그대로 남는다. */
|
|
688
820
|
uninstall() {
|
|
@@ -709,18 +841,24 @@ var DiagnosticsCollector = class {
|
|
|
709
841
|
this.originalLog = null;
|
|
710
842
|
this.excludeMatcher = null;
|
|
711
843
|
this.fetchDepth = 0;
|
|
844
|
+
this.routes.unwatchRoutes();
|
|
712
845
|
}
|
|
713
846
|
/**
|
|
714
847
|
* 지금까지 모인 것을 그대로 스냅샷으로 낸다.
|
|
715
848
|
* 반환 배열은 사본이라 이후 수집이 제보 페이로드를 바꾸지 않는다.
|
|
716
849
|
*/
|
|
717
850
|
snapshot() {
|
|
718
|
-
return {
|
|
851
|
+
return {
|
|
852
|
+
network: this.network.toArray(),
|
|
853
|
+
logs: this.logs.toArray(),
|
|
854
|
+
routes: this.routes.snapshot()
|
|
855
|
+
};
|
|
719
856
|
}
|
|
720
|
-
/**
|
|
857
|
+
/** 세 버퍼를 비운다. */
|
|
721
858
|
clear() {
|
|
722
859
|
this.network.clear();
|
|
723
860
|
this.logs.clear();
|
|
861
|
+
this.routes.clear();
|
|
724
862
|
}
|
|
725
863
|
// ── 내부: 기록 ────────────────────────────────────────────
|
|
726
864
|
/** 실패 사유 한 줄. 예외 객체엔 요청 본문이 들어 있을 수 있어 메시지만, 길이도 자른다. */
|
|
@@ -1125,9 +1263,9 @@ function shouldShowWidget(resolved, input = {}) {
|
|
|
1125
1263
|
return false;
|
|
1126
1264
|
}
|
|
1127
1265
|
}
|
|
1128
|
-
function diagnosticsProviderFor(resolved, collector) {
|
|
1266
|
+
function diagnosticsProviderFor(resolved, collector, installOpts) {
|
|
1129
1267
|
if (!resolved.captureDiagnostics) return void 0;
|
|
1130
|
-
collector.install();
|
|
1268
|
+
collector.install(installOpts);
|
|
1131
1269
|
return () => collector.snapshot();
|
|
1132
1270
|
}
|
|
1133
1271
|
|
|
@@ -2277,6 +2415,9 @@ function buildLassoContext(report) {
|
|
|
2277
2415
|
device: { ...c.native.device },
|
|
2278
2416
|
display: { ...c.native.display }
|
|
2279
2417
|
} : null,
|
|
2418
|
+
// 미수집(진단 자체가 null)과 "이동이 없었음"을 굳이 나누지 않는다 — 둘 다 빈 배열.
|
|
2419
|
+
// 나누려면 서버가 null 을 보존해야 하는데, 그건 확인된 계약이 아니다.
|
|
2420
|
+
routes: [...c.diagnostics?.routes ?? []],
|
|
2280
2421
|
extra: c.extra
|
|
2281
2422
|
};
|
|
2282
2423
|
}
|
|
@@ -2940,8 +3081,11 @@ export {
|
|
|
2940
3081
|
PinController,
|
|
2941
3082
|
RATE_LIMIT_MIN_DELAY_MS,
|
|
2942
3083
|
REDACTED,
|
|
3084
|
+
ROUTE_TRAIL_KEY,
|
|
3085
|
+
ROUTE_TRAIL_LIMIT,
|
|
2943
3086
|
ReportModalController,
|
|
2944
3087
|
RingBuffer,
|
|
3088
|
+
RouteTrail,
|
|
2945
3089
|
SCORE_ELEMENT,
|
|
2946
3090
|
SCORE_PATH_EXACT,
|
|
2947
3091
|
SCORE_PATH_PATTERN,
|
|
@@ -2977,6 +3121,7 @@ export {
|
|
|
2977
3121
|
matchElement,
|
|
2978
3122
|
matchPath,
|
|
2979
3123
|
normalizePin,
|
|
3124
|
+
normalizeRoutePath,
|
|
2980
3125
|
normalizeUser,
|
|
2981
3126
|
parseHintCatalog,
|
|
2982
3127
|
parseSourceAttr,
|