@ait-co/devtools 0.2.0 → 0.2.1
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/README.en.md +14 -1
- package/README.md +3 -0
- package/dist/mock/index.d.ts +17 -2
- package/dist/mock/index.d.ts.map +1 -1
- package/dist/mock/index.js +81 -2
- package/dist/mock/index.js.map +1 -1
- package/dist/panel/index.js +80 -2
- package/dist/panel/index.js.map +1 -1
- package/dist/{relay-url-store-DLjlvMSA.cjs → relay-url-store-CkVSQZMq.cjs} +4 -2
- package/dist/relay-url-store-CkVSQZMq.cjs.map +1 -0
- package/dist/{relay-url-store-CPZAn-T5.js → relay-url-store-dkII-DHD.js} +4 -2
- package/dist/relay-url-store-dkII-DHD.js.map +1 -0
- package/dist/unplugin/index.cjs +1 -1
- package/dist/unplugin/index.cjs.map +1 -1
- package/dist/unplugin/index.d.cts +4 -2
- package/dist/unplugin/index.d.cts.map +1 -1
- package/dist/unplugin/index.d.ts +4 -2
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +1 -1
- package/dist/unplugin/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/relay-url-store-CPZAn-T5.js.map +0 -1
- package/dist/relay-url-store-DLjlvMSA.cjs.map +0 -1
package/dist/panel/index.js
CHANGED
|
@@ -25685,6 +25685,24 @@ function useT() {
|
|
|
25685
25685
|
return t;
|
|
25686
25686
|
}
|
|
25687
25687
|
//#endregion
|
|
25688
|
+
//#region src/mock/throttle-registry.ts
|
|
25689
|
+
/**
|
|
25690
|
+
* throttle 레지스트리 (devtools#834).
|
|
25691
|
+
*
|
|
25692
|
+
* 메서드별 마지막 *허용* 호출 시각만 담는 leaf 모듈이다. `state.ts`가 `reset()`에서
|
|
25693
|
+
* 이걸 비워야 하는데, 로직 본체(`throttle.ts`)는 거꾸로 `state.ts`를 읽는다 —
|
|
25694
|
+
* 그래서 **의존이 없는 이 파일로 레지스트리를 분리해** 순환 import를 피한다.
|
|
25695
|
+
*/
|
|
25696
|
+
/** 메서드별 마지막 *허용* 호출 시각(ms). */
|
|
25697
|
+
const lastAllowedAt = /* @__PURE__ */ new Map();
|
|
25698
|
+
/**
|
|
25699
|
+
* throttle 레지스트리를 비운다. `aitState.reset()`이 호출한다 — 상태를 되돌렸는데
|
|
25700
|
+
* 직전 세션의 호출 시각이 남아 있으면 첫 호출이 이유 없이 거부된다.
|
|
25701
|
+
*/
|
|
25702
|
+
function resetThrottleRegistry() {
|
|
25703
|
+
lastAllowedAt.clear();
|
|
25704
|
+
}
|
|
25705
|
+
//#endregion
|
|
25688
25706
|
//#region src/mock/state.ts
|
|
25689
25707
|
/** SDK 호출 로그 ring buffer 상한 */
|
|
25690
25708
|
const SDK_CALL_LOG_MAX = 200;
|
|
@@ -25914,6 +25932,7 @@ var AitStateManager = class {
|
|
|
25914
25932
|
...structuredClone(DEFAULT_STATE),
|
|
25915
25933
|
deviceId
|
|
25916
25934
|
};
|
|
25935
|
+
resetThrottleRegistry();
|
|
25917
25936
|
this._notify();
|
|
25918
25937
|
}
|
|
25919
25938
|
_notify() {
|
|
@@ -26331,7 +26350,9 @@ function waitForPromptResponse(type) {
|
|
|
26331
26350
|
* - 3.x 라인: 같은 실패가 "맨 Error"(위 envelope 필드 없음)로 평탄화된다 —
|
|
26332
26351
|
* sdk-example#284 "패턴 ① 오류 envelope 평탄화" 표.
|
|
26333
26352
|
*
|
|
26334
|
-
* `src/test-runner/bridge-stub.ts
|
|
26353
|
+
* `@ait-co/debugger`의 `packages/debugger/src/test-runner/bridge-stub.ts`(분리 전
|
|
26354
|
+
* 이 repo의 `src/test-runner/bridge-stub.ts`)에 있는
|
|
26355
|
+
* `NativeBridgeErrorShape`/`makeNativeError`가
|
|
26335
26356
|
* 같은 shape을 이미 CI 러너 쪽에서 재현하고 있다(devtools#740) — 이 모듈은 그
|
|
26336
26357
|
* shape을 mock 소비자 표면(`src/mock/**`)에 대해 재사용 가능한 형태로 옮긴 것이다.
|
|
26337
26358
|
* 두 모듈은 레이어가 다르다: test-runner 쪽은 env3 blocking-call 인터셉터(빌드에
|
|
@@ -26709,6 +26730,50 @@ const _fetchAlbumItems = async (options) => {
|
|
|
26709
26730
|
const _fetchAlbumItemsWithPermission = withPermission(_fetchAlbumItems, "photos");
|
|
26710
26731
|
Object.assign(_fetchAlbumItemsWithPermission, { isSupported: () => true });
|
|
26711
26732
|
//#endregion
|
|
26733
|
+
//#region src/mock/throttle.ts
|
|
26734
|
+
/**
|
|
26735
|
+
* THROTTLED 시뮬레이션 (devtools#834 — #770 §3 분리).
|
|
26736
|
+
*
|
|
26737
|
+
* 실기기 네이티브 브리지는 같은 메서드를 짧은 간격으로 연타하면
|
|
26738
|
+
* `APP_BRIDGE_THROTTLED`로 거부한다. 이 모듈은 그 per-method rate limit을
|
|
26739
|
+
* env1(mock)에서 **opt-in으로** 재현한다 — 다이얼(`failureModes.throttled`)이
|
|
26740
|
+
* 미설정이면 아무 일도 하지 않으므로 zero behavior change다.
|
|
26741
|
+
*
|
|
26742
|
+
* 설계 메모 두 가지:
|
|
26743
|
+
*
|
|
26744
|
+
* 1. **거부된 호출은 창을 갱신하지 않는다.** 마지막 *허용된* 호출 시각만 기록하므로,
|
|
26745
|
+
* `intervalMs` 안에서 계속 연타해도 창이 밀려나지 않고 최초 허용 시점 기준으로
|
|
26746
|
+
* 풀린다. 네이티브 rate limit의 통상 동작이자, 연타 중 영원히 막히는(창이 계속
|
|
26747
|
+
* 갱신되는) 반-패턴을 피한다.
|
|
26748
|
+
* 2. **`observe()`에 걸지 않는다.** `observe()`는 `fn`을 호출하기 *전에* 감싸므로
|
|
26749
|
+
* 거기서 throw하면 원래 Promise를 반환하는 API가 **동기 throw**로 바뀐다.
|
|
26750
|
+
* `threwSync`는 env1↔env3 동치 diff의 관측 축이라 그 차이가 곧 가짜 불일치가 된다.
|
|
26751
|
+
* 그래서 각 mock 구현 본문 안(= 그 API 자신의 sync/async 계약 안)에 수동 삽입한다.
|
|
26752
|
+
*/
|
|
26753
|
+
/**
|
|
26754
|
+
* `method`가 지금 throttle에 걸리면 네이티브 실패 에러를 돌려주고, 아니면
|
|
26755
|
+
* 호출 시각을 기록한 뒤 `undefined`를 돌려준다.
|
|
26756
|
+
*
|
|
26757
|
+
* 반환된 에러를 **호출부가 자신의 계약대로** 흘려보낸다 — async mock은 `throw`
|
|
26758
|
+
* (= reject), 콜백형 mock은 `onError(err)`. 그래서 이 함수 자체는 던지지 않는다.
|
|
26759
|
+
*/
|
|
26760
|
+
function throttleErrorFor(method) {
|
|
26761
|
+
const dial = aitState.state.failureModes.throttled;
|
|
26762
|
+
if (!dial?.methods.includes(method)) return void 0;
|
|
26763
|
+
const now = Date.now();
|
|
26764
|
+
const prev = lastAllowedAt.get(method);
|
|
26765
|
+
if (prev !== void 0 && now - prev < dial.intervalMs) return buildNativeError("APP_BRIDGE_THROTTLED");
|
|
26766
|
+
lastAllowedAt.set(method, now);
|
|
26767
|
+
}
|
|
26768
|
+
/**
|
|
26769
|
+
* `throttleErrorFor`의 throw 버전 — async mock 본문에서 한 줄로 쓴다.
|
|
26770
|
+
* async 함수 안에서 던지므로 호출자에게는 rejection으로 도달한다.
|
|
26771
|
+
*/
|
|
26772
|
+
function checkThrottle(method) {
|
|
26773
|
+
const err = throttleErrorFor(method);
|
|
26774
|
+
if (err) throw err;
|
|
26775
|
+
}
|
|
26776
|
+
//#endregion
|
|
26712
26777
|
//#region src/mock/device/clipboard.ts
|
|
26713
26778
|
/**
|
|
26714
26779
|
* Clipboard mock
|
|
@@ -26716,6 +26781,7 @@ Object.assign(_fetchAlbumItemsWithPermission, { isSupported: () => true });
|
|
|
26716
26781
|
*/
|
|
26717
26782
|
const _getClipboardText = async () => {
|
|
26718
26783
|
checkPermission("clipboard", "getClipboardText");
|
|
26784
|
+
checkThrottle("getClipboardText");
|
|
26719
26785
|
if (aitState.state.deviceModes.clipboard === "mock") return aitState.state.mockData.clipboardText;
|
|
26720
26786
|
try {
|
|
26721
26787
|
return await navigator.clipboard.readText();
|
|
@@ -26726,6 +26792,7 @@ const _getClipboardText = async () => {
|
|
|
26726
26792
|
withPermission(_getClipboardText, "clipboard");
|
|
26727
26793
|
const _setClipboardText = async (text) => {
|
|
26728
26794
|
checkPermission("clipboard", "setClipboardText");
|
|
26795
|
+
checkThrottle("setClipboardText");
|
|
26729
26796
|
if (aitState.state.deviceModes.clipboard === "mock") {
|
|
26730
26797
|
aitState.patch("mockData", { clipboardText: text });
|
|
26731
26798
|
return { text };
|
|
@@ -26877,6 +26944,7 @@ async function getCurrentLocationPrompt() {
|
|
|
26877
26944
|
}
|
|
26878
26945
|
const _getCurrentLocation = async (_options) => {
|
|
26879
26946
|
checkPermission("geolocation", "getCurrentLocation");
|
|
26947
|
+
checkThrottle("getCurrentLocation");
|
|
26880
26948
|
const mode = aitState.state.deviceModes.location;
|
|
26881
26949
|
if (mode === "web") return getCurrentLocationWeb();
|
|
26882
26950
|
if (mode === "prompt") return getCurrentLocationPrompt();
|
|
@@ -27344,6 +27412,11 @@ const GoogleAdMob = createMockProxy("GoogleAdMob", {
|
|
|
27344
27412
|
args.onError(buildNativeError(failureCode));
|
|
27345
27413
|
return;
|
|
27346
27414
|
}
|
|
27415
|
+
const throttleError = throttleErrorFor("loadAppsInTossAdMob");
|
|
27416
|
+
if (throttleError) {
|
|
27417
|
+
args.onError(throttleError);
|
|
27418
|
+
return;
|
|
27419
|
+
}
|
|
27347
27420
|
if (aitState.state.ads.forceNoFill) {
|
|
27348
27421
|
args.onError(/* @__PURE__ */ new Error("No fill"));
|
|
27349
27422
|
return;
|
|
@@ -27472,6 +27545,11 @@ const loadFullScreenAd = observe("loadFullScreenAd", "faithful", (args) => {
|
|
|
27472
27545
|
args.onError(buildNativeError(failureCode));
|
|
27473
27546
|
return;
|
|
27474
27547
|
}
|
|
27548
|
+
const throttleError = throttleErrorFor("loadFullScreenAd");
|
|
27549
|
+
if (throttleError) {
|
|
27550
|
+
args.onError(throttleError);
|
|
27551
|
+
return;
|
|
27552
|
+
}
|
|
27475
27553
|
if (aitState.state.ads.forceNoFill) {
|
|
27476
27554
|
args.onError(/* @__PURE__ */ new Error("No fill"));
|
|
27477
27555
|
return;
|
|
@@ -30193,7 +30271,7 @@ function Panel() {
|
|
|
30193
30271
|
color: "#666",
|
|
30194
30272
|
fontWeight: 400
|
|
30195
30273
|
},
|
|
30196
|
-
children: ["v", "0.2.
|
|
30274
|
+
children: ["v", "0.2.1"]
|
|
30197
30275
|
}),
|
|
30198
30276
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
30199
30277
|
type: "button",
|