@aippy/runtime 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.
@@ -0,0 +1,408 @@
1
+ import { UAParser } from "ua-parser-js";
2
+ class PlatformDetector {
3
+ parser;
4
+ constructor() {
5
+ this.parser = new UAParser();
6
+ }
7
+ /**
8
+ * Get platform information
9
+ */
10
+ getPlatformInfo() {
11
+ const result = this.parser.getResult();
12
+ const platformName = this.normalizePlatformName(result.os.name);
13
+ const browserName = this.normalizeBrowserName(result.browser.name);
14
+ const isMobile = this.isMobileDevice();
15
+ const isDesktop = !isMobile;
16
+ return {
17
+ name: platformName,
18
+ version: result.os.version,
19
+ browser: browserName,
20
+ browserVersion: result.browser.version,
21
+ isMobile,
22
+ isDesktop
23
+ };
24
+ }
25
+ /**
26
+ * Get platform capabilities
27
+ */
28
+ getCapabilities() {
29
+ return {
30
+ serviceWorker: "serviceWorker" in navigator,
31
+ pushNotifications: "PushManager" in window,
32
+ webShare: "share" in navigator,
33
+ clipboard: "clipboard" in navigator,
34
+ webRTC: !!(window.RTCPeerConnection || window.webkitRTCPeerConnection),
35
+ webGL: !!this.getWebGLContext(),
36
+ webAssembly: "WebAssembly" in window
37
+ };
38
+ }
39
+ /**
40
+ * Check if device is mobile
41
+ */
42
+ isMobileDevice() {
43
+ const userAgent = navigator.userAgent;
44
+ const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
45
+ return mobileRegex.test(userAgent) || this.isTouchDevice();
46
+ }
47
+ /**
48
+ * Check if device supports touch
49
+ */
50
+ isTouchDevice() {
51
+ return "ontouchstart" in window || navigator.maxTouchPoints > 0;
52
+ }
53
+ /**
54
+ * Check if running in standalone mode (PWA)
55
+ */
56
+ isStandalone() {
57
+ return window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true;
58
+ }
59
+ /**
60
+ * Check if running in iOS Safari
61
+ */
62
+ isIOSSafari() {
63
+ const info = this.getPlatformInfo();
64
+ return info.name === "ios" && info.browser === "safari";
65
+ }
66
+ /**
67
+ * Check if running in Android Chrome
68
+ */
69
+ isAndroidChrome() {
70
+ const info = this.getPlatformInfo();
71
+ return info.name === "android" && info.browser === "chrome";
72
+ }
73
+ normalizePlatformName(name) {
74
+ if (!name) return "unknown";
75
+ const normalized = name.toLowerCase();
76
+ if (normalized.includes("ios")) return "ios";
77
+ if (normalized.includes("android")) return "android";
78
+ if (normalized.includes("windows")) return "windows";
79
+ if (normalized.includes("mac")) return "macos";
80
+ if (normalized.includes("linux")) return "linux";
81
+ return "unknown";
82
+ }
83
+ normalizeBrowserName(name) {
84
+ if (!name) return "unknown";
85
+ const normalized = name.toLowerCase();
86
+ if (normalized.includes("chrome")) return "chrome";
87
+ if (normalized.includes("firefox")) return "firefox";
88
+ if (normalized.includes("safari")) return "safari";
89
+ if (normalized.includes("edge")) return "edge";
90
+ return "unknown";
91
+ }
92
+ getWebGLContext() {
93
+ try {
94
+ const canvas = document.createElement("canvas");
95
+ return canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+ }
101
+ const platform = new PlatformDetector();
102
+ class PerformanceMonitor {
103
+ /**
104
+ * Get Core Web Vitals metrics
105
+ */
106
+ async getCoreWebVitals() {
107
+ const metrics = {};
108
+ try {
109
+ const fcpEntry = performance.getEntriesByName("first-contentful-paint")[0];
110
+ if (fcpEntry) {
111
+ metrics.fcp = fcpEntry.startTime;
112
+ }
113
+ const lcpEntries = performance.getEntriesByType("largest-contentful-paint");
114
+ if (lcpEntries.length > 0) {
115
+ const lcpEntry = lcpEntries[lcpEntries.length - 1];
116
+ metrics.lcp = lcpEntry.startTime;
117
+ }
118
+ const fidEntries = performance.getEntriesByType("first-input");
119
+ if (fidEntries.length > 0) {
120
+ const fidEntry = fidEntries[0];
121
+ metrics.fid = fidEntry.processingStart - fidEntry.startTime;
122
+ }
123
+ const clsEntries = performance.getEntriesByType("layout-shift");
124
+ if (clsEntries.length > 0) {
125
+ let clsValue = 0;
126
+ for (const entry of clsEntries) {
127
+ const clsEntry = entry;
128
+ if (!clsEntry.hadRecentInput) {
129
+ clsValue += clsEntry.value;
130
+ }
131
+ }
132
+ metrics.cls = clsValue;
133
+ }
134
+ const tti = this.calculateTTI();
135
+ if (tti) {
136
+ metrics.tti = tti;
137
+ }
138
+ } catch (error) {
139
+ console.warn("Failed to get Core Web Vitals:", error);
140
+ }
141
+ return metrics;
142
+ }
143
+ /**
144
+ * Get navigation timing metrics
145
+ */
146
+ getNavigationTiming() {
147
+ const navigation = performance.getEntriesByType("navigation")[0];
148
+ if (!navigation) {
149
+ return null;
150
+ }
151
+ return {
152
+ // DNS lookup time
153
+ dns: navigation.domainLookupEnd - navigation.domainLookupStart,
154
+ // TCP connection time
155
+ tcp: navigation.connectEnd - navigation.connectStart,
156
+ // Request time
157
+ request: navigation.responseStart - navigation.requestStart,
158
+ // Response time
159
+ response: navigation.responseEnd - navigation.responseStart,
160
+ // DOM processing time
161
+ domProcessing: navigation.domComplete - navigation.domInteractive,
162
+ // Total page load time
163
+ total: navigation.loadEventEnd - navigation.fetchStart
164
+ };
165
+ }
166
+ /**
167
+ * Get resource timing metrics
168
+ */
169
+ getResourceTiming() {
170
+ const resources = performance.getEntriesByType("resource");
171
+ return resources.map((resource) => ({
172
+ name: resource.name,
173
+ duration: resource.duration,
174
+ size: resource.transferSize,
175
+ type: this.getResourceType(resource.name)
176
+ }));
177
+ }
178
+ /**
179
+ * Measure function execution time
180
+ */
181
+ measureFunction(fn, name) {
182
+ const start = performance.now();
183
+ const result = fn();
184
+ const end = performance.now();
185
+ if (name) {
186
+ performance.mark(`${name}-start`);
187
+ performance.mark(`${name}-end`);
188
+ performance.measure(name, `${name}-start`, `${name}-end`);
189
+ }
190
+ console.log(`Function ${name || "anonymous"} took ${end - start} milliseconds`);
191
+ return result;
192
+ }
193
+ /**
194
+ * Measure async function execution time
195
+ */
196
+ async measureAsyncFunction(fn, name) {
197
+ const start = performance.now();
198
+ const result = await fn();
199
+ const end = performance.now();
200
+ if (name) {
201
+ performance.mark(`${name}-start`);
202
+ performance.mark(`${name}-end`);
203
+ performance.measure(name, `${name}-start`, `${name}-end`);
204
+ }
205
+ console.log(`Async function ${name || "anonymous"} took ${end - start} milliseconds`);
206
+ return result;
207
+ }
208
+ /**
209
+ * Get memory usage (if available)
210
+ */
211
+ getMemoryUsage() {
212
+ if ("memory" in performance) {
213
+ const memory = performance.memory;
214
+ return {
215
+ used: memory.usedJSHeapSize,
216
+ total: memory.totalJSHeapSize,
217
+ limit: memory.jsHeapSizeLimit
218
+ };
219
+ }
220
+ return null;
221
+ }
222
+ calculateTTI() {
223
+ const navigation = performance.getEntriesByType("navigation")[0];
224
+ if (navigation) {
225
+ return navigation.domContentLoadedEventEnd - navigation.fetchStart;
226
+ }
227
+ return null;
228
+ }
229
+ getResourceType(url) {
230
+ if (url.includes(".js")) return "script";
231
+ if (url.includes(".css")) return "stylesheet";
232
+ if (url.includes(".png") || url.includes(".jpg") || url.includes(".gif")) return "image";
233
+ if (url.includes(".woff") || url.includes(".ttf")) return "font";
234
+ return "other";
235
+ }
236
+ }
237
+ const performanceMonitor = new PerformanceMonitor();
238
+ class PWAUtils {
239
+ /**
240
+ * Get PWA information
241
+ */
242
+ getPWAInfo() {
243
+ return {
244
+ isInstalled: this.isInstalled(),
245
+ isInstallable: this.isInstallable(),
246
+ canInstall: this.canInstall(),
247
+ isStandalone: this.isStandalone()
248
+ };
249
+ }
250
+ /**
251
+ * Check if PWA is installed
252
+ */
253
+ isInstalled() {
254
+ return this.isStandalone() || this.isInApp();
255
+ }
256
+ /**
257
+ * Check if PWA is installable
258
+ */
259
+ isInstallable() {
260
+ return "serviceWorker" in navigator && "PushManager" in window;
261
+ }
262
+ /**
263
+ * Check if install prompt is available
264
+ */
265
+ canInstall() {
266
+ return this.isInstallable() && !this.isInstalled();
267
+ }
268
+ /**
269
+ * Check if running in standalone mode
270
+ */
271
+ isStandalone() {
272
+ return window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true;
273
+ }
274
+ /**
275
+ * Check if running in app (iOS)
276
+ */
277
+ isInApp() {
278
+ return window.navigator.standalone === true;
279
+ }
280
+ /**
281
+ * Register service worker
282
+ */
283
+ async registerServiceWorker(swPath) {
284
+ if (!("serviceWorker" in navigator)) {
285
+ console.warn("Service Worker not supported");
286
+ return null;
287
+ }
288
+ try {
289
+ const registration = await navigator.serviceWorker.register(swPath);
290
+ console.log("Service Worker registered:", registration);
291
+ return registration;
292
+ } catch (error) {
293
+ console.error("Service Worker registration failed:", error);
294
+ return null;
295
+ }
296
+ }
297
+ /**
298
+ * Unregister service worker
299
+ */
300
+ async unregisterServiceWorker() {
301
+ if (!("serviceWorker" in navigator)) {
302
+ return false;
303
+ }
304
+ try {
305
+ const registrations = await navigator.serviceWorker.getRegistrations();
306
+ for (const registration of registrations) {
307
+ await registration.unregister();
308
+ }
309
+ return true;
310
+ } catch (error) {
311
+ console.error("Service Worker unregistration failed:", error);
312
+ return false;
313
+ }
314
+ }
315
+ /**
316
+ * Request notification permission
317
+ */
318
+ async requestNotificationPermission() {
319
+ if (!("Notification" in window)) {
320
+ throw new Error("Notifications not supported");
321
+ }
322
+ if (Notification.permission === "granted") {
323
+ return "granted";
324
+ }
325
+ if (Notification.permission === "denied") {
326
+ return "denied";
327
+ }
328
+ return await Notification.requestPermission();
329
+ }
330
+ /**
331
+ * Send notification
332
+ */
333
+ async sendNotification(title, options) {
334
+ if (!("Notification" in window)) {
335
+ throw new Error("Notifications not supported");
336
+ }
337
+ if (Notification.permission !== "granted") {
338
+ throw new Error("Notification permission not granted");
339
+ }
340
+ const notification = new Notification(title, options);
341
+ setTimeout(() => {
342
+ notification.close();
343
+ }, 5e3);
344
+ }
345
+ /**
346
+ * Share content using Web Share API
347
+ */
348
+ async share(data) {
349
+ if (!("share" in navigator)) {
350
+ throw new Error("Web Share API not supported");
351
+ }
352
+ try {
353
+ await navigator.share(data);
354
+ } catch (error) {
355
+ if (error instanceof Error && error.name !== "AbortError") {
356
+ throw new Error(`Share failed: ${error.message}`);
357
+ }
358
+ }
359
+ }
360
+ /**
361
+ * Copy text to clipboard
362
+ */
363
+ async copyToClipboard(text) {
364
+ if (!("clipboard" in navigator)) {
365
+ throw new Error("Clipboard API not supported");
366
+ }
367
+ try {
368
+ await navigator.clipboard.writeText(text);
369
+ } catch (error) {
370
+ throw new Error(`Copy to clipboard failed: ${error instanceof Error ? error.message : "Unknown error"}`);
371
+ }
372
+ }
373
+ /**
374
+ * Read text from clipboard
375
+ */
376
+ async readFromClipboard() {
377
+ if (!("clipboard" in navigator)) {
378
+ throw new Error("Clipboard API not supported");
379
+ }
380
+ try {
381
+ return await navigator.clipboard.readText();
382
+ } catch (error) {
383
+ throw new Error(`Read from clipboard failed: ${error instanceof Error ? error.message : "Unknown error"}`);
384
+ }
385
+ }
386
+ /**
387
+ * Get install prompt event
388
+ */
389
+ getInstallPromptEvent() {
390
+ return new Promise((resolve) => {
391
+ const handleBeforeInstallPrompt = (e) => {
392
+ e.preventDefault();
393
+ resolve(e);
394
+ window.removeEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
395
+ };
396
+ window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
397
+ });
398
+ }
399
+ }
400
+ const pwa = new PWAUtils();
401
+ export {
402
+ PlatformDetector as P,
403
+ PerformanceMonitor as a,
404
+ performanceMonitor as b,
405
+ PWAUtils as c,
406
+ pwa as d,
407
+ platform as p
408
+ };
@@ -0,0 +1,2 @@
1
+ export * from './useTweaks';
2
+ export type * from './types';
@@ -0,0 +1,5 @@
1
+ import { a, b } from "../useTweaks-mK5PAWOs.js";
2
+ export {
3
+ a as aippyTweaks,
4
+ b as aippyTweaksRuntime
5
+ };
@@ -0,0 +1,10 @@
1
+ import { TweaksConfig } from './types';
2
+ declare const aippyTweaksRuntime: {
3
+ tweaks: (config: TweaksConfig) => Record<string, {
4
+ useState: () => any;
5
+ }>;
6
+ };
7
+ export declare const aippyTweaks: (config: TweaksConfig) => Record<string, {
8
+ useState: () => any;
9
+ }>;
10
+ export { aippyTweaksRuntime };
@@ -0,0 +1,2 @@
1
+ export * from './tweaks/index'
2
+ export {}
@@ -0,0 +1,258 @@
1
+ import { useReducer, useEffect } from "react";
2
+ class Cancellable {
3
+ constructor(cancelFn) {
4
+ this.cancelFn = cancelFn;
5
+ }
6
+ cancelled = false;
7
+ cancel() {
8
+ if (!this.cancelled) {
9
+ this.cancelled = true;
10
+ this.cancelFn?.();
11
+ }
12
+ }
13
+ get isCancelled() {
14
+ return this.cancelled;
15
+ }
16
+ }
17
+ class PassthroughSubject {
18
+ listeners = [];
19
+ send(data) {
20
+ this.listeners.forEach((listener) => listener(data));
21
+ }
22
+ subscribe(callback) {
23
+ this.listeners.push(callback);
24
+ return new Cancellable(() => {
25
+ const index = this.listeners.indexOf(callback);
26
+ if (index > -1) {
27
+ this.listeners.splice(index, 1);
28
+ }
29
+ });
30
+ }
31
+ }
32
+ class TweaksManager {
33
+ values = {};
34
+ subject = new PassthroughSubject();
35
+ observable;
36
+ constructor(observable, config) {
37
+ this.observable = observable;
38
+ for (const [key, item] of Object.entries(config)) {
39
+ this.values[key] = item.value;
40
+ }
41
+ this.observable.subscribe((data) => {
42
+ if (data && data.values) {
43
+ console.log("🔄 [Aippy Tweaks] Processing external update:", data.values);
44
+ const changedKeys = [];
45
+ for (const [key, newValue] of Object.entries(data.values)) {
46
+ let convertedValue = newValue;
47
+ if (this.values[key] !== void 0) {
48
+ const originalType = typeof this.values[key];
49
+ if (originalType === "number" && typeof newValue === "string") {
50
+ convertedValue = parseFloat(newValue);
51
+ } else if (originalType === "boolean" && typeof newValue !== "boolean") {
52
+ convertedValue = newValue === "true" || newValue === true;
53
+ }
54
+ }
55
+ if (this.values[key] !== convertedValue) {
56
+ const oldValue = this.values[key];
57
+ this.values[key] = convertedValue;
58
+ this.subject.send(key);
59
+ changedKeys.push(key);
60
+ console.log(`📝 [Aippy Tweaks] Updated ${key}: ${oldValue} → ${convertedValue} (type: ${typeof convertedValue})`);
61
+ }
62
+ }
63
+ if (changedKeys.length > 0) {
64
+ console.log(`✨ [Aippy Tweaks] Successfully updated ${changedKeys.length} tweak(s):`, changedKeys);
65
+ } else {
66
+ console.log("💡 [Aippy Tweaks] No values changed in this update");
67
+ }
68
+ }
69
+ });
70
+ }
71
+ getValue(key) {
72
+ const value = this.values[key];
73
+ if (value === void 0) {
74
+ throw new Error(`Tweak key not found in values: ${key}`);
75
+ }
76
+ return value;
77
+ }
78
+ // React Hook integration
79
+ useState(key) {
80
+ const [value, forceUpdate] = useReducer(() => this.getValue(key), this.getValue(key));
81
+ useEffect(() => {
82
+ const subscription = this.subject.subscribe((changedKey) => {
83
+ if (changedKey === key) {
84
+ forceUpdate();
85
+ }
86
+ });
87
+ return () => {
88
+ subscription.cancel();
89
+ };
90
+ }, [key]);
91
+ return value;
92
+ }
93
+ }
94
+ class TweaksRuntime {
95
+ tweaksInstance = null;
96
+ tweaksDidWarn = false;
97
+ updateCallback;
98
+ // Store update callback function
99
+ originalConfig;
100
+ // Store original config for merging
101
+ tweaks(config) {
102
+ if (this.tweaksInstance) {
103
+ if (!this.tweaksDidWarn) {
104
+ this.tweaksDidWarn = true;
105
+ console.warn("⚠️ [Aippy Tweaks] tweaks() is expected to only be called once, returning previous value");
106
+ }
107
+ return this.tweaksInstance;
108
+ }
109
+ console.log("🚀 [Aippy Tweaks] Creating new tweaks runtime instance");
110
+ this.originalConfig = { ...config };
111
+ const subject = new PassthroughSubject();
112
+ const manager = new TweaksManager(subject, config);
113
+ const instance = {};
114
+ for (const key of Object.keys(config)) {
115
+ instance[key] = {
116
+ useState: () => manager.useState(key)
117
+ };
118
+ }
119
+ this.tweaksInstance = instance;
120
+ console.log("📋 [Aippy Tweaks] Created tweaks instance with keys:", Object.keys(config));
121
+ if (typeof window !== "undefined" && window.aippyTweaksRuntime) {
122
+ window.aippyTweaksRuntime.tweaksInstance = instance;
123
+ console.log("🌐 [Aippy Tweaks] Exposed tweaksInstance to window.aippyTweaksRuntime.tweaksInstance");
124
+ }
125
+ this.initializeExternalCommunication(config, (data) => {
126
+ subject.send(data);
127
+ });
128
+ return instance;
129
+ }
130
+ initializeExternalCommunication(config, callback) {
131
+ this.updateCallback = callback;
132
+ console.log("🎛️ [Aippy Tweaks] Initializing tweaks with config:", config);
133
+ console.log("🎛️ [Aippy Tweaks] Total tweaks count:", Object.keys(config).length);
134
+ const aippyListener = window.webkit?.messageHandlers?.aippyListener;
135
+ if (aippyListener) {
136
+ try {
137
+ const data = {
138
+ command: "tweaks.initialize",
139
+ parameters: JSON.stringify(config)
140
+ };
141
+ aippyListener.postMessage(data);
142
+ console.log("✅ [Aippy Tweaks] Successfully sent tweaks config to iOS app via aippyListener");
143
+ console.log("📤 [Aippy Tweaks] Sent data:", data);
144
+ } catch (error) {
145
+ console.warn("❌ [Aippy Tweaks] Failed to send tweaks config to iOS app:", error);
146
+ }
147
+ } else {
148
+ console.warn("⚠️ [Aippy Tweaks] webkit.messageHandlers.aippyListener not available");
149
+ console.log(
150
+ "🔍 [Aippy Tweaks] Available webkit handlers:",
151
+ window.webkit?.messageHandlers ? Object.keys(window.webkit.messageHandlers) : "none"
152
+ );
153
+ }
154
+ if (window.parent && window.parent !== window) {
155
+ try {
156
+ const message = {
157
+ type: "tweaks-initialize",
158
+ config: JSON.stringify(config)
159
+ };
160
+ window.parent.postMessage(message, "*");
161
+ console.log("📡 [Aippy Tweaks] Sent tweaks config to parent window:", message);
162
+ const runtimeMessage = {
163
+ type: "aippy-tweaks-ready",
164
+ aippyTweaksRuntime: {
165
+ tweaks: window.aippyTweaksRuntime?.tweaks,
166
+ tweaksInstance: window.aippyTweaksRuntime?.tweaksInstance
167
+ }
168
+ };
169
+ window.parent.postMessage(runtimeMessage, "*");
170
+ console.log("📡 [Aippy Tweaks] Sent aippyTweaksRuntime to parent window:", runtimeMessage);
171
+ } catch (error) {
172
+ console.warn("❌ [Aippy Tweaks] Failed to send to parent window:", error);
173
+ }
174
+ }
175
+ const messageHandler = (event) => {
176
+ if (event.data && event.data.type === "tweaks-update") {
177
+ console.log("📥 [Aippy Tweaks] Received tweaks update:", event.data);
178
+ callback(event.data);
179
+ }
180
+ };
181
+ window.addEventListener("message", messageHandler);
182
+ console.log("👂 [Aippy Tweaks] Listening for tweaks updates via window.postMessage");
183
+ }
184
+ // Process native data updates - Public method for external calls
185
+ processNativeData(data) {
186
+ if (!this.tweaksInstance) {
187
+ console.warn("⚠️ [Aippy Tweaks] processNativeData called but no tweaks instance exists");
188
+ return;
189
+ }
190
+ if (!this.updateCallback) {
191
+ console.warn("⚠️ [Aippy Tweaks] processNativeData called but no update callback available");
192
+ return;
193
+ }
194
+ try {
195
+ if (!data || typeof data !== "object") {
196
+ console.warn("⚠️ [Aippy Tweaks] Invalid data type received from native:", typeof data);
197
+ return;
198
+ }
199
+ if (!this.originalConfig) {
200
+ console.warn("⚠️ [Aippy Tweaks] No original config available for merging");
201
+ return;
202
+ }
203
+ const values = {};
204
+ const mergedConfig = {};
205
+ let processedCount = 0;
206
+ console.log("📱 [DEBUG] Starting to process iOS data and merge with original config...");
207
+ for (const [key, iosConfigItem] of Object.entries(data)) {
208
+ console.log(`📱 [DEBUG] Processing key: ${key}`, iosConfigItem);
209
+ if (iosConfigItem && typeof iosConfigItem === "object" && "value" in iosConfigItem) {
210
+ const iosItem = iosConfigItem;
211
+ const originalItem = this.originalConfig[key];
212
+ if (!originalItem) {
213
+ console.warn(`⚠️ [Aippy Tweaks] Key "${key}" not found in original config, skipping`);
214
+ continue;
215
+ }
216
+ const iosSpecificFields = /* @__PURE__ */ new Set(["tweakKey", "valueBefore", "tweaksType", "editType"]);
217
+ const mergedItem = { ...originalItem };
218
+ for (const [fieldName, fieldValue] of Object.entries(iosItem)) {
219
+ if (iosSpecificFields.has(fieldName)) {
220
+ continue;
221
+ }
222
+ if (fieldValue !== void 0 && fieldValue !== null) {
223
+ mergedItem[fieldName] = fieldValue;
224
+ }
225
+ }
226
+ mergedConfig[key] = mergedItem;
227
+ values[key] = iosItem.value;
228
+ processedCount++;
229
+ }
230
+ }
231
+ if (processedCount === 0) {
232
+ console.warn("⚠️ [Aippy Tweaks] No valid values found in iOS data:", data);
233
+ return;
234
+ }
235
+ const updateMessage = {
236
+ type: "tweaks-update",
237
+ values
238
+ };
239
+ this.updateCallback(updateMessage);
240
+ } catch (error) {
241
+ console.error("❌ [Aippy Tweaks] Error processing native data:", error);
242
+ }
243
+ }
244
+ }
245
+ const globalTweaksRuntime = new TweaksRuntime();
246
+ const aippyTweaksRuntime = {
247
+ tweaks: (config) => globalTweaksRuntime.tweaks(config)
248
+ };
249
+ const aippyTweaks = aippyTweaksRuntime.tweaks;
250
+ if (typeof window !== "undefined") {
251
+ window.aippyTweaksRuntime = aippyTweaksRuntime;
252
+ window.processNativeData = globalTweaksRuntime.processNativeData.bind(globalTweaksRuntime);
253
+ console.log("🌐 [Aippy Tweaks] Exposed processNativeData to window.processNativeData");
254
+ }
255
+ export {
256
+ aippyTweaks as a,
257
+ aippyTweaksRuntime as b
258
+ };
@@ -0,0 +1,4 @@
1
+ export * from './platform';
2
+ export * from './performance';
3
+ export * from './pwa';
4
+ export * from './types';
@@ -0,0 +1,9 @@
1
+ import { c, a, P, b, p, d } from "../pwa-BkviTQoN.js";
2
+ export {
3
+ c as PWAUtils,
4
+ a as PerformanceMonitor,
5
+ P as PlatformDetector,
6
+ b as performanceMonitor,
7
+ p as platform,
8
+ d as pwa
9
+ };