@ait-co/devtools 0.0.1 → 0.0.2
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 +430 -134
- package/dist/chunk-6PPZTREF.js +569 -0
- package/dist/chunk-6PPZTREF.js.map +1 -0
- package/dist/mock/index.d.ts +22 -6
- package/dist/mock/index.js +87 -272
- package/dist/mock/index.js.map +1 -1
- package/dist/panel/index.d.ts +1 -1
- package/dist/panel/index.js +588 -32
- package/dist/panel/index.js.map +1 -1
- package/dist/unplugin/index.cjs +13 -12
- package/dist/unplugin/index.cjs.map +1 -1
- package/dist/unplugin/index.d.cts +51 -0
- package/dist/unplugin/index.d.ts +51 -0
- package/dist/unplugin/index.js +13 -12
- package/dist/unplugin/index.js.map +1 -1
- package/package.json +19 -10
- package/dist/chunk-YYIIG3JT.js +0 -146
- package/dist/chunk-YYIIG3JT.js.map +0 -1
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
// src/mock/state.ts
|
|
2
|
+
var 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: { top: 47, bottom: 34, left: 0, right: 0 },
|
|
38
|
+
contacts: [
|
|
39
|
+
{ name: "\uD64D\uAE38\uB3D9", phoneNumber: "010-1234-5678" },
|
|
40
|
+
{ name: "\uAE40\uD1A0\uC2A4", phoneNumber: "010-9876-5432" }
|
|
41
|
+
],
|
|
42
|
+
iap: {
|
|
43
|
+
products: [
|
|
44
|
+
{
|
|
45
|
+
sku: "mock-gem-100",
|
|
46
|
+
type: "CONSUMABLE",
|
|
47
|
+
displayName: "\uBCF4\uC11D 100\uAC1C",
|
|
48
|
+
displayAmount: "1,000\uC6D0",
|
|
49
|
+
iconUrl: "",
|
|
50
|
+
description: "\uAC8C\uC784\uC5D0\uC11C \uC0AC\uC6A9\uD560 \uC218 \uC788\uB294 \uBCF4\uC11D 100\uAC1C"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
nextResult: "success",
|
|
54
|
+
pendingOrders: [],
|
|
55
|
+
completedOrders: []
|
|
56
|
+
},
|
|
57
|
+
payment: {
|
|
58
|
+
nextResult: "success",
|
|
59
|
+
failReason: ""
|
|
60
|
+
},
|
|
61
|
+
auth: {
|
|
62
|
+
isLoggedIn: true,
|
|
63
|
+
isTossLoginIntegrated: true,
|
|
64
|
+
userKeyHash: "mock-user-hash-abc123"
|
|
65
|
+
},
|
|
66
|
+
ads: {
|
|
67
|
+
isLoaded: false,
|
|
68
|
+
nextEvent: "loaded"
|
|
69
|
+
},
|
|
70
|
+
game: {
|
|
71
|
+
profile: { nickname: "MockPlayer", profileImageUri: "" },
|
|
72
|
+
leaderboardScores: []
|
|
73
|
+
},
|
|
74
|
+
analyticsLog: [],
|
|
75
|
+
deviceModes: {
|
|
76
|
+
camera: "mock",
|
|
77
|
+
photos: "mock",
|
|
78
|
+
location: "mock",
|
|
79
|
+
network: "mock",
|
|
80
|
+
clipboard: "web"
|
|
81
|
+
},
|
|
82
|
+
mockData: {
|
|
83
|
+
images: [],
|
|
84
|
+
clipboardText: ""
|
|
85
|
+
},
|
|
86
|
+
panelEditable: true
|
|
87
|
+
};
|
|
88
|
+
function generateDeviceId() {
|
|
89
|
+
const stored = localStorage.getItem("__ait_device_id");
|
|
90
|
+
if (stored) return stored;
|
|
91
|
+
const id = crypto.randomUUID();
|
|
92
|
+
localStorage.setItem("__ait_device_id", id);
|
|
93
|
+
return id;
|
|
94
|
+
}
|
|
95
|
+
var AitStateManager = class {
|
|
96
|
+
_state;
|
|
97
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
98
|
+
constructor() {
|
|
99
|
+
this._state = structuredClone(DEFAULT_STATE);
|
|
100
|
+
try {
|
|
101
|
+
this._state.deviceId = generateDeviceId();
|
|
102
|
+
} catch {
|
|
103
|
+
this._state.deviceId = "mock-device-" + Math.random().toString(36).slice(2);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
get state() {
|
|
107
|
+
return this._state;
|
|
108
|
+
}
|
|
109
|
+
update(partial) {
|
|
110
|
+
this._state = { ...this._state, ...partial };
|
|
111
|
+
this._notify();
|
|
112
|
+
}
|
|
113
|
+
/** 중첩 객체 업데이트용 */
|
|
114
|
+
patch(key, partial) {
|
|
115
|
+
const current = this._state[key];
|
|
116
|
+
if (typeof current === "object" && current !== null && !Array.isArray(current)) {
|
|
117
|
+
this._state = { ...this._state, [key]: { ...current, ...partial } };
|
|
118
|
+
} else {
|
|
119
|
+
this._state = { ...this._state, [key]: partial };
|
|
120
|
+
}
|
|
121
|
+
this._notify();
|
|
122
|
+
}
|
|
123
|
+
subscribe(listener) {
|
|
124
|
+
this._listeners.add(listener);
|
|
125
|
+
return () => this._listeners.delete(listener);
|
|
126
|
+
}
|
|
127
|
+
/** 분석 로그 추가 */
|
|
128
|
+
logAnalytics(entry) {
|
|
129
|
+
this._state = {
|
|
130
|
+
...this._state,
|
|
131
|
+
analyticsLog: [...this._state.analyticsLog, { ...entry, timestamp: Date.now() }]
|
|
132
|
+
};
|
|
133
|
+
this._notify();
|
|
134
|
+
}
|
|
135
|
+
/** 이벤트 트리거 (backEvent, homeEvent 등) */
|
|
136
|
+
trigger(event) {
|
|
137
|
+
window.dispatchEvent(new CustomEvent(`__ait:${event}`));
|
|
138
|
+
}
|
|
139
|
+
reset() {
|
|
140
|
+
const deviceId = this._state.deviceId;
|
|
141
|
+
this._state = { ...structuredClone(DEFAULT_STATE), deviceId };
|
|
142
|
+
this._notify();
|
|
143
|
+
}
|
|
144
|
+
_notify() {
|
|
145
|
+
for (const listener of this._listeners) {
|
|
146
|
+
listener();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
var aitState = new AitStateManager();
|
|
151
|
+
if (typeof window !== "undefined") {
|
|
152
|
+
window.__ait = aitState;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/mock/permissions.ts
|
|
156
|
+
async function getPermission(name) {
|
|
157
|
+
return aitState.state.permissions[name];
|
|
158
|
+
}
|
|
159
|
+
async function openPermissionDialog(name) {
|
|
160
|
+
const current = aitState.state.permissions[name];
|
|
161
|
+
if (current === "allowed") return "allowed";
|
|
162
|
+
aitState.patch("permissions", { [name]: "allowed" });
|
|
163
|
+
return "allowed";
|
|
164
|
+
}
|
|
165
|
+
async function requestPermission(permission) {
|
|
166
|
+
return openPermissionDialog(permission.name);
|
|
167
|
+
}
|
|
168
|
+
function withPermission(fn, permissionName) {
|
|
169
|
+
const enhanced = fn;
|
|
170
|
+
enhanced.getPermission = () => getPermission(permissionName);
|
|
171
|
+
enhanced.openPermissionDialog = () => openPermissionDialog(permissionName);
|
|
172
|
+
return enhanced;
|
|
173
|
+
}
|
|
174
|
+
function checkPermission(name, fnName) {
|
|
175
|
+
const status = aitState.state.permissions[name];
|
|
176
|
+
if (status === "denied") {
|
|
177
|
+
throw new Error(`[@ait-co/devtools] ${fnName}: Permission "${name}" is denied. Change it in the DevTools panel.`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/mock/proxy.ts
|
|
182
|
+
var WARNED = /* @__PURE__ */ new Set();
|
|
183
|
+
function createMockProxy(moduleName, implementations) {
|
|
184
|
+
return new Proxy(implementations, {
|
|
185
|
+
get(target, prop) {
|
|
186
|
+
if (prop in target) return target[prop];
|
|
187
|
+
if (typeof prop === "symbol") return void 0;
|
|
188
|
+
if (!WARNED.has(`${moduleName}.${prop}`)) {
|
|
189
|
+
console.warn(
|
|
190
|
+
`[@ait-co/devtools] ${moduleName}.${prop} is not mocked yet. Returning no-op. Please update @ait-co/devtools or file an issue.`
|
|
191
|
+
);
|
|
192
|
+
WARNED.add(`${moduleName}.${prop}`);
|
|
193
|
+
}
|
|
194
|
+
return async () => void 0;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/mock/device/index.ts
|
|
200
|
+
function generatePlaceholderImage(width, height, text, color) {
|
|
201
|
+
const canvas = document.createElement("canvas");
|
|
202
|
+
canvas.width = width;
|
|
203
|
+
canvas.height = height;
|
|
204
|
+
const ctx = canvas.getContext("2d");
|
|
205
|
+
if (!ctx) {
|
|
206
|
+
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>`;
|
|
207
|
+
return "data:image/svg+xml;base64," + btoa(svg);
|
|
208
|
+
}
|
|
209
|
+
ctx.fillStyle = color;
|
|
210
|
+
ctx.fillRect(0, 0, width, height);
|
|
211
|
+
ctx.fillStyle = "white";
|
|
212
|
+
ctx.font = "16px sans-serif";
|
|
213
|
+
ctx.textAlign = "center";
|
|
214
|
+
ctx.textBaseline = "middle";
|
|
215
|
+
ctx.fillText(text, width / 2, height / 2);
|
|
216
|
+
return canvas.toDataURL("image/png");
|
|
217
|
+
}
|
|
218
|
+
var DEFAULT_PLACEHOLDERS = [
|
|
219
|
+
{ text: "Mock Photo 1", color: "#3182F6" },
|
|
220
|
+
{ text: "Mock Photo 2", color: "#27ae60" },
|
|
221
|
+
{ text: "Mock Photo 3", color: "#e67e22" }
|
|
222
|
+
];
|
|
223
|
+
var cachedPlaceholders = null;
|
|
224
|
+
function getDefaultPlaceholderImages() {
|
|
225
|
+
if (!cachedPlaceholders) {
|
|
226
|
+
cachedPlaceholders = DEFAULT_PLACEHOLDERS.map((p) => generatePlaceholderImage(320, 240, p.text, p.color));
|
|
227
|
+
}
|
|
228
|
+
return [...cachedPlaceholders];
|
|
229
|
+
}
|
|
230
|
+
function getMockImages() {
|
|
231
|
+
const images = aitState.state.mockData.images;
|
|
232
|
+
if (images.length > 0) return images;
|
|
233
|
+
return getDefaultPlaceholderImages();
|
|
234
|
+
}
|
|
235
|
+
var PROMPT_TIMEOUT_MS = 3e4;
|
|
236
|
+
function waitForPromptResponse(type) {
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
const eventName = "__ait:prompt-response:" + type;
|
|
239
|
+
const cancelName = "__ait:prompt-cancel";
|
|
240
|
+
function cleanup() {
|
|
241
|
+
clearTimeout(timer);
|
|
242
|
+
window.removeEventListener(eventName, handler);
|
|
243
|
+
window.removeEventListener(cancelName, cancelHandler);
|
|
244
|
+
}
|
|
245
|
+
const timer = setTimeout(() => {
|
|
246
|
+
cleanup();
|
|
247
|
+
reject(new Error(`[@ait-co/devtools] Prompt timeout for "${type}" after ${PROMPT_TIMEOUT_MS / 1e3}s. Is @ait-co/devtools/panel imported?`));
|
|
248
|
+
}, PROMPT_TIMEOUT_MS);
|
|
249
|
+
const handler = (e) => {
|
|
250
|
+
cleanup();
|
|
251
|
+
resolve(e.detail);
|
|
252
|
+
};
|
|
253
|
+
const cancelHandler = () => {
|
|
254
|
+
cleanup();
|
|
255
|
+
reject(new Error(`[@ait-co/devtools] Prompt cancelled for "${type}"`));
|
|
256
|
+
};
|
|
257
|
+
window.addEventListener(eventName, handler);
|
|
258
|
+
window.addEventListener(cancelName, cancelHandler);
|
|
259
|
+
window.dispatchEvent(new CustomEvent("__ait:prompt-request", { detail: { type } }));
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
var Storage = createMockProxy("Storage", {
|
|
263
|
+
getItem: async (key) => {
|
|
264
|
+
return localStorage.getItem(`__ait_storage:${key}`);
|
|
265
|
+
},
|
|
266
|
+
setItem: async (key, value) => {
|
|
267
|
+
localStorage.setItem(`__ait_storage:${key}`, value);
|
|
268
|
+
},
|
|
269
|
+
removeItem: async (key) => {
|
|
270
|
+
localStorage.removeItem(`__ait_storage:${key}`);
|
|
271
|
+
},
|
|
272
|
+
clearItems: async () => {
|
|
273
|
+
const keys = Object.keys(localStorage).filter((k) => k.startsWith("__ait_storage:"));
|
|
274
|
+
keys.forEach((k) => localStorage.removeItem(k));
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
var Accuracy = /* @__PURE__ */ ((Accuracy2) => {
|
|
278
|
+
Accuracy2[Accuracy2["Lowest"] = 1] = "Lowest";
|
|
279
|
+
Accuracy2[Accuracy2["Low"] = 2] = "Low";
|
|
280
|
+
Accuracy2[Accuracy2["Balanced"] = 3] = "Balanced";
|
|
281
|
+
Accuracy2[Accuracy2["High"] = 4] = "High";
|
|
282
|
+
Accuracy2[Accuracy2["Highest"] = 5] = "Highest";
|
|
283
|
+
Accuracy2[Accuracy2["BestForNavigation"] = 6] = "BestForNavigation";
|
|
284
|
+
return Accuracy2;
|
|
285
|
+
})(Accuracy || {});
|
|
286
|
+
function buildLocation() {
|
|
287
|
+
return {
|
|
288
|
+
coords: { ...aitState.state.location.coords },
|
|
289
|
+
timestamp: Date.now(),
|
|
290
|
+
accessLocation: aitState.state.location.accessLocation
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
async function getCurrentLocationMock() {
|
|
294
|
+
return buildLocation();
|
|
295
|
+
}
|
|
296
|
+
async function getCurrentLocationWeb() {
|
|
297
|
+
return new Promise((resolve) => {
|
|
298
|
+
if (!navigator.geolocation) {
|
|
299
|
+
console.warn("[@ait-co/devtools] Geolocation API not available, falling back to mock");
|
|
300
|
+
resolve(buildLocation());
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
navigator.geolocation.getCurrentPosition(
|
|
304
|
+
(pos) => {
|
|
305
|
+
resolve({
|
|
306
|
+
coords: {
|
|
307
|
+
latitude: pos.coords.latitude,
|
|
308
|
+
longitude: pos.coords.longitude,
|
|
309
|
+
altitude: pos.coords.altitude ?? 0,
|
|
310
|
+
accuracy: pos.coords.accuracy,
|
|
311
|
+
altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,
|
|
312
|
+
heading: pos.coords.heading ?? 0
|
|
313
|
+
},
|
|
314
|
+
timestamp: pos.timestamp,
|
|
315
|
+
accessLocation: "FINE"
|
|
316
|
+
});
|
|
317
|
+
},
|
|
318
|
+
() => {
|
|
319
|
+
console.warn("[@ait-co/devtools] Geolocation failed, falling back to mock");
|
|
320
|
+
resolve(buildLocation());
|
|
321
|
+
}
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
async function getCurrentLocationPrompt() {
|
|
326
|
+
return waitForPromptResponse("location");
|
|
327
|
+
}
|
|
328
|
+
var _getCurrentLocation = async (_options) => {
|
|
329
|
+
checkPermission("geolocation", "getCurrentLocation");
|
|
330
|
+
const mode = aitState.state.deviceModes.location;
|
|
331
|
+
if (mode === "web") return getCurrentLocationWeb();
|
|
332
|
+
if (mode === "prompt") return getCurrentLocationPrompt();
|
|
333
|
+
return getCurrentLocationMock();
|
|
334
|
+
};
|
|
335
|
+
var getCurrentLocation = withPermission(_getCurrentLocation, "geolocation");
|
|
336
|
+
function startUpdateLocationMock(eventParams) {
|
|
337
|
+
const { onEvent, options } = eventParams;
|
|
338
|
+
const interval = Math.max(options.timeInterval, 500);
|
|
339
|
+
const id = setInterval(() => {
|
|
340
|
+
const loc = buildLocation();
|
|
341
|
+
loc.coords.latitude += (Math.random() - 0.5) * 1e-4;
|
|
342
|
+
loc.coords.longitude += (Math.random() - 0.5) * 1e-4;
|
|
343
|
+
onEvent(loc);
|
|
344
|
+
}, interval);
|
|
345
|
+
return () => clearInterval(id);
|
|
346
|
+
}
|
|
347
|
+
function startUpdateLocationWeb(eventParams) {
|
|
348
|
+
const { onEvent, onError } = eventParams;
|
|
349
|
+
if (!navigator.geolocation) {
|
|
350
|
+
console.warn("[@ait-co/devtools] Geolocation API not available, falling back to mock");
|
|
351
|
+
return startUpdateLocationMock(eventParams);
|
|
352
|
+
}
|
|
353
|
+
const watchId = navigator.geolocation.watchPosition(
|
|
354
|
+
(pos) => {
|
|
355
|
+
onEvent({
|
|
356
|
+
coords: {
|
|
357
|
+
latitude: pos.coords.latitude,
|
|
358
|
+
longitude: pos.coords.longitude,
|
|
359
|
+
altitude: pos.coords.altitude ?? 0,
|
|
360
|
+
accuracy: pos.coords.accuracy,
|
|
361
|
+
altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,
|
|
362
|
+
heading: pos.coords.heading ?? 0
|
|
363
|
+
},
|
|
364
|
+
timestamp: pos.timestamp,
|
|
365
|
+
accessLocation: "FINE"
|
|
366
|
+
});
|
|
367
|
+
},
|
|
368
|
+
(err) => onError(err)
|
|
369
|
+
);
|
|
370
|
+
return () => navigator.geolocation.clearWatch(watchId);
|
|
371
|
+
}
|
|
372
|
+
function startUpdateLocationPrompt(eventParams) {
|
|
373
|
+
const { onEvent } = eventParams;
|
|
374
|
+
const handler = (e) => {
|
|
375
|
+
onEvent(e.detail);
|
|
376
|
+
};
|
|
377
|
+
window.addEventListener("__ait:prompt-response:location-update", handler);
|
|
378
|
+
window.dispatchEvent(new CustomEvent("__ait:prompt-request", { detail: { type: "location-update" } }));
|
|
379
|
+
return () => window.removeEventListener("__ait:prompt-response:location-update", handler);
|
|
380
|
+
}
|
|
381
|
+
function _startUpdateLocation(eventParams) {
|
|
382
|
+
const mode = aitState.state.deviceModes.location;
|
|
383
|
+
if (mode === "web") return startUpdateLocationWeb(eventParams);
|
|
384
|
+
if (mode === "prompt") return startUpdateLocationPrompt(eventParams);
|
|
385
|
+
return startUpdateLocationMock(eventParams);
|
|
386
|
+
}
|
|
387
|
+
var startUpdateLocation = Object.assign(_startUpdateLocation, {
|
|
388
|
+
getPermission: () => withPermission(_getCurrentLocation, "geolocation").getPermission(),
|
|
389
|
+
openPermissionDialog: () => withPermission(_getCurrentLocation, "geolocation").openPermissionDialog()
|
|
390
|
+
});
|
|
391
|
+
async function openCameraMock() {
|
|
392
|
+
const images = getMockImages();
|
|
393
|
+
return { id: crypto.randomUUID(), dataUri: images[0] };
|
|
394
|
+
}
|
|
395
|
+
async function openCameraWeb() {
|
|
396
|
+
return new Promise((resolve, reject) => {
|
|
397
|
+
const input = document.createElement("input");
|
|
398
|
+
input.type = "file";
|
|
399
|
+
input.accept = "image/*";
|
|
400
|
+
input.capture = "environment";
|
|
401
|
+
let settled = false;
|
|
402
|
+
input.onchange = () => {
|
|
403
|
+
settled = true;
|
|
404
|
+
const file = input.files?.[0];
|
|
405
|
+
if (!file) {
|
|
406
|
+
reject(new Error("No file selected"));
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const reader = new FileReader();
|
|
410
|
+
reader.onload = () => resolve({ id: crypto.randomUUID(), dataUri: reader.result });
|
|
411
|
+
reader.onerror = () => reject(new Error("Failed to read file"));
|
|
412
|
+
reader.readAsDataURL(file);
|
|
413
|
+
};
|
|
414
|
+
const onFocus = () => {
|
|
415
|
+
setTimeout(() => {
|
|
416
|
+
if (!settled) reject(new Error("File picker cancelled"));
|
|
417
|
+
window.removeEventListener("focus", onFocus);
|
|
418
|
+
}, 300);
|
|
419
|
+
};
|
|
420
|
+
window.addEventListener("focus", onFocus);
|
|
421
|
+
input.click();
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async function openCameraPrompt() {
|
|
425
|
+
const dataUri = await waitForPromptResponse("camera");
|
|
426
|
+
return { id: crypto.randomUUID(), dataUri };
|
|
427
|
+
}
|
|
428
|
+
var _openCamera = async (_options) => {
|
|
429
|
+
checkPermission("camera", "openCamera");
|
|
430
|
+
const mode = aitState.state.deviceModes.camera;
|
|
431
|
+
if (mode === "web") return openCameraWeb();
|
|
432
|
+
if (mode === "prompt") return openCameraPrompt();
|
|
433
|
+
return openCameraMock();
|
|
434
|
+
};
|
|
435
|
+
var openCamera = withPermission(_openCamera, "camera");
|
|
436
|
+
async function fetchAlbumPhotosMock(maxCount) {
|
|
437
|
+
const images = getMockImages();
|
|
438
|
+
return images.slice(0, maxCount).map((dataUri) => ({ id: crypto.randomUUID(), dataUri }));
|
|
439
|
+
}
|
|
440
|
+
async function fetchAlbumPhotosWeb(maxCount) {
|
|
441
|
+
return new Promise((resolve, reject) => {
|
|
442
|
+
const input = document.createElement("input");
|
|
443
|
+
input.type = "file";
|
|
444
|
+
input.accept = "image/*";
|
|
445
|
+
input.multiple = true;
|
|
446
|
+
let settled = false;
|
|
447
|
+
input.onchange = async () => {
|
|
448
|
+
settled = true;
|
|
449
|
+
const files = Array.from(input.files ?? []).slice(0, maxCount);
|
|
450
|
+
if (files.length === 0) {
|
|
451
|
+
reject(new Error("No files selected"));
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const results = await Promise.all(
|
|
455
|
+
files.map((file) => new Promise((res, rej) => {
|
|
456
|
+
const reader = new FileReader();
|
|
457
|
+
reader.onload = () => res({ id: crypto.randomUUID(), dataUri: reader.result });
|
|
458
|
+
reader.onerror = () => rej(new Error("Failed to read file"));
|
|
459
|
+
reader.readAsDataURL(file);
|
|
460
|
+
}))
|
|
461
|
+
);
|
|
462
|
+
resolve(results);
|
|
463
|
+
};
|
|
464
|
+
const onFocus = () => {
|
|
465
|
+
setTimeout(() => {
|
|
466
|
+
if (!settled) reject(new Error("File picker cancelled"));
|
|
467
|
+
window.removeEventListener("focus", onFocus);
|
|
468
|
+
}, 300);
|
|
469
|
+
};
|
|
470
|
+
window.addEventListener("focus", onFocus);
|
|
471
|
+
input.click();
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
async function fetchAlbumPhotosPrompt(maxCount) {
|
|
475
|
+
const dataUris = await waitForPromptResponse("photos");
|
|
476
|
+
return dataUris.slice(0, maxCount).map((dataUri) => ({ id: crypto.randomUUID(), dataUri }));
|
|
477
|
+
}
|
|
478
|
+
var _fetchAlbumPhotos = async (options) => {
|
|
479
|
+
checkPermission("photos", "fetchAlbumPhotos");
|
|
480
|
+
const maxCount = options?.maxCount ?? 10;
|
|
481
|
+
const mode = aitState.state.deviceModes.photos;
|
|
482
|
+
if (mode === "web") return fetchAlbumPhotosWeb(maxCount);
|
|
483
|
+
if (mode === "prompt") return fetchAlbumPhotosPrompt(maxCount);
|
|
484
|
+
return fetchAlbumPhotosMock(maxCount);
|
|
485
|
+
};
|
|
486
|
+
var fetchAlbumPhotos = withPermission(_fetchAlbumPhotos, "photos");
|
|
487
|
+
var _fetchContacts = async (options) => {
|
|
488
|
+
checkPermission("contacts", "fetchContacts");
|
|
489
|
+
let contacts = aitState.state.contacts;
|
|
490
|
+
if (options.query?.contains) {
|
|
491
|
+
const q = options.query.contains.toLowerCase();
|
|
492
|
+
contacts = contacts.filter((c) => c.name.toLowerCase().includes(q) || c.phoneNumber.includes(q));
|
|
493
|
+
}
|
|
494
|
+
const sliced = contacts.slice(options.offset, options.offset + options.size);
|
|
495
|
+
const nextOffset = options.offset + options.size;
|
|
496
|
+
return {
|
|
497
|
+
result: sliced,
|
|
498
|
+
nextOffset: nextOffset < contacts.length ? nextOffset : null,
|
|
499
|
+
done: nextOffset >= contacts.length
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
var fetchContacts = withPermission(_fetchContacts, "contacts");
|
|
503
|
+
var _getClipboardText = async () => {
|
|
504
|
+
checkPermission("clipboard", "getClipboardText");
|
|
505
|
+
const mode = aitState.state.deviceModes.clipboard;
|
|
506
|
+
if (mode === "mock") return aitState.state.mockData.clipboardText;
|
|
507
|
+
try {
|
|
508
|
+
return await navigator.clipboard.readText();
|
|
509
|
+
} catch {
|
|
510
|
+
return "";
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
var getClipboardText = withPermission(_getClipboardText, "clipboard");
|
|
514
|
+
var _setClipboardText = async (text) => {
|
|
515
|
+
checkPermission("clipboard", "setClipboardText");
|
|
516
|
+
const mode = aitState.state.deviceModes.clipboard;
|
|
517
|
+
if (mode === "mock") {
|
|
518
|
+
aitState.patch("mockData", { clipboardText: text });
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
await navigator.clipboard.writeText(text);
|
|
522
|
+
};
|
|
523
|
+
var setClipboardText = withPermission(_setClipboardText, "clipboard");
|
|
524
|
+
function getNetworkStatusByMode() {
|
|
525
|
+
const mode = aitState.state.deviceModes.network;
|
|
526
|
+
if (mode === "mock") return null;
|
|
527
|
+
if (mode === "web") {
|
|
528
|
+
if (!navigator.onLine) return "OFFLINE";
|
|
529
|
+
const conn = navigator.connection;
|
|
530
|
+
if (conn?.effectiveType) {
|
|
531
|
+
const mapping = { "4g": "4G", "3g": "3G", "2g": "2G", "slow-2g": "2G" };
|
|
532
|
+
return mapping[conn.effectiveType] ?? "UNKNOWN";
|
|
533
|
+
}
|
|
534
|
+
return aitState.state.networkStatus;
|
|
535
|
+
}
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
async function generateHapticFeedback(options) {
|
|
539
|
+
console.log(`[@ait-co/devtools] haptic: ${options.type}`);
|
|
540
|
+
aitState.logAnalytics({ type: "haptic", params: { hapticType: options.type } });
|
|
541
|
+
}
|
|
542
|
+
async function saveBase64Data(params) {
|
|
543
|
+
const a = document.createElement("a");
|
|
544
|
+
a.href = `data:${params.mimeType};base64,${params.data}`;
|
|
545
|
+
a.download = params.fileName;
|
|
546
|
+
a.click();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export {
|
|
550
|
+
aitState,
|
|
551
|
+
createMockProxy,
|
|
552
|
+
getPermission,
|
|
553
|
+
openPermissionDialog,
|
|
554
|
+
requestPermission,
|
|
555
|
+
getDefaultPlaceholderImages,
|
|
556
|
+
Storage,
|
|
557
|
+
Accuracy,
|
|
558
|
+
getCurrentLocation,
|
|
559
|
+
startUpdateLocation,
|
|
560
|
+
openCamera,
|
|
561
|
+
fetchAlbumPhotos,
|
|
562
|
+
fetchContacts,
|
|
563
|
+
getClipboardText,
|
|
564
|
+
setClipboardText,
|
|
565
|
+
getNetworkStatusByMode,
|
|
566
|
+
generateHapticFeedback,
|
|
567
|
+
saveBase64Data
|
|
568
|
+
};
|
|
569
|
+
//# sourceMappingURL=chunk-6PPZTREF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mock/state.ts","../src/mock/permissions.ts","../src/mock/proxy.ts","../src/mock/device/index.ts"],"sourcesContent":["/**\n * @ait-co/devtools 중앙 상태 관리\n * DevTools Panel과 mock 구현체가 이 상태를 공유한다.\n */\n\nexport type PlatformOS = 'ios' | 'android';\nexport type OperationalEnvironment = 'toss' | 'sandbox';\nexport type NetworkStatus = 'OFFLINE' | 'WIFI' | '2G' | '3G' | '4G' | '5G' | 'WWAN' | 'UNKNOWN';\nexport type PermissionStatus = 'notDetermined' | 'denied' | 'allowed';\nexport type PermissionName = 'clipboard' | 'contacts' | 'photos' | 'geolocation' | 'camera' | 'microphone';\nexport type HapticFeedbackType = 'tickWeak' | 'tap' | 'tickMedium' | 'softMedium' | 'basicWeak' | 'basicMedium' | 'success' | 'error' | 'wiggle' | 'confetti';\n\nexport type DeviceApiMode = 'mock' | 'web' | 'prompt';\n\nexport interface DeviceModes {\n camera: DeviceApiMode;\n photos: DeviceApiMode;\n location: DeviceApiMode;\n network: 'mock' | 'web';\n clipboard: 'mock' | 'web';\n}\n\nexport interface MockData {\n images: string[];\n clipboardText: string;\n}\n\nexport interface LocationCoords {\n latitude: number;\n longitude: number;\n altitude: number;\n accuracy: number;\n altitudeAccuracy: number;\n heading: number;\n}\n\nexport interface MockLocation {\n coords: LocationCoords;\n timestamp: number;\n accessLocation?: 'FINE' | 'COARSE';\n}\n\nexport interface MockContact {\n name: string;\n phoneNumber: string;\n}\n\nexport interface MockIapProduct {\n sku: string;\n type: 'CONSUMABLE' | 'NON_CONSUMABLE' | 'SUBSCRIPTION';\n displayName: string;\n displayAmount: string;\n iconUrl: string;\n description: string;\n renewalCycle?: 'WEEKLY' | 'MONTHLY' | 'YEARLY';\n}\n\nexport type IapNextResult = 'success' | 'USER_CANCELED' | 'INVALID_PRODUCT_ID' | 'PAYMENT_PENDING' | 'NETWORK_ERROR' | 'ITEM_ALREADY_OWNED' | 'INTERNAL_ERROR';\n\nexport interface AnalyticsLogEntry {\n timestamp: number;\n type: string;\n params: Record<string, unknown>;\n}\n\nexport interface SafeAreaInsets {\n top: number;\n bottom: number;\n left: number;\n right: number;\n}\n\ntype Listener = () => void;\n\nexport interface AitDevtoolsState {\n // 환경\n platform: PlatformOS;\n environment: OperationalEnvironment;\n appVersion: string;\n locale: string;\n schemeUri: string;\n groupId: string;\n deploymentId: string;\n deviceId: string;\n\n // 브랜드\n brand: {\n displayName: string;\n icon: string;\n primaryColor: string;\n };\n\n // 네트워크\n networkStatus: NetworkStatus;\n\n // 권한\n permissions: Record<PermissionName, PermissionStatus>;\n\n // 위치\n location: MockLocation;\n\n // Safe Area\n safeAreaInsets: SafeAreaInsets;\n\n // 연락처\n contacts: MockContact[];\n\n // IAP\n iap: {\n products: MockIapProduct[];\n nextResult: IapNextResult;\n pendingOrders: Array<{ orderId: string; sku: string; paymentCompletedDate: string }>;\n completedOrders: Array<{ orderId: string; sku: string; status: 'COMPLETED' | 'REFUNDED'; date: string }>;\n };\n\n // 결제 (TossPay)\n payment: {\n nextResult: 'success' | 'fail';\n failReason: string;\n };\n\n // 로그인\n auth: {\n isLoggedIn: boolean;\n isTossLoginIntegrated: boolean;\n userKeyHash: string;\n };\n\n // 광고\n ads: {\n isLoaded: boolean;\n nextEvent: 'loaded' | 'clicked' | 'dismissed' | 'failedToShow' | 'impression' | 'userEarnedReward';\n };\n\n // 게임\n game: {\n profile: { nickname: string; profileImageUri: string } | null;\n leaderboardScores: Array<{ score: string; timestamp: number }>;\n };\n\n // 분석 로그\n analyticsLog: AnalyticsLogEntry[];\n\n // 디바이스 API 모드\n deviceModes: DeviceModes;\n\n // mock 모드용 더미 데이터\n mockData: MockData;\n\n // mock 활성화 상태\n panelEditable: boolean;\n}\n\nconst DEFAULT_STATE: AitDevtoolsState = {\n platform: 'ios',\n environment: 'sandbox',\n appVersion: '5.240.0',\n locale: 'ko-KR',\n schemeUri: '/',\n groupId: 'mock-group-id',\n deploymentId: 'mock-deployment-id',\n deviceId: '',\n\n brand: {\n displayName: 'Mock App',\n icon: '',\n primaryColor: '#3182F6',\n },\n\n networkStatus: 'WIFI',\n\n permissions: {\n clipboard: 'allowed',\n contacts: 'allowed',\n photos: 'allowed',\n geolocation: 'allowed',\n camera: 'allowed',\n microphone: 'notDetermined',\n },\n\n location: {\n coords: {\n latitude: 37.5665,\n longitude: 126.978,\n altitude: 0,\n accuracy: 10,\n altitudeAccuracy: 0,\n heading: 0,\n },\n timestamp: Date.now(),\n accessLocation: 'FINE',\n },\n\n safeAreaInsets: { top: 47, bottom: 34, left: 0, right: 0 },\n\n contacts: [\n { name: '홍길동', phoneNumber: '010-1234-5678' },\n { name: '김토스', phoneNumber: '010-9876-5432' },\n ],\n\n iap: {\n products: [\n {\n sku: 'mock-gem-100',\n type: 'CONSUMABLE',\n displayName: '보석 100개',\n displayAmount: '1,000원',\n iconUrl: '',\n description: '게임에서 사용할 수 있는 보석 100개',\n },\n ],\n nextResult: 'success',\n pendingOrders: [],\n completedOrders: [],\n },\n\n payment: {\n nextResult: 'success',\n failReason: '',\n },\n\n auth: {\n isLoggedIn: true,\n isTossLoginIntegrated: true,\n userKeyHash: 'mock-user-hash-abc123',\n },\n\n ads: {\n isLoaded: false,\n nextEvent: 'loaded',\n },\n\n game: {\n profile: { nickname: 'MockPlayer', profileImageUri: '' },\n leaderboardScores: [],\n },\n\n analyticsLog: [],\n\n deviceModes: {\n camera: 'mock',\n photos: 'mock',\n location: 'mock',\n network: 'mock',\n clipboard: 'web',\n },\n\n mockData: {\n images: [],\n clipboardText: '',\n },\n\n panelEditable: true,\n};\n\nfunction generateDeviceId(): string {\n const stored = localStorage.getItem('__ait_device_id');\n if (stored) return stored;\n const id = crypto.randomUUID();\n localStorage.setItem('__ait_device_id', id);\n return id;\n}\n\nexport class AitStateManager {\n private _state: AitDevtoolsState;\n private _listeners = new Set<Listener>();\n\n constructor() {\n this._state = structuredClone(DEFAULT_STATE);\n try {\n this._state.deviceId = generateDeviceId();\n } catch {\n this._state.deviceId = 'mock-device-' + Math.random().toString(36).slice(2);\n }\n }\n\n get state(): AitDevtoolsState {\n return this._state;\n }\n\n update(partial: Partial<AitDevtoolsState>) {\n this._state = { ...this._state, ...partial };\n this._notify();\n }\n\n /** 중첩 객체 업데이트용 */\n patch<K extends keyof AitDevtoolsState>(\n key: K,\n partial: Partial<AitDevtoolsState[K]>,\n ) {\n const current = this._state[key];\n if (typeof current === 'object' && current !== null && !Array.isArray(current)) {\n this._state = { ...this._state, [key]: { ...(current as Record<string, unknown>), ...(partial as Record<string, unknown>) } };\n } else {\n this._state = { ...this._state, [key]: partial as AitDevtoolsState[K] };\n }\n this._notify();\n }\n\n subscribe(listener: Listener): () => void {\n this._listeners.add(listener);\n return () => this._listeners.delete(listener);\n }\n\n /** 분석 로그 추가 */\n logAnalytics(entry: Omit<AnalyticsLogEntry, 'timestamp'>) {\n this._state = {\n ...this._state,\n analyticsLog: [...this._state.analyticsLog, { ...entry, timestamp: Date.now() }],\n };\n this._notify();\n }\n\n /** 이벤트 트리거 (backEvent, homeEvent 등) */\n trigger(event: string) {\n window.dispatchEvent(new CustomEvent(`__ait:${event}`));\n }\n\n reset() {\n const deviceId = this._state.deviceId;\n this._state = { ...structuredClone(DEFAULT_STATE), deviceId };\n this._notify();\n }\n\n private _notify() {\n for (const listener of this._listeners) {\n listener();\n }\n }\n}\n\nexport const aitState = new AitStateManager();\n\n// 브라우저 콘솔에서 접근 가능하도록\nif (typeof window !== 'undefined') {\n window.__ait = aitState;\n}\n","/**\n * 권한 시스템 mock\n * 각 디바이스 API (.getPermission, .openPermissionDialog)에 부착된다.\n */\n\nimport { aitState, type PermissionName, type PermissionStatus } from './state.js';\n\nexport async function getPermission(name: PermissionName): Promise<PermissionStatus> {\n return aitState.state.permissions[name];\n}\n\nexport async function openPermissionDialog(name: PermissionName): Promise<'allowed' | 'denied'> {\n const current = aitState.state.permissions[name];\n if (current === 'allowed') return 'allowed';\n // notDetermined나 denied일 때 — Panel에서 설정된 값을 사용\n // 기본적으로는 allowed로 전환\n aitState.patch('permissions', { [name]: 'allowed' });\n return 'allowed';\n}\n\nexport async function requestPermission(permission: { name: PermissionName; access: string }): Promise<'allowed' | 'denied'> {\n return openPermissionDialog(permission.name);\n}\n\n/** 권한이 필요한 함수에 .getPermission(), .openPermissionDialog()를 부착 */\nexport function withPermission<T extends (...args: never[]) => unknown>(\n fn: T,\n permissionName: PermissionName,\n): T & { getPermission: () => Promise<PermissionStatus>; openPermissionDialog: () => Promise<'allowed' | 'denied'> } {\n const enhanced = fn as T & {\n getPermission: () => Promise<PermissionStatus>;\n openPermissionDialog: () => Promise<'allowed' | 'denied'>;\n };\n enhanced.getPermission = () => getPermission(permissionName);\n enhanced.openPermissionDialog = () => openPermissionDialog(permissionName);\n return enhanced;\n}\n\n/** 권한 체크 후 denied면 에러 throw */\nexport function checkPermission(name: PermissionName, fnName: string): void {\n const status = aitState.state.permissions[name];\n if (status === 'denied') {\n throw new Error(`[@ait-co/devtools] ${fnName}: Permission \"${name}\" is denied. Change it in the DevTools panel.`);\n }\n}\n","/**\n * 미구현 API용 Proxy fallback\n * SDK에 새 메서드가 추가되었을 때 크래시 없이 경고만 출력한다.\n */\n\nconst WARNED = new Set<string>();\n\n/** 테스트에서 WARNED 캐시를 초기화할 때 사용 */\nexport function resetWarned(): void {\n WARNED.clear();\n}\n\nexport function createMockProxy<T extends Record<string, unknown>>(\n moduleName: string,\n implementations: T,\n): T {\n return new Proxy(implementations, {\n get(target, prop: string) {\n if (prop in target) return target[prop];\n if (typeof prop === 'symbol') return undefined;\n\n if (!WARNED.has(`${moduleName}.${prop}`)) {\n console.warn(\n `[@ait-co/devtools] ${moduleName}.${prop} is not mocked yet. Returning no-op. ` +\n `Please update @ait-co/devtools or file an issue.`,\n );\n WARNED.add(`${moduleName}.${prop}`);\n }\n return async () => undefined;\n },\n }) as T;\n}\n","/**\n * 디바이스 기능 mock\n * Storage, Location, Camera, Photos, Contacts, Clipboard, Haptic\n *\n * 각 API는 deviceModes 설정에 따라 mock/web/prompt 모드로 동작한다.\n */\n\nimport { aitState, type MockLocation, type NetworkStatus } from '../state.js';\nimport { createMockProxy } from '../proxy.js';\nimport { withPermission, checkPermission } from '../permissions.js';\n\n// --- Placeholder Image Generator ---\n\nfunction generatePlaceholderImage(width: number, height: number, text: string, color: string): string {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n // jsdom 등 Canvas API 미지원 환경에서는 간단한 SVG data URI 반환\n 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>`;\n return 'data:image/svg+xml;base64,' + btoa(svg);\n }\n ctx.fillStyle = color;\n ctx.fillRect(0, 0, width, height);\n ctx.fillStyle = 'white';\n ctx.font = '16px sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, width / 2, height / 2);\n return canvas.toDataURL('image/png');\n}\n\nconst DEFAULT_PLACEHOLDERS = [\n { text: 'Mock Photo 1', color: '#3182F6' },\n { text: 'Mock Photo 2', color: '#27ae60' },\n { text: 'Mock Photo 3', color: '#e67e22' },\n];\n\nlet cachedPlaceholders: string[] | null = null;\n\nexport function getDefaultPlaceholderImages(): string[] {\n if (!cachedPlaceholders) {\n cachedPlaceholders = DEFAULT_PLACEHOLDERS.map(p => generatePlaceholderImage(320, 240, p.text, p.color));\n }\n return [...cachedPlaceholders];\n}\n\nfunction getMockImages(): string[] {\n const images = aitState.state.mockData.images;\n if (images.length > 0) return images;\n return getDefaultPlaceholderImages();\n}\n\n// --- Prompt Mode Helper ---\n\nconst PROMPT_TIMEOUT_MS = 30_000;\n\nfunction waitForPromptResponse<T>(type: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const eventName = '__ait:prompt-response:' + type;\n const cancelName = '__ait:prompt-cancel';\n\n function cleanup() {\n clearTimeout(timer);\n window.removeEventListener(eventName, handler);\n window.removeEventListener(cancelName, cancelHandler);\n }\n\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(`[@ait-co/devtools] Prompt timeout for \"${type}\" after ${PROMPT_TIMEOUT_MS / 1000}s. Is @ait-co/devtools/panel imported?`));\n }, PROMPT_TIMEOUT_MS);\n\n const handler = (e: Event) => {\n cleanup();\n resolve((e as CustomEvent).detail as T);\n };\n\n const cancelHandler = () => {\n cleanup();\n reject(new Error(`[@ait-co/devtools] Prompt cancelled for \"${type}\"`));\n };\n\n window.addEventListener(eventName, handler);\n window.addEventListener(cancelName, cancelHandler);\n window.dispatchEvent(new CustomEvent('__ait:prompt-request', { detail: { type } }));\n });\n}\n\n// --- Storage ---\n\nexport const Storage = createMockProxy('Storage', {\n getItem: async (key: string): Promise<string | null> => {\n return localStorage.getItem(`__ait_storage:${key}`);\n },\n setItem: async (key: string, value: string): Promise<void> => {\n localStorage.setItem(`__ait_storage:${key}`, value);\n },\n removeItem: async (key: string): Promise<void> => {\n localStorage.removeItem(`__ait_storage:${key}`);\n },\n clearItems: async (): Promise<void> => {\n const keys = Object.keys(localStorage).filter(k => k.startsWith('__ait_storage:'));\n keys.forEach(k => localStorage.removeItem(k));\n },\n});\n\n// --- Location ---\n\nenum Accuracy { Lowest = 1, Low = 2, Balanced = 3, High = 4, Highest = 5, BestForNavigation = 6 }\nexport { Accuracy };\n\nfunction buildLocation(): MockLocation {\n return {\n coords: { ...aitState.state.location.coords },\n timestamp: Date.now(),\n accessLocation: aitState.state.location.accessLocation,\n };\n}\n\n// -- getCurrentLocation --\n\nasync function getCurrentLocationMock(): Promise<MockLocation> {\n return buildLocation();\n}\n\nasync function getCurrentLocationWeb(): Promise<MockLocation> {\n return new Promise((resolve) => {\n if (!navigator.geolocation) {\n console.warn('[@ait-co/devtools] Geolocation API not available, falling back to mock');\n resolve(buildLocation());\n return;\n }\n navigator.geolocation.getCurrentPosition(\n (pos) => {\n resolve({\n coords: {\n latitude: pos.coords.latitude,\n longitude: pos.coords.longitude,\n altitude: pos.coords.altitude ?? 0,\n accuracy: pos.coords.accuracy,\n altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,\n heading: pos.coords.heading ?? 0,\n },\n timestamp: pos.timestamp,\n accessLocation: 'FINE',\n });\n },\n () => {\n console.warn('[@ait-co/devtools] Geolocation failed, falling back to mock');\n resolve(buildLocation());\n },\n );\n });\n}\n\nasync function getCurrentLocationPrompt(): Promise<MockLocation> {\n return waitForPromptResponse<MockLocation>('location');\n}\n\nconst _getCurrentLocation = async (_options?: { accuracy: Accuracy }): Promise<MockLocation> => {\n checkPermission('geolocation', 'getCurrentLocation');\n const mode = aitState.state.deviceModes.location;\n if (mode === 'web') return getCurrentLocationWeb();\n if (mode === 'prompt') return getCurrentLocationPrompt();\n return getCurrentLocationMock();\n};\nexport const getCurrentLocation = withPermission(_getCurrentLocation, 'geolocation');\n\n// -- startUpdateLocation --\n\ninterface StartUpdateLocationEventParams {\n onEvent: (response: MockLocation) => void;\n onError: (error: unknown) => void;\n options: { accuracy: Accuracy; timeInterval: number; distanceInterval: number };\n}\n\nfunction startUpdateLocationMock(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent, options } = eventParams;\n const interval = Math.max(options.timeInterval, 500);\n const id = setInterval(() => {\n const loc = buildLocation();\n loc.coords.latitude += (Math.random() - 0.5) * 0.0001;\n loc.coords.longitude += (Math.random() - 0.5) * 0.0001;\n onEvent(loc);\n }, interval);\n return () => clearInterval(id);\n}\n\nfunction startUpdateLocationWeb(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent, onError } = eventParams;\n if (!navigator.geolocation) {\n console.warn('[@ait-co/devtools] Geolocation API not available, falling back to mock');\n return startUpdateLocationMock(eventParams);\n }\n const watchId = navigator.geolocation.watchPosition(\n (pos) => {\n onEvent({\n coords: {\n latitude: pos.coords.latitude,\n longitude: pos.coords.longitude,\n altitude: pos.coords.altitude ?? 0,\n accuracy: pos.coords.accuracy,\n altitudeAccuracy: pos.coords.altitudeAccuracy ?? 0,\n heading: pos.coords.heading ?? 0,\n },\n timestamp: pos.timestamp,\n accessLocation: 'FINE',\n });\n },\n (err) => onError(err),\n );\n return () => navigator.geolocation.clearWatch(watchId);\n}\n\nfunction startUpdateLocationPrompt(eventParams: StartUpdateLocationEventParams): () => void {\n const { onEvent } = eventParams;\n const handler = (e: Event) => {\n onEvent((e as CustomEvent).detail as MockLocation);\n };\n window.addEventListener('__ait:prompt-response:location-update', handler);\n window.dispatchEvent(new CustomEvent('__ait:prompt-request', { detail: { type: 'location-update' } }));\n return () => window.removeEventListener('__ait:prompt-response:location-update', handler);\n}\n\nfunction _startUpdateLocation(eventParams: StartUpdateLocationEventParams): () => void {\n const mode = aitState.state.deviceModes.location;\n if (mode === 'web') return startUpdateLocationWeb(eventParams);\n if (mode === 'prompt') return startUpdateLocationPrompt(eventParams);\n return startUpdateLocationMock(eventParams);\n}\n\nexport const startUpdateLocation = Object.assign(_startUpdateLocation, {\n getPermission: () => withPermission(_getCurrentLocation, 'geolocation').getPermission(),\n openPermissionDialog: () => withPermission(_getCurrentLocation, 'geolocation').openPermissionDialog(),\n});\n\n// --- Camera ---\n\nasync function openCameraMock(): Promise<{ id: string; dataUri: string }> {\n const images = getMockImages();\n return { id: crypto.randomUUID(), dataUri: images[0] };\n}\n\nasync function openCameraWeb(): Promise<{ id: string; dataUri: string }> {\n return new Promise((resolve, reject) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.capture = 'environment';\n let settled = false;\n input.onchange = () => {\n settled = true;\n const file = input.files?.[0];\n if (!file) { reject(new Error('No file selected')); return; }\n const reader = new FileReader();\n reader.onload = () => resolve({ id: crypto.randomUUID(), dataUri: reader.result as string });\n reader.onerror = () => reject(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n };\n // Detect file picker cancel via focus heuristic.\n // Note: unreliable on some mobile browsers and Safari where focus events differ.\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) reject(new Error('File picker cancelled'));\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function openCameraPrompt(): Promise<{ id: string; dataUri: string }> {\n const dataUri = await waitForPromptResponse<string>('camera');\n return { id: crypto.randomUUID(), dataUri };\n}\n\nconst _openCamera = async (_options?: { base64?: boolean; maxWidth?: number }): Promise<{ id: string; dataUri: string }> => {\n checkPermission('camera', 'openCamera');\n const mode = aitState.state.deviceModes.camera;\n if (mode === 'web') return openCameraWeb();\n if (mode === 'prompt') return openCameraPrompt();\n return openCameraMock();\n};\nexport const openCamera = withPermission(_openCamera, 'camera');\n\n// --- Album Photos ---\n\nasync function fetchAlbumPhotosMock(maxCount: number): Promise<Array<{ id: string; dataUri: string }>> {\n const images = getMockImages();\n return images.slice(0, maxCount).map(dataUri => ({ id: crypto.randomUUID(), dataUri }));\n}\n\nasync function fetchAlbumPhotosWeb(maxCount: number): Promise<Array<{ id: string; dataUri: string }>> {\n return new Promise((resolve, reject) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = 'image/*';\n input.multiple = true;\n let settled = false;\n input.onchange = async () => {\n settled = true;\n const files = Array.from(input.files ?? []).slice(0, maxCount);\n if (files.length === 0) { reject(new Error('No files selected')); return; }\n const results = await Promise.all(\n files.map(file => new Promise<{ id: string; dataUri: string }>((res, rej) => {\n const reader = new FileReader();\n reader.onload = () => res({ id: crypto.randomUUID(), dataUri: reader.result as string });\n reader.onerror = () => rej(new Error('Failed to read file'));\n reader.readAsDataURL(file);\n })),\n );\n resolve(results);\n };\n const onFocus = () => {\n setTimeout(() => {\n if (!settled) reject(new Error('File picker cancelled'));\n window.removeEventListener('focus', onFocus);\n }, 300);\n };\n window.addEventListener('focus', onFocus);\n input.click();\n });\n}\n\nasync function fetchAlbumPhotosPrompt(maxCount: number): Promise<Array<{ id: string; dataUri: string }>> {\n const dataUris = await waitForPromptResponse<string[]>('photos');\n return dataUris.slice(0, maxCount).map(dataUri => ({ id: crypto.randomUUID(), dataUri }));\n}\n\nconst _fetchAlbumPhotos = async (options?: { maxCount?: number; maxWidth?: number; base64?: boolean }): Promise<Array<{ id: string; dataUri: string }>> => {\n checkPermission('photos', 'fetchAlbumPhotos');\n const maxCount = options?.maxCount ?? 10;\n const mode = aitState.state.deviceModes.photos;\n if (mode === 'web') return fetchAlbumPhotosWeb(maxCount);\n if (mode === 'prompt') return fetchAlbumPhotosPrompt(maxCount);\n return fetchAlbumPhotosMock(maxCount);\n};\nexport const fetchAlbumPhotos = withPermission(_fetchAlbumPhotos, 'photos');\n\n// --- Contacts ---\n\nconst _fetchContacts = async (options: { size: number; offset: number; query?: { contains?: string } }) => {\n checkPermission('contacts', 'fetchContacts');\n let contacts = aitState.state.contacts;\n if (options.query?.contains) {\n const q = options.query.contains.toLowerCase();\n contacts = contacts.filter(c => c.name.toLowerCase().includes(q) || c.phoneNumber.includes(q));\n }\n const sliced = contacts.slice(options.offset, options.offset + options.size);\n const nextOffset = options.offset + options.size;\n return {\n result: sliced,\n nextOffset: nextOffset < contacts.length ? nextOffset : null,\n done: nextOffset >= contacts.length,\n };\n};\nexport const fetchContacts = withPermission(_fetchContacts, 'contacts');\n\n// --- Clipboard ---\n\nconst _getClipboardText = async (): Promise<string> => {\n checkPermission('clipboard', 'getClipboardText');\n const mode = aitState.state.deviceModes.clipboard;\n if (mode === 'mock') return aitState.state.mockData.clipboardText;\n // web mode (default)\n try {\n return await navigator.clipboard.readText();\n } catch {\n return '';\n }\n};\nexport const getClipboardText = withPermission(_getClipboardText, 'clipboard');\n\nconst _setClipboardText = async (text: string): Promise<void> => {\n checkPermission('clipboard', 'setClipboardText');\n const mode = aitState.state.deviceModes.clipboard;\n if (mode === 'mock') {\n aitState.patch('mockData', { clipboardText: text });\n return;\n }\n // web mode (default)\n await navigator.clipboard.writeText(text);\n};\nexport const setClipboardText = withPermission(_setClipboardText, 'clipboard');\n\n// --- Network Status (mode-aware helper, lives in device to avoid circular dep with navigation) ---\n\n/**\n * Web mode: uses navigator.connection.effectiveType (4g/3g/2g) and navigator.onLine.\n * Limitations: WIFI, 5G, WWAN cannot be detected via the Network Information API.\n * Falls back to state-based value when effectiveType is unavailable.\n */\nexport function getNetworkStatusByMode(): NetworkStatus | null {\n const mode = aitState.state.deviceModes.network;\n if (mode === 'mock') return null; // use default state-based logic\n if (mode === 'web') {\n if (!navigator.onLine) return 'OFFLINE';\n const conn = (navigator as unknown as Record<string, unknown>).connection as { effectiveType?: string } | undefined;\n if (conn?.effectiveType) {\n const mapping: Record<string, NetworkStatus> = { '4g': '4G', '3g': '3G', '2g': '2G', 'slow-2g': '2G' };\n return mapping[conn.effectiveType] ?? 'UNKNOWN';\n }\n return aitState.state.networkStatus;\n }\n // prompt mode: not supported for network, fall back to mock\n return null;\n}\n\n// --- Haptic Feedback ---\n\nexport async function generateHapticFeedback(options: { type: string }): Promise<void> {\n console.log(`[@ait-co/devtools] haptic: ${options.type}`);\n aitState.logAnalytics({ type: 'haptic', params: { hapticType: options.type } });\n}\n\n// --- Save Base64 ---\n\nexport async function saveBase64Data(params: { data: string; fileName: string; mimeType: string }): Promise<void> {\n const a = document.createElement('a');\n a.href = `data:${params.mimeType};base64,${params.data}`;\n a.download = params.fileName;\n a.click();\n}\n"],"mappings":"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","names":["Accuracy"]}
|