@thelacanians/vue-native-runtime 0.2.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
@@ -56,7 +56,7 @@ function createCommentNode(_text) {
56
56
  }
57
57
 
58
58
  // src/bridge.ts
59
- var NativeBridgeImpl = class {
59
+ var _NativeBridgeImpl = class _NativeBridgeImpl {
60
60
  constructor() {
61
61
  /** Pending operations waiting to be flushed to native */
62
62
  this.pendingOps = [];
@@ -66,7 +66,8 @@ var NativeBridgeImpl = class {
66
66
  this.eventHandlers = /* @__PURE__ */ new Map();
67
67
  /** Pending async callbacks from native module invocations */
68
68
  this.pendingCallbacks = /* @__PURE__ */ new Map();
69
- /** Auto-incrementing callback ID for async native module calls */
69
+ /** Auto-incrementing callback ID for async native module calls.
70
+ * Wraps around at MAX_SAFE_CALLBACK_ID to prevent overflow. */
70
71
  this.nextCallbackId = 1;
71
72
  /** Global event listeners: eventName -> Set of callbacks */
72
73
  this.globalEventHandlers = /* @__PURE__ */ new Map();
@@ -98,10 +99,14 @@ var NativeBridgeImpl = class {
98
99
  const json = JSON.stringify(ops);
99
100
  const flushFn = globalThis.__VN_flushOperations;
100
101
  if (typeof flushFn === "function") {
101
- flushFn(json);
102
- } else if (__DEV__) {
102
+ try {
103
+ flushFn(json);
104
+ } catch (err) {
105
+ console.error("[VueNative] Error in __VN_flushOperations:", err);
106
+ }
107
+ } else {
103
108
  console.warn(
104
- "[VueNative] __VN_flushOperations is not registered. Make sure the Swift runtime has been initialized."
109
+ "[VueNative] __VN_flushOperations is not registered. Make sure the native runtime has been initialized."
105
110
  );
106
111
  }
107
112
  }
@@ -261,7 +266,12 @@ var NativeBridgeImpl = class {
261
266
  */
262
267
  invokeNativeModule(moduleName, methodName, args = [], timeoutMs = 3e4) {
263
268
  return new Promise((resolve, reject) => {
264
- const callbackId = this.nextCallbackId++;
269
+ const callbackId = this.nextCallbackId;
270
+ if (this.nextCallbackId >= _NativeBridgeImpl.MAX_CALLBACK_ID) {
271
+ this.nextCallbackId = 1;
272
+ } else {
273
+ this.nextCallbackId++;
274
+ }
265
275
  const timeoutId = setTimeout(() => {
266
276
  if (this.pendingCallbacks.has(callbackId)) {
267
277
  this.pendingCallbacks.delete(callbackId);
@@ -270,6 +280,17 @@ var NativeBridgeImpl = class {
270
280
  ));
271
281
  }
272
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
+ }
273
294
  this.pendingCallbacks.set(callbackId, { resolve, reject, timeoutId });
274
295
  this.enqueue("invokeNativeModule", [moduleName, methodName, args, callbackId]);
275
296
  });
@@ -289,11 +310,9 @@ var NativeBridgeImpl = class {
289
310
  resolveCallback(callbackId, result, error) {
290
311
  const pending = this.pendingCallbacks.get(callbackId);
291
312
  if (!pending) {
292
- if (__DEV__) {
293
- console.warn(
294
- `[VueNative] Received callback for unknown callbackId: ${callbackId}`
295
- );
296
- }
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
+ );
297
316
  return;
298
317
  }
299
318
  clearTimeout(pending.timeoutId);
@@ -356,6 +375,11 @@ var NativeBridgeImpl = class {
356
375
  this.globalEventHandlers.clear();
357
376
  }
358
377
  };
378
+ /** Maximum callback ID before wraparound (safe for 32-bit signed int) */
379
+ _NativeBridgeImpl.MAX_CALLBACK_ID = 2147483647;
380
+ /** Maximum number of pending callbacks before evicting the oldest */
381
+ _NativeBridgeImpl.MAX_PENDING_CALLBACKS = 1e3;
382
+ var NativeBridgeImpl = _NativeBridgeImpl;
359
383
  if (typeof globalThis.__DEV__ === "undefined") {
360
384
  ;
361
385
  globalThis.__DEV__ = true;
@@ -374,17 +398,21 @@ function toEventName(key) {
374
398
  return key.slice(2).toLowerCase();
375
399
  }
376
400
  function patchStyle(nodeId, prevStyle, nextStyle) {
377
- const prev = prevStyle || {};
378
- const next = nextStyle || {};
379
- for (const key in next) {
380
- if (next[key] !== prev[key]) {
381
- NativeBridge.updateStyle(nodeId, key, next[key]);
401
+ try {
402
+ const prev = prevStyle || {};
403
+ const next = nextStyle || {};
404
+ for (const key in next) {
405
+ if (next[key] !== prev[key]) {
406
+ NativeBridge.updateStyle(nodeId, key, next[key]);
407
+ }
382
408
  }
383
- }
384
- for (const key in prev) {
385
- if (!(key in next)) {
386
- NativeBridge.updateStyle(nodeId, key, null);
409
+ for (const key in prev) {
410
+ if (!(key in next)) {
411
+ NativeBridge.updateStyle(nodeId, key, null);
412
+ }
387
413
  }
414
+ } catch (err) {
415
+ console.error(`[VueNative] Error patching style on node ${nodeId}:`, err);
388
416
  }
389
417
  }
390
418
  var nodeOps = {
@@ -437,22 +465,26 @@ var nodeOps = {
437
465
  * - all else -> updateProp
438
466
  */
439
467
  patchProp(el, key, prevValue, nextValue) {
440
- if (key.startsWith("on") && key.length > 2 && key[2] === key[2].toUpperCase()) {
441
- const eventName = toEventName(key);
442
- if (prevValue) {
443
- NativeBridge.removeEventListener(el.id, eventName);
468
+ try {
469
+ if (key.startsWith("on") && key.length > 2 && key[2] === key[2].toUpperCase()) {
470
+ const eventName = toEventName(key);
471
+ if (prevValue) {
472
+ NativeBridge.removeEventListener(el.id, eventName);
473
+ }
474
+ if (nextValue) {
475
+ NativeBridge.addEventListener(el.id, eventName, nextValue);
476
+ }
477
+ return;
444
478
  }
445
- if (nextValue) {
446
- NativeBridge.addEventListener(el.id, eventName, nextValue);
479
+ if (key === "style") {
480
+ patchStyle(el.id, prevValue, nextValue);
481
+ return;
447
482
  }
448
- return;
449
- }
450
- if (key === "style") {
451
- patchStyle(el.id, prevValue, nextValue);
452
- return;
483
+ el.props[key] = nextValue;
484
+ NativeBridge.updateProp(el.id, key, nextValue);
485
+ } catch (err) {
486
+ console.error(`[VueNative] Error patching prop "${key}" on node ${el.id}:`, err);
453
487
  }
454
- el.props[key] = nextValue;
455
- NativeBridge.updateProp(el.id, key, nextValue);
456
488
  },
457
489
  /**
458
490
  * Insert a child node into a parent, optionally before an anchor node.
@@ -467,30 +499,34 @@ var nodeOps = {
467
499
  }
468
500
  }
469
501
  child.parent = parent;
470
- if (anchor) {
471
- const anchorIdx = parent.children.indexOf(anchor);
472
- if (anchorIdx !== -1) {
473
- parent.children.splice(anchorIdx, 0, child);
474
- } else {
475
- parent.children.push(child);
476
- }
477
- if (child.type !== "__COMMENT__") {
478
- if (anchor.type !== "__COMMENT__") {
479
- NativeBridge.insertBefore(parent.id, child.id, anchor.id);
502
+ try {
503
+ if (anchor) {
504
+ const anchorIdx = parent.children.indexOf(anchor);
505
+ if (anchorIdx !== -1) {
506
+ parent.children.splice(anchorIdx, 0, child);
480
507
  } else {
481
- const realAnchor = findNextNonComment(parent, anchor);
482
- if (realAnchor) {
483
- NativeBridge.insertBefore(parent.id, child.id, realAnchor.id);
508
+ parent.children.push(child);
509
+ }
510
+ if (child.type !== "__COMMENT__") {
511
+ if (anchor.type !== "__COMMENT__") {
512
+ NativeBridge.insertBefore(parent.id, child.id, anchor.id);
484
513
  } else {
485
- NativeBridge.appendChild(parent.id, child.id);
514
+ const realAnchor = findNextNonComment(parent, anchor);
515
+ if (realAnchor) {
516
+ NativeBridge.insertBefore(parent.id, child.id, realAnchor.id);
517
+ } else {
518
+ NativeBridge.appendChild(parent.id, child.id);
519
+ }
486
520
  }
487
521
  }
522
+ } else {
523
+ parent.children.push(child);
524
+ if (child.type !== "__COMMENT__") {
525
+ NativeBridge.appendChild(parent.id, child.id);
526
+ }
488
527
  }
489
- } else {
490
- parent.children.push(child);
491
- if (child.type !== "__COMMENT__") {
492
- NativeBridge.appendChild(parent.id, child.id);
493
- }
528
+ } catch (err) {
529
+ console.error(`[VueNative] Error inserting node ${child.id} into ${parent.id}:`, err);
494
530
  }
495
531
  },
496
532
  /**
@@ -504,8 +540,12 @@ var nodeOps = {
504
540
  parent.children.splice(idx, 1);
505
541
  }
506
542
  child.parent = null;
507
- if (child.type !== "__COMMENT__") {
508
- NativeBridge.removeChild(parent.id, child.id);
543
+ try {
544
+ if (child.type !== "__COMMENT__") {
545
+ NativeBridge.removeChild(parent.id, child.id);
546
+ }
547
+ } catch (err) {
548
+ console.error(`[VueNative] Error removing node ${child.id}:`, err);
509
549
  }
510
550
  }
511
551
  },
@@ -611,7 +651,7 @@ var VButton = defineComponent3({
611
651
  });
612
652
 
613
653
  // src/components/VInput.ts
614
- import { defineComponent as defineComponent4, h as h4 } from "@vue/runtime-core";
654
+ import { defineComponent as defineComponent4, h as h4, ref } from "@vue/runtime-core";
615
655
  var VInput = defineComponent4({
616
656
  name: "VInput",
617
657
  props: {
@@ -653,7 +693,17 @@ var VInput = defineComponent4({
653
693
  },
654
694
  emits: ["update:modelValue", "focus", "blur", "submit"],
655
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
+ };
656
705
  const onChangetext = (payload) => {
706
+ if (isComposing.value) return;
657
707
  const text = typeof payload === "string" ? payload : payload?.text ?? "";
658
708
  emit("update:modelValue", text);
659
709
  };
@@ -682,6 +732,8 @@ var VInput = defineComponent4({
682
732
  accessibilityHint: props.accessibilityHint,
683
733
  accessibilityState: props.accessibilityState,
684
734
  onChangetext,
735
+ onCompositionstart,
736
+ onCompositionend,
685
737
  onFocus,
686
738
  onBlur,
687
739
  onSubmit
@@ -794,6 +846,11 @@ var VScrollView = defineComponent7({
794
846
  default: false
795
847
  },
796
848
  contentContainerStyle: Object,
849
+ /** Minimum interval in ms between scroll event emissions. Default: 16 (~60fps) */
850
+ scrollEventThrottle: {
851
+ type: Number,
852
+ default: 16
853
+ },
797
854
  /** Whether the pull-to-refresh indicator is active */
798
855
  refreshing: {
799
856
  type: Boolean,
@@ -807,8 +864,13 @@ var VScrollView = defineComponent7({
807
864
  },
808
865
  emits: ["scroll", "refresh"],
809
866
  setup(props, { slots, emit }) {
867
+ let lastScrollEmit = 0;
810
868
  const onScroll = (payload) => {
811
- emit("scroll", payload);
869
+ const now = Date.now();
870
+ if (now - lastScrollEmit >= props.scrollEventThrottle) {
871
+ lastScrollEmit = now;
872
+ emit("scroll", payload);
873
+ }
812
874
  };
813
875
  const onRefresh = () => {
814
876
  emit("refresh");
@@ -838,7 +900,7 @@ var VScrollView = defineComponent7({
838
900
  });
839
901
 
840
902
  // src/components/VImage.ts
841
- 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";
842
904
  var VImage = defineComponent8({
843
905
  name: "VImage",
844
906
  props: {
@@ -855,13 +917,29 @@ var VImage = defineComponent8({
855
917
  accessibilityState: Object
856
918
  },
857
919
  emits: ["load", "error"],
858
- 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 });
859
937
  return () => h8(
860
938
  "VImage",
861
939
  {
862
940
  ...props,
863
- onLoad: () => emit("load"),
864
- onError: (e) => emit("error", e)
941
+ onLoad,
942
+ onError
865
943
  }
866
944
  );
867
945
  }
@@ -967,8 +1045,29 @@ var VList = defineComponent12({
967
1045
  },
968
1046
  emits: ["scroll", "endReached"],
969
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
+ };
970
1056
  return () => {
971
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
+ }
972
1071
  const children = [];
973
1072
  if (slots.header) {
974
1073
  children.push(
@@ -1006,7 +1105,7 @@ var VList = defineComponent12({
1006
1105
  showsScrollIndicator: props.showsScrollIndicator,
1007
1106
  bounces: props.bounces,
1008
1107
  horizontal: props.horizontal,
1009
- onScroll: (e) => emit("scroll", e),
1108
+ onScroll,
1010
1109
  onEndReached: () => emit("endReached")
1011
1110
  },
1012
1111
  children
@@ -1016,7 +1115,7 @@ var VList = defineComponent12({
1016
1115
  });
1017
1116
 
1018
1117
  // src/components/VModal.ts
1019
- 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";
1020
1119
  var VModal = defineComponent13({
1021
1120
  name: "VModal",
1022
1121
  props: {
@@ -1031,10 +1130,24 @@ var VModal = defineComponent13({
1031
1130
  },
1032
1131
  emits: ["dismiss"],
1033
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
+ });
1034
1147
  return () => h13(
1035
1148
  "VModal",
1036
1149
  {
1037
- visible: props.visible,
1150
+ visible: debouncedVisible.value,
1038
1151
  style: props.style,
1039
1152
  onDismiss: () => emit("dismiss")
1040
1153
  },
@@ -1044,7 +1157,7 @@ var VModal = defineComponent13({
1044
1157
  });
1045
1158
 
1046
1159
  // src/components/VAlertDialog.ts
1047
- 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";
1048
1161
  var VAlertDialog = defineComponent14({
1049
1162
  name: "VAlertDialog",
1050
1163
  props: {
@@ -1055,8 +1168,22 @@ var VAlertDialog = defineComponent14({
1055
1168
  },
1056
1169
  emits: ["confirm", "cancel", "action"],
1057
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
+ });
1058
1185
  return () => h14("VAlertDialog", {
1059
- visible: props.visible,
1186
+ visible: debouncedVisible.value,
1060
1187
  title: props.title,
1061
1188
  message: props.message,
1062
1189
  buttons: props.buttons,
@@ -1086,7 +1213,7 @@ var VStatusBar = defineComponent15({
1086
1213
  });
1087
1214
 
1088
1215
  // src/components/VWebView.ts
1089
- import { defineComponent as defineComponent16, h as h16 } from "@vue/runtime-core";
1216
+ import { computed, defineComponent as defineComponent16, h as h16 } from "@vue/runtime-core";
1090
1217
  var VWebView = defineComponent16({
1091
1218
  name: "VWebView",
1092
1219
  props: {
@@ -1096,8 +1223,18 @@ var VWebView = defineComponent16({
1096
1223
  },
1097
1224
  emits: ["load", "error", "message"],
1098
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
+ });
1099
1236
  return () => h16("VWebView", {
1100
- source: props.source,
1237
+ source: sanitizedSource.value,
1101
1238
  style: props.style,
1102
1239
  javaScriptEnabled: props.javaScriptEnabled,
1103
1240
  onLoad: (e) => emit("load", e),
@@ -1561,7 +1698,7 @@ var vShow = {
1561
1698
  };
1562
1699
 
1563
1700
  // src/errorBoundary.ts
1564
- 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";
1565
1702
  var ErrorBoundary = defineComponent28({
1566
1703
  name: "ErrorBoundary",
1567
1704
  props: {
@@ -1572,8 +1709,8 @@ var ErrorBoundary = defineComponent28({
1572
1709
  }
1573
1710
  },
1574
1711
  setup(props, { slots }) {
1575
- const error = ref(null);
1576
- const errorInfo = ref("");
1712
+ const error = ref5(null);
1713
+ const errorInfo = ref5("");
1577
1714
  onErrorCaptured((err, _instance, info) => {
1578
1715
  const normalizedError = err instanceof Error ? err : new Error(String(err));
1579
1716
  error.value = normalizedError;
@@ -1587,7 +1724,7 @@ var ErrorBoundary = defineComponent28({
1587
1724
  error.value = null;
1588
1725
  errorInfo.value = "";
1589
1726
  }
1590
- watch(
1727
+ watch4(
1591
1728
  () => props.resetKeys,
1592
1729
  () => {
1593
1730
  if (error.value) {
@@ -1743,15 +1880,33 @@ function useHaptics() {
1743
1880
  }
1744
1881
 
1745
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
+ }
1746
1895
  function useAsyncStorage() {
1747
1896
  function getItem(key) {
1748
1897
  return NativeBridge.invokeNativeModule("AsyncStorage", "getItem", [key]);
1749
1898
  }
1750
1899
  function setItem(key, value) {
1751
- 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
+ );
1752
1904
  }
1753
1905
  function removeItem(key) {
1754
- return NativeBridge.invokeNativeModule("AsyncStorage", "removeItem", [key]).then(() => void 0);
1906
+ return queueWrite(
1907
+ key,
1908
+ () => NativeBridge.invokeNativeModule("AsyncStorage", "removeItem", [key]).then(() => void 0)
1909
+ );
1755
1910
  }
1756
1911
  function getAllKeys() {
1757
1912
  return NativeBridge.invokeNativeModule("AsyncStorage", "getAllKeys", []);
@@ -1763,9 +1918,9 @@ function useAsyncStorage() {
1763
1918
  }
1764
1919
 
1765
1920
  // src/composables/useClipboard.ts
1766
- import { ref as ref2 } from "@vue/runtime-core";
1921
+ import { ref as ref6 } from "@vue/runtime-core";
1767
1922
  function useClipboard() {
1768
- const content = ref2("");
1923
+ const content = ref6("");
1769
1924
  function copy(text) {
1770
1925
  return NativeBridge.invokeNativeModule("Clipboard", "copy", [text]).then(() => void 0);
1771
1926
  }
@@ -1779,16 +1934,16 @@ function useClipboard() {
1779
1934
  }
1780
1935
 
1781
1936
  // src/composables/useDeviceInfo.ts
1782
- import { ref as ref3, onMounted } from "@vue/runtime-core";
1937
+ import { ref as ref7, onMounted } from "@vue/runtime-core";
1783
1938
  function useDeviceInfo() {
1784
- const model = ref3("");
1785
- const systemVersion = ref3("");
1786
- const systemName = ref3("");
1787
- const name = ref3("");
1788
- const screenWidth = ref3(0);
1789
- const screenHeight = ref3(0);
1790
- const scale = ref3(1);
1791
- 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);
1792
1947
  async function fetchInfo() {
1793
1948
  const info = await NativeBridge.invokeNativeModule("DeviceInfo", "getInfo", []);
1794
1949
  model.value = info.model ?? "";
@@ -1817,10 +1972,10 @@ function useDeviceInfo() {
1817
1972
  }
1818
1973
 
1819
1974
  // src/composables/useKeyboard.ts
1820
- import { ref as ref4 } from "@vue/runtime-core";
1975
+ import { ref as ref8 } from "@vue/runtime-core";
1821
1976
  function useKeyboard() {
1822
- const isVisible = ref4(false);
1823
- const height = ref4(0);
1977
+ const isVisible = ref8(false);
1978
+ const height = ref8(0);
1824
1979
  function dismiss() {
1825
1980
  return NativeBridge.invokeNativeModule("Keyboard", "dismiss", []).then(() => void 0);
1826
1981
  }
@@ -1884,27 +2039,32 @@ function useAnimation() {
1884
2039
  }
1885
2040
 
1886
2041
  // src/composables/useNetwork.ts
1887
- import { ref as ref5, onUnmounted } from "@vue/runtime-core";
2042
+ import { ref as ref9, onUnmounted as onUnmounted3 } from "@vue/runtime-core";
1888
2043
  function useNetwork() {
1889
- const isConnected = ref5(true);
1890
- const connectionType = ref5("unknown");
1891
- NativeBridge.invokeNativeModule("Network", "getStatus").then((status) => {
1892
- isConnected.value = status.isConnected;
1893
- connectionType.value = status.connectionType;
1894
- }).catch(() => {
1895
- });
2044
+ const isConnected = ref9(true);
2045
+ const connectionType = ref9("unknown");
2046
+ let lastEventTime = 0;
1896
2047
  const unsubscribe = NativeBridge.onGlobalEvent("network:change", (payload) => {
2048
+ lastEventTime = Date.now();
1897
2049
  isConnected.value = payload.isConnected;
1898
2050
  connectionType.value = payload.connectionType;
1899
2051
  });
1900
- 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);
1901
2061
  return { isConnected, connectionType };
1902
2062
  }
1903
2063
 
1904
2064
  // src/composables/useAppState.ts
1905
- import { ref as ref6, onUnmounted as onUnmounted2 } from "@vue/runtime-core";
2065
+ import { ref as ref10, onUnmounted as onUnmounted4 } from "@vue/runtime-core";
1906
2066
  function useAppState() {
1907
- const state = ref6("active");
2067
+ const state = ref10("active");
1908
2068
  NativeBridge.invokeNativeModule("AppState", "getState").then((s) => {
1909
2069
  state.value = s;
1910
2070
  }).catch(() => {
@@ -1912,7 +2072,7 @@ function useAppState() {
1912
2072
  const unsubscribe = NativeBridge.onGlobalEvent("appState:change", (payload) => {
1913
2073
  state.value = payload.state;
1914
2074
  });
1915
- onUnmounted2(unsubscribe);
2075
+ onUnmounted4(unsubscribe);
1916
2076
  return { state };
1917
2077
  }
1918
2078
 
@@ -1947,27 +2107,45 @@ function usePermissions() {
1947
2107
  }
1948
2108
 
1949
2109
  // src/composables/useGeolocation.ts
1950
- import { ref as ref7, onUnmounted as onUnmounted3 } from "@vue/runtime-core";
2110
+ import { ref as ref11, onUnmounted as onUnmounted5 } from "@vue/runtime-core";
1951
2111
  function useGeolocation() {
1952
- const coords = ref7(null);
1953
- const error = ref7(null);
2112
+ const coords = ref11(null);
2113
+ const error = ref11(null);
1954
2114
  let watchId = null;
1955
2115
  async function getCurrentPosition() {
1956
- const result = await NativeBridge.invokeNativeModule("Geolocation", "getCurrentPosition");
1957
- coords.value = result;
1958
- 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
+ }
1959
2126
  }
1960
2127
  async function watchPosition() {
1961
- const id = await NativeBridge.invokeNativeModule("Geolocation", "watchPosition");
1962
- watchId = id;
1963
- const unsubscribe = NativeBridge.onGlobalEvent("location:update", (payload) => {
1964
- coords.value = payload;
1965
- });
1966
- onUnmounted3(() => {
1967
- unsubscribe();
1968
- if (watchId !== null) clearWatch(watchId);
1969
- });
1970
- 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
+ }
1971
2149
  }
1972
2150
  async function clearWatch(id) {
1973
2151
  await NativeBridge.invokeNativeModule("Geolocation", "clearWatch", [id]);
@@ -1977,7 +2155,7 @@ function useGeolocation() {
1977
2155
  }
1978
2156
 
1979
2157
  // src/composables/useCamera.ts
1980
- import { onUnmounted as onUnmounted4 } from "@vue/runtime-core";
2158
+ import { onUnmounted as onUnmounted6 } from "@vue/runtime-core";
1981
2159
  function useCamera() {
1982
2160
  const qrCleanups = [];
1983
2161
  async function launchCamera(options = {}) {
@@ -2000,7 +2178,7 @@ function useCamera() {
2000
2178
  qrCleanups.push(unsubscribe);
2001
2179
  return unsubscribe;
2002
2180
  }
2003
- onUnmounted4(() => {
2181
+ onUnmounted6(() => {
2004
2182
  NativeBridge.invokeNativeModule("Camera", "stopQRScan").catch(() => {
2005
2183
  });
2006
2184
  qrCleanups.forEach((fn) => fn());
@@ -2010,10 +2188,10 @@ function useCamera() {
2010
2188
  }
2011
2189
 
2012
2190
  // src/composables/useNotifications.ts
2013
- import { ref as ref8, onUnmounted as onUnmounted5 } from "@vue/runtime-core";
2191
+ import { ref as ref12, onUnmounted as onUnmounted7 } from "@vue/runtime-core";
2014
2192
  function useNotifications() {
2015
- const isGranted = ref8(false);
2016
- const pushToken = ref8(null);
2193
+ const isGranted = ref12(false);
2194
+ const pushToken = ref12(null);
2017
2195
  async function requestPermission() {
2018
2196
  const granted = await NativeBridge.invokeNativeModule("Notifications", "requestPermission");
2019
2197
  isGranted.value = granted;
@@ -2033,7 +2211,7 @@ function useNotifications() {
2033
2211
  }
2034
2212
  function onNotification(handler) {
2035
2213
  const unsubscribe = NativeBridge.onGlobalEvent("notification:received", handler);
2036
- onUnmounted5(unsubscribe);
2214
+ onUnmounted7(unsubscribe);
2037
2215
  return unsubscribe;
2038
2216
  }
2039
2217
  async function registerForPush() {
@@ -2047,12 +2225,12 @@ function useNotifications() {
2047
2225
  pushToken.value = payload.token;
2048
2226
  handler(payload.token);
2049
2227
  });
2050
- onUnmounted5(unsubscribe);
2228
+ onUnmounted7(unsubscribe);
2051
2229
  return unsubscribe;
2052
2230
  }
2053
2231
  function onPushReceived(handler) {
2054
2232
  const unsubscribe = NativeBridge.onGlobalEvent("push:received", handler);
2055
- onUnmounted5(unsubscribe);
2233
+ onUnmounted7(unsubscribe);
2056
2234
  return unsubscribe;
2057
2235
  }
2058
2236
  return {
@@ -2088,7 +2266,7 @@ function useBiometry() {
2088
2266
  }
2089
2267
 
2090
2268
  // src/composables/useHttp.ts
2091
- import { ref as ref9 } from "@vue/runtime-core";
2269
+ import { ref as ref13, onUnmounted as onUnmounted8 } from "@vue/runtime-core";
2092
2270
  function useHttp(config = {}) {
2093
2271
  if (config.pins && Object.keys(config.pins).length > 0) {
2094
2272
  const configurePins = globalThis.__VN_configurePins;
@@ -2098,8 +2276,12 @@ function useHttp(config = {}) {
2098
2276
  NativeBridge.invokeNativeModule("Http", "configurePins", [config.pins]);
2099
2277
  }
2100
2278
  }
2101
- const loading = ref9(false);
2102
- const error = ref9(null);
2279
+ const loading = ref13(false);
2280
+ const error = ref13(null);
2281
+ let isMounted = true;
2282
+ onUnmounted8(() => {
2283
+ isMounted = false;
2284
+ });
2103
2285
  async function request(method, url, options = {}) {
2104
2286
  const fullUrl = config.baseURL ? `${config.baseURL}${url}` : url;
2105
2287
  loading.value = true;
@@ -2119,6 +2301,9 @@ function useHttp(config = {}) {
2119
2301
  }
2120
2302
  const response = await fetch(fullUrl, fetchOptions);
2121
2303
  const data = await response.json();
2304
+ if (!isMounted) {
2305
+ return { data, status: response.status, ok: response.ok, headers: {} };
2306
+ }
2122
2307
  return {
2123
2308
  data,
2124
2309
  status: response.status,
@@ -2127,10 +2312,14 @@ function useHttp(config = {}) {
2127
2312
  };
2128
2313
  } catch (e) {
2129
2314
  const msg = e instanceof Error ? e.message : String(e);
2130
- error.value = msg;
2315
+ if (isMounted) {
2316
+ error.value = msg;
2317
+ }
2131
2318
  throw e;
2132
2319
  } finally {
2133
- loading.value = false;
2320
+ if (isMounted) {
2321
+ loading.value = false;
2322
+ }
2134
2323
  }
2135
2324
  }
2136
2325
  return {
@@ -2145,10 +2334,10 @@ function useHttp(config = {}) {
2145
2334
  }
2146
2335
 
2147
2336
  // src/composables/useColorScheme.ts
2148
- import { ref as ref10, onUnmounted as onUnmounted6 } from "@vue/runtime-core";
2337
+ import { ref as ref14, onUnmounted as onUnmounted9 } from "@vue/runtime-core";
2149
2338
  function useColorScheme() {
2150
- const colorScheme = ref10("light");
2151
- const isDark = ref10(false);
2339
+ const colorScheme = ref14("light");
2340
+ const isDark = ref14(false);
2152
2341
  const unsubscribe = NativeBridge.onGlobalEvent(
2153
2342
  "colorScheme:change",
2154
2343
  (payload) => {
@@ -2156,20 +2345,24 @@ function useColorScheme() {
2156
2345
  isDark.value = payload.colorScheme === "dark";
2157
2346
  }
2158
2347
  );
2159
- onUnmounted6(unsubscribe);
2348
+ onUnmounted9(unsubscribe);
2160
2349
  return { colorScheme, isDark };
2161
2350
  }
2162
2351
 
2163
2352
  // src/composables/useBackHandler.ts
2164
- import { onMounted as onMounted2, onUnmounted as onUnmounted7 } from "@vue/runtime-core";
2353
+ import { onMounted as onMounted2, onUnmounted as onUnmounted10 } from "@vue/runtime-core";
2165
2354
  function useBackHandler(handler) {
2166
2355
  let unsubscribe = null;
2167
2356
  onMounted2(() => {
2168
2357
  unsubscribe = NativeBridge.onGlobalEvent("hardware:backPress", () => {
2169
- handler();
2358
+ const handled = handler();
2359
+ if (!handled) {
2360
+ NativeBridge.invokeNativeModule("BackHandler", "exitApp", []).catch(() => {
2361
+ });
2362
+ }
2170
2363
  });
2171
2364
  });
2172
- onUnmounted7(() => {
2365
+ onUnmounted10(() => {
2173
2366
  unsubscribe?.();
2174
2367
  unsubscribe = null;
2175
2368
  });
@@ -2193,10 +2386,10 @@ function useSecureStorage() {
2193
2386
  }
2194
2387
 
2195
2388
  // src/composables/useI18n.ts
2196
- import { ref as ref11, onMounted as onMounted3 } from "@vue/runtime-core";
2389
+ import { ref as ref15, onMounted as onMounted3 } from "@vue/runtime-core";
2197
2390
  function useI18n() {
2198
- const isRTL = ref11(false);
2199
- const locale = ref11("en");
2391
+ const isRTL = ref15(false);
2392
+ const locale = ref15("en");
2200
2393
  onMounted3(async () => {
2201
2394
  try {
2202
2395
  const info = await NativeBridge.invokeNativeModule("DeviceInfo", "getDeviceInfo", []);
@@ -2217,11 +2410,11 @@ function usePlatform() {
2217
2410
  }
2218
2411
 
2219
2412
  // src/composables/useDimensions.ts
2220
- 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";
2221
2414
  function useDimensions() {
2222
- const width = ref12(0);
2223
- const height = ref12(0);
2224
- const scale = ref12(1);
2415
+ const width = ref16(0);
2416
+ const height = ref16(0);
2417
+ const scale = ref16(1);
2225
2418
  onMounted4(async () => {
2226
2419
  try {
2227
2420
  const info = await NativeBridge.invokeNativeModule("DeviceInfo", "getInfo", []);
@@ -2236,12 +2429,12 @@ function useDimensions() {
2236
2429
  if (payload.height != null) height.value = payload.height;
2237
2430
  if (payload.scale != null) scale.value = payload.scale;
2238
2431
  });
2239
- onUnmounted8(cleanup);
2432
+ onUnmounted11(cleanup);
2240
2433
  return { width, height, scale };
2241
2434
  }
2242
2435
 
2243
2436
  // src/composables/useWebSocket.ts
2244
- import { ref as ref13, onUnmounted as onUnmounted9 } from "@vue/runtime-core";
2437
+ import { ref as ref17, onUnmounted as onUnmounted12 } from "@vue/runtime-core";
2245
2438
  var connectionCounter = 0;
2246
2439
  function useWebSocket(url, options = {}) {
2247
2440
  const {
@@ -2251,11 +2444,13 @@ function useWebSocket(url, options = {}) {
2251
2444
  reconnectInterval = 1e3
2252
2445
  } = options;
2253
2446
  const connectionId = `ws_${++connectionCounter}_${Date.now()}`;
2254
- const status = ref13("CLOSED");
2255
- const lastMessage = ref13(null);
2256
- const error = ref13(null);
2447
+ const status = ref17("CLOSED");
2448
+ const lastMessage = ref17(null);
2449
+ const error = ref17(null);
2257
2450
  let reconnectAttempts = 0;
2258
2451
  let reconnectTimer = null;
2452
+ const MAX_PENDING_MESSAGES = 100;
2453
+ const pendingMessages = [];
2259
2454
  const unsubscribers = [];
2260
2455
  unsubscribers.push(
2261
2456
  NativeBridge.onGlobalEvent("websocket:open", (payload) => {
@@ -2263,6 +2458,12 @@ function useWebSocket(url, options = {}) {
2263
2458
  status.value = "OPEN";
2264
2459
  error.value = null;
2265
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
+ }
2266
2467
  })
2267
2468
  );
2268
2469
  unsubscribers.push(
@@ -2277,9 +2478,10 @@ function useWebSocket(url, options = {}) {
2277
2478
  status.value = "CLOSED";
2278
2479
  if (autoReconnect && reconnectAttempts < maxReconnectAttempts && payload.code !== 1e3) {
2279
2480
  reconnectAttempts++;
2481
+ const backoffMs = reconnectInterval * Math.pow(2, reconnectAttempts - 1);
2280
2482
  reconnectTimer = setTimeout(() => {
2281
2483
  open();
2282
- }, reconnectInterval);
2484
+ }, backoffMs);
2283
2485
  }
2284
2486
  })
2285
2487
  );
@@ -2299,8 +2501,17 @@ function useWebSocket(url, options = {}) {
2299
2501
  });
2300
2502
  }
2301
2503
  function send(data) {
2302
- if (status.value !== "OPEN") return;
2303
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
+ }
2304
2515
  NativeBridge.invokeNativeModule("WebSocket", "send", [connectionId, message]).catch((err) => {
2305
2516
  error.value = err.message;
2306
2517
  });
@@ -2320,7 +2531,7 @@ function useWebSocket(url, options = {}) {
2320
2531
  if (autoConnect) {
2321
2532
  open();
2322
2533
  }
2323
- onUnmounted9(() => {
2534
+ onUnmounted12(() => {
2324
2535
  if (reconnectTimer) {
2325
2536
  clearTimeout(reconnectTimer);
2326
2537
  }
@@ -2389,12 +2600,12 @@ function useFileSystem() {
2389
2600
  }
2390
2601
 
2391
2602
  // src/composables/useSensors.ts
2392
- import { ref as ref14, onUnmounted as onUnmounted10 } from "@vue/runtime-core";
2603
+ import { ref as ref18, onUnmounted as onUnmounted13 } from "@vue/runtime-core";
2393
2604
  function useAccelerometer(options = {}) {
2394
- const x = ref14(0);
2395
- const y = ref14(0);
2396
- const z = ref14(0);
2397
- const isAvailable = ref14(false);
2605
+ const x = ref18(0);
2606
+ const y = ref18(0);
2607
+ const z = ref18(0);
2608
+ const isAvailable = ref18(false);
2398
2609
  let running = false;
2399
2610
  let unsubscribe = null;
2400
2611
  NativeBridge.invokeNativeModule("Sensors", "isAvailable", ["accelerometer"]).then((result) => {
@@ -2422,16 +2633,16 @@ function useAccelerometer(options = {}) {
2422
2633
  NativeBridge.invokeNativeModule("Sensors", "stopAccelerometer").catch(() => {
2423
2634
  });
2424
2635
  }
2425
- onUnmounted10(() => {
2636
+ onUnmounted13(() => {
2426
2637
  stop();
2427
2638
  });
2428
2639
  return { x, y, z, isAvailable, start, stop };
2429
2640
  }
2430
2641
  function useGyroscope(options = {}) {
2431
- const x = ref14(0);
2432
- const y = ref14(0);
2433
- const z = ref14(0);
2434
- const isAvailable = ref14(false);
2642
+ const x = ref18(0);
2643
+ const y = ref18(0);
2644
+ const z = ref18(0);
2645
+ const isAvailable = ref18(false);
2435
2646
  let running = false;
2436
2647
  let unsubscribe = null;
2437
2648
  NativeBridge.invokeNativeModule("Sensors", "isAvailable", ["gyroscope"]).then((result) => {
@@ -2459,20 +2670,20 @@ function useGyroscope(options = {}) {
2459
2670
  NativeBridge.invokeNativeModule("Sensors", "stopGyroscope").catch(() => {
2460
2671
  });
2461
2672
  }
2462
- onUnmounted10(() => {
2673
+ onUnmounted13(() => {
2463
2674
  stop();
2464
2675
  });
2465
2676
  return { x, y, z, isAvailable, start, stop };
2466
2677
  }
2467
2678
 
2468
2679
  // src/composables/useAudio.ts
2469
- import { ref as ref15, onUnmounted as onUnmounted11 } from "@vue/runtime-core";
2680
+ import { ref as ref19, onUnmounted as onUnmounted14 } from "@vue/runtime-core";
2470
2681
  function useAudio() {
2471
- const duration = ref15(0);
2472
- const position = ref15(0);
2473
- const isPlaying = ref15(false);
2474
- const isRecording = ref15(false);
2475
- 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);
2476
2687
  const unsubProgress = NativeBridge.onGlobalEvent("audio:progress", (payload) => {
2477
2688
  position.value = payload.currentTime ?? 0;
2478
2689
  duration.value = payload.duration ?? 0;
@@ -2485,7 +2696,7 @@ function useAudio() {
2485
2696
  error.value = payload.message ?? "Unknown audio error";
2486
2697
  isPlaying.value = false;
2487
2698
  });
2488
- onUnmounted11(() => {
2699
+ onUnmounted14(() => {
2489
2700
  unsubProgress();
2490
2701
  unsubComplete();
2491
2702
  unsubError();
@@ -2564,9 +2775,9 @@ function useAudio() {
2564
2775
  }
2565
2776
 
2566
2777
  // src/composables/useDatabase.ts
2567
- import { ref as ref16, onUnmounted as onUnmounted12 } from "@vue/runtime-core";
2778
+ import { ref as ref20, onUnmounted as onUnmounted15 } from "@vue/runtime-core";
2568
2779
  function useDatabase(name = "default") {
2569
- const isOpen = ref16(false);
2780
+ const isOpen = ref20(false);
2570
2781
  let opened = false;
2571
2782
  async function ensureOpen() {
2572
2783
  if (opened) return;
@@ -2584,22 +2795,22 @@ function useDatabase(name = "default") {
2584
2795
  }
2585
2796
  async function transaction(callback) {
2586
2797
  await ensureOpen();
2587
- const statements = [];
2588
- const ctx = {
2589
- execute: async (sql, params) => {
2590
- statements.push({ sql, params: params ?? [] });
2591
- return { rowsAffected: 0 };
2592
- },
2593
- query: async (sql, params) => {
2594
- if (statements.length > 0) {
2595
- 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 ?? []]);
2596
2806
  }
2597
- return NativeBridge.invokeNativeModule("Database", "query", [name, sql, params ?? []]);
2598
- }
2599
- };
2600
- await callback(ctx);
2601
- if (statements.length > 0) {
2602
- 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;
2603
2814
  }
2604
2815
  }
2605
2816
  async function close() {
@@ -2608,7 +2819,7 @@ function useDatabase(name = "default") {
2608
2819
  opened = false;
2609
2820
  isOpen.value = false;
2610
2821
  }
2611
- onUnmounted12(() => {
2822
+ onUnmounted15(() => {
2612
2823
  if (opened) {
2613
2824
  NativeBridge.invokeNativeModule("Database", "close", [name]).catch(() => {
2614
2825
  });
@@ -2620,12 +2831,12 @@ function useDatabase(name = "default") {
2620
2831
  }
2621
2832
 
2622
2833
  // src/composables/usePerformance.ts
2623
- import { ref as ref17, onUnmounted as onUnmounted13 } from "@vue/runtime-core";
2834
+ import { ref as ref21, onUnmounted as onUnmounted16 } from "@vue/runtime-core";
2624
2835
  function usePerformance() {
2625
- const isProfiling = ref17(false);
2626
- const fps = ref17(0);
2627
- const memoryMB = ref17(0);
2628
- const bridgeOps = ref17(0);
2836
+ const isProfiling = ref21(false);
2837
+ const fps = ref21(0);
2838
+ const memoryMB = ref21(0);
2839
+ const bridgeOps = ref21(0);
2629
2840
  let unsubscribe = null;
2630
2841
  function handleMetrics(payload) {
2631
2842
  fps.value = payload.fps ?? 0;
@@ -2650,7 +2861,7 @@ function usePerformance() {
2650
2861
  async function getMetrics() {
2651
2862
  return NativeBridge.invokeNativeModule("Performance", "getMetrics", []);
2652
2863
  }
2653
- onUnmounted13(() => {
2864
+ onUnmounted16(() => {
2654
2865
  if (isProfiling.value) {
2655
2866
  NativeBridge.invokeNativeModule("Performance", "stopProfiling", []).catch(() => {
2656
2867
  });
@@ -2673,10 +2884,10 @@ function usePerformance() {
2673
2884
  }
2674
2885
 
2675
2886
  // src/composables/useSharedElementTransition.ts
2676
- import { ref as ref18, onUnmounted as onUnmounted14 } from "@vue/runtime-core";
2887
+ import { ref as ref22, onUnmounted as onUnmounted17 } from "@vue/runtime-core";
2677
2888
  var sharedElementRegistry = /* @__PURE__ */ new Map();
2678
2889
  function useSharedElementTransition(elementId) {
2679
- const viewId = ref18(null);
2890
+ const viewId = ref22(null);
2680
2891
  function register(nativeViewId) {
2681
2892
  viewId.value = nativeViewId;
2682
2893
  sharedElementRegistry.set(elementId, nativeViewId);
@@ -2685,7 +2896,7 @@ function useSharedElementTransition(elementId) {
2685
2896
  viewId.value = null;
2686
2897
  sharedElementRegistry.delete(elementId);
2687
2898
  }
2688
- onUnmounted14(() => {
2899
+ onUnmounted17(() => {
2689
2900
  unregister();
2690
2901
  });
2691
2902
  return {
@@ -2709,11 +2920,11 @@ function clearSharedElementRegistry() {
2709
2920
  }
2710
2921
 
2711
2922
  // src/composables/useIAP.ts
2712
- import { ref as ref19, onUnmounted as onUnmounted15 } from "@vue/runtime-core";
2923
+ import { ref as ref23, onUnmounted as onUnmounted18 } from "@vue/runtime-core";
2713
2924
  function useIAP() {
2714
- const products = ref19([]);
2715
- const isReady = ref19(false);
2716
- const error = ref19(null);
2925
+ const products = ref23([]);
2926
+ const isReady = ref23(false);
2927
+ const error = ref23(null);
2717
2928
  const cleanups = [];
2718
2929
  const unsubscribe = NativeBridge.onGlobalEvent("iap:transactionUpdate", (payload) => {
2719
2930
  if (payload.state === "failed" && payload.error) {
@@ -2770,7 +2981,7 @@ function useIAP() {
2770
2981
  cleanups.push(unsub);
2771
2982
  return unsub;
2772
2983
  }
2773
- onUnmounted15(() => {
2984
+ onUnmounted18(() => {
2774
2985
  cleanups.forEach((fn) => fn());
2775
2986
  cleanups.length = 0;
2776
2987
  });
@@ -2787,11 +2998,11 @@ function useIAP() {
2787
2998
  }
2788
2999
 
2789
3000
  // src/composables/useAppleSignIn.ts
2790
- import { ref as ref20, onUnmounted as onUnmounted16 } from "@vue/runtime-core";
3001
+ import { ref as ref24, onUnmounted as onUnmounted19 } from "@vue/runtime-core";
2791
3002
  function useAppleSignIn() {
2792
- const user = ref20(null);
2793
- const isAuthenticated = ref20(false);
2794
- const error = ref20(null);
3003
+ const user = ref24(null);
3004
+ const isAuthenticated = ref24(false);
3005
+ const error = ref24(null);
2795
3006
  const cleanups = [];
2796
3007
  const unsubscribe = NativeBridge.onGlobalEvent("auth:appleCredentialRevoked", () => {
2797
3008
  user.value = null;
@@ -2829,7 +3040,7 @@ function useAppleSignIn() {
2829
3040
  error.value = String(err);
2830
3041
  }
2831
3042
  }
2832
- onUnmounted16(() => {
3043
+ onUnmounted19(() => {
2833
3044
  cleanups.forEach((fn) => fn());
2834
3045
  cleanups.length = 0;
2835
3046
  });
@@ -2837,11 +3048,11 @@ function useAppleSignIn() {
2837
3048
  }
2838
3049
 
2839
3050
  // src/composables/useGoogleSignIn.ts
2840
- import { ref as ref21, onUnmounted as onUnmounted17 } from "@vue/runtime-core";
3051
+ import { ref as ref25, onUnmounted as onUnmounted20 } from "@vue/runtime-core";
2841
3052
  function useGoogleSignIn(clientId) {
2842
- const user = ref21(null);
2843
- const isAuthenticated = ref21(false);
2844
- const error = ref21(null);
3053
+ const user = ref25(null);
3054
+ const isAuthenticated = ref25(false);
3055
+ const error = ref25(null);
2845
3056
  const cleanups = [];
2846
3057
  NativeBridge.invokeNativeModule("SocialAuth", "getCurrentUser", ["google"]).then((result) => {
2847
3058
  if (result && result.userId) {
@@ -2874,7 +3085,7 @@ function useGoogleSignIn(clientId) {
2874
3085
  error.value = String(err);
2875
3086
  }
2876
3087
  }
2877
- onUnmounted17(() => {
3088
+ onUnmounted20(() => {
2878
3089
  cleanups.forEach((fn) => fn());
2879
3090
  cleanups.length = 0;
2880
3091
  });
@@ -2882,17 +3093,17 @@ function useGoogleSignIn(clientId) {
2882
3093
  }
2883
3094
 
2884
3095
  // src/composables/useBackgroundTask.ts
2885
- import { ref as ref22, onUnmounted as onUnmounted18 } from "@vue/runtime-core";
3096
+ import { ref as ref26, onUnmounted as onUnmounted21 } from "@vue/runtime-core";
2886
3097
  function useBackgroundTask() {
2887
3098
  const taskHandlers = /* @__PURE__ */ new Map();
2888
- const defaultHandler = ref22(null);
3099
+ const defaultHandler = ref26(null);
2889
3100
  const unsubscribe = NativeBridge.onGlobalEvent("background:taskExecute", (payload) => {
2890
3101
  const handler = taskHandlers.get(payload.taskId) || defaultHandler.value;
2891
3102
  if (handler) {
2892
3103
  handler(payload.taskId);
2893
3104
  }
2894
3105
  });
2895
- onUnmounted18(unsubscribe);
3106
+ onUnmounted21(unsubscribe);
2896
3107
  function registerTask(taskId) {
2897
3108
  return NativeBridge.invokeNativeModule("BackgroundTask", "registerTask", [taskId]).then(() => void 0);
2898
3109
  }
@@ -2931,20 +3142,20 @@ function useBackgroundTask() {
2931
3142
  }
2932
3143
 
2933
3144
  // src/composables/useOTAUpdate.ts
2934
- import { ref as ref23, onUnmounted as onUnmounted19 } from "@vue/runtime-core";
3145
+ import { ref as ref27, onUnmounted as onUnmounted22 } from "@vue/runtime-core";
2935
3146
  function useOTAUpdate(serverUrl) {
2936
- const currentVersion = ref23("embedded");
2937
- const availableVersion = ref23(null);
2938
- const downloadProgress = ref23(0);
2939
- const isChecking = ref23(false);
2940
- const isDownloading = ref23(false);
2941
- const status = ref23("idle");
2942
- 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);
2943
3154
  let lastUpdateInfo = null;
2944
3155
  const unsubscribe = NativeBridge.onGlobalEvent("ota:downloadProgress", (payload) => {
2945
3156
  downloadProgress.value = payload.progress;
2946
3157
  });
2947
- onUnmounted19(unsubscribe);
3158
+ onUnmounted22(unsubscribe);
2948
3159
  NativeBridge.invokeNativeModule("OTA", "getCurrentVersion", []).then((info) => {
2949
3160
  currentVersion.value = info.version;
2950
3161
  }).catch(() => {
@@ -2988,6 +3199,8 @@ function useOTAUpdate(serverUrl) {
2988
3199
  await NativeBridge.invokeNativeModule("OTA", "downloadUpdate", [downloadUrl, expectedHash || ""]);
2989
3200
  status.value = "ready";
2990
3201
  } catch (err) {
3202
+ await NativeBridge.invokeNativeModule("OTA", "cleanupPartialDownload", []).catch(() => {
3203
+ });
2991
3204
  error.value = err?.message || String(err);
2992
3205
  status.value = "error";
2993
3206
  throw err;
@@ -2996,7 +3209,17 @@ function useOTAUpdate(serverUrl) {
2996
3209
  }
2997
3210
  }
2998
3211
  async function applyUpdate() {
3212
+ if (status.value !== "ready") {
3213
+ throw new Error("No update ready to apply. Call downloadUpdate() first.");
3214
+ }
2999
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
+ }
3000
3223
  try {
3001
3224
  await NativeBridge.invokeNativeModule("OTA", "applyUpdate", []);
3002
3225
  const info = await NativeBridge.invokeNativeModule("OTA", "getCurrentVersion", []);
@@ -3044,13 +3267,13 @@ function useOTAUpdate(serverUrl) {
3044
3267
  }
3045
3268
 
3046
3269
  // src/composables/useBluetooth.ts
3047
- import { ref as ref24, onUnmounted as onUnmounted20 } from "@vue/runtime-core";
3270
+ import { ref as ref28, onUnmounted as onUnmounted23 } from "@vue/runtime-core";
3048
3271
  function useBluetooth() {
3049
- const devices = ref24([]);
3050
- const connectedDevice = ref24(null);
3051
- const isScanning = ref24(false);
3052
- const isAvailable = ref24(false);
3053
- 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);
3054
3277
  const cleanups = [];
3055
3278
  NativeBridge.invokeNativeModule("Bluetooth", "getState").then((state) => {
3056
3279
  isAvailable.value = state === "poweredOn";
@@ -3129,7 +3352,7 @@ function useBluetooth() {
3129
3352
  ]);
3130
3353
  };
3131
3354
  }
3132
- onUnmounted20(() => {
3355
+ onUnmounted23(() => {
3133
3356
  if (isScanning.value) {
3134
3357
  NativeBridge.invokeNativeModule("Bluetooth", "stopScan").catch(() => {
3135
3358
  });
@@ -3154,10 +3377,10 @@ function useBluetooth() {
3154
3377
  }
3155
3378
 
3156
3379
  // src/composables/useCalendar.ts
3157
- import { ref as ref25 } from "@vue/runtime-core";
3380
+ import { ref as ref29 } from "@vue/runtime-core";
3158
3381
  function useCalendar() {
3159
- const hasAccess = ref25(false);
3160
- const error = ref25(null);
3382
+ const hasAccess = ref29(false);
3383
+ const error = ref29(null);
3161
3384
  async function requestAccess() {
3162
3385
  try {
3163
3386
  const result = await NativeBridge.invokeNativeModule("Calendar", "requestAccess");
@@ -3190,10 +3413,10 @@ function useCalendar() {
3190
3413
  }
3191
3414
 
3192
3415
  // src/composables/useContacts.ts
3193
- import { ref as ref26 } from "@vue/runtime-core";
3416
+ import { ref as ref30 } from "@vue/runtime-core";
3194
3417
  function useContacts() {
3195
- const hasAccess = ref26(false);
3196
- const error = ref26(null);
3418
+ const hasAccess = ref30(false);
3419
+ const error = ref30(null);
3197
3420
  async function requestAccess() {
3198
3421
  try {
3199
3422
  const result = await NativeBridge.invokeNativeModule("Contacts", "requestAccess");