@thelacanians/vue-native-runtime 0.3.0 → 0.4.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/dist/index.js CHANGED
@@ -280,6 +280,17 @@ var _NativeBridgeImpl = class _NativeBridgeImpl {
280
280
  ));
281
281
  }
282
282
  }, timeoutMs);
283
+ if (this.pendingCallbacks.size >= _NativeBridgeImpl.MAX_PENDING_CALLBACKS) {
284
+ const oldestKey = this.pendingCallbacks.keys().next().value;
285
+ if (oldestKey !== void 0) {
286
+ const oldest = this.pendingCallbacks.get(oldestKey);
287
+ if (oldest) {
288
+ clearTimeout(oldest.timeoutId);
289
+ oldest.reject(new Error("Callback queue full, evicting oldest pending callback"));
290
+ this.pendingCallbacks.delete(oldestKey);
291
+ }
292
+ }
293
+ }
283
294
  this.pendingCallbacks.set(callbackId, { resolve, reject, timeoutId });
284
295
  this.enqueue("invokeNativeModule", [moduleName, methodName, args, callbackId]);
285
296
  });
@@ -299,11 +310,9 @@ var _NativeBridgeImpl = class _NativeBridgeImpl {
299
310
  resolveCallback(callbackId, result, error) {
300
311
  const pending = this.pendingCallbacks.get(callbackId);
301
312
  if (!pending) {
302
- if (__DEV__) {
303
- console.warn(
304
- `[VueNative] Received callback for unknown callbackId: ${callbackId}`
305
- );
306
- }
313
+ console.warn(
314
+ `[VueNative] Received callback for unknown callbackId: ${callbackId}. This likely means the callback already timed out or was evicted. The late response has been discarded.`
315
+ );
307
316
  return;
308
317
  }
309
318
  clearTimeout(pending.timeoutId);
@@ -368,6 +377,8 @@ var _NativeBridgeImpl = class _NativeBridgeImpl {
368
377
  };
369
378
  /** Maximum callback ID before wraparound (safe for 32-bit signed int) */
370
379
  _NativeBridgeImpl.MAX_CALLBACK_ID = 2147483647;
380
+ /** Maximum number of pending callbacks before evicting the oldest */
381
+ _NativeBridgeImpl.MAX_PENDING_CALLBACKS = 1e3;
371
382
  var NativeBridgeImpl = _NativeBridgeImpl;
372
383
  if (typeof globalThis.__DEV__ === "undefined") {
373
384
  ;
@@ -640,7 +651,7 @@ var VButton = defineComponent3({
640
651
  });
641
652
 
642
653
  // src/components/VInput.ts
643
- import { defineComponent as defineComponent4, h as h4 } from "@vue/runtime-core";
654
+ import { defineComponent as defineComponent4, h as h4, ref } from "@vue/runtime-core";
644
655
  var VInput = defineComponent4({
645
656
  name: "VInput",
646
657
  props: {
@@ -682,7 +693,17 @@ var VInput = defineComponent4({
682
693
  },
683
694
  emits: ["update:modelValue", "focus", "blur", "submit"],
684
695
  setup(props, { emit }) {
696
+ const isComposing = ref(false);
697
+ const onCompositionstart = () => {
698
+ isComposing.value = true;
699
+ };
700
+ const onCompositionend = (payload) => {
701
+ isComposing.value = false;
702
+ const text = typeof payload === "string" ? payload : payload?.text ?? "";
703
+ emit("update:modelValue", text);
704
+ };
685
705
  const onChangetext = (payload) => {
706
+ if (isComposing.value) return;
686
707
  const text = typeof payload === "string" ? payload : payload?.text ?? "";
687
708
  emit("update:modelValue", text);
688
709
  };
@@ -711,6 +732,8 @@ var VInput = defineComponent4({
711
732
  accessibilityHint: props.accessibilityHint,
712
733
  accessibilityState: props.accessibilityState,
713
734
  onChangetext,
735
+ onCompositionstart,
736
+ onCompositionend,
714
737
  onFocus,
715
738
  onBlur,
716
739
  onSubmit
@@ -823,6 +846,11 @@ var VScrollView = defineComponent7({
823
846
  default: false
824
847
  },
825
848
  contentContainerStyle: Object,
849
+ /** Minimum interval in ms between scroll event emissions. Default: 16 (~60fps) */
850
+ scrollEventThrottle: {
851
+ type: Number,
852
+ default: 16
853
+ },
826
854
  /** Whether the pull-to-refresh indicator is active */
827
855
  refreshing: {
828
856
  type: Boolean,
@@ -836,8 +864,13 @@ var VScrollView = defineComponent7({
836
864
  },
837
865
  emits: ["scroll", "refresh"],
838
866
  setup(props, { slots, emit }) {
867
+ let lastScrollEmit = 0;
839
868
  const onScroll = (payload) => {
840
- emit("scroll", payload);
869
+ const now = Date.now();
870
+ if (now - lastScrollEmit >= props.scrollEventThrottle) {
871
+ lastScrollEmit = now;
872
+ emit("scroll", payload);
873
+ }
841
874
  };
842
875
  const onRefresh = () => {
843
876
  emit("refresh");
@@ -867,7 +900,7 @@ var VScrollView = defineComponent7({
867
900
  });
868
901
 
869
902
  // src/components/VImage.ts
870
- import { defineComponent as defineComponent8, h as h8 } from "@vue/runtime-core";
903
+ import { defineComponent as defineComponent8, h as h8, ref as ref2, watch } from "@vue/runtime-core";
871
904
  var VImage = defineComponent8({
872
905
  name: "VImage",
873
906
  props: {
@@ -884,13 +917,29 @@ var VImage = defineComponent8({
884
917
  accessibilityState: Object
885
918
  },
886
919
  emits: ["load", "error"],
887
- setup(props, { emit }) {
920
+ setup(props, { emit, expose }) {
921
+ const loading = ref2(true);
922
+ watch(
923
+ () => props.source?.uri,
924
+ () => {
925
+ loading.value = true;
926
+ }
927
+ );
928
+ const onLoad = () => {
929
+ loading.value = false;
930
+ emit("load");
931
+ };
932
+ const onError = (e) => {
933
+ loading.value = false;
934
+ emit("error", e);
935
+ };
936
+ expose({ loading });
888
937
  return () => h8(
889
938
  "VImage",
890
939
  {
891
940
  ...props,
892
- onLoad: () => emit("load"),
893
- onError: (e) => emit("error", e)
941
+ onLoad,
942
+ onError
894
943
  }
895
944
  );
896
945
  }
@@ -996,8 +1045,29 @@ var VList = defineComponent12({
996
1045
  },
997
1046
  emits: ["scroll", "endReached"],
998
1047
  setup(props, { slots, emit }) {
1048
+ let lastScrollEmit = 0;
1049
+ const onScroll = (e) => {
1050
+ const now = Date.now();
1051
+ if (now - lastScrollEmit >= 16) {
1052
+ lastScrollEmit = now;
1053
+ emit("scroll", e);
1054
+ }
1055
+ };
999
1056
  return () => {
1000
1057
  const items = props.data ?? [];
1058
+ if (typeof __DEV__ !== "undefined" && __DEV__ && items.length > 0) {
1059
+ const keys = /* @__PURE__ */ new Set();
1060
+ for (let index = 0; index < items.length; index++) {
1061
+ const key = props.keyExtractor(items[index], index);
1062
+ if (keys.has(key)) {
1063
+ console.warn(
1064
+ `[VueNative] VList: Duplicate key "${key}" at index ${index}. Each item must have a unique key for correct reconciliation.`
1065
+ );
1066
+ break;
1067
+ }
1068
+ keys.add(key);
1069
+ }
1070
+ }
1001
1071
  const children = [];
1002
1072
  if (slots.header) {
1003
1073
  children.push(
@@ -1035,7 +1105,7 @@ var VList = defineComponent12({
1035
1105
  showsScrollIndicator: props.showsScrollIndicator,
1036
1106
  bounces: props.bounces,
1037
1107
  horizontal: props.horizontal,
1038
- onScroll: (e) => emit("scroll", e),
1108
+ onScroll,
1039
1109
  onEndReached: () => emit("endReached")
1040
1110
  },
1041
1111
  children
@@ -1045,7 +1115,7 @@ var VList = defineComponent12({
1045
1115
  });
1046
1116
 
1047
1117
  // src/components/VModal.ts
1048
- import { defineComponent as defineComponent13, h as h13 } from "@vue/runtime-core";
1118
+ import { defineComponent as defineComponent13, h as h13, ref as ref3, watch as watch2, onUnmounted } from "@vue/runtime-core";
1049
1119
  var VModal = defineComponent13({
1050
1120
  name: "VModal",
1051
1121
  props: {
@@ -1060,10 +1130,24 @@ var VModal = defineComponent13({
1060
1130
  },
1061
1131
  emits: ["dismiss"],
1062
1132
  setup(props, { slots, emit }) {
1133
+ const debouncedVisible = ref3(props.visible);
1134
+ let visibleTimer;
1135
+ watch2(
1136
+ () => props.visible,
1137
+ (val) => {
1138
+ if (visibleTimer) clearTimeout(visibleTimer);
1139
+ visibleTimer = setTimeout(() => {
1140
+ debouncedVisible.value = val;
1141
+ }, 50);
1142
+ }
1143
+ );
1144
+ onUnmounted(() => {
1145
+ if (visibleTimer) clearTimeout(visibleTimer);
1146
+ });
1063
1147
  return () => h13(
1064
1148
  "VModal",
1065
1149
  {
1066
- visible: props.visible,
1150
+ visible: debouncedVisible.value,
1067
1151
  style: props.style,
1068
1152
  onDismiss: () => emit("dismiss")
1069
1153
  },
@@ -1073,7 +1157,7 @@ var VModal = defineComponent13({
1073
1157
  });
1074
1158
 
1075
1159
  // src/components/VAlertDialog.ts
1076
- import { defineComponent as defineComponent14, h as h14 } from "@vue/runtime-core";
1160
+ import { defineComponent as defineComponent14, h as h14, ref as ref4, watch as watch3, onUnmounted as onUnmounted2 } from "@vue/runtime-core";
1077
1161
  var VAlertDialog = defineComponent14({
1078
1162
  name: "VAlertDialog",
1079
1163
  props: {
@@ -1084,8 +1168,22 @@ var VAlertDialog = defineComponent14({
1084
1168
  },
1085
1169
  emits: ["confirm", "cancel", "action"],
1086
1170
  setup(props, { emit }) {
1171
+ const debouncedVisible = ref4(props.visible);
1172
+ let visibleTimer;
1173
+ watch3(
1174
+ () => props.visible,
1175
+ (val) => {
1176
+ if (visibleTimer) clearTimeout(visibleTimer);
1177
+ visibleTimer = setTimeout(() => {
1178
+ debouncedVisible.value = val;
1179
+ }, 50);
1180
+ }
1181
+ );
1182
+ onUnmounted2(() => {
1183
+ if (visibleTimer) clearTimeout(visibleTimer);
1184
+ });
1087
1185
  return () => h14("VAlertDialog", {
1088
- visible: props.visible,
1186
+ visible: debouncedVisible.value,
1089
1187
  title: props.title,
1090
1188
  message: props.message,
1091
1189
  buttons: props.buttons,
@@ -1115,7 +1213,7 @@ var VStatusBar = defineComponent15({
1115
1213
  });
1116
1214
 
1117
1215
  // src/components/VWebView.ts
1118
- import { defineComponent as defineComponent16, h as h16 } from "@vue/runtime-core";
1216
+ import { computed, defineComponent as defineComponent16, h as h16 } from "@vue/runtime-core";
1119
1217
  var VWebView = defineComponent16({
1120
1218
  name: "VWebView",
1121
1219
  props: {
@@ -1125,8 +1223,18 @@ var VWebView = defineComponent16({
1125
1223
  },
1126
1224
  emits: ["load", "error", "message"],
1127
1225
  setup(props, { emit }) {
1226
+ const sanitizedSource = computed(() => {
1227
+ const source = props.source;
1228
+ if (!source?.uri) return source;
1229
+ const lower = source.uri.toLowerCase().trim();
1230
+ if (lower.startsWith("javascript:") || lower.startsWith("data:text/html")) {
1231
+ console.warn("[VueNative] VWebView: Blocked potentially unsafe URI scheme");
1232
+ return { ...source, uri: void 0 };
1233
+ }
1234
+ return source;
1235
+ });
1128
1236
  return () => h16("VWebView", {
1129
- source: props.source,
1237
+ source: sanitizedSource.value,
1130
1238
  style: props.style,
1131
1239
  javaScriptEnabled: props.javaScriptEnabled,
1132
1240
  onLoad: (e) => emit("load", e),
@@ -1590,7 +1698,7 @@ var vShow = {
1590
1698
  };
1591
1699
 
1592
1700
  // src/errorBoundary.ts
1593
- import { defineComponent as defineComponent28, ref, watch, onErrorCaptured } from "@vue/runtime-core";
1701
+ import { defineComponent as defineComponent28, ref as ref5, watch as watch4, onErrorCaptured } from "@vue/runtime-core";
1594
1702
  var ErrorBoundary = defineComponent28({
1595
1703
  name: "ErrorBoundary",
1596
1704
  props: {
@@ -1601,8 +1709,8 @@ var ErrorBoundary = defineComponent28({
1601
1709
  }
1602
1710
  },
1603
1711
  setup(props, { slots }) {
1604
- const error = ref(null);
1605
- const errorInfo = ref("");
1712
+ const error = ref5(null);
1713
+ const errorInfo = ref5("");
1606
1714
  onErrorCaptured((err, _instance, info) => {
1607
1715
  const normalizedError = err instanceof Error ? err : new Error(String(err));
1608
1716
  error.value = normalizedError;
@@ -1616,7 +1724,7 @@ var ErrorBoundary = defineComponent28({
1616
1724
  error.value = null;
1617
1725
  errorInfo.value = "";
1618
1726
  }
1619
- watch(
1727
+ watch4(
1620
1728
  () => props.resetKeys,
1621
1729
  () => {
1622
1730
  if (error.value) {
@@ -1772,15 +1880,33 @@ function useHaptics() {
1772
1880
  }
1773
1881
 
1774
1882
  // src/composables/useAsyncStorage.ts
1883
+ var writeQueues = /* @__PURE__ */ new Map();
1884
+ function queueWrite(key, fn) {
1885
+ const prev = writeQueues.get(key) ?? Promise.resolve();
1886
+ const next = prev.then(fn, fn);
1887
+ writeQueues.set(key, next);
1888
+ next.then(() => {
1889
+ if (writeQueues.get(key) === next) {
1890
+ writeQueues.delete(key);
1891
+ }
1892
+ });
1893
+ return next;
1894
+ }
1775
1895
  function useAsyncStorage() {
1776
1896
  function getItem(key) {
1777
1897
  return NativeBridge.invokeNativeModule("AsyncStorage", "getItem", [key]);
1778
1898
  }
1779
1899
  function setItem(key, value) {
1780
- return NativeBridge.invokeNativeModule("AsyncStorage", "setItem", [key, value]).then(() => void 0);
1900
+ return queueWrite(
1901
+ key,
1902
+ () => NativeBridge.invokeNativeModule("AsyncStorage", "setItem", [key, value]).then(() => void 0)
1903
+ );
1781
1904
  }
1782
1905
  function removeItem(key) {
1783
- return NativeBridge.invokeNativeModule("AsyncStorage", "removeItem", [key]).then(() => void 0);
1906
+ return queueWrite(
1907
+ key,
1908
+ () => NativeBridge.invokeNativeModule("AsyncStorage", "removeItem", [key]).then(() => void 0)
1909
+ );
1784
1910
  }
1785
1911
  function getAllKeys() {
1786
1912
  return NativeBridge.invokeNativeModule("AsyncStorage", "getAllKeys", []);
@@ -1792,9 +1918,9 @@ function useAsyncStorage() {
1792
1918
  }
1793
1919
 
1794
1920
  // src/composables/useClipboard.ts
1795
- import { ref as ref2 } from "@vue/runtime-core";
1921
+ import { ref as ref6 } from "@vue/runtime-core";
1796
1922
  function useClipboard() {
1797
- const content = ref2("");
1923
+ const content = ref6("");
1798
1924
  function copy(text) {
1799
1925
  return NativeBridge.invokeNativeModule("Clipboard", "copy", [text]).then(() => void 0);
1800
1926
  }
@@ -1808,16 +1934,16 @@ function useClipboard() {
1808
1934
  }
1809
1935
 
1810
1936
  // src/composables/useDeviceInfo.ts
1811
- import { ref as ref3, onMounted } from "@vue/runtime-core";
1937
+ import { ref as ref7, onMounted } from "@vue/runtime-core";
1812
1938
  function useDeviceInfo() {
1813
- const model = ref3("");
1814
- const systemVersion = ref3("");
1815
- const systemName = ref3("");
1816
- const name = ref3("");
1817
- const screenWidth = ref3(0);
1818
- const screenHeight = ref3(0);
1819
- const scale = ref3(1);
1820
- const isLoaded = ref3(false);
1939
+ const model = ref7("");
1940
+ const systemVersion = ref7("");
1941
+ const systemName = ref7("");
1942
+ const name = ref7("");
1943
+ const screenWidth = ref7(0);
1944
+ const screenHeight = ref7(0);
1945
+ const scale = ref7(1);
1946
+ const isLoaded = ref7(false);
1821
1947
  async function fetchInfo() {
1822
1948
  const info = await NativeBridge.invokeNativeModule("DeviceInfo", "getInfo", []);
1823
1949
  model.value = info.model ?? "";
@@ -1846,10 +1972,10 @@ function useDeviceInfo() {
1846
1972
  }
1847
1973
 
1848
1974
  // src/composables/useKeyboard.ts
1849
- import { ref as ref4 } from "@vue/runtime-core";
1975
+ import { ref as ref8 } from "@vue/runtime-core";
1850
1976
  function useKeyboard() {
1851
- const isVisible = ref4(false);
1852
- const height = ref4(0);
1977
+ const isVisible = ref8(false);
1978
+ const height = ref8(0);
1853
1979
  function dismiss() {
1854
1980
  return NativeBridge.invokeNativeModule("Keyboard", "dismiss", []).then(() => void 0);
1855
1981
  }
@@ -1913,27 +2039,32 @@ function useAnimation() {
1913
2039
  }
1914
2040
 
1915
2041
  // src/composables/useNetwork.ts
1916
- import { ref as ref5, onUnmounted } from "@vue/runtime-core";
2042
+ import { ref as ref9, onUnmounted as onUnmounted3 } from "@vue/runtime-core";
1917
2043
  function useNetwork() {
1918
- const isConnected = ref5(true);
1919
- const connectionType = ref5("unknown");
1920
- NativeBridge.invokeNativeModule("Network", "getStatus").then((status) => {
1921
- isConnected.value = status.isConnected;
1922
- connectionType.value = status.connectionType;
1923
- }).catch(() => {
1924
- });
2044
+ const isConnected = ref9(true);
2045
+ const connectionType = ref9("unknown");
2046
+ let lastEventTime = 0;
1925
2047
  const unsubscribe = NativeBridge.onGlobalEvent("network:change", (payload) => {
2048
+ lastEventTime = Date.now();
1926
2049
  isConnected.value = payload.isConnected;
1927
2050
  connectionType.value = payload.connectionType;
1928
2051
  });
1929
- onUnmounted(unsubscribe);
2052
+ const initTime = Date.now();
2053
+ NativeBridge.invokeNativeModule("Network", "getStatus").then((status) => {
2054
+ if (lastEventTime <= initTime) {
2055
+ isConnected.value = status.isConnected;
2056
+ connectionType.value = status.connectionType;
2057
+ }
2058
+ }).catch(() => {
2059
+ });
2060
+ onUnmounted3(unsubscribe);
1930
2061
  return { isConnected, connectionType };
1931
2062
  }
1932
2063
 
1933
2064
  // src/composables/useAppState.ts
1934
- import { ref as ref6, onUnmounted as onUnmounted2 } from "@vue/runtime-core";
2065
+ import { ref as ref10, onUnmounted as onUnmounted4 } from "@vue/runtime-core";
1935
2066
  function useAppState() {
1936
- const state = ref6("active");
2067
+ const state = ref10("active");
1937
2068
  NativeBridge.invokeNativeModule("AppState", "getState").then((s) => {
1938
2069
  state.value = s;
1939
2070
  }).catch(() => {
@@ -1941,7 +2072,7 @@ function useAppState() {
1941
2072
  const unsubscribe = NativeBridge.onGlobalEvent("appState:change", (payload) => {
1942
2073
  state.value = payload.state;
1943
2074
  });
1944
- onUnmounted2(unsubscribe);
2075
+ onUnmounted4(unsubscribe);
1945
2076
  return { state };
1946
2077
  }
1947
2078
 
@@ -1976,27 +2107,45 @@ function usePermissions() {
1976
2107
  }
1977
2108
 
1978
2109
  // src/composables/useGeolocation.ts
1979
- import { ref as ref7, onUnmounted as onUnmounted3 } from "@vue/runtime-core";
2110
+ import { ref as ref11, onUnmounted as onUnmounted5 } from "@vue/runtime-core";
1980
2111
  function useGeolocation() {
1981
- const coords = ref7(null);
1982
- const error = ref7(null);
2112
+ const coords = ref11(null);
2113
+ const error = ref11(null);
1983
2114
  let watchId = null;
1984
2115
  async function getCurrentPosition() {
1985
- const result = await NativeBridge.invokeNativeModule("Geolocation", "getCurrentPosition");
1986
- coords.value = result;
1987
- return result;
2116
+ try {
2117
+ error.value = null;
2118
+ const result = await NativeBridge.invokeNativeModule("Geolocation", "getCurrentPosition");
2119
+ coords.value = result;
2120
+ return result;
2121
+ } catch (e) {
2122
+ const msg = e instanceof Error ? e.message : String(e);
2123
+ error.value = msg;
2124
+ throw e;
2125
+ }
1988
2126
  }
1989
2127
  async function watchPosition() {
1990
- const id = await NativeBridge.invokeNativeModule("Geolocation", "watchPosition");
1991
- watchId = id;
1992
- const unsubscribe = NativeBridge.onGlobalEvent("location:update", (payload) => {
1993
- coords.value = payload;
1994
- });
1995
- onUnmounted3(() => {
1996
- unsubscribe();
1997
- if (watchId !== null) clearWatch(watchId);
1998
- });
1999
- return id;
2128
+ try {
2129
+ error.value = null;
2130
+ const id = await NativeBridge.invokeNativeModule("Geolocation", "watchPosition");
2131
+ watchId = id;
2132
+ const unsubscribe = NativeBridge.onGlobalEvent("location:update", (payload) => {
2133
+ coords.value = payload;
2134
+ });
2135
+ const unsubscribeError = NativeBridge.onGlobalEvent("location:error", (payload) => {
2136
+ error.value = payload.message;
2137
+ });
2138
+ onUnmounted5(() => {
2139
+ unsubscribe();
2140
+ unsubscribeError();
2141
+ if (watchId !== null) clearWatch(watchId);
2142
+ });
2143
+ return id;
2144
+ } catch (e) {
2145
+ const msg = e instanceof Error ? e.message : String(e);
2146
+ error.value = msg;
2147
+ throw e;
2148
+ }
2000
2149
  }
2001
2150
  async function clearWatch(id) {
2002
2151
  await NativeBridge.invokeNativeModule("Geolocation", "clearWatch", [id]);
@@ -2006,7 +2155,7 @@ function useGeolocation() {
2006
2155
  }
2007
2156
 
2008
2157
  // src/composables/useCamera.ts
2009
- import { onUnmounted as onUnmounted4 } from "@vue/runtime-core";
2158
+ import { onUnmounted as onUnmounted6 } from "@vue/runtime-core";
2010
2159
  function useCamera() {
2011
2160
  const qrCleanups = [];
2012
2161
  async function launchCamera(options = {}) {
@@ -2029,7 +2178,7 @@ function useCamera() {
2029
2178
  qrCleanups.push(unsubscribe);
2030
2179
  return unsubscribe;
2031
2180
  }
2032
- onUnmounted4(() => {
2181
+ onUnmounted6(() => {
2033
2182
  NativeBridge.invokeNativeModule("Camera", "stopQRScan").catch(() => {
2034
2183
  });
2035
2184
  qrCleanups.forEach((fn) => fn());
@@ -2039,10 +2188,10 @@ function useCamera() {
2039
2188
  }
2040
2189
 
2041
2190
  // src/composables/useNotifications.ts
2042
- import { ref as ref8, onUnmounted as onUnmounted5 } from "@vue/runtime-core";
2191
+ import { ref as ref12, onUnmounted as onUnmounted7 } from "@vue/runtime-core";
2043
2192
  function useNotifications() {
2044
- const isGranted = ref8(false);
2045
- const pushToken = ref8(null);
2193
+ const isGranted = ref12(false);
2194
+ const pushToken = ref12(null);
2046
2195
  async function requestPermission() {
2047
2196
  const granted = await NativeBridge.invokeNativeModule("Notifications", "requestPermission");
2048
2197
  isGranted.value = granted;
@@ -2062,7 +2211,7 @@ function useNotifications() {
2062
2211
  }
2063
2212
  function onNotification(handler) {
2064
2213
  const unsubscribe = NativeBridge.onGlobalEvent("notification:received", handler);
2065
- onUnmounted5(unsubscribe);
2214
+ onUnmounted7(unsubscribe);
2066
2215
  return unsubscribe;
2067
2216
  }
2068
2217
  async function registerForPush() {
@@ -2076,12 +2225,12 @@ function useNotifications() {
2076
2225
  pushToken.value = payload.token;
2077
2226
  handler(payload.token);
2078
2227
  });
2079
- onUnmounted5(unsubscribe);
2228
+ onUnmounted7(unsubscribe);
2080
2229
  return unsubscribe;
2081
2230
  }
2082
2231
  function onPushReceived(handler) {
2083
2232
  const unsubscribe = NativeBridge.onGlobalEvent("push:received", handler);
2084
- onUnmounted5(unsubscribe);
2233
+ onUnmounted7(unsubscribe);
2085
2234
  return unsubscribe;
2086
2235
  }
2087
2236
  return {
@@ -2117,7 +2266,7 @@ function useBiometry() {
2117
2266
  }
2118
2267
 
2119
2268
  // src/composables/useHttp.ts
2120
- import { ref as ref9 } from "@vue/runtime-core";
2269
+ import { ref as ref13, onUnmounted as onUnmounted8 } from "@vue/runtime-core";
2121
2270
  function useHttp(config = {}) {
2122
2271
  if (config.pins && Object.keys(config.pins).length > 0) {
2123
2272
  const configurePins = globalThis.__VN_configurePins;
@@ -2127,8 +2276,12 @@ function useHttp(config = {}) {
2127
2276
  NativeBridge.invokeNativeModule("Http", "configurePins", [config.pins]);
2128
2277
  }
2129
2278
  }
2130
- const loading = ref9(false);
2131
- const error = ref9(null);
2279
+ const loading = ref13(false);
2280
+ const error = ref13(null);
2281
+ let isMounted = true;
2282
+ onUnmounted8(() => {
2283
+ isMounted = false;
2284
+ });
2132
2285
  async function request(method, url, options = {}) {
2133
2286
  const fullUrl = config.baseURL ? `${config.baseURL}${url}` : url;
2134
2287
  loading.value = true;
@@ -2148,6 +2301,9 @@ function useHttp(config = {}) {
2148
2301
  }
2149
2302
  const response = await fetch(fullUrl, fetchOptions);
2150
2303
  const data = await response.json();
2304
+ if (!isMounted) {
2305
+ return { data, status: response.status, ok: response.ok, headers: {} };
2306
+ }
2151
2307
  return {
2152
2308
  data,
2153
2309
  status: response.status,
@@ -2156,10 +2312,14 @@ function useHttp(config = {}) {
2156
2312
  };
2157
2313
  } catch (e) {
2158
2314
  const msg = e instanceof Error ? e.message : String(e);
2159
- error.value = msg;
2315
+ if (isMounted) {
2316
+ error.value = msg;
2317
+ }
2160
2318
  throw e;
2161
2319
  } finally {
2162
- loading.value = false;
2320
+ if (isMounted) {
2321
+ loading.value = false;
2322
+ }
2163
2323
  }
2164
2324
  }
2165
2325
  return {
@@ -2174,10 +2334,10 @@ function useHttp(config = {}) {
2174
2334
  }
2175
2335
 
2176
2336
  // src/composables/useColorScheme.ts
2177
- import { ref as ref10, onUnmounted as onUnmounted6 } from "@vue/runtime-core";
2337
+ import { ref as ref14, onUnmounted as onUnmounted9 } from "@vue/runtime-core";
2178
2338
  function useColorScheme() {
2179
- const colorScheme = ref10("light");
2180
- const isDark = ref10(false);
2339
+ const colorScheme = ref14("light");
2340
+ const isDark = ref14(false);
2181
2341
  const unsubscribe = NativeBridge.onGlobalEvent(
2182
2342
  "colorScheme:change",
2183
2343
  (payload) => {
@@ -2185,20 +2345,24 @@ function useColorScheme() {
2185
2345
  isDark.value = payload.colorScheme === "dark";
2186
2346
  }
2187
2347
  );
2188
- onUnmounted6(unsubscribe);
2348
+ onUnmounted9(unsubscribe);
2189
2349
  return { colorScheme, isDark };
2190
2350
  }
2191
2351
 
2192
2352
  // src/composables/useBackHandler.ts
2193
- import { onMounted as onMounted2, onUnmounted as onUnmounted7 } from "@vue/runtime-core";
2353
+ import { onMounted as onMounted2, onUnmounted as onUnmounted10 } from "@vue/runtime-core";
2194
2354
  function useBackHandler(handler) {
2195
2355
  let unsubscribe = null;
2196
2356
  onMounted2(() => {
2197
2357
  unsubscribe = NativeBridge.onGlobalEvent("hardware:backPress", () => {
2198
- handler();
2358
+ const handled = handler();
2359
+ if (!handled) {
2360
+ NativeBridge.invokeNativeModule("BackHandler", "exitApp", []).catch(() => {
2361
+ });
2362
+ }
2199
2363
  });
2200
2364
  });
2201
- onUnmounted7(() => {
2365
+ onUnmounted10(() => {
2202
2366
  unsubscribe?.();
2203
2367
  unsubscribe = null;
2204
2368
  });
@@ -2222,10 +2386,10 @@ function useSecureStorage() {
2222
2386
  }
2223
2387
 
2224
2388
  // src/composables/useI18n.ts
2225
- import { ref as ref11, onMounted as onMounted3 } from "@vue/runtime-core";
2389
+ import { ref as ref15, onMounted as onMounted3 } from "@vue/runtime-core";
2226
2390
  function useI18n() {
2227
- const isRTL = ref11(false);
2228
- const locale = ref11("en");
2391
+ const isRTL = ref15(false);
2392
+ const locale = ref15("en");
2229
2393
  onMounted3(async () => {
2230
2394
  try {
2231
2395
  const info = await NativeBridge.invokeNativeModule("DeviceInfo", "getDeviceInfo", []);
@@ -2246,11 +2410,11 @@ function usePlatform() {
2246
2410
  }
2247
2411
 
2248
2412
  // src/composables/useDimensions.ts
2249
- import { ref as ref12, onMounted as onMounted4, onUnmounted as onUnmounted8 } from "@vue/runtime-core";
2413
+ import { ref as ref16, onMounted as onMounted4, onUnmounted as onUnmounted11 } from "@vue/runtime-core";
2250
2414
  function useDimensions() {
2251
- const width = ref12(0);
2252
- const height = ref12(0);
2253
- const scale = ref12(1);
2415
+ const width = ref16(0);
2416
+ const height = ref16(0);
2417
+ const scale = ref16(1);
2254
2418
  onMounted4(async () => {
2255
2419
  try {
2256
2420
  const info = await NativeBridge.invokeNativeModule("DeviceInfo", "getInfo", []);
@@ -2265,12 +2429,12 @@ function useDimensions() {
2265
2429
  if (payload.height != null) height.value = payload.height;
2266
2430
  if (payload.scale != null) scale.value = payload.scale;
2267
2431
  });
2268
- onUnmounted8(cleanup);
2432
+ onUnmounted11(cleanup);
2269
2433
  return { width, height, scale };
2270
2434
  }
2271
2435
 
2272
2436
  // src/composables/useWebSocket.ts
2273
- import { ref as ref13, onUnmounted as onUnmounted9 } from "@vue/runtime-core";
2437
+ import { ref as ref17, onUnmounted as onUnmounted12 } from "@vue/runtime-core";
2274
2438
  var connectionCounter = 0;
2275
2439
  function useWebSocket(url, options = {}) {
2276
2440
  const {
@@ -2280,11 +2444,13 @@ function useWebSocket(url, options = {}) {
2280
2444
  reconnectInterval = 1e3
2281
2445
  } = options;
2282
2446
  const connectionId = `ws_${++connectionCounter}_${Date.now()}`;
2283
- const status = ref13("CLOSED");
2284
- const lastMessage = ref13(null);
2285
- const error = ref13(null);
2447
+ const status = ref17("CLOSED");
2448
+ const lastMessage = ref17(null);
2449
+ const error = ref17(null);
2286
2450
  let reconnectAttempts = 0;
2287
2451
  let reconnectTimer = null;
2452
+ const MAX_PENDING_MESSAGES = 100;
2453
+ const pendingMessages = [];
2288
2454
  const unsubscribers = [];
2289
2455
  unsubscribers.push(
2290
2456
  NativeBridge.onGlobalEvent("websocket:open", (payload) => {
@@ -2292,6 +2458,12 @@ function useWebSocket(url, options = {}) {
2292
2458
  status.value = "OPEN";
2293
2459
  error.value = null;
2294
2460
  reconnectAttempts = 0;
2461
+ while (pendingMessages.length > 0) {
2462
+ const msg = pendingMessages.shift();
2463
+ NativeBridge.invokeNativeModule("WebSocket", "send", [connectionId, msg]).catch((err) => {
2464
+ error.value = err.message;
2465
+ });
2466
+ }
2295
2467
  })
2296
2468
  );
2297
2469
  unsubscribers.push(
@@ -2306,9 +2478,10 @@ function useWebSocket(url, options = {}) {
2306
2478
  status.value = "CLOSED";
2307
2479
  if (autoReconnect && reconnectAttempts < maxReconnectAttempts && payload.code !== 1e3) {
2308
2480
  reconnectAttempts++;
2481
+ const backoffMs = reconnectInterval * Math.pow(2, reconnectAttempts - 1);
2309
2482
  reconnectTimer = setTimeout(() => {
2310
2483
  open();
2311
- }, reconnectInterval);
2484
+ }, backoffMs);
2312
2485
  }
2313
2486
  })
2314
2487
  );
@@ -2328,8 +2501,17 @@ function useWebSocket(url, options = {}) {
2328
2501
  });
2329
2502
  }
2330
2503
  function send(data) {
2331
- if (status.value !== "OPEN") return;
2332
2504
  const message = typeof data === "string" ? data : JSON.stringify(data);
2505
+ if (status.value !== "OPEN") {
2506
+ if (pendingMessages.length >= MAX_PENDING_MESSAGES) {
2507
+ pendingMessages.shift();
2508
+ if (__DEV__) {
2509
+ console.warn("[VueNative] WebSocket pending message queue full, dropping oldest message");
2510
+ }
2511
+ }
2512
+ pendingMessages.push(message);
2513
+ return;
2514
+ }
2333
2515
  NativeBridge.invokeNativeModule("WebSocket", "send", [connectionId, message]).catch((err) => {
2334
2516
  error.value = err.message;
2335
2517
  });
@@ -2349,7 +2531,7 @@ function useWebSocket(url, options = {}) {
2349
2531
  if (autoConnect) {
2350
2532
  open();
2351
2533
  }
2352
- onUnmounted9(() => {
2534
+ onUnmounted12(() => {
2353
2535
  if (reconnectTimer) {
2354
2536
  clearTimeout(reconnectTimer);
2355
2537
  }
@@ -2418,12 +2600,12 @@ function useFileSystem() {
2418
2600
  }
2419
2601
 
2420
2602
  // src/composables/useSensors.ts
2421
- import { ref as ref14, onUnmounted as onUnmounted10 } from "@vue/runtime-core";
2603
+ import { ref as ref18, onUnmounted as onUnmounted13 } from "@vue/runtime-core";
2422
2604
  function useAccelerometer(options = {}) {
2423
- const x = ref14(0);
2424
- const y = ref14(0);
2425
- const z = ref14(0);
2426
- const isAvailable = ref14(false);
2605
+ const x = ref18(0);
2606
+ const y = ref18(0);
2607
+ const z = ref18(0);
2608
+ const isAvailable = ref18(false);
2427
2609
  let running = false;
2428
2610
  let unsubscribe = null;
2429
2611
  NativeBridge.invokeNativeModule("Sensors", "isAvailable", ["accelerometer"]).then((result) => {
@@ -2451,16 +2633,16 @@ function useAccelerometer(options = {}) {
2451
2633
  NativeBridge.invokeNativeModule("Sensors", "stopAccelerometer").catch(() => {
2452
2634
  });
2453
2635
  }
2454
- onUnmounted10(() => {
2636
+ onUnmounted13(() => {
2455
2637
  stop();
2456
2638
  });
2457
2639
  return { x, y, z, isAvailable, start, stop };
2458
2640
  }
2459
2641
  function useGyroscope(options = {}) {
2460
- const x = ref14(0);
2461
- const y = ref14(0);
2462
- const z = ref14(0);
2463
- const isAvailable = ref14(false);
2642
+ const x = ref18(0);
2643
+ const y = ref18(0);
2644
+ const z = ref18(0);
2645
+ const isAvailable = ref18(false);
2464
2646
  let running = false;
2465
2647
  let unsubscribe = null;
2466
2648
  NativeBridge.invokeNativeModule("Sensors", "isAvailable", ["gyroscope"]).then((result) => {
@@ -2488,20 +2670,20 @@ function useGyroscope(options = {}) {
2488
2670
  NativeBridge.invokeNativeModule("Sensors", "stopGyroscope").catch(() => {
2489
2671
  });
2490
2672
  }
2491
- onUnmounted10(() => {
2673
+ onUnmounted13(() => {
2492
2674
  stop();
2493
2675
  });
2494
2676
  return { x, y, z, isAvailable, start, stop };
2495
2677
  }
2496
2678
 
2497
2679
  // src/composables/useAudio.ts
2498
- import { ref as ref15, onUnmounted as onUnmounted11 } from "@vue/runtime-core";
2680
+ import { ref as ref19, onUnmounted as onUnmounted14 } from "@vue/runtime-core";
2499
2681
  function useAudio() {
2500
- const duration = ref15(0);
2501
- const position = ref15(0);
2502
- const isPlaying = ref15(false);
2503
- const isRecording = ref15(false);
2504
- const error = ref15(null);
2682
+ const duration = ref19(0);
2683
+ const position = ref19(0);
2684
+ const isPlaying = ref19(false);
2685
+ const isRecording = ref19(false);
2686
+ const error = ref19(null);
2505
2687
  const unsubProgress = NativeBridge.onGlobalEvent("audio:progress", (payload) => {
2506
2688
  position.value = payload.currentTime ?? 0;
2507
2689
  duration.value = payload.duration ?? 0;
@@ -2514,7 +2696,7 @@ function useAudio() {
2514
2696
  error.value = payload.message ?? "Unknown audio error";
2515
2697
  isPlaying.value = false;
2516
2698
  });
2517
- onUnmounted11(() => {
2699
+ onUnmounted14(() => {
2518
2700
  unsubProgress();
2519
2701
  unsubComplete();
2520
2702
  unsubError();
@@ -2593,9 +2775,9 @@ function useAudio() {
2593
2775
  }
2594
2776
 
2595
2777
  // src/composables/useDatabase.ts
2596
- import { ref as ref16, onUnmounted as onUnmounted12 } from "@vue/runtime-core";
2778
+ import { ref as ref20, onUnmounted as onUnmounted15 } from "@vue/runtime-core";
2597
2779
  function useDatabase(name = "default") {
2598
- const isOpen = ref16(false);
2780
+ const isOpen = ref20(false);
2599
2781
  let opened = false;
2600
2782
  async function ensureOpen() {
2601
2783
  if (opened) return;
@@ -2613,22 +2795,22 @@ function useDatabase(name = "default") {
2613
2795
  }
2614
2796
  async function transaction(callback) {
2615
2797
  await ensureOpen();
2616
- const statements = [];
2617
- const ctx = {
2618
- execute: async (sql, params) => {
2619
- statements.push({ sql, params: params ?? [] });
2620
- return { rowsAffected: 0 };
2621
- },
2622
- query: async (sql, params) => {
2623
- if (statements.length > 0) {
2624
- await NativeBridge.invokeNativeModule("Database", "executeTransaction", [name, statements.splice(0)]);
2798
+ await NativeBridge.invokeNativeModule("Database", "execute", [name, "BEGIN TRANSACTION", []]);
2799
+ try {
2800
+ const ctx = {
2801
+ execute: async (sql, params) => {
2802
+ return NativeBridge.invokeNativeModule("Database", "execute", [name, sql, params ?? []]);
2803
+ },
2804
+ query: async (sql, params) => {
2805
+ return NativeBridge.invokeNativeModule("Database", "query", [name, sql, params ?? []]);
2625
2806
  }
2626
- return NativeBridge.invokeNativeModule("Database", "query", [name, sql, params ?? []]);
2627
- }
2628
- };
2629
- await callback(ctx);
2630
- if (statements.length > 0) {
2631
- await NativeBridge.invokeNativeModule("Database", "executeTransaction", [name, statements]);
2807
+ };
2808
+ await callback(ctx);
2809
+ await NativeBridge.invokeNativeModule("Database", "execute", [name, "COMMIT", []]);
2810
+ } catch (err) {
2811
+ await NativeBridge.invokeNativeModule("Database", "execute", [name, "ROLLBACK", []]).catch(() => {
2812
+ });
2813
+ throw err;
2632
2814
  }
2633
2815
  }
2634
2816
  async function close() {
@@ -2637,7 +2819,7 @@ function useDatabase(name = "default") {
2637
2819
  opened = false;
2638
2820
  isOpen.value = false;
2639
2821
  }
2640
- onUnmounted12(() => {
2822
+ onUnmounted15(() => {
2641
2823
  if (opened) {
2642
2824
  NativeBridge.invokeNativeModule("Database", "close", [name]).catch(() => {
2643
2825
  });
@@ -2649,12 +2831,12 @@ function useDatabase(name = "default") {
2649
2831
  }
2650
2832
 
2651
2833
  // src/composables/usePerformance.ts
2652
- import { ref as ref17, onUnmounted as onUnmounted13 } from "@vue/runtime-core";
2834
+ import { ref as ref21, onUnmounted as onUnmounted16 } from "@vue/runtime-core";
2653
2835
  function usePerformance() {
2654
- const isProfiling = ref17(false);
2655
- const fps = ref17(0);
2656
- const memoryMB = ref17(0);
2657
- const bridgeOps = ref17(0);
2836
+ const isProfiling = ref21(false);
2837
+ const fps = ref21(0);
2838
+ const memoryMB = ref21(0);
2839
+ const bridgeOps = ref21(0);
2658
2840
  let unsubscribe = null;
2659
2841
  function handleMetrics(payload) {
2660
2842
  fps.value = payload.fps ?? 0;
@@ -2679,7 +2861,7 @@ function usePerformance() {
2679
2861
  async function getMetrics() {
2680
2862
  return NativeBridge.invokeNativeModule("Performance", "getMetrics", []);
2681
2863
  }
2682
- onUnmounted13(() => {
2864
+ onUnmounted16(() => {
2683
2865
  if (isProfiling.value) {
2684
2866
  NativeBridge.invokeNativeModule("Performance", "stopProfiling", []).catch(() => {
2685
2867
  });
@@ -2702,10 +2884,10 @@ function usePerformance() {
2702
2884
  }
2703
2885
 
2704
2886
  // src/composables/useSharedElementTransition.ts
2705
- import { ref as ref18, onUnmounted as onUnmounted14 } from "@vue/runtime-core";
2887
+ import { ref as ref22, onUnmounted as onUnmounted17 } from "@vue/runtime-core";
2706
2888
  var sharedElementRegistry = /* @__PURE__ */ new Map();
2707
2889
  function useSharedElementTransition(elementId) {
2708
- const viewId = ref18(null);
2890
+ const viewId = ref22(null);
2709
2891
  function register(nativeViewId) {
2710
2892
  viewId.value = nativeViewId;
2711
2893
  sharedElementRegistry.set(elementId, nativeViewId);
@@ -2714,7 +2896,7 @@ function useSharedElementTransition(elementId) {
2714
2896
  viewId.value = null;
2715
2897
  sharedElementRegistry.delete(elementId);
2716
2898
  }
2717
- onUnmounted14(() => {
2899
+ onUnmounted17(() => {
2718
2900
  unregister();
2719
2901
  });
2720
2902
  return {
@@ -2738,11 +2920,11 @@ function clearSharedElementRegistry() {
2738
2920
  }
2739
2921
 
2740
2922
  // src/composables/useIAP.ts
2741
- import { ref as ref19, onUnmounted as onUnmounted15 } from "@vue/runtime-core";
2923
+ import { ref as ref23, onUnmounted as onUnmounted18 } from "@vue/runtime-core";
2742
2924
  function useIAP() {
2743
- const products = ref19([]);
2744
- const isReady = ref19(false);
2745
- const error = ref19(null);
2925
+ const products = ref23([]);
2926
+ const isReady = ref23(false);
2927
+ const error = ref23(null);
2746
2928
  const cleanups = [];
2747
2929
  const unsubscribe = NativeBridge.onGlobalEvent("iap:transactionUpdate", (payload) => {
2748
2930
  if (payload.state === "failed" && payload.error) {
@@ -2799,7 +2981,7 @@ function useIAP() {
2799
2981
  cleanups.push(unsub);
2800
2982
  return unsub;
2801
2983
  }
2802
- onUnmounted15(() => {
2984
+ onUnmounted18(() => {
2803
2985
  cleanups.forEach((fn) => fn());
2804
2986
  cleanups.length = 0;
2805
2987
  });
@@ -2816,11 +2998,11 @@ function useIAP() {
2816
2998
  }
2817
2999
 
2818
3000
  // src/composables/useAppleSignIn.ts
2819
- import { ref as ref20, onUnmounted as onUnmounted16 } from "@vue/runtime-core";
3001
+ import { ref as ref24, onUnmounted as onUnmounted19 } from "@vue/runtime-core";
2820
3002
  function useAppleSignIn() {
2821
- const user = ref20(null);
2822
- const isAuthenticated = ref20(false);
2823
- const error = ref20(null);
3003
+ const user = ref24(null);
3004
+ const isAuthenticated = ref24(false);
3005
+ const error = ref24(null);
2824
3006
  const cleanups = [];
2825
3007
  const unsubscribe = NativeBridge.onGlobalEvent("auth:appleCredentialRevoked", () => {
2826
3008
  user.value = null;
@@ -2858,7 +3040,7 @@ function useAppleSignIn() {
2858
3040
  error.value = String(err);
2859
3041
  }
2860
3042
  }
2861
- onUnmounted16(() => {
3043
+ onUnmounted19(() => {
2862
3044
  cleanups.forEach((fn) => fn());
2863
3045
  cleanups.length = 0;
2864
3046
  });
@@ -2866,11 +3048,11 @@ function useAppleSignIn() {
2866
3048
  }
2867
3049
 
2868
3050
  // src/composables/useGoogleSignIn.ts
2869
- import { ref as ref21, onUnmounted as onUnmounted17 } from "@vue/runtime-core";
3051
+ import { ref as ref25, onUnmounted as onUnmounted20 } from "@vue/runtime-core";
2870
3052
  function useGoogleSignIn(clientId) {
2871
- const user = ref21(null);
2872
- const isAuthenticated = ref21(false);
2873
- const error = ref21(null);
3053
+ const user = ref25(null);
3054
+ const isAuthenticated = ref25(false);
3055
+ const error = ref25(null);
2874
3056
  const cleanups = [];
2875
3057
  NativeBridge.invokeNativeModule("SocialAuth", "getCurrentUser", ["google"]).then((result) => {
2876
3058
  if (result && result.userId) {
@@ -2903,7 +3085,7 @@ function useGoogleSignIn(clientId) {
2903
3085
  error.value = String(err);
2904
3086
  }
2905
3087
  }
2906
- onUnmounted17(() => {
3088
+ onUnmounted20(() => {
2907
3089
  cleanups.forEach((fn) => fn());
2908
3090
  cleanups.length = 0;
2909
3091
  });
@@ -2911,17 +3093,17 @@ function useGoogleSignIn(clientId) {
2911
3093
  }
2912
3094
 
2913
3095
  // src/composables/useBackgroundTask.ts
2914
- import { ref as ref22, onUnmounted as onUnmounted18 } from "@vue/runtime-core";
3096
+ import { ref as ref26, onUnmounted as onUnmounted21 } from "@vue/runtime-core";
2915
3097
  function useBackgroundTask() {
2916
3098
  const taskHandlers = /* @__PURE__ */ new Map();
2917
- const defaultHandler = ref22(null);
3099
+ const defaultHandler = ref26(null);
2918
3100
  const unsubscribe = NativeBridge.onGlobalEvent("background:taskExecute", (payload) => {
2919
3101
  const handler = taskHandlers.get(payload.taskId) || defaultHandler.value;
2920
3102
  if (handler) {
2921
3103
  handler(payload.taskId);
2922
3104
  }
2923
3105
  });
2924
- onUnmounted18(unsubscribe);
3106
+ onUnmounted21(unsubscribe);
2925
3107
  function registerTask(taskId) {
2926
3108
  return NativeBridge.invokeNativeModule("BackgroundTask", "registerTask", [taskId]).then(() => void 0);
2927
3109
  }
@@ -2960,20 +3142,20 @@ function useBackgroundTask() {
2960
3142
  }
2961
3143
 
2962
3144
  // src/composables/useOTAUpdate.ts
2963
- import { ref as ref23, onUnmounted as onUnmounted19 } from "@vue/runtime-core";
3145
+ import { ref as ref27, onUnmounted as onUnmounted22 } from "@vue/runtime-core";
2964
3146
  function useOTAUpdate(serverUrl) {
2965
- const currentVersion = ref23("embedded");
2966
- const availableVersion = ref23(null);
2967
- const downloadProgress = ref23(0);
2968
- const isChecking = ref23(false);
2969
- const isDownloading = ref23(false);
2970
- const status = ref23("idle");
2971
- const error = ref23(null);
3147
+ const currentVersion = ref27("embedded");
3148
+ const availableVersion = ref27(null);
3149
+ const downloadProgress = ref27(0);
3150
+ const isChecking = ref27(false);
3151
+ const isDownloading = ref27(false);
3152
+ const status = ref27("idle");
3153
+ const error = ref27(null);
2972
3154
  let lastUpdateInfo = null;
2973
3155
  const unsubscribe = NativeBridge.onGlobalEvent("ota:downloadProgress", (payload) => {
2974
3156
  downloadProgress.value = payload.progress;
2975
3157
  });
2976
- onUnmounted19(unsubscribe);
3158
+ onUnmounted22(unsubscribe);
2977
3159
  NativeBridge.invokeNativeModule("OTA", "getCurrentVersion", []).then((info) => {
2978
3160
  currentVersion.value = info.version;
2979
3161
  }).catch(() => {
@@ -3017,6 +3199,8 @@ function useOTAUpdate(serverUrl) {
3017
3199
  await NativeBridge.invokeNativeModule("OTA", "downloadUpdate", [downloadUrl, expectedHash || ""]);
3018
3200
  status.value = "ready";
3019
3201
  } catch (err) {
3202
+ await NativeBridge.invokeNativeModule("OTA", "cleanupPartialDownload", []).catch(() => {
3203
+ });
3020
3204
  error.value = err?.message || String(err);
3021
3205
  status.value = "error";
3022
3206
  throw err;
@@ -3025,7 +3209,17 @@ function useOTAUpdate(serverUrl) {
3025
3209
  }
3026
3210
  }
3027
3211
  async function applyUpdate() {
3212
+ if (status.value !== "ready") {
3213
+ throw new Error("No update ready to apply. Call downloadUpdate() first.");
3214
+ }
3028
3215
  error.value = null;
3216
+ try {
3217
+ await NativeBridge.invokeNativeModule("OTA", "verifyBundle", []);
3218
+ } catch (err) {
3219
+ status.value = "error";
3220
+ error.value = "Bundle verification failed: " + (err?.message || String(err));
3221
+ throw err;
3222
+ }
3029
3223
  try {
3030
3224
  await NativeBridge.invokeNativeModule("OTA", "applyUpdate", []);
3031
3225
  const info = await NativeBridge.invokeNativeModule("OTA", "getCurrentVersion", []);
@@ -3073,13 +3267,13 @@ function useOTAUpdate(serverUrl) {
3073
3267
  }
3074
3268
 
3075
3269
  // src/composables/useBluetooth.ts
3076
- import { ref as ref24, onUnmounted as onUnmounted20 } from "@vue/runtime-core";
3270
+ import { ref as ref28, onUnmounted as onUnmounted23 } from "@vue/runtime-core";
3077
3271
  function useBluetooth() {
3078
- const devices = ref24([]);
3079
- const connectedDevice = ref24(null);
3080
- const isScanning = ref24(false);
3081
- const isAvailable = ref24(false);
3082
- const error = ref24(null);
3272
+ const devices = ref28([]);
3273
+ const connectedDevice = ref28(null);
3274
+ const isScanning = ref28(false);
3275
+ const isAvailable = ref28(false);
3276
+ const error = ref28(null);
3083
3277
  const cleanups = [];
3084
3278
  NativeBridge.invokeNativeModule("Bluetooth", "getState").then((state) => {
3085
3279
  isAvailable.value = state === "poweredOn";
@@ -3158,7 +3352,7 @@ function useBluetooth() {
3158
3352
  ]);
3159
3353
  };
3160
3354
  }
3161
- onUnmounted20(() => {
3355
+ onUnmounted23(() => {
3162
3356
  if (isScanning.value) {
3163
3357
  NativeBridge.invokeNativeModule("Bluetooth", "stopScan").catch(() => {
3164
3358
  });
@@ -3183,10 +3377,10 @@ function useBluetooth() {
3183
3377
  }
3184
3378
 
3185
3379
  // src/composables/useCalendar.ts
3186
- import { ref as ref25 } from "@vue/runtime-core";
3380
+ import { ref as ref29 } from "@vue/runtime-core";
3187
3381
  function useCalendar() {
3188
- const hasAccess = ref25(false);
3189
- const error = ref25(null);
3382
+ const hasAccess = ref29(false);
3383
+ const error = ref29(null);
3190
3384
  async function requestAccess() {
3191
3385
  try {
3192
3386
  const result = await NativeBridge.invokeNativeModule("Calendar", "requestAccess");
@@ -3219,10 +3413,10 @@ function useCalendar() {
3219
3413
  }
3220
3414
 
3221
3415
  // src/composables/useContacts.ts
3222
- import { ref as ref26 } from "@vue/runtime-core";
3416
+ import { ref as ref30 } from "@vue/runtime-core";
3223
3417
  function useContacts() {
3224
- const hasAccess = ref26(false);
3225
- const error = ref26(null);
3418
+ const hasAccess = ref30(false);
3419
+ const error = ref30(null);
3226
3420
  async function requestAccess() {
3227
3421
  try {
3228
3422
  const result = await NativeBridge.invokeNativeModule("Contacts", "requestAccess");