@ait-co/devtools 0.0.1 → 0.0.3
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 +439 -135
- package/dist/mock/index.d.ts +479 -426
- package/dist/mock/index.d.ts.map +1 -0
- package/dist/mock/index.js +1036 -646
- package/dist/mock/index.js.map +1 -1
- package/dist/panel/index.d.ts +4 -2
- package/dist/panel/index.d.ts.map +1 -0
- package/dist/panel/index.js +1415 -317
- package/dist/panel/index.js.map +1 -1
- package/dist/unplugin/index.cjs +68 -72
- package/dist/unplugin/index.cjs.map +1 -1
- package/dist/unplugin/index.d.cts +1395 -0
- package/dist/unplugin/index.d.cts.map +1 -0
- package/dist/unplugin/index.d.ts +1395 -0
- package/dist/unplugin/index.d.ts.map +1 -0
- package/dist/unplugin/index.js +59 -43
- package/dist/unplugin/index.js.map +1 -1
- package/package.json +27 -16
- package/dist/chunk-YYIIG3JT.js +0 -146
- package/dist/chunk-YYIIG3JT.js.map +0 -1
package/dist/mock/index.js
CHANGED
|
@@ -1,696 +1,1086 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
//#region src/mock/state.ts
|
|
2
|
+
const DEFAULT_STATE = {
|
|
3
|
+
platform: "ios",
|
|
4
|
+
environment: "sandbox",
|
|
5
|
+
appVersion: "5.240.0",
|
|
6
|
+
locale: "ko-KR",
|
|
7
|
+
schemeUri: "/",
|
|
8
|
+
groupId: "mock-group-id",
|
|
9
|
+
deploymentId: "mock-deployment-id",
|
|
10
|
+
deviceId: "",
|
|
11
|
+
brand: {
|
|
12
|
+
displayName: "Mock App",
|
|
13
|
+
icon: "",
|
|
14
|
+
primaryColor: "#3182F6"
|
|
15
|
+
},
|
|
16
|
+
networkStatus: "WIFI",
|
|
17
|
+
permissions: {
|
|
18
|
+
clipboard: "allowed",
|
|
19
|
+
contacts: "allowed",
|
|
20
|
+
photos: "allowed",
|
|
21
|
+
geolocation: "allowed",
|
|
22
|
+
camera: "allowed",
|
|
23
|
+
microphone: "notDetermined"
|
|
24
|
+
},
|
|
25
|
+
location: {
|
|
26
|
+
coords: {
|
|
27
|
+
latitude: 37.5665,
|
|
28
|
+
longitude: 126.978,
|
|
29
|
+
altitude: 0,
|
|
30
|
+
accuracy: 10,
|
|
31
|
+
altitudeAccuracy: 0,
|
|
32
|
+
heading: 0
|
|
33
|
+
},
|
|
34
|
+
timestamp: Date.now(),
|
|
35
|
+
accessLocation: "FINE"
|
|
36
|
+
},
|
|
37
|
+
safeAreaInsets: {
|
|
38
|
+
top: 47,
|
|
39
|
+
bottom: 34,
|
|
40
|
+
left: 0,
|
|
41
|
+
right: 0
|
|
42
|
+
},
|
|
43
|
+
contacts: [{
|
|
44
|
+
name: "홍길동",
|
|
45
|
+
phoneNumber: "010-1234-5678"
|
|
46
|
+
}, {
|
|
47
|
+
name: "김토스",
|
|
48
|
+
phoneNumber: "010-9876-5432"
|
|
49
|
+
}],
|
|
50
|
+
iap: {
|
|
51
|
+
products: [{
|
|
52
|
+
sku: "mock-gem-100",
|
|
53
|
+
type: "CONSUMABLE",
|
|
54
|
+
displayName: "보석 100개",
|
|
55
|
+
displayAmount: "1,000원",
|
|
56
|
+
iconUrl: "",
|
|
57
|
+
description: "게임에서 사용할 수 있는 보석 100개"
|
|
58
|
+
}],
|
|
59
|
+
nextResult: "success",
|
|
60
|
+
pendingOrders: [],
|
|
61
|
+
completedOrders: []
|
|
62
|
+
},
|
|
63
|
+
payment: {
|
|
64
|
+
nextResult: "success",
|
|
65
|
+
failReason: ""
|
|
66
|
+
},
|
|
67
|
+
auth: {
|
|
68
|
+
isLoggedIn: true,
|
|
69
|
+
isTossLoginIntegrated: true,
|
|
70
|
+
userKeyHash: "mock-user-hash-abc123"
|
|
71
|
+
},
|
|
72
|
+
ads: {
|
|
73
|
+
isLoaded: false,
|
|
74
|
+
nextEvent: "loaded"
|
|
75
|
+
},
|
|
76
|
+
game: {
|
|
77
|
+
profile: {
|
|
78
|
+
nickname: "MockPlayer",
|
|
79
|
+
profileImageUri: ""
|
|
80
|
+
},
|
|
81
|
+
leaderboardScores: []
|
|
82
|
+
},
|
|
83
|
+
analyticsLog: [],
|
|
84
|
+
deviceModes: {
|
|
85
|
+
camera: "mock",
|
|
86
|
+
photos: "mock",
|
|
87
|
+
location: "mock",
|
|
88
|
+
network: "mock",
|
|
89
|
+
clipboard: "web"
|
|
90
|
+
},
|
|
91
|
+
mockData: {
|
|
92
|
+
images: [],
|
|
93
|
+
clipboardText: ""
|
|
94
|
+
},
|
|
95
|
+
panelEditable: true
|
|
96
|
+
};
|
|
97
|
+
function generateDeviceId() {
|
|
98
|
+
const stored = localStorage.getItem("__ait_device_id");
|
|
99
|
+
if (stored) return stored;
|
|
100
|
+
const id = crypto.randomUUID();
|
|
101
|
+
localStorage.setItem("__ait_device_id", id);
|
|
102
|
+
return id;
|
|
103
|
+
}
|
|
104
|
+
var AitStateManager = class {
|
|
105
|
+
_state;
|
|
106
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
107
|
+
constructor() {
|
|
108
|
+
this._state = structuredClone(DEFAULT_STATE);
|
|
109
|
+
try {
|
|
110
|
+
this._state.deviceId = generateDeviceId();
|
|
111
|
+
} catch {
|
|
112
|
+
this._state.deviceId = "mock-device-" + Math.random().toString(36).slice(2);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
get state() {
|
|
116
|
+
return this._state;
|
|
117
|
+
}
|
|
118
|
+
update(partial) {
|
|
119
|
+
this._state = {
|
|
120
|
+
...this._state,
|
|
121
|
+
...partial
|
|
122
|
+
};
|
|
123
|
+
this._notify();
|
|
124
|
+
}
|
|
125
|
+
/** 중첩 객체 업데이트용 */
|
|
126
|
+
patch(key, partial) {
|
|
127
|
+
const current = this._state[key];
|
|
128
|
+
if (typeof current === "object" && current !== null && !Array.isArray(current)) this._state = {
|
|
129
|
+
...this._state,
|
|
130
|
+
[key]: {
|
|
131
|
+
...current,
|
|
132
|
+
...partial
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
else this._state = {
|
|
136
|
+
...this._state,
|
|
137
|
+
[key]: partial
|
|
138
|
+
};
|
|
139
|
+
this._notify();
|
|
140
|
+
}
|
|
141
|
+
subscribe(listener) {
|
|
142
|
+
this._listeners.add(listener);
|
|
143
|
+
return () => this._listeners.delete(listener);
|
|
144
|
+
}
|
|
145
|
+
/** 분석 로그 추가 */
|
|
146
|
+
logAnalytics(entry) {
|
|
147
|
+
this._state = {
|
|
148
|
+
...this._state,
|
|
149
|
+
analyticsLog: [...this._state.analyticsLog, {
|
|
150
|
+
...entry,
|
|
151
|
+
timestamp: Date.now()
|
|
152
|
+
}]
|
|
153
|
+
};
|
|
154
|
+
this._notify();
|
|
155
|
+
}
|
|
156
|
+
/** 이벤트 트리거 (backEvent, homeEvent 등) */
|
|
157
|
+
trigger(event) {
|
|
158
|
+
window.dispatchEvent(new CustomEvent(`__ait:${event}`));
|
|
159
|
+
}
|
|
160
|
+
reset() {
|
|
161
|
+
const deviceId = this._state.deviceId;
|
|
162
|
+
this._state = {
|
|
163
|
+
...structuredClone(DEFAULT_STATE),
|
|
164
|
+
deviceId
|
|
165
|
+
};
|
|
166
|
+
this._notify();
|
|
167
|
+
}
|
|
168
|
+
_notify() {
|
|
169
|
+
for (const listener of this._listeners) listener();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const aitState = new AitStateManager();
|
|
173
|
+
if (typeof window !== "undefined") window.__ait = aitState;
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/mock/auth/index.ts
|
|
176
|
+
/**
|
|
177
|
+
* 인증/로그인 mock
|
|
178
|
+
*/
|
|
179
|
+
async function appLogin() {
|
|
180
|
+
return {
|
|
181
|
+
authorizationCode: `mock-auth-${crypto.randomUUID()}`,
|
|
182
|
+
referrer: aitState.state.environment === "toss" ? "DEFAULT" : "SANDBOX"
|
|
183
|
+
};
|
|
11
184
|
}
|
|
12
|
-
function getIsTossLoginIntegratedService() {
|
|
13
|
-
|
|
185
|
+
async function getIsTossLoginIntegratedService() {
|
|
186
|
+
return aitState.state.auth.isTossLoginIntegrated;
|
|
14
187
|
}
|
|
15
|
-
function getUserKeyForGame() {
|
|
16
|
-
|
|
17
|
-
|
|
188
|
+
async function getUserKeyForGame() {
|
|
189
|
+
if (!aitState.state.auth.userKeyHash) return void 0;
|
|
190
|
+
return {
|
|
191
|
+
hash: aitState.state.auth.userKeyHash,
|
|
192
|
+
type: "HASH"
|
|
193
|
+
};
|
|
18
194
|
}
|
|
19
|
-
function appsInTossSignTossCert(_params) {
|
|
20
|
-
|
|
21
|
-
return Promise.resolve();
|
|
195
|
+
async function appsInTossSignTossCert(_params) {
|
|
196
|
+
console.log("[@ait-co/devtools] appsInTossSignTossCert called (no-op in mock)");
|
|
22
197
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/mock/device/_helpers.ts
|
|
200
|
+
/**
|
|
201
|
+
* 디바이스 모듈 내부 공유 헬퍼
|
|
202
|
+
*/
|
|
203
|
+
function generatePlaceholderImage(width, height, text, color) {
|
|
204
|
+
const canvas = document.createElement("canvas");
|
|
205
|
+
canvas.width = width;
|
|
206
|
+
canvas.height = height;
|
|
207
|
+
const ctx = canvas.getContext("2d");
|
|
208
|
+
if (!ctx) {
|
|
209
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><rect fill="${color}" width="${width}" height="${height}"/><text x="50%" y="50%" fill="white" font-size="16" text-anchor="middle" dominant-baseline="middle">${text}</text></svg>`;
|
|
210
|
+
return "data:image/svg+xml;base64," + btoa(svg);
|
|
211
|
+
}
|
|
212
|
+
ctx.fillStyle = color;
|
|
213
|
+
ctx.fillRect(0, 0, width, height);
|
|
214
|
+
ctx.fillStyle = "white";
|
|
215
|
+
ctx.font = "16px sans-serif";
|
|
216
|
+
ctx.textAlign = "center";
|
|
217
|
+
ctx.textBaseline = "middle";
|
|
218
|
+
ctx.fillText(text, width / 2, height / 2);
|
|
219
|
+
return canvas.toDataURL("image/png");
|
|
220
|
+
}
|
|
221
|
+
const DEFAULT_PLACEHOLDERS = [
|
|
222
|
+
{
|
|
223
|
+
text: "Mock Photo 1",
|
|
224
|
+
color: "#3182F6"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
text: "Mock Photo 2",
|
|
228
|
+
color: "#27ae60"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
text: "Mock Photo 3",
|
|
232
|
+
color: "#e67e22"
|
|
233
|
+
}
|
|
234
|
+
];
|
|
235
|
+
let cachedPlaceholders = null;
|
|
236
|
+
function getDefaultPlaceholderImages() {
|
|
237
|
+
if (!cachedPlaceholders) cachedPlaceholders = DEFAULT_PLACEHOLDERS.map((p) => generatePlaceholderImage(320, 240, p.text, p.color));
|
|
238
|
+
return [...cachedPlaceholders];
|
|
239
|
+
}
|
|
240
|
+
/** @internal device 모듈 내부 전용 */
|
|
241
|
+
function getMockImages() {
|
|
242
|
+
const images = aitState.state.mockData.images;
|
|
243
|
+
if (images.length > 0) return images;
|
|
244
|
+
return getDefaultPlaceholderImages();
|
|
245
|
+
}
|
|
246
|
+
const PROMPT_TIMEOUT_MS = 3e4;
|
|
247
|
+
/** @internal device 모듈 내부 전용 */
|
|
248
|
+
function waitForPromptResponse(type) {
|
|
249
|
+
return new Promise((resolve, reject) => {
|
|
250
|
+
const eventName = "__ait:prompt-response:" + type;
|
|
251
|
+
const cancelName = "__ait:prompt-cancel";
|
|
252
|
+
function cleanup() {
|
|
253
|
+
clearTimeout(timer);
|
|
254
|
+
window.removeEventListener(eventName, handler);
|
|
255
|
+
window.removeEventListener(cancelName, cancelHandler);
|
|
256
|
+
}
|
|
257
|
+
const timer = setTimeout(() => {
|
|
258
|
+
cleanup();
|
|
259
|
+
const hint = !!document.querySelector(".ait-panel") ? "Please provide input via the DevTools panel." : "Is @ait-co/devtools/panel imported?";
|
|
260
|
+
reject(/* @__PURE__ */ new Error(`[@ait-co/devtools] Prompt timeout for "${type}" after ${PROMPT_TIMEOUT_MS / 1e3}s. ${hint}`));
|
|
261
|
+
}, PROMPT_TIMEOUT_MS);
|
|
262
|
+
const handler = (e) => {
|
|
263
|
+
cleanup();
|
|
264
|
+
resolve(e.detail);
|
|
265
|
+
};
|
|
266
|
+
const cancelHandler = () => {
|
|
267
|
+
cleanup();
|
|
268
|
+
reject(/* @__PURE__ */ new Error(`[@ait-co/devtools] Prompt cancelled for "${type}"`));
|
|
269
|
+
};
|
|
270
|
+
window.addEventListener(eventName, handler);
|
|
271
|
+
window.addEventListener(cancelName, cancelHandler);
|
|
272
|
+
window.dispatchEvent(new CustomEvent("__ait:prompt-request", { detail: { type } }));
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/mock/proxy.ts
|
|
277
|
+
/**
|
|
278
|
+
* 미구현 API용 Proxy 트립와이어.
|
|
279
|
+
*
|
|
280
|
+
* 미구현 프로퍼티에 접근하면 throw한다. 이는 "devtools에서는 멀쩡히 돌지만
|
|
281
|
+
* 실 SDK에선 실제로 동작하는" 시나리오를 차단하기 위한 의도적 선택이다.
|
|
282
|
+
* mock이 미구현인 API는 실 SDK에서는 존재할 수 있고, 사용자가 이를 인지하지
|
|
283
|
+
* 못한 채 개발을 이어가면 배포 시점에 놀라게 된다. 에러 메시지에 이슈 URL을
|
|
284
|
+
* 포함해 사용자가 mock 누락을 제보할 수 있게 한다.
|
|
285
|
+
*/
|
|
286
|
+
const ISSUES_URL = "https://github.com/apps-in-toss-community/devtools/issues";
|
|
287
|
+
function createMockProxy(moduleName, implementations) {
|
|
288
|
+
return new Proxy(implementations, { get(target, prop) {
|
|
289
|
+
if (typeof prop === "symbol") return void 0;
|
|
290
|
+
if (prop in target) return target[prop];
|
|
291
|
+
throw new Error(`[@ait-co/devtools] ${moduleName}.${prop} is not mocked. This API may exist in @apps-in-toss/web-framework, but devtools' mock does not cover it yet. Please file an issue: ${ISSUES_URL}`);
|
|
292
|
+
} });
|
|
293
|
+
}
|
|
294
|
+
//#endregion
|
|
295
|
+
//#region src/mock/device/storage.ts
|
|
296
|
+
/**
|
|
297
|
+
* Storage mock
|
|
298
|
+
* localStorage에 `__ait_storage:` prefix로 저장하여 앱 자체 localStorage와 분리
|
|
299
|
+
*/
|
|
300
|
+
const Storage = createMockProxy("Storage", {
|
|
301
|
+
getItem: async (key) => {
|
|
302
|
+
return localStorage.getItem(`__ait_storage:${key}`);
|
|
303
|
+
},
|
|
304
|
+
setItem: async (key, value) => {
|
|
305
|
+
localStorage.setItem(`__ait_storage:${key}`, value);
|
|
306
|
+
},
|
|
307
|
+
removeItem: async (key) => {
|
|
308
|
+
localStorage.removeItem(`__ait_storage:${key}`);
|
|
309
|
+
},
|
|
310
|
+
clearItems: async () => {
|
|
311
|
+
Object.keys(localStorage).filter((k) => k.startsWith("__ait_storage:")).forEach((k) => localStorage.removeItem(k));
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/mock/permissions.ts
|
|
316
|
+
/**
|
|
317
|
+
* 권한 시스템 mock
|
|
318
|
+
* 각 디바이스 API (.getPermission, .openPermissionDialog)에 부착된다.
|
|
319
|
+
*/
|
|
320
|
+
async function getPermission(name) {
|
|
321
|
+
return aitState.state.permissions[name];
|
|
322
|
+
}
|
|
323
|
+
async function openPermissionDialog(name) {
|
|
324
|
+
if (aitState.state.permissions[name] === "allowed") return "allowed";
|
|
325
|
+
aitState.patch("permissions", { [name]: "allowed" });
|
|
326
|
+
return "allowed";
|
|
327
|
+
}
|
|
328
|
+
async function requestPermission(permission) {
|
|
329
|
+
return openPermissionDialog(permission.name);
|
|
330
|
+
}
|
|
331
|
+
/** 권한이 필요한 함수에 .getPermission(), .openPermissionDialog()를 부착 */
|
|
332
|
+
function withPermission(fn, permissionName) {
|
|
333
|
+
const enhanced = fn;
|
|
334
|
+
enhanced.getPermission = () => getPermission(permissionName);
|
|
335
|
+
enhanced.openPermissionDialog = () => openPermissionDialog(permissionName);
|
|
336
|
+
return enhanced;
|
|
337
|
+
}
|
|
338
|
+
/** 권한 체크 후 denied면 에러 throw */
|
|
339
|
+
function checkPermission(name, fnName) {
|
|
340
|
+
if (aitState.state.permissions[name] === "denied") throw new Error(`[@ait-co/devtools] ${fnName}: Permission "${name}" is denied. Change it in the DevTools panel.`);
|
|
341
|
+
}
|
|
342
|
+
//#endregion
|
|
343
|
+
//#region src/mock/device/location.ts
|
|
344
|
+
/**
|
|
345
|
+
* Location mock (getCurrentLocation, startUpdateLocation)
|
|
346
|
+
* mock/web/prompt 모드 지원
|
|
347
|
+
*/
|
|
348
|
+
var Accuracy = /* @__PURE__ */ function(Accuracy) {
|
|
349
|
+
Accuracy[Accuracy["Lowest"] = 1] = "Lowest";
|
|
350
|
+
Accuracy[Accuracy["Low"] = 2] = "Low";
|
|
351
|
+
Accuracy[Accuracy["Balanced"] = 3] = "Balanced";
|
|
352
|
+
Accuracy[Accuracy["High"] = 4] = "High";
|
|
353
|
+
Accuracy[Accuracy["Highest"] = 5] = "Highest";
|
|
354
|
+
Accuracy[Accuracy["BestForNavigation"] = 6] = "BestForNavigation";
|
|
355
|
+
return Accuracy;
|
|
356
|
+
}(Accuracy || {});
|
|
357
|
+
function buildLocation() {
|
|
358
|
+
return {
|
|
359
|
+
coords: { ...aitState.state.location.coords },
|
|
360
|
+
timestamp: Date.now(),
|
|
361
|
+
accessLocation: aitState.state.location.accessLocation
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
async function getCurrentLocationMock() {
|
|
365
|
+
return buildLocation();
|
|
366
|
+
}
|
|
367
|
+
async function getCurrentLocationWeb() {
|
|
368
|
+
return new Promise((resolve) => {
|
|
369
|
+
if (!navigator.geolocation) {
|
|
370
|
+
console.warn("[@ait-co/devtools] Geolocation API not available, falling back to mock");
|
|
371
|
+
resolve(buildLocation());
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
navigator.geolocation.getCurrentPosition((pos) => {
|
|
375
|
+
resolve({
|
|
376
|
+
coords: {
|
|
377
|
+
latitude: pos.coords.latitude,
|
|
378
|
+
longitude: pos.coords.longitude,
|
|
379
|
+
altitude: pos.coords.altitude ?? 0,
|
|
380
|
+
accuracy: pos.coords.accuracy,
|
|
381
|
+
altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,
|
|
382
|
+
heading: pos.coords.heading ?? 0
|
|
383
|
+
},
|
|
384
|
+
timestamp: pos.timestamp,
|
|
385
|
+
accessLocation: "FINE"
|
|
386
|
+
});
|
|
387
|
+
}, () => {
|
|
388
|
+
console.warn("[@ait-co/devtools] Geolocation failed, falling back to mock");
|
|
389
|
+
resolve(buildLocation());
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
async function getCurrentLocationPrompt() {
|
|
394
|
+
return waitForPromptResponse("location");
|
|
395
|
+
}
|
|
396
|
+
const _getCurrentLocation = async (_options) => {
|
|
397
|
+
checkPermission("geolocation", "getCurrentLocation");
|
|
398
|
+
const mode = aitState.state.deviceModes.location;
|
|
399
|
+
if (mode === "web") return getCurrentLocationWeb();
|
|
400
|
+
if (mode === "prompt") return getCurrentLocationPrompt();
|
|
401
|
+
return getCurrentLocationMock();
|
|
402
|
+
};
|
|
403
|
+
const getCurrentLocation = withPermission(_getCurrentLocation, "geolocation");
|
|
404
|
+
function startUpdateLocationMock(eventParams) {
|
|
405
|
+
const { onEvent, options } = eventParams;
|
|
406
|
+
const interval = Math.max(options.timeInterval, 500);
|
|
407
|
+
const id = setInterval(() => {
|
|
408
|
+
const loc = buildLocation();
|
|
409
|
+
loc.coords.latitude += (Math.random() - .5) * 1e-4;
|
|
410
|
+
loc.coords.longitude += (Math.random() - .5) * 1e-4;
|
|
411
|
+
onEvent(loc);
|
|
412
|
+
}, interval);
|
|
413
|
+
return () => clearInterval(id);
|
|
414
|
+
}
|
|
415
|
+
function startUpdateLocationWeb(eventParams) {
|
|
416
|
+
const { onEvent, onError } = eventParams;
|
|
417
|
+
if (!navigator.geolocation) {
|
|
418
|
+
console.warn("[@ait-co/devtools] Geolocation API not available, falling back to mock");
|
|
419
|
+
return startUpdateLocationMock(eventParams);
|
|
420
|
+
}
|
|
421
|
+
const watchId = navigator.geolocation.watchPosition((pos) => {
|
|
422
|
+
onEvent({
|
|
423
|
+
coords: {
|
|
424
|
+
latitude: pos.coords.latitude,
|
|
425
|
+
longitude: pos.coords.longitude,
|
|
426
|
+
altitude: pos.coords.altitude ?? 0,
|
|
427
|
+
accuracy: pos.coords.accuracy,
|
|
428
|
+
altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,
|
|
429
|
+
heading: pos.coords.heading ?? 0
|
|
430
|
+
},
|
|
431
|
+
timestamp: pos.timestamp,
|
|
432
|
+
accessLocation: "FINE"
|
|
433
|
+
});
|
|
434
|
+
}, (err) => onError(err));
|
|
435
|
+
return () => navigator.geolocation.clearWatch(watchId);
|
|
436
|
+
}
|
|
437
|
+
function startUpdateLocationPrompt(eventParams) {
|
|
438
|
+
const { onEvent } = eventParams;
|
|
439
|
+
const handler = (e) => {
|
|
440
|
+
onEvent(e.detail);
|
|
441
|
+
};
|
|
442
|
+
window.addEventListener("__ait:prompt-response:location-update", handler);
|
|
443
|
+
window.dispatchEvent(new CustomEvent("__ait:prompt-request", { detail: { type: "location-update" } }));
|
|
444
|
+
return () => window.removeEventListener("__ait:prompt-response:location-update", handler);
|
|
445
|
+
}
|
|
446
|
+
const _startUpdateLocation = (eventParams) => {
|
|
447
|
+
const mode = aitState.state.deviceModes.location;
|
|
448
|
+
if (mode === "web") return startUpdateLocationWeb(eventParams);
|
|
449
|
+
if (mode === "prompt") return startUpdateLocationPrompt(eventParams);
|
|
450
|
+
return startUpdateLocationMock(eventParams);
|
|
451
|
+
};
|
|
452
|
+
const startUpdateLocation = withPermission(_startUpdateLocation, "geolocation");
|
|
453
|
+
//#endregion
|
|
454
|
+
//#region src/mock/device/camera.ts
|
|
455
|
+
/**
|
|
456
|
+
* Camera & Album Photos mock
|
|
457
|
+
* mock/web/prompt 모드 지원
|
|
458
|
+
*/
|
|
459
|
+
async function openCameraMock() {
|
|
460
|
+
const images = getMockImages();
|
|
461
|
+
return {
|
|
462
|
+
id: crypto.randomUUID(),
|
|
463
|
+
dataUri: images[0]
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
async function openCameraWeb() {
|
|
467
|
+
return new Promise((resolve, reject) => {
|
|
468
|
+
const input = document.createElement("input");
|
|
469
|
+
input.type = "file";
|
|
470
|
+
input.accept = "image/*";
|
|
471
|
+
input.capture = "environment";
|
|
472
|
+
let settled = false;
|
|
473
|
+
input.onchange = () => {
|
|
474
|
+
settled = true;
|
|
475
|
+
const file = input.files?.[0];
|
|
476
|
+
if (!file) {
|
|
477
|
+
reject(/* @__PURE__ */ new Error("No file selected"));
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const reader = new FileReader();
|
|
481
|
+
reader.onload = () => resolve({
|
|
482
|
+
id: crypto.randomUUID(),
|
|
483
|
+
dataUri: reader.result
|
|
484
|
+
});
|
|
485
|
+
reader.onerror = () => reject(/* @__PURE__ */ new Error("Failed to read file"));
|
|
486
|
+
reader.readAsDataURL(file);
|
|
487
|
+
};
|
|
488
|
+
const onFocus = () => {
|
|
489
|
+
setTimeout(() => {
|
|
490
|
+
if (!settled) reject(/* @__PURE__ */ new Error("File picker cancelled"));
|
|
491
|
+
window.removeEventListener("focus", onFocus);
|
|
492
|
+
}, 300);
|
|
493
|
+
};
|
|
494
|
+
window.addEventListener("focus", onFocus);
|
|
495
|
+
input.click();
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
async function openCameraPrompt() {
|
|
499
|
+
const dataUri = await waitForPromptResponse("camera");
|
|
500
|
+
return {
|
|
501
|
+
id: crypto.randomUUID(),
|
|
502
|
+
dataUri
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const _openCamera = async (_options) => {
|
|
506
|
+
checkPermission("camera", "openCamera");
|
|
507
|
+
const mode = aitState.state.deviceModes.camera;
|
|
508
|
+
if (mode === "web") return openCameraWeb();
|
|
509
|
+
if (mode === "prompt") return openCameraPrompt();
|
|
510
|
+
return openCameraMock();
|
|
511
|
+
};
|
|
512
|
+
const openCamera = withPermission(_openCamera, "camera");
|
|
513
|
+
async function fetchAlbumPhotosMock(maxCount) {
|
|
514
|
+
return getMockImages().slice(0, maxCount).map((dataUri) => ({
|
|
515
|
+
id: crypto.randomUUID(),
|
|
516
|
+
dataUri
|
|
517
|
+
}));
|
|
518
|
+
}
|
|
519
|
+
async function fetchAlbumPhotosWeb(maxCount) {
|
|
520
|
+
return new Promise((resolve, reject) => {
|
|
521
|
+
const input = document.createElement("input");
|
|
522
|
+
input.type = "file";
|
|
523
|
+
input.accept = "image/*";
|
|
524
|
+
input.multiple = true;
|
|
525
|
+
let settled = false;
|
|
526
|
+
input.onchange = async () => {
|
|
527
|
+
settled = true;
|
|
528
|
+
const files = Array.from(input.files ?? []).slice(0, maxCount);
|
|
529
|
+
if (files.length === 0) {
|
|
530
|
+
reject(/* @__PURE__ */ new Error("No files selected"));
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
resolve(await Promise.all(files.map((file) => new Promise((res, rej) => {
|
|
534
|
+
const reader = new FileReader();
|
|
535
|
+
reader.onload = () => res({
|
|
536
|
+
id: crypto.randomUUID(),
|
|
537
|
+
dataUri: reader.result
|
|
538
|
+
});
|
|
539
|
+
reader.onerror = () => rej(/* @__PURE__ */ new Error("Failed to read file"));
|
|
540
|
+
reader.readAsDataURL(file);
|
|
541
|
+
}))));
|
|
542
|
+
};
|
|
543
|
+
const onFocus = () => {
|
|
544
|
+
setTimeout(() => {
|
|
545
|
+
if (!settled) reject(/* @__PURE__ */ new Error("File picker cancelled"));
|
|
546
|
+
window.removeEventListener("focus", onFocus);
|
|
547
|
+
}, 300);
|
|
548
|
+
};
|
|
549
|
+
window.addEventListener("focus", onFocus);
|
|
550
|
+
input.click();
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
async function fetchAlbumPhotosPrompt(maxCount) {
|
|
554
|
+
return (await waitForPromptResponse("photos")).slice(0, maxCount).map((dataUri) => ({
|
|
555
|
+
id: crypto.randomUUID(),
|
|
556
|
+
dataUri
|
|
557
|
+
}));
|
|
558
|
+
}
|
|
559
|
+
const _fetchAlbumPhotos = async (options) => {
|
|
560
|
+
checkPermission("photos", "fetchAlbumPhotos");
|
|
561
|
+
const maxCount = options?.maxCount ?? 10;
|
|
562
|
+
const mode = aitState.state.deviceModes.photos;
|
|
563
|
+
if (mode === "web") return fetchAlbumPhotosWeb(maxCount);
|
|
564
|
+
if (mode === "prompt") return fetchAlbumPhotosPrompt(maxCount);
|
|
565
|
+
return fetchAlbumPhotosMock(maxCount);
|
|
566
|
+
};
|
|
567
|
+
const fetchAlbumPhotos = withPermission(_fetchAlbumPhotos, "photos");
|
|
568
|
+
//#endregion
|
|
569
|
+
//#region src/mock/device/clipboard.ts
|
|
570
|
+
/**
|
|
571
|
+
* Clipboard mock
|
|
572
|
+
* mock/web 모드 지원
|
|
573
|
+
*/
|
|
574
|
+
const _getClipboardText = async () => {
|
|
575
|
+
checkPermission("clipboard", "getClipboardText");
|
|
576
|
+
if (aitState.state.deviceModes.clipboard === "mock") return aitState.state.mockData.clipboardText;
|
|
577
|
+
try {
|
|
578
|
+
return await navigator.clipboard.readText();
|
|
579
|
+
} catch {
|
|
580
|
+
return "";
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
const getClipboardText = withPermission(_getClipboardText, "clipboard");
|
|
584
|
+
const _setClipboardText = async (text) => {
|
|
585
|
+
checkPermission("clipboard", "setClipboardText");
|
|
586
|
+
if (aitState.state.deviceModes.clipboard === "mock") {
|
|
587
|
+
aitState.patch("mockData", { clipboardText: text });
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
await navigator.clipboard.writeText(text);
|
|
591
|
+
};
|
|
592
|
+
const setClipboardText = withPermission(_setClipboardText, "clipboard");
|
|
593
|
+
//#endregion
|
|
594
|
+
//#region src/mock/device/contacts.ts
|
|
595
|
+
/**
|
|
596
|
+
* Contacts mock
|
|
597
|
+
*/
|
|
598
|
+
const _fetchContacts = async (options) => {
|
|
599
|
+
checkPermission("contacts", "fetchContacts");
|
|
600
|
+
let contacts = aitState.state.contacts;
|
|
601
|
+
if (options.query?.contains) {
|
|
602
|
+
const q = options.query.contains.toLowerCase();
|
|
603
|
+
contacts = contacts.filter((c) => c.name.toLowerCase().includes(q) || c.phoneNumber.includes(q));
|
|
604
|
+
}
|
|
605
|
+
const sliced = contacts.slice(options.offset, options.offset + options.size);
|
|
606
|
+
const nextOffset = options.offset + options.size;
|
|
607
|
+
return {
|
|
608
|
+
result: sliced,
|
|
609
|
+
nextOffset: nextOffset < contacts.length ? nextOffset : null,
|
|
610
|
+
done: nextOffset >= contacts.length
|
|
611
|
+
};
|
|
612
|
+
};
|
|
613
|
+
const fetchContacts = withPermission(_fetchContacts, "contacts");
|
|
614
|
+
//#endregion
|
|
615
|
+
//#region src/mock/device/haptic.ts
|
|
616
|
+
/**
|
|
617
|
+
* Haptic Feedback & saveBase64Data mock
|
|
618
|
+
*/
|
|
619
|
+
async function generateHapticFeedback(options) {
|
|
620
|
+
console.log(`[@ait-co/devtools] haptic: ${options.type}`);
|
|
621
|
+
aitState.logAnalytics({
|
|
622
|
+
type: "haptic",
|
|
623
|
+
params: { hapticType: options.type }
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
async function saveBase64Data(params) {
|
|
627
|
+
const a = document.createElement("a");
|
|
628
|
+
a.href = `data:${params.mimeType};base64,${params.data}`;
|
|
629
|
+
a.download = params.fileName;
|
|
630
|
+
a.click();
|
|
631
|
+
}
|
|
632
|
+
//#endregion
|
|
633
|
+
//#region src/mock/device/network.ts
|
|
634
|
+
/**
|
|
635
|
+
* Network Status mock (mode-aware helper)
|
|
636
|
+
* navigation 모듈에서 사용. circular dep 방지를 위해 device에 위치.
|
|
637
|
+
*/
|
|
638
|
+
/**
|
|
639
|
+
* Web mode: uses navigator.connection.effectiveType (4g/3g/2g) and navigator.onLine.
|
|
640
|
+
* Limitations: WIFI, 5G, WWAN cannot be detected via the Network Information API.
|
|
641
|
+
* Falls back to state-based value when effectiveType is unavailable.
|
|
642
|
+
*/
|
|
643
|
+
function getNetworkStatusByMode() {
|
|
644
|
+
const mode = aitState.state.deviceModes.network;
|
|
645
|
+
if (mode === "mock") return null;
|
|
646
|
+
if (mode === "web") {
|
|
647
|
+
if (!navigator.onLine) return "OFFLINE";
|
|
648
|
+
const conn = navigator.connection;
|
|
649
|
+
if (conn?.effectiveType) return {
|
|
650
|
+
"4g": "4G",
|
|
651
|
+
"3g": "3G",
|
|
652
|
+
"2g": "2G",
|
|
653
|
+
"slow-2g": "2G"
|
|
654
|
+
}[conn.effectiveType] ?? "UNKNOWN";
|
|
655
|
+
return aitState.state.networkStatus;
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
//#endregion
|
|
660
|
+
//#region src/mock/navigation/index.ts
|
|
661
|
+
/**
|
|
662
|
+
* 화면/네비게이션/이벤트 mock
|
|
663
|
+
*/
|
|
664
|
+
async function closeView() {
|
|
665
|
+
console.log("[@ait-co/devtools] closeView called");
|
|
666
|
+
window.history.back();
|
|
667
|
+
}
|
|
668
|
+
async function openURL(url) {
|
|
669
|
+
console.log("[@ait-co/devtools] openURL:", url);
|
|
670
|
+
window.open(url, "_blank");
|
|
671
|
+
}
|
|
672
|
+
async function share(message) {
|
|
673
|
+
if (navigator.share) {
|
|
674
|
+
await navigator.share({ text: message.message });
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
console.log("[@ait-co/devtools] share:", message.message);
|
|
678
|
+
}
|
|
679
|
+
async function getTossShareLink(path, _ogImageUrl) {
|
|
680
|
+
return `https://toss.im/share/mock${path}`;
|
|
681
|
+
}
|
|
682
|
+
async function setIosSwipeGestureEnabled(_options) {
|
|
683
|
+
console.log("[@ait-co/devtools] setIosSwipeGestureEnabled:", _options.isEnabled);
|
|
684
|
+
}
|
|
685
|
+
async function setDeviceOrientation(_options) {
|
|
686
|
+
console.log("[@ait-co/devtools] setDeviceOrientation:", _options.type);
|
|
687
|
+
}
|
|
688
|
+
async function setScreenAwakeMode(options) {
|
|
689
|
+
console.log("[@ait-co/devtools] setScreenAwakeMode:", options.enabled);
|
|
690
|
+
return { enabled: options.enabled };
|
|
691
|
+
}
|
|
692
|
+
async function setSecureScreen(options) {
|
|
693
|
+
console.log("[@ait-co/devtools] setSecureScreen:", options.enabled);
|
|
694
|
+
return { enabled: options.enabled };
|
|
695
|
+
}
|
|
696
|
+
async function requestReview() {
|
|
697
|
+
console.log("[@ait-co/devtools] requestReview called");
|
|
65
698
|
}
|
|
66
699
|
requestReview.isSupported = () => true;
|
|
67
700
|
function getPlatformOS() {
|
|
68
|
-
|
|
701
|
+
return aitState.state.platform;
|
|
69
702
|
}
|
|
70
703
|
function getOperationalEnvironment() {
|
|
71
|
-
|
|
704
|
+
return aitState.state.environment;
|
|
72
705
|
}
|
|
73
706
|
function getTossAppVersion() {
|
|
74
|
-
|
|
707
|
+
return aitState.state.appVersion;
|
|
75
708
|
}
|
|
76
709
|
function isMinVersionSupported(minVersions) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return true;
|
|
710
|
+
const required = aitState.state.platform === "ios" ? minVersions.ios : minVersions.android;
|
|
711
|
+
if (required === "always") return true;
|
|
712
|
+
if (required === "never") return false;
|
|
713
|
+
const current = aitState.state.appVersion.split(".").map(Number);
|
|
714
|
+
const min = required.split(".").map(Number);
|
|
715
|
+
for (let i = 0; i < 3; i++) {
|
|
716
|
+
if ((current[i] ?? 0) > (min[i] ?? 0)) return true;
|
|
717
|
+
if ((current[i] ?? 0) < (min[i] ?? 0)) return false;
|
|
718
|
+
}
|
|
719
|
+
return true;
|
|
88
720
|
}
|
|
89
721
|
function getSchemeUri() {
|
|
90
|
-
|
|
722
|
+
return aitState.state.schemeUri || window.location.pathname;
|
|
91
723
|
}
|
|
92
724
|
function getLocale() {
|
|
93
|
-
|
|
725
|
+
return aitState.state.locale;
|
|
94
726
|
}
|
|
95
727
|
function getDeviceId() {
|
|
96
|
-
|
|
728
|
+
return aitState.state.deviceId;
|
|
97
729
|
}
|
|
98
730
|
function getGroupId() {
|
|
99
|
-
|
|
731
|
+
return aitState.state.groupId;
|
|
100
732
|
}
|
|
101
|
-
function getNetworkStatus() {
|
|
102
|
-
|
|
733
|
+
async function getNetworkStatus() {
|
|
734
|
+
const modeResult = getNetworkStatusByMode();
|
|
735
|
+
if (modeResult) return modeResult;
|
|
736
|
+
return aitState.state.networkStatus;
|
|
103
737
|
}
|
|
104
|
-
function getServerTime() {
|
|
105
|
-
|
|
738
|
+
async function getServerTime() {
|
|
739
|
+
return Date.now();
|
|
106
740
|
}
|
|
107
741
|
getServerTime.isSupported = () => true;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const detail = e.detail;
|
|
131
|
-
onEvent(detail);
|
|
132
|
-
};
|
|
133
|
-
window.addEventListener(`__ait:${event}`, handler);
|
|
134
|
-
return () => window.removeEventListener(`__ait:${event}`, handler);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
742
|
+
const graniteEvent = { addEventListener(event, { onEvent, onError }) {
|
|
743
|
+
const handler = () => {
|
|
744
|
+
try {
|
|
745
|
+
onEvent();
|
|
746
|
+
} catch (e) {
|
|
747
|
+
onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
window.addEventListener(`__ait:${event}`, handler);
|
|
751
|
+
return () => window.removeEventListener(`__ait:${event}`, handler);
|
|
752
|
+
} };
|
|
753
|
+
const appsInTossEvent = { addEventListener(_event, _handlers) {
|
|
754
|
+
return () => {};
|
|
755
|
+
} };
|
|
756
|
+
const tdsEvent = { addEventListener(event, { onEvent }) {
|
|
757
|
+
const handler = (e) => {
|
|
758
|
+
const detail = e.detail;
|
|
759
|
+
onEvent(detail);
|
|
760
|
+
};
|
|
761
|
+
window.addEventListener(`__ait:${event}`, handler);
|
|
762
|
+
return () => window.removeEventListener(`__ait:${event}`, handler);
|
|
763
|
+
} };
|
|
137
764
|
function onVisibilityChangedByTransparentServiceWeb(eventParams) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
765
|
+
const handler = () => eventParams.onEvent(!document.hidden);
|
|
766
|
+
document.addEventListener("visibilitychange", handler);
|
|
767
|
+
return () => document.removeEventListener("visibilitychange", handler);
|
|
141
768
|
}
|
|
142
|
-
|
|
143
|
-
getDeploymentId: () => aitState.state.deploymentId
|
|
144
|
-
};
|
|
769
|
+
const env = { getDeploymentId: () => aitState.state.deploymentId };
|
|
145
770
|
function getAppsInTossGlobals() {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
var SafeAreaInsets = {
|
|
154
|
-
get: () => ({ ...aitState.state.safeAreaInsets }),
|
|
155
|
-
// NOTE: aitState.subscribe에 위임하므로 safeAreaInsets 외 상태 변경에도 콜백이 호출된다.
|
|
156
|
-
// 실제 SDK는 insets 변경 시에만 호출되지만, mock에서는 간소화를 위해 필터링하지 않는다.
|
|
157
|
-
subscribe: ({ onEvent }) => {
|
|
158
|
-
return aitState.subscribe(() => onEvent({ ...aitState.state.safeAreaInsets }));
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
function getSafeAreaInsets() {
|
|
162
|
-
return aitState.state.safeAreaInsets.top;
|
|
771
|
+
return {
|
|
772
|
+
deploymentId: aitState.state.deploymentId,
|
|
773
|
+
brandDisplayName: aitState.state.brand.displayName,
|
|
774
|
+
brandIcon: aitState.state.brand.icon,
|
|
775
|
+
brandPrimaryColor: aitState.state.brand.primaryColor
|
|
776
|
+
};
|
|
163
777
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
get(target, prop) {
|
|
170
|
-
if (prop in target) return target[prop];
|
|
171
|
-
if (typeof prop === "symbol") return void 0;
|
|
172
|
-
if (!WARNED.has(`${moduleName}.${prop}`)) {
|
|
173
|
-
console.warn(
|
|
174
|
-
`[ait-devtools] ${moduleName}.${prop} is not mocked yet. Returning no-op. Please update ait-devtools or file an issue.`
|
|
175
|
-
);
|
|
176
|
-
WARNED.add(`${moduleName}.${prop}`);
|
|
177
|
-
}
|
|
178
|
-
return async () => void 0;
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// src/mock/permissions.ts
|
|
184
|
-
function getPermission(name) {
|
|
185
|
-
return Promise.resolve(aitState.state.permissions[name]);
|
|
186
|
-
}
|
|
187
|
-
function openPermissionDialog(name) {
|
|
188
|
-
const current = aitState.state.permissions[name];
|
|
189
|
-
if (current === "allowed") return Promise.resolve("allowed");
|
|
190
|
-
aitState.patch("permissions", { [name]: "allowed" });
|
|
191
|
-
return Promise.resolve("allowed");
|
|
192
|
-
}
|
|
193
|
-
function requestPermission(permission) {
|
|
194
|
-
return openPermissionDialog(permission.name);
|
|
195
|
-
}
|
|
196
|
-
function withPermission(fn, permissionName) {
|
|
197
|
-
const enhanced = fn;
|
|
198
|
-
enhanced.getPermission = () => getPermission(permissionName);
|
|
199
|
-
enhanced.openPermissionDialog = () => openPermissionDialog(permissionName);
|
|
200
|
-
return enhanced;
|
|
201
|
-
}
|
|
202
|
-
function checkPermission(name, fnName) {
|
|
203
|
-
const status = aitState.state.permissions[name];
|
|
204
|
-
if (status === "denied") {
|
|
205
|
-
throw new Error(`[ait-devtools] ${fnName}: Permission "${name}" is denied. Change it in the DevTools panel.`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// src/mock/device/index.ts
|
|
210
|
-
var Storage = createMockProxy("Storage", {
|
|
211
|
-
getItem: async (key) => {
|
|
212
|
-
return localStorage.getItem(`__ait_storage:${key}`);
|
|
213
|
-
},
|
|
214
|
-
setItem: async (key, value) => {
|
|
215
|
-
localStorage.setItem(`__ait_storage:${key}`, value);
|
|
216
|
-
},
|
|
217
|
-
removeItem: async (key) => {
|
|
218
|
-
localStorage.removeItem(`__ait_storage:${key}`);
|
|
219
|
-
},
|
|
220
|
-
clearItems: async () => {
|
|
221
|
-
const keys = Object.keys(localStorage).filter((k) => k.startsWith("__ait_storage:"));
|
|
222
|
-
keys.forEach((k) => localStorage.removeItem(k));
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
var Accuracy = /* @__PURE__ */ ((Accuracy2) => {
|
|
226
|
-
Accuracy2[Accuracy2["Lowest"] = 1] = "Lowest";
|
|
227
|
-
Accuracy2[Accuracy2["Low"] = 2] = "Low";
|
|
228
|
-
Accuracy2[Accuracy2["Balanced"] = 3] = "Balanced";
|
|
229
|
-
Accuracy2[Accuracy2["High"] = 4] = "High";
|
|
230
|
-
Accuracy2[Accuracy2["Highest"] = 5] = "Highest";
|
|
231
|
-
Accuracy2[Accuracy2["BestForNavigation"] = 6] = "BestForNavigation";
|
|
232
|
-
return Accuracy2;
|
|
233
|
-
})(Accuracy || {});
|
|
234
|
-
function buildLocation() {
|
|
235
|
-
return {
|
|
236
|
-
coords: { ...aitState.state.location.coords },
|
|
237
|
-
timestamp: Date.now(),
|
|
238
|
-
accessLocation: aitState.state.location.accessLocation
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
var _getCurrentLocation = async (_options) => {
|
|
242
|
-
checkPermission("geolocation", "getCurrentLocation");
|
|
243
|
-
return buildLocation();
|
|
244
|
-
};
|
|
245
|
-
var getCurrentLocation = withPermission(_getCurrentLocation, "geolocation");
|
|
246
|
-
function _startUpdateLocation(eventParams) {
|
|
247
|
-
const { onEvent, options } = eventParams;
|
|
248
|
-
const interval = Math.max(options.timeInterval, 500);
|
|
249
|
-
const id = setInterval(() => {
|
|
250
|
-
const loc = buildLocation();
|
|
251
|
-
loc.coords.latitude += (Math.random() - 0.5) * 1e-4;
|
|
252
|
-
loc.coords.longitude += (Math.random() - 0.5) * 1e-4;
|
|
253
|
-
onEvent(loc);
|
|
254
|
-
}, interval);
|
|
255
|
-
return () => clearInterval(id);
|
|
256
|
-
}
|
|
257
|
-
var startUpdateLocation = Object.assign(_startUpdateLocation, {
|
|
258
|
-
getPermission: () => withPermission(_getCurrentLocation, "geolocation").getPermission(),
|
|
259
|
-
openPermissionDialog: () => withPermission(_getCurrentLocation, "geolocation").openPermissionDialog()
|
|
260
|
-
});
|
|
261
|
-
var _openCamera = async (options) => {
|
|
262
|
-
checkPermission("camera", "openCamera");
|
|
263
|
-
return new Promise((resolve, reject) => {
|
|
264
|
-
const input = document.createElement("input");
|
|
265
|
-
input.type = "file";
|
|
266
|
-
input.accept = "image/*";
|
|
267
|
-
input.capture = "environment";
|
|
268
|
-
input.onchange = () => {
|
|
269
|
-
const file = input.files?.[0];
|
|
270
|
-
if (!file) {
|
|
271
|
-
reject(new Error("No file selected"));
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
const reader = new FileReader();
|
|
275
|
-
reader.onload = () => {
|
|
276
|
-
resolve({
|
|
277
|
-
id: crypto.randomUUID(),
|
|
278
|
-
dataUri: reader.result
|
|
279
|
-
});
|
|
280
|
-
};
|
|
281
|
-
reader.readAsDataURL(file);
|
|
282
|
-
};
|
|
283
|
-
input.click();
|
|
284
|
-
});
|
|
285
|
-
};
|
|
286
|
-
var openCamera = withPermission(_openCamera, "camera");
|
|
287
|
-
var _fetchAlbumPhotos = async (options) => {
|
|
288
|
-
checkPermission("photos", "fetchAlbumPhotos");
|
|
289
|
-
const maxCount = options?.maxCount ?? 10;
|
|
290
|
-
return new Promise((resolve, reject) => {
|
|
291
|
-
const input = document.createElement("input");
|
|
292
|
-
input.type = "file";
|
|
293
|
-
input.accept = "image/*";
|
|
294
|
-
input.multiple = true;
|
|
295
|
-
input.onchange = async () => {
|
|
296
|
-
const files = Array.from(input.files ?? []).slice(0, maxCount);
|
|
297
|
-
if (files.length === 0) {
|
|
298
|
-
reject(new Error("No files selected"));
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
const results = await Promise.all(
|
|
302
|
-
files.map((file) => new Promise((res) => {
|
|
303
|
-
const reader = new FileReader();
|
|
304
|
-
reader.onload = () => res({ id: crypto.randomUUID(), dataUri: reader.result });
|
|
305
|
-
reader.readAsDataURL(file);
|
|
306
|
-
}))
|
|
307
|
-
);
|
|
308
|
-
resolve(results);
|
|
309
|
-
};
|
|
310
|
-
input.click();
|
|
311
|
-
});
|
|
312
|
-
};
|
|
313
|
-
var fetchAlbumPhotos = withPermission(_fetchAlbumPhotos, "photos");
|
|
314
|
-
var _fetchContacts = async (options) => {
|
|
315
|
-
checkPermission("contacts", "fetchContacts");
|
|
316
|
-
let contacts = aitState.state.contacts;
|
|
317
|
-
if (options.query?.contains) {
|
|
318
|
-
const q = options.query.contains.toLowerCase();
|
|
319
|
-
contacts = contacts.filter((c) => c.name.toLowerCase().includes(q) || c.phoneNumber.includes(q));
|
|
320
|
-
}
|
|
321
|
-
const sliced = contacts.slice(options.offset, options.offset + options.size);
|
|
322
|
-
const nextOffset = options.offset + options.size;
|
|
323
|
-
return {
|
|
324
|
-
result: sliced,
|
|
325
|
-
nextOffset: nextOffset < contacts.length ? nextOffset : null,
|
|
326
|
-
done: nextOffset >= contacts.length
|
|
327
|
-
};
|
|
778
|
+
const SafeAreaInsets = {
|
|
779
|
+
get: () => ({ ...aitState.state.safeAreaInsets }),
|
|
780
|
+
subscribe: ({ onEvent }) => {
|
|
781
|
+
return aitState.subscribe(() => onEvent({ ...aitState.state.safeAreaInsets }));
|
|
782
|
+
}
|
|
328
783
|
};
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
try {
|
|
333
|
-
return await navigator.clipboard.readText();
|
|
334
|
-
} catch {
|
|
335
|
-
return "";
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
var getClipboardText = withPermission(_getClipboardText, "clipboard");
|
|
339
|
-
var _setClipboardText = async (text) => {
|
|
340
|
-
checkPermission("clipboard", "setClipboardText");
|
|
341
|
-
await navigator.clipboard.writeText(text);
|
|
342
|
-
};
|
|
343
|
-
var setClipboardText = withPermission(_setClipboardText, "clipboard");
|
|
344
|
-
function generateHapticFeedback(options) {
|
|
345
|
-
console.log(`[ait-devtools] haptic: ${options.type}`);
|
|
346
|
-
aitState.logAnalytics({ type: "haptic", params: { hapticType: options.type } });
|
|
347
|
-
return Promise.resolve();
|
|
348
|
-
}
|
|
349
|
-
function saveBase64Data(params) {
|
|
350
|
-
const a = document.createElement("a");
|
|
351
|
-
a.href = `data:${params.mimeType};base64,${params.data}`;
|
|
352
|
-
a.download = params.fileName;
|
|
353
|
-
a.click();
|
|
354
|
-
return Promise.resolve();
|
|
784
|
+
/** @deprecated */
|
|
785
|
+
function getSafeAreaInsets() {
|
|
786
|
+
return aitState.state.safeAreaInsets.top;
|
|
355
787
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
788
|
+
//#endregion
|
|
789
|
+
//#region src/mock/iap/index.ts
|
|
790
|
+
/**
|
|
791
|
+
* IAP (인앱결제) mock
|
|
792
|
+
*/
|
|
793
|
+
let orderCounter = 0;
|
|
359
794
|
function generateOrderId() {
|
|
360
|
-
|
|
795
|
+
return `mock-order-${++orderCounter}-${Date.now()}`;
|
|
361
796
|
}
|
|
362
797
|
function buildOrderResult(sku) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
798
|
+
const product = aitState.state.iap.products.find((p) => p.sku === sku);
|
|
799
|
+
const amountStr = product?.displayAmount?.replace(/[^0-9]/g, "") ?? "1000";
|
|
800
|
+
return {
|
|
801
|
+
orderId: generateOrderId(),
|
|
802
|
+
displayName: product?.displayName ?? "Mock Product",
|
|
803
|
+
displayAmount: product?.displayAmount ?? "1,000원",
|
|
804
|
+
amount: parseInt(amountStr, 10) || 1e3,
|
|
805
|
+
currency: "KRW",
|
|
806
|
+
fraction: 0,
|
|
807
|
+
miniAppIconUrl: product?.iconUrl || null
|
|
808
|
+
};
|
|
374
809
|
}
|
|
375
810
|
async function handlePurchase(sku, processProductGrant, onEvent, onError) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
gracePeriodExpiresAt: null,
|
|
457
|
-
isAccessible: true
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
}
|
|
811
|
+
const nextResult = aitState.state.iap.nextResult;
|
|
812
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
813
|
+
if (nextResult !== "success") {
|
|
814
|
+
onError({ code: nextResult });
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
const result = buildOrderResult(sku);
|
|
818
|
+
try {
|
|
819
|
+
if (!await processProductGrant({ orderId: result.orderId })) {
|
|
820
|
+
onError({ code: "PRODUCT_NOT_GRANTED_BY_PARTNER" });
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
} catch (e) {
|
|
824
|
+
onError(e);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
aitState.patch("iap", { completedOrders: [...aitState.state.iap.completedOrders, {
|
|
828
|
+
orderId: result.orderId,
|
|
829
|
+
sku,
|
|
830
|
+
status: "COMPLETED",
|
|
831
|
+
date: (/* @__PURE__ */ new Date()).toISOString()
|
|
832
|
+
}] });
|
|
833
|
+
await onEvent({
|
|
834
|
+
type: "success",
|
|
835
|
+
data: result
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
const IAP = createMockProxy("IAP", {
|
|
839
|
+
createOneTimePurchaseOrder(params) {
|
|
840
|
+
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));
|
|
841
|
+
return () => {};
|
|
842
|
+
},
|
|
843
|
+
createSubscriptionPurchaseOrder(params) {
|
|
844
|
+
handlePurchase(params.options.sku, params.options.processProductGrant, params.onEvent, params.onError).catch((e) => console.error("[@ait-co/devtools] IAP unexpected error:", e));
|
|
845
|
+
return () => {};
|
|
846
|
+
},
|
|
847
|
+
async getProductItemList() {
|
|
848
|
+
return { products: aitState.state.iap.products.map((p) => ({
|
|
849
|
+
...p,
|
|
850
|
+
...p.type === "SUBSCRIPTION" ? { renewalCycle: p.renewalCycle ?? "MONTHLY" } : {}
|
|
851
|
+
})) };
|
|
852
|
+
},
|
|
853
|
+
async getPendingOrders() {
|
|
854
|
+
return { orders: [...aitState.state.iap.pendingOrders] };
|
|
855
|
+
},
|
|
856
|
+
async getCompletedOrRefundedOrders() {
|
|
857
|
+
return {
|
|
858
|
+
hasNext: false,
|
|
859
|
+
nextKey: null,
|
|
860
|
+
orders: [...aitState.state.iap.completedOrders]
|
|
861
|
+
};
|
|
862
|
+
},
|
|
863
|
+
async completeProductGrant(args) {
|
|
864
|
+
const idx = aitState.state.iap.pendingOrders.findIndex((o) => o.orderId === args.params.orderId);
|
|
865
|
+
if (idx !== -1) {
|
|
866
|
+
const order = aitState.state.iap.pendingOrders[idx];
|
|
867
|
+
const pendingOrders = aitState.state.iap.pendingOrders.filter((_, i) => i !== idx);
|
|
868
|
+
const completedOrders = [...aitState.state.iap.completedOrders, {
|
|
869
|
+
orderId: order.orderId,
|
|
870
|
+
sku: order.sku,
|
|
871
|
+
status: "COMPLETED",
|
|
872
|
+
date: (/* @__PURE__ */ new Date()).toISOString()
|
|
873
|
+
}];
|
|
874
|
+
aitState.patch("iap", {
|
|
875
|
+
pendingOrders,
|
|
876
|
+
completedOrders
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
return true;
|
|
880
|
+
},
|
|
881
|
+
async getSubscriptionInfo(_args) {
|
|
882
|
+
return { subscription: {
|
|
883
|
+
catalogId: 1,
|
|
884
|
+
status: "ACTIVE",
|
|
885
|
+
expiresAt: new Date(Date.now() + 720 * 60 * 60 * 1e3).toISOString(),
|
|
886
|
+
isAutoRenew: true,
|
|
887
|
+
gracePeriodExpiresAt: null,
|
|
888
|
+
isAccessible: true
|
|
889
|
+
} };
|
|
890
|
+
}
|
|
461
891
|
});
|
|
462
|
-
function checkoutPayment(options) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
}, 300);
|
|
473
|
-
});
|
|
892
|
+
async function checkoutPayment(options) {
|
|
893
|
+
const { nextResult, failReason } = aitState.state.payment;
|
|
894
|
+
console.log("[@ait-co/devtools] checkoutPayment:", options.params.payToken);
|
|
895
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
896
|
+
if (nextResult === "success") return { success: true };
|
|
897
|
+
return {
|
|
898
|
+
success: false,
|
|
899
|
+
reason: failReason || "Mock payment failed"
|
|
900
|
+
};
|
|
474
901
|
}
|
|
475
|
-
|
|
476
|
-
|
|
902
|
+
//#endregion
|
|
903
|
+
//#region src/mock/ads/index.ts
|
|
904
|
+
/**
|
|
905
|
+
* 광고 mock (GoogleAdMob, TossAds, FullScreenAd)
|
|
906
|
+
*/
|
|
477
907
|
function withIsSupported(fn) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
908
|
+
fn.isSupported = () => true;
|
|
909
|
+
return fn;
|
|
910
|
+
}
|
|
911
|
+
const GoogleAdMob = createMockProxy("GoogleAdMob", {
|
|
912
|
+
loadAppsInTossAdMob: withIsSupported((args) => {
|
|
913
|
+
setTimeout(() => {
|
|
914
|
+
aitState.patch("ads", { isLoaded: true });
|
|
915
|
+
args.onEvent({
|
|
916
|
+
type: "loaded",
|
|
917
|
+
data: { adGroupId: args.options?.adGroupId }
|
|
918
|
+
});
|
|
919
|
+
}, 200);
|
|
920
|
+
return () => {};
|
|
921
|
+
}),
|
|
922
|
+
showAppsInTossAdMob: withIsSupported((args) => {
|
|
923
|
+
if (!aitState.state.ads.isLoaded) {
|
|
924
|
+
args.onError(/* @__PURE__ */ new Error("Ad not loaded"));
|
|
925
|
+
return () => {};
|
|
926
|
+
}
|
|
927
|
+
setTimeout(() => args.onEvent({ type: "requested" }), 50);
|
|
928
|
+
setTimeout(() => args.onEvent({ type: "show" }), 100);
|
|
929
|
+
setTimeout(() => args.onEvent({ type: "impression" }), 150);
|
|
930
|
+
setTimeout(() => {
|
|
931
|
+
args.onEvent({
|
|
932
|
+
type: "userEarnedReward",
|
|
933
|
+
data: {
|
|
934
|
+
unitType: "coins",
|
|
935
|
+
unitAmount: 10
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
}, 1e3);
|
|
939
|
+
setTimeout(() => {
|
|
940
|
+
args.onEvent({ type: "dismissed" });
|
|
941
|
+
aitState.patch("ads", { isLoaded: false });
|
|
942
|
+
}, 1500);
|
|
943
|
+
return () => {};
|
|
944
|
+
}),
|
|
945
|
+
isAppsInTossAdMobLoaded: withIsSupported(async (_options) => aitState.state.ads.isLoaded)
|
|
512
946
|
});
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}),
|
|
539
|
-
destroyAll: withIsSupported(() => {
|
|
540
|
-
})
|
|
947
|
+
const TossAds = createMockProxy("TossAds", {
|
|
948
|
+
initialize: withIsSupported((_options) => {
|
|
949
|
+
console.log("[@ait-co/devtools] TossAds.initialize (mock)");
|
|
950
|
+
}),
|
|
951
|
+
attach: withIsSupported((_adGroupId, target, _options) => {
|
|
952
|
+
const el = typeof target === "string" ? document.querySelector(target) : target;
|
|
953
|
+
if (el) {
|
|
954
|
+
const placeholder = document.createElement("div");
|
|
955
|
+
placeholder.style.cssText = "background:#f0f0f0;border:1px dashed #999;padding:16px;text-align:center;color:#666;font-size:14px;";
|
|
956
|
+
placeholder.textContent = "[@ait-co/devtools] TossAds Placeholder";
|
|
957
|
+
el.appendChild(placeholder);
|
|
958
|
+
}
|
|
959
|
+
}),
|
|
960
|
+
attachBanner: withIsSupported((_adGroupId, target, _options) => {
|
|
961
|
+
const el = typeof target === "string" ? document.querySelector(target) : target;
|
|
962
|
+
if (el) {
|
|
963
|
+
const placeholder = document.createElement("div");
|
|
964
|
+
placeholder.style.cssText = "background:#f0f0f0;border:1px dashed #999;padding:12px;text-align:center;color:#666;font-size:12px;";
|
|
965
|
+
placeholder.textContent = "[@ait-co/devtools] Banner Ad Placeholder";
|
|
966
|
+
el.appendChild(placeholder);
|
|
967
|
+
}
|
|
968
|
+
return { destroy: () => {} };
|
|
969
|
+
}),
|
|
970
|
+
destroy: withIsSupported((_slotId) => {}),
|
|
971
|
+
destroyAll: withIsSupported(() => {})
|
|
541
972
|
});
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
973
|
+
const loadFullScreenAd = withIsSupported((args) => {
|
|
974
|
+
setTimeout(() => {
|
|
975
|
+
aitState.patch("ads", { isLoaded: true });
|
|
976
|
+
args.onEvent({
|
|
977
|
+
type: "loaded",
|
|
978
|
+
data: { adGroupId: args.options?.adGroupId }
|
|
979
|
+
});
|
|
980
|
+
}, 200);
|
|
981
|
+
return () => {};
|
|
549
982
|
});
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
return () => {
|
|
559
|
-
};
|
|
983
|
+
const showFullScreenAd = withIsSupported((args) => {
|
|
984
|
+
if (!aitState.state.ads.isLoaded) {
|
|
985
|
+
args.onError(/* @__PURE__ */ new Error("Ad not loaded"));
|
|
986
|
+
return () => {};
|
|
987
|
+
}
|
|
988
|
+
setTimeout(() => args.onEvent({ type: "show" }), 100);
|
|
989
|
+
setTimeout(() => args.onEvent({ type: "dismissed" }), 1500);
|
|
990
|
+
return () => {};
|
|
560
991
|
});
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
992
|
+
//#endregion
|
|
993
|
+
//#region src/mock/game/index.ts
|
|
994
|
+
/**
|
|
995
|
+
* 게임/프로모션 mock
|
|
996
|
+
*/
|
|
997
|
+
async function grantPromotionReward(params) {
|
|
998
|
+
console.log("[@ait-co/devtools] grantPromotionReward:", params.params);
|
|
999
|
+
return { key: `mock-reward-${Date.now()}` };
|
|
1000
|
+
}
|
|
1001
|
+
async function grantPromotionRewardForGame(params) {
|
|
1002
|
+
console.log("[@ait-co/devtools] grantPromotionRewardForGame:", params.params);
|
|
1003
|
+
return { key: `mock-reward-${Date.now()}` };
|
|
1004
|
+
}
|
|
1005
|
+
async function submitGameCenterLeaderBoardScore(params) {
|
|
1006
|
+
aitState.patch("game", { leaderboardScores: [...aitState.state.game.leaderboardScores, {
|
|
1007
|
+
score: params.score,
|
|
1008
|
+
timestamp: Date.now()
|
|
1009
|
+
}] });
|
|
1010
|
+
return { statusCode: "SUCCESS" };
|
|
1011
|
+
}
|
|
1012
|
+
async function getGameCenterGameProfile() {
|
|
1013
|
+
const profile = aitState.state.game.profile;
|
|
1014
|
+
if (!profile) return { statusCode: "PROFILE_NOT_FOUND" };
|
|
1015
|
+
return {
|
|
1016
|
+
statusCode: "SUCCESS",
|
|
1017
|
+
nickname: profile.nickname,
|
|
1018
|
+
profileImageUri: profile.profileImageUri
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
async function openGameCenterLeaderboard() {
|
|
1022
|
+
console.log("[@ait-co/devtools] openGameCenterLeaderboard (no-op in browser)");
|
|
589
1023
|
}
|
|
590
1024
|
function contactsViral(params) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
};
|
|
1025
|
+
setTimeout(() => {
|
|
1026
|
+
params.onEvent({
|
|
1027
|
+
type: "close",
|
|
1028
|
+
data: {
|
|
1029
|
+
closeReason: "noReward",
|
|
1030
|
+
sentRewardsCount: 0
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
}, 500);
|
|
1034
|
+
return () => {};
|
|
602
1035
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region src/mock/analytics/index.ts
|
|
1038
|
+
/**
|
|
1039
|
+
* Analytics mock
|
|
1040
|
+
*/
|
|
1041
|
+
const Analytics = {
|
|
1042
|
+
screen: (params) => {
|
|
1043
|
+
aitState.logAnalytics({
|
|
1044
|
+
type: "screen",
|
|
1045
|
+
params: params ?? {}
|
|
1046
|
+
});
|
|
1047
|
+
return Promise.resolve();
|
|
1048
|
+
},
|
|
1049
|
+
impression: (params) => {
|
|
1050
|
+
aitState.logAnalytics({
|
|
1051
|
+
type: "impression",
|
|
1052
|
+
params: params ?? {}
|
|
1053
|
+
});
|
|
1054
|
+
return Promise.resolve();
|
|
1055
|
+
},
|
|
1056
|
+
click: (params) => {
|
|
1057
|
+
aitState.logAnalytics({
|
|
1058
|
+
type: "click",
|
|
1059
|
+
params: params ?? {}
|
|
1060
|
+
});
|
|
1061
|
+
return Promise.resolve();
|
|
1062
|
+
}
|
|
618
1063
|
};
|
|
619
|
-
function eventLog(params) {
|
|
620
|
-
|
|
621
|
-
|
|
1064
|
+
async function eventLog(params) {
|
|
1065
|
+
aitState.logAnalytics({
|
|
1066
|
+
type: params.log_type,
|
|
1067
|
+
params: {
|
|
1068
|
+
log_name: params.log_name,
|
|
1069
|
+
...params.params
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
622
1072
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
};
|
|
633
|
-
export {
|
|
634
|
-
Accuracy,
|
|
635
|
-
Analytics,
|
|
636
|
-
GoogleAdMob,
|
|
637
|
-
IAP,
|
|
638
|
-
SafeAreaInsets,
|
|
639
|
-
Storage,
|
|
640
|
-
TossAds,
|
|
641
|
-
aitState,
|
|
642
|
-
appLogin,
|
|
643
|
-
appsInTossEvent,
|
|
644
|
-
appsInTossSignTossCert,
|
|
645
|
-
checkoutPayment,
|
|
646
|
-
closeView,
|
|
647
|
-
contactsViral,
|
|
648
|
-
env,
|
|
649
|
-
eventLog,
|
|
650
|
-
fetchAlbumPhotos,
|
|
651
|
-
fetchContacts,
|
|
652
|
-
generateHapticFeedback,
|
|
653
|
-
getAppsInTossGlobals,
|
|
654
|
-
getClipboardText,
|
|
655
|
-
getCurrentLocation,
|
|
656
|
-
getDeviceId,
|
|
657
|
-
getGameCenterGameProfile,
|
|
658
|
-
getGroupId,
|
|
659
|
-
getIsTossLoginIntegratedService,
|
|
660
|
-
getLocale,
|
|
661
|
-
getNetworkStatus,
|
|
662
|
-
getOperationalEnvironment,
|
|
663
|
-
getPermission,
|
|
664
|
-
getPlatformOS,
|
|
665
|
-
getSafeAreaInsets,
|
|
666
|
-
getSchemeUri,
|
|
667
|
-
getServerTime,
|
|
668
|
-
getTossAppVersion,
|
|
669
|
-
getTossShareLink,
|
|
670
|
-
getUserKeyForGame,
|
|
671
|
-
graniteEvent,
|
|
672
|
-
grantPromotionReward,
|
|
673
|
-
grantPromotionRewardForGame,
|
|
674
|
-
isMinVersionSupported,
|
|
675
|
-
loadFullScreenAd,
|
|
676
|
-
onVisibilityChangedByTransparentServiceWeb,
|
|
677
|
-
openCamera,
|
|
678
|
-
openGameCenterLeaderboard,
|
|
679
|
-
openPermissionDialog,
|
|
680
|
-
openURL,
|
|
681
|
-
partner,
|
|
682
|
-
requestPermission,
|
|
683
|
-
requestReview,
|
|
684
|
-
saveBase64Data,
|
|
685
|
-
setClipboardText,
|
|
686
|
-
setDeviceOrientation,
|
|
687
|
-
setIosSwipeGestureEnabled,
|
|
688
|
-
setScreenAwakeMode,
|
|
689
|
-
setSecureScreen,
|
|
690
|
-
share,
|
|
691
|
-
showFullScreenAd,
|
|
692
|
-
startUpdateLocation,
|
|
693
|
-
submitGameCenterLeaderBoardScore,
|
|
694
|
-
tdsEvent
|
|
1073
|
+
//#endregion
|
|
1074
|
+
//#region src/mock/partner/index.ts
|
|
1075
|
+
const partner = {
|
|
1076
|
+
async addAccessoryButton(options) {
|
|
1077
|
+
console.log("[@ait-co/devtools] partner.addAccessoryButton:", options);
|
|
1078
|
+
},
|
|
1079
|
+
async removeAccessoryButton() {
|
|
1080
|
+
console.log("[@ait-co/devtools] partner.removeAccessoryButton");
|
|
1081
|
+
}
|
|
695
1082
|
};
|
|
1083
|
+
//#endregion
|
|
1084
|
+
export { Accuracy, Analytics, GoogleAdMob, IAP, SafeAreaInsets, Storage, TossAds, aitState, appLogin, appsInTossEvent, appsInTossSignTossCert, checkoutPayment, closeView, contactsViral, env, eventLog, fetchAlbumPhotos, fetchContacts, generateHapticFeedback, getAppsInTossGlobals, getClipboardText, getCurrentLocation, getDefaultPlaceholderImages, getDeviceId, getGameCenterGameProfile, getGroupId, getIsTossLoginIntegratedService, getLocale, getNetworkStatus, getOperationalEnvironment, getPermission, getPlatformOS, getSafeAreaInsets, getSchemeUri, getServerTime, getTossAppVersion, getTossShareLink, getUserKeyForGame, graniteEvent, grantPromotionReward, grantPromotionRewardForGame, isMinVersionSupported, loadFullScreenAd, onVisibilityChangedByTransparentServiceWeb, openCamera, openGameCenterLeaderboard, openPermissionDialog, openURL, partner, requestPermission, requestReview, saveBase64Data, setClipboardText, setDeviceOrientation, setIosSwipeGestureEnabled, setScreenAwakeMode, setSecureScreen, share, showFullScreenAd, startUpdateLocation, submitGameCenterLeaderBoardScore, tdsEvent };
|
|
1085
|
+
|
|
696
1086
|
//# sourceMappingURL=index.js.map
|