@ait-co/devtools 0.1.5 → 0.1.7
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.md +19 -2
- package/dist/mock/index.d.ts +8 -0
- package/dist/mock/index.d.ts.map +1 -1
- package/dist/mock/index.js +2 -1
- package/dist/mock/index.js.map +1 -1
- package/dist/panel/index.js +185 -22
- package/dist/panel/index.js.map +1 -1
- package/package.json +1 -1
package/dist/panel/index.js
CHANGED
|
@@ -101,7 +101,8 @@ const DEFAULT_STATE = {
|
|
|
101
101
|
customWidth: 402,
|
|
102
102
|
customHeight: 874,
|
|
103
103
|
frame: false,
|
|
104
|
-
aitNavBar: true
|
|
104
|
+
aitNavBar: true,
|
|
105
|
+
aitNavBarType: "partner"
|
|
105
106
|
}
|
|
106
107
|
};
|
|
107
108
|
function generateDeviceId() {
|
|
@@ -707,6 +708,22 @@ const PANEL_STYLES = `
|
|
|
707
708
|
.ait-navbar-back { padding: 0 8px; font-size: 24px; }
|
|
708
709
|
.ait-navbar-divider { width: 1px; height: 16px; background: rgba(0, 0, 0, 0.15); }
|
|
709
710
|
|
|
711
|
+
/* Game variant: 투명 배경, 우측 actions만 — 풀스크린 게임 캔버스를 가리지 않는다 */
|
|
712
|
+
.ait-navbar.ait-navbar-game {
|
|
713
|
+
background: transparent;
|
|
714
|
+
backdrop-filter: none;
|
|
715
|
+
justify-content: flex-end;
|
|
716
|
+
color: #fff;
|
|
717
|
+
}
|
|
718
|
+
.ait-navbar.ait-navbar-game .ait-navbar-actions {
|
|
719
|
+
background: rgba(0, 0, 0, 0.35);
|
|
720
|
+
color: #fff;
|
|
721
|
+
}
|
|
722
|
+
.ait-navbar.ait-navbar-game .ait-navbar-divider {
|
|
723
|
+
background: rgba(255, 255, 255, 0.3);
|
|
724
|
+
}
|
|
725
|
+
.ait-navbar.ait-navbar-game .ait-navbar-btn:hover { color: #8ab4ff; }
|
|
726
|
+
|
|
710
727
|
@media (max-width: 720px) {
|
|
711
728
|
.ait-panel.open {
|
|
712
729
|
position: fixed;
|
|
@@ -1373,7 +1390,119 @@ function renderEventsTab() {
|
|
|
1373
1390
|
return container;
|
|
1374
1391
|
}
|
|
1375
1392
|
//#endregion
|
|
1393
|
+
//#region src/mock/iap/index.ts
|
|
1394
|
+
/**
|
|
1395
|
+
* IAP (인앱결제) mock
|
|
1396
|
+
*/
|
|
1397
|
+
let orderCounter = 0;
|
|
1398
|
+
function generateOrderId() {
|
|
1399
|
+
return `mock-order-${++orderCounter}-${Date.now()}`;
|
|
1400
|
+
}
|
|
1401
|
+
function buildOrderResult(sku) {
|
|
1402
|
+
const product = aitState.state.iap.products.find((p) => p.sku === sku);
|
|
1403
|
+
const amountStr = product?.displayAmount?.replace(/[^0-9]/g, "") ?? "1000";
|
|
1404
|
+
return {
|
|
1405
|
+
orderId: generateOrderId(),
|
|
1406
|
+
displayName: product?.displayName ?? "Mock Product",
|
|
1407
|
+
displayAmount: product?.displayAmount ?? "1,000원",
|
|
1408
|
+
amount: parseInt(amountStr, 10) || 1e3,
|
|
1409
|
+
currency: "KRW",
|
|
1410
|
+
fraction: 0,
|
|
1411
|
+
miniAppIconUrl: product?.iconUrl || null
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
async function handlePurchase(sku, processProductGrant, onEvent, onError) {
|
|
1415
|
+
const nextResult = aitState.state.iap.nextResult;
|
|
1416
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
1417
|
+
if (nextResult !== "success") {
|
|
1418
|
+
onError({ code: nextResult });
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
const result = buildOrderResult(sku);
|
|
1422
|
+
try {
|
|
1423
|
+
if (!await processProductGrant({ orderId: result.orderId })) {
|
|
1424
|
+
onError({ code: "PRODUCT_NOT_GRANTED_BY_PARTNER" });
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
} catch (e) {
|
|
1428
|
+
onError(e);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
aitState.patch("iap", { completedOrders: [...aitState.state.iap.completedOrders, {
|
|
1432
|
+
orderId: result.orderId,
|
|
1433
|
+
sku,
|
|
1434
|
+
status: "COMPLETED",
|
|
1435
|
+
date: (/* @__PURE__ */ new Date()).toISOString()
|
|
1436
|
+
}] });
|
|
1437
|
+
await onEvent({
|
|
1438
|
+
type: "success",
|
|
1439
|
+
data: result
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
const IAP = createMockProxy("IAP", {
|
|
1443
|
+
createOneTimePurchaseOrder(params) {
|
|
1444
|
+
handlePurchase(params.options.sku ?? params.options.productId ?? "", params.options.processProductGrant, params.onEvent, params.onError).catch((e) => console.error("[@ait-co/devtools] IAP unexpected error:", e));
|
|
1445
|
+
return () => {};
|
|
1446
|
+
},
|
|
1447
|
+
createSubscriptionPurchaseOrder(params) {
|
|
1448
|
+
handlePurchase(params.options.sku, params.options.processProductGrant, params.onEvent, params.onError).catch((e) => console.error("[@ait-co/devtools] IAP unexpected error:", e));
|
|
1449
|
+
return () => {};
|
|
1450
|
+
},
|
|
1451
|
+
async getProductItemList() {
|
|
1452
|
+
return { products: aitState.state.iap.products.map((p) => ({
|
|
1453
|
+
...p,
|
|
1454
|
+
...p.type === "SUBSCRIPTION" ? { renewalCycle: p.renewalCycle ?? "MONTHLY" } : {}
|
|
1455
|
+
})) };
|
|
1456
|
+
},
|
|
1457
|
+
async getPendingOrders() {
|
|
1458
|
+
return { orders: [...aitState.state.iap.pendingOrders] };
|
|
1459
|
+
},
|
|
1460
|
+
async getCompletedOrRefundedOrders() {
|
|
1461
|
+
return {
|
|
1462
|
+
hasNext: false,
|
|
1463
|
+
nextKey: null,
|
|
1464
|
+
orders: [...aitState.state.iap.completedOrders]
|
|
1465
|
+
};
|
|
1466
|
+
},
|
|
1467
|
+
async completeProductGrant(args) {
|
|
1468
|
+
const idx = aitState.state.iap.pendingOrders.findIndex((o) => o.orderId === args.params.orderId);
|
|
1469
|
+
if (idx !== -1) {
|
|
1470
|
+
const order = aitState.state.iap.pendingOrders[idx];
|
|
1471
|
+
const pendingOrders = aitState.state.iap.pendingOrders.filter((_, i) => i !== idx);
|
|
1472
|
+
const completedOrders = [...aitState.state.iap.completedOrders, {
|
|
1473
|
+
orderId: order.orderId,
|
|
1474
|
+
sku: order.sku,
|
|
1475
|
+
status: "COMPLETED",
|
|
1476
|
+
date: (/* @__PURE__ */ new Date()).toISOString()
|
|
1477
|
+
}];
|
|
1478
|
+
aitState.patch("iap", {
|
|
1479
|
+
pendingOrders,
|
|
1480
|
+
completedOrders
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
return true;
|
|
1484
|
+
},
|
|
1485
|
+
async getSubscriptionInfo(_args) {
|
|
1486
|
+
return { subscription: {
|
|
1487
|
+
catalogId: 1,
|
|
1488
|
+
status: "ACTIVE",
|
|
1489
|
+
expiresAt: new Date(Date.now() + 720 * 60 * 60 * 1e3).toISOString(),
|
|
1490
|
+
isAutoRenew: true,
|
|
1491
|
+
gracePeriodExpiresAt: null,
|
|
1492
|
+
isAccessible: true
|
|
1493
|
+
} };
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
//#endregion
|
|
1376
1497
|
//#region src/panel/tabs/iap.ts
|
|
1498
|
+
function formatTimestamp(iso) {
|
|
1499
|
+
const d = new Date(iso);
|
|
1500
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
1501
|
+
return d.toLocaleTimeString();
|
|
1502
|
+
}
|
|
1503
|
+
function shortOrderId(orderId) {
|
|
1504
|
+
return orderId.length > 12 ? `…${orderId.slice(-10)}` : orderId;
|
|
1505
|
+
}
|
|
1377
1506
|
function renderIapTab() {
|
|
1378
1507
|
const s = aitState.state;
|
|
1379
1508
|
const disabled = !s.panelEditable;
|
|
@@ -1388,11 +1517,26 @@ function renderIapTab() {
|
|
|
1388
1517
|
"INTERNAL_ERROR"
|
|
1389
1518
|
];
|
|
1390
1519
|
if (disabled) container.appendChild(monitoringNotice());
|
|
1520
|
+
const pendingOrders = s.iap.pendingOrders;
|
|
1521
|
+
const pendingSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, `Pending Orders (${pendingOrders.length})`));
|
|
1522
|
+
if (pendingOrders.length === 0) pendingSection.appendChild(h("div", { className: "ait-log-entry" }, "(no pending orders)"));
|
|
1523
|
+
else for (const o of pendingOrders) {
|
|
1524
|
+
const completeBtn = h("button", { className: "ait-btn ait-btn-sm" }, "Complete");
|
|
1525
|
+
if (disabled) completeBtn.disabled = true;
|
|
1526
|
+
completeBtn.addEventListener("click", () => {
|
|
1527
|
+
IAP.completeProductGrant({ params: { orderId: o.orderId } }).catch((err) => console.error("[@ait-co/devtools] completeProductGrant error:", err));
|
|
1528
|
+
});
|
|
1529
|
+
pendingSection.appendChild(h("div", { className: "ait-log-entry" }, h("span", { className: "ait-log-type" }, "PENDING"), `${o.sku} (${shortOrderId(o.orderId)}) · ${formatTimestamp(o.paymentCompletedDate)} `, completeBtn));
|
|
1530
|
+
}
|
|
1531
|
+
const completedOrders = s.iap.completedOrders;
|
|
1532
|
+
const completedSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, `Completed Orders (${completedOrders.length})`));
|
|
1533
|
+
if (completedOrders.length === 0) completedSection.appendChild(h("div", { className: "ait-log-entry" }, "(no completed orders)"));
|
|
1534
|
+
else for (const o of completedOrders) completedSection.appendChild(h("div", { className: "ait-log-entry" }, h("span", { className: "ait-log-type" }, o.status), `${o.sku} (${shortOrderId(o.orderId)}) · ${formatTimestamp(o.date)}`));
|
|
1391
1535
|
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "IAP Simulator"), selectRow("Next Purchase Result", results, s.iap.nextResult, (v) => {
|
|
1392
1536
|
aitState.patch("iap", { nextResult: v });
|
|
1393
1537
|
}, disabled)), h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "TossPay"), selectRow("Next Payment Result", ["success", "fail"], s.payment.nextResult, (v) => {
|
|
1394
1538
|
aitState.patch("payment", { nextResult: v });
|
|
1395
|
-
}, disabled)),
|
|
1539
|
+
}, disabled)), pendingSection, completedSection);
|
|
1396
1540
|
return container;
|
|
1397
1541
|
}
|
|
1398
1542
|
//#endregion
|
|
@@ -1755,7 +1899,11 @@ function removeNavBarElement() {
|
|
|
1755
1899
|
}
|
|
1756
1900
|
/**
|
|
1757
1901
|
* Apps in Toss host nav bar 렌더. OS status bar 아래에 48px 높이로 쌓인다.
|
|
1758
|
-
*
|
|
1902
|
+
*
|
|
1903
|
+
* 변형(SDK `webViewProps.type`과 의미 일치):
|
|
1904
|
+
* - `partner` (기본): 흰 배경, 좌측 뒤로가기(‹), 앱 아이콘 + 이름(`brand.displayName`),
|
|
1905
|
+
* 우측 `⋯` + 구분선 + `×`.
|
|
1906
|
+
* - `game`: 투명 배경, 게임 캔버스를 가리지 않도록 우측 `⋯` + 구분선 + `×`만.
|
|
1759
1907
|
*
|
|
1760
1908
|
* `env(safe-area-inset-top)`에는 이 높이가 포함되지 않으므로 (공식 SDK 확인),
|
|
1761
1909
|
* 오버레이는 preset.safeAreaTop만큼 아래로 내려서 그린다.
|
|
@@ -1763,23 +1911,14 @@ function removeNavBarElement() {
|
|
|
1763
1911
|
* 뒤로가기 버튼은 `__ait:backEvent`를 트리거하고, X 버튼은 `closeView()`를 호출한다.
|
|
1764
1912
|
* 실제 SDK 이벤트 플러밍을 한 곳에서 검증할 수 있다.
|
|
1765
1913
|
*/
|
|
1766
|
-
function renderNavBar(preset, displayName) {
|
|
1914
|
+
function renderNavBar(preset, displayName, type) {
|
|
1767
1915
|
removeNavBarElement();
|
|
1768
1916
|
const el = h("div", {
|
|
1769
1917
|
id: NAV_BAR_ELEMENT_ID,
|
|
1770
|
-
className:
|
|
1918
|
+
className: `ait-navbar ait-navbar-${type}`,
|
|
1771
1919
|
"aria-hidden": "true"
|
|
1772
1920
|
});
|
|
1773
1921
|
el.style.top = `${preset.safeAreaTop}px`;
|
|
1774
|
-
const backBtn = h("button", {
|
|
1775
|
-
className: "ait-navbar-btn ait-navbar-back",
|
|
1776
|
-
type: "button",
|
|
1777
|
-
"aria-label": "Back"
|
|
1778
|
-
});
|
|
1779
|
-
backBtn.textContent = "‹";
|
|
1780
|
-
backBtn.addEventListener("click", () => {
|
|
1781
|
-
aitState.trigger("backEvent");
|
|
1782
|
-
});
|
|
1783
1922
|
const moreBtn = h("button", {
|
|
1784
1923
|
className: "ait-navbar-btn",
|
|
1785
1924
|
type: "button",
|
|
@@ -1795,9 +1934,22 @@ function renderNavBar(preset, displayName) {
|
|
|
1795
1934
|
closeBtn.addEventListener("click", () => {
|
|
1796
1935
|
closeView().catch((err) => console.error("[@ait-co/devtools] navbar close failed:", err));
|
|
1797
1936
|
});
|
|
1798
|
-
const
|
|
1799
|
-
|
|
1800
|
-
|
|
1937
|
+
const actions = h("div", { className: "ait-navbar-actions" }, moreBtn, h("span", { className: "ait-navbar-divider" }), closeBtn);
|
|
1938
|
+
if (type === "game") el.append(actions);
|
|
1939
|
+
else {
|
|
1940
|
+
const backBtn = h("button", {
|
|
1941
|
+
className: "ait-navbar-btn ait-navbar-back",
|
|
1942
|
+
type: "button",
|
|
1943
|
+
"aria-label": "Back"
|
|
1944
|
+
});
|
|
1945
|
+
backBtn.textContent = "‹";
|
|
1946
|
+
backBtn.addEventListener("click", () => {
|
|
1947
|
+
aitState.trigger("backEvent");
|
|
1948
|
+
});
|
|
1949
|
+
const nameSpan = h("span", { className: "ait-navbar-name" });
|
|
1950
|
+
nameSpan.textContent = displayName;
|
|
1951
|
+
el.append(backBtn, h("div", { className: "ait-navbar-title" }, h("span", { className: "ait-navbar-icon" }), nameSpan), actions);
|
|
1952
|
+
}
|
|
1801
1953
|
document.body.appendChild(el);
|
|
1802
1954
|
}
|
|
1803
1955
|
/**
|
|
@@ -1869,7 +2021,7 @@ function applyViewport(state) {
|
|
|
1869
2021
|
else removeNotchElement();
|
|
1870
2022
|
if (preset && state.frame && !landscape && preset.safeAreaBottom > 0) renderHomeIndicator();
|
|
1871
2023
|
else removeHomeIndicator();
|
|
1872
|
-
if (preset && state.aitNavBar && !landscape) renderNavBar(preset, aitState.state.brand.displayName);
|
|
2024
|
+
if (preset && state.aitNavBar && !landscape) renderNavBar(preset, aitState.state.brand.displayName, state.aitNavBarType);
|
|
1873
2025
|
else removeNavBarElement();
|
|
1874
2026
|
}
|
|
1875
2027
|
function isViewportPresetId(v) {
|
|
@@ -1916,6 +2068,7 @@ function loadViewportFromStorage() {
|
|
|
1916
2068
|
if (isValidCustomDimension(obj.customHeight)) next.customHeight = obj.customHeight;
|
|
1917
2069
|
if (typeof obj.frame === "boolean") next.frame = obj.frame;
|
|
1918
2070
|
if (typeof obj.aitNavBar === "boolean") next.aitNavBar = obj.aitNavBar;
|
|
2071
|
+
if (obj.aitNavBarType === "partner" || obj.aitNavBarType === "game") next.aitNavBarType = obj.aitNavBarType;
|
|
1919
2072
|
return next;
|
|
1920
2073
|
} catch {
|
|
1921
2074
|
return null;
|
|
@@ -2063,6 +2216,16 @@ function renderViewportTab() {
|
|
|
2063
2216
|
navBarCheckbox.addEventListener("change", () => {
|
|
2064
2217
|
aitState.patch("viewport", { aitNavBar: navBarCheckbox.checked });
|
|
2065
2218
|
});
|
|
2219
|
+
const navBarTypeSelect = h("select", { className: "ait-select" });
|
|
2220
|
+
if (disabled || !vp.aitNavBar) navBarTypeSelect.disabled = true;
|
|
2221
|
+
for (const opt of ["partner", "game"]) {
|
|
2222
|
+
const option = h("option", { value: opt }, opt);
|
|
2223
|
+
if (opt === vp.aitNavBarType) option.selected = true;
|
|
2224
|
+
navBarTypeSelect.appendChild(option);
|
|
2225
|
+
}
|
|
2226
|
+
navBarTypeSelect.addEventListener("change", () => {
|
|
2227
|
+
aitState.patch("viewport", { aitNavBarType: navBarTypeSelect.value });
|
|
2228
|
+
});
|
|
2066
2229
|
const size = resolveViewportSize(vp);
|
|
2067
2230
|
const statusEl = h("div", { className: "ait-section" });
|
|
2068
2231
|
if (vp.preset === "none" || size.width === 0) statusEl.appendChild(h("div", { style: "color:#888;font-size:11px" }, "No viewport constraint — body fills the window."));
|
|
@@ -2080,7 +2243,7 @@ function renderViewportTab() {
|
|
|
2080
2243
|
const insets = computeSafeAreaInsets(preset, landscape, vp.landscapeSide);
|
|
2081
2244
|
rows.push(h("div", { className: "ait-status-row" }, h("span", {}, "Safe area"), h("span", { className: "ait-status-value" }, `T${insets.top} R${insets.right} B${insets.bottom} L${insets.left}`)));
|
|
2082
2245
|
}
|
|
2083
|
-
if (vp.aitNavBar && !landscape) rows.push(h("div", { className: "ait-status-row" }, h("span", {}, "AIT nav bar"), h("span", { className: "ait-status-value" }, `48px (excl. SafeArea)`)));
|
|
2246
|
+
if (vp.aitNavBar && !landscape) rows.push(h("div", { className: "ait-status-row" }, h("span", {}, "AIT nav bar"), h("span", { className: "ait-status-value" }, `48px (excl. SafeArea) · ${vp.aitNavBarType}`)));
|
|
2084
2247
|
for (const row of rows) statusEl.appendChild(row);
|
|
2085
2248
|
}
|
|
2086
2249
|
const deviceSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "Device"), h("div", { className: "ait-row" }, h("label", {}, "Preset"), presetSelect), h("div", { className: "ait-row" }, h("label", {}, "Orientation"), orientationSelect));
|
|
@@ -2088,7 +2251,7 @@ function renderViewportTab() {
|
|
|
2088
2251
|
const notch = getPreset(vp.preset).notch;
|
|
2089
2252
|
if (notch === "notch" || notch === "dynamic-island") deviceSection.appendChild(h("div", { className: "ait-row" }, h("label", {}, "Notch side"), landscapeSideSelect));
|
|
2090
2253
|
}
|
|
2091
|
-
container.append(deviceSection, customRow, h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "Appearance"), h("div", { className: "ait-row" }, h("label", {}, "Show frame"), frameCheckbox), h("div", { className: "ait-row" }, h("label", {}, "Show Apps in Toss nav bar"), navBarCheckbox)), statusEl);
|
|
2254
|
+
container.append(deviceSection, customRow, h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "Appearance"), h("div", { className: "ait-row" }, h("label", {}, "Show frame"), frameCheckbox), h("div", { className: "ait-row" }, h("label", {}, "Show Apps in Toss nav bar"), navBarCheckbox), h("div", { className: "ait-row" }, h("label", {}, "Nav bar type"), navBarTypeSelect)), statusEl);
|
|
2092
2255
|
return container;
|
|
2093
2256
|
}
|
|
2094
2257
|
//#endregion
|
|
@@ -2339,7 +2502,7 @@ function mount() {
|
|
|
2339
2502
|
mockBadge.textContent = aitState.state.panelEditable ? "EDIT" : "READ-ONLY";
|
|
2340
2503
|
refreshPanel();
|
|
2341
2504
|
});
|
|
2342
|
-
const headerRight = h("span", { style: "display:flex;align-items:center;gap:6px" }, mockBadge, h("span", { style: "font-size:11px;color:#666;font-weight:400" }, `v0.1.
|
|
2505
|
+
const headerRight = h("span", { style: "display:flex;align-items:center;gap:6px" }, mockBadge, h("span", { style: "font-size:11px;color:#666;font-weight:400" }, `v0.1.7`), closeBtn);
|
|
2343
2506
|
const header = h("div", { className: "ait-panel-header" }, h("span", {}, "AIT DevTools"), headerRight);
|
|
2344
2507
|
tabsEl = h("div", { className: "ait-panel-tabs" });
|
|
2345
2508
|
for (const tab of TABS) {
|
|
@@ -2378,7 +2541,7 @@ function mount() {
|
|
|
2378
2541
|
});
|
|
2379
2542
|
aitState.subscribe(() => {
|
|
2380
2543
|
try {
|
|
2381
|
-
if (isOpen && (currentTab === "analytics" || currentTab === "storage" || currentTab === "device" || currentTab === "viewport")) refreshPanel();
|
|
2544
|
+
if (isOpen && (currentTab === "analytics" || currentTab === "storage" || currentTab === "device" || currentTab === "viewport" || currentTab === "iap")) refreshPanel();
|
|
2382
2545
|
} catch (err) {
|
|
2383
2546
|
console.error("[@ait-co/devtools] Error in subscribe callback:", err);
|
|
2384
2547
|
}
|