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