@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.cjs +325 -102
- package/dist/index.d.cts +29 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +458 -235
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -160,7 +160,7 @@ function createCommentNode(_text) {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
// src/bridge.ts
|
|
163
|
-
var
|
|
163
|
+
var _NativeBridgeImpl = class _NativeBridgeImpl {
|
|
164
164
|
constructor() {
|
|
165
165
|
/** Pending operations waiting to be flushed to native */
|
|
166
166
|
this.pendingOps = [];
|
|
@@ -170,7 +170,8 @@ var NativeBridgeImpl = class {
|
|
|
170
170
|
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
171
171
|
/** Pending async callbacks from native module invocations */
|
|
172
172
|
this.pendingCallbacks = /* @__PURE__ */ new Map();
|
|
173
|
-
/** Auto-incrementing callback ID for async native module calls
|
|
173
|
+
/** Auto-incrementing callback ID for async native module calls.
|
|
174
|
+
* Wraps around at MAX_SAFE_CALLBACK_ID to prevent overflow. */
|
|
174
175
|
this.nextCallbackId = 1;
|
|
175
176
|
/** Global event listeners: eventName -> Set of callbacks */
|
|
176
177
|
this.globalEventHandlers = /* @__PURE__ */ new Map();
|
|
@@ -202,10 +203,14 @@ var NativeBridgeImpl = class {
|
|
|
202
203
|
const json = JSON.stringify(ops);
|
|
203
204
|
const flushFn = globalThis.__VN_flushOperations;
|
|
204
205
|
if (typeof flushFn === "function") {
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
try {
|
|
207
|
+
flushFn(json);
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.error("[VueNative] Error in __VN_flushOperations:", err);
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
207
212
|
console.warn(
|
|
208
|
-
"[VueNative] __VN_flushOperations is not registered. Make sure the
|
|
213
|
+
"[VueNative] __VN_flushOperations is not registered. Make sure the native runtime has been initialized."
|
|
209
214
|
);
|
|
210
215
|
}
|
|
211
216
|
}
|
|
@@ -365,7 +370,12 @@ var NativeBridgeImpl = class {
|
|
|
365
370
|
*/
|
|
366
371
|
invokeNativeModule(moduleName, methodName, args = [], timeoutMs = 3e4) {
|
|
367
372
|
return new Promise((resolve, reject) => {
|
|
368
|
-
const callbackId = this.nextCallbackId
|
|
373
|
+
const callbackId = this.nextCallbackId;
|
|
374
|
+
if (this.nextCallbackId >= _NativeBridgeImpl.MAX_CALLBACK_ID) {
|
|
375
|
+
this.nextCallbackId = 1;
|
|
376
|
+
} else {
|
|
377
|
+
this.nextCallbackId++;
|
|
378
|
+
}
|
|
369
379
|
const timeoutId = setTimeout(() => {
|
|
370
380
|
if (this.pendingCallbacks.has(callbackId)) {
|
|
371
381
|
this.pendingCallbacks.delete(callbackId);
|
|
@@ -374,6 +384,17 @@ var NativeBridgeImpl = class {
|
|
|
374
384
|
));
|
|
375
385
|
}
|
|
376
386
|
}, timeoutMs);
|
|
387
|
+
if (this.pendingCallbacks.size >= _NativeBridgeImpl.MAX_PENDING_CALLBACKS) {
|
|
388
|
+
const oldestKey = this.pendingCallbacks.keys().next().value;
|
|
389
|
+
if (oldestKey !== void 0) {
|
|
390
|
+
const oldest = this.pendingCallbacks.get(oldestKey);
|
|
391
|
+
if (oldest) {
|
|
392
|
+
clearTimeout(oldest.timeoutId);
|
|
393
|
+
oldest.reject(new Error("Callback queue full, evicting oldest pending callback"));
|
|
394
|
+
this.pendingCallbacks.delete(oldestKey);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
377
398
|
this.pendingCallbacks.set(callbackId, { resolve, reject, timeoutId });
|
|
378
399
|
this.enqueue("invokeNativeModule", [moduleName, methodName, args, callbackId]);
|
|
379
400
|
});
|
|
@@ -393,11 +414,9 @@ var NativeBridgeImpl = class {
|
|
|
393
414
|
resolveCallback(callbackId, result, error) {
|
|
394
415
|
const pending = this.pendingCallbacks.get(callbackId);
|
|
395
416
|
if (!pending) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
);
|
|
400
|
-
}
|
|
417
|
+
console.warn(
|
|
418
|
+
`[VueNative] Received callback for unknown callbackId: ${callbackId}. This likely means the callback already timed out or was evicted. The late response has been discarded.`
|
|
419
|
+
);
|
|
401
420
|
return;
|
|
402
421
|
}
|
|
403
422
|
clearTimeout(pending.timeoutId);
|
|
@@ -460,6 +479,11 @@ var NativeBridgeImpl = class {
|
|
|
460
479
|
this.globalEventHandlers.clear();
|
|
461
480
|
}
|
|
462
481
|
};
|
|
482
|
+
/** Maximum callback ID before wraparound (safe for 32-bit signed int) */
|
|
483
|
+
_NativeBridgeImpl.MAX_CALLBACK_ID = 2147483647;
|
|
484
|
+
/** Maximum number of pending callbacks before evicting the oldest */
|
|
485
|
+
_NativeBridgeImpl.MAX_PENDING_CALLBACKS = 1e3;
|
|
486
|
+
var NativeBridgeImpl = _NativeBridgeImpl;
|
|
463
487
|
if (typeof globalThis.__DEV__ === "undefined") {
|
|
464
488
|
;
|
|
465
489
|
globalThis.__DEV__ = true;
|
|
@@ -478,17 +502,21 @@ function toEventName(key) {
|
|
|
478
502
|
return key.slice(2).toLowerCase();
|
|
479
503
|
}
|
|
480
504
|
function patchStyle(nodeId, prevStyle, nextStyle) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
505
|
+
try {
|
|
506
|
+
const prev = prevStyle || {};
|
|
507
|
+
const next = nextStyle || {};
|
|
508
|
+
for (const key in next) {
|
|
509
|
+
if (next[key] !== prev[key]) {
|
|
510
|
+
NativeBridge.updateStyle(nodeId, key, next[key]);
|
|
511
|
+
}
|
|
486
512
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
513
|
+
for (const key in prev) {
|
|
514
|
+
if (!(key in next)) {
|
|
515
|
+
NativeBridge.updateStyle(nodeId, key, null);
|
|
516
|
+
}
|
|
491
517
|
}
|
|
518
|
+
} catch (err) {
|
|
519
|
+
console.error(`[VueNative] Error patching style on node ${nodeId}:`, err);
|
|
492
520
|
}
|
|
493
521
|
}
|
|
494
522
|
var nodeOps = {
|
|
@@ -541,22 +569,26 @@ var nodeOps = {
|
|
|
541
569
|
* - all else -> updateProp
|
|
542
570
|
*/
|
|
543
571
|
patchProp(el, key, prevValue, nextValue) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
572
|
+
try {
|
|
573
|
+
if (key.startsWith("on") && key.length > 2 && key[2] === key[2].toUpperCase()) {
|
|
574
|
+
const eventName = toEventName(key);
|
|
575
|
+
if (prevValue) {
|
|
576
|
+
NativeBridge.removeEventListener(el.id, eventName);
|
|
577
|
+
}
|
|
578
|
+
if (nextValue) {
|
|
579
|
+
NativeBridge.addEventListener(el.id, eventName, nextValue);
|
|
580
|
+
}
|
|
581
|
+
return;
|
|
548
582
|
}
|
|
549
|
-
if (
|
|
550
|
-
|
|
583
|
+
if (key === "style") {
|
|
584
|
+
patchStyle(el.id, prevValue, nextValue);
|
|
585
|
+
return;
|
|
551
586
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
return;
|
|
587
|
+
el.props[key] = nextValue;
|
|
588
|
+
NativeBridge.updateProp(el.id, key, nextValue);
|
|
589
|
+
} catch (err) {
|
|
590
|
+
console.error(`[VueNative] Error patching prop "${key}" on node ${el.id}:`, err);
|
|
557
591
|
}
|
|
558
|
-
el.props[key] = nextValue;
|
|
559
|
-
NativeBridge.updateProp(el.id, key, nextValue);
|
|
560
592
|
},
|
|
561
593
|
/**
|
|
562
594
|
* Insert a child node into a parent, optionally before an anchor node.
|
|
@@ -571,30 +603,34 @@ var nodeOps = {
|
|
|
571
603
|
}
|
|
572
604
|
}
|
|
573
605
|
child.parent = parent;
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
parent.children.push(child);
|
|
580
|
-
}
|
|
581
|
-
if (child.type !== "__COMMENT__") {
|
|
582
|
-
if (anchor.type !== "__COMMENT__") {
|
|
583
|
-
NativeBridge.insertBefore(parent.id, child.id, anchor.id);
|
|
606
|
+
try {
|
|
607
|
+
if (anchor) {
|
|
608
|
+
const anchorIdx = parent.children.indexOf(anchor);
|
|
609
|
+
if (anchorIdx !== -1) {
|
|
610
|
+
parent.children.splice(anchorIdx, 0, child);
|
|
584
611
|
} else {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
612
|
+
parent.children.push(child);
|
|
613
|
+
}
|
|
614
|
+
if (child.type !== "__COMMENT__") {
|
|
615
|
+
if (anchor.type !== "__COMMENT__") {
|
|
616
|
+
NativeBridge.insertBefore(parent.id, child.id, anchor.id);
|
|
588
617
|
} else {
|
|
589
|
-
|
|
618
|
+
const realAnchor = findNextNonComment(parent, anchor);
|
|
619
|
+
if (realAnchor) {
|
|
620
|
+
NativeBridge.insertBefore(parent.id, child.id, realAnchor.id);
|
|
621
|
+
} else {
|
|
622
|
+
NativeBridge.appendChild(parent.id, child.id);
|
|
623
|
+
}
|
|
590
624
|
}
|
|
591
625
|
}
|
|
626
|
+
} else {
|
|
627
|
+
parent.children.push(child);
|
|
628
|
+
if (child.type !== "__COMMENT__") {
|
|
629
|
+
NativeBridge.appendChild(parent.id, child.id);
|
|
630
|
+
}
|
|
592
631
|
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
if (child.type !== "__COMMENT__") {
|
|
596
|
-
NativeBridge.appendChild(parent.id, child.id);
|
|
597
|
-
}
|
|
632
|
+
} catch (err) {
|
|
633
|
+
console.error(`[VueNative] Error inserting node ${child.id} into ${parent.id}:`, err);
|
|
598
634
|
}
|
|
599
635
|
},
|
|
600
636
|
/**
|
|
@@ -608,8 +644,12 @@ var nodeOps = {
|
|
|
608
644
|
parent.children.splice(idx, 1);
|
|
609
645
|
}
|
|
610
646
|
child.parent = null;
|
|
611
|
-
|
|
612
|
-
|
|
647
|
+
try {
|
|
648
|
+
if (child.type !== "__COMMENT__") {
|
|
649
|
+
NativeBridge.removeChild(parent.id, child.id);
|
|
650
|
+
}
|
|
651
|
+
} catch (err) {
|
|
652
|
+
console.error(`[VueNative] Error removing node ${child.id}:`, err);
|
|
613
653
|
}
|
|
614
654
|
}
|
|
615
655
|
},
|
|
@@ -757,7 +797,17 @@ var VInput = (0, import_runtime_core5.defineComponent)({
|
|
|
757
797
|
},
|
|
758
798
|
emits: ["update:modelValue", "focus", "blur", "submit"],
|
|
759
799
|
setup(props, { emit }) {
|
|
800
|
+
const isComposing = (0, import_runtime_core5.ref)(false);
|
|
801
|
+
const onCompositionstart = () => {
|
|
802
|
+
isComposing.value = true;
|
|
803
|
+
};
|
|
804
|
+
const onCompositionend = (payload) => {
|
|
805
|
+
isComposing.value = false;
|
|
806
|
+
const text = typeof payload === "string" ? payload : payload?.text ?? "";
|
|
807
|
+
emit("update:modelValue", text);
|
|
808
|
+
};
|
|
760
809
|
const onChangetext = (payload) => {
|
|
810
|
+
if (isComposing.value) return;
|
|
761
811
|
const text = typeof payload === "string" ? payload : payload?.text ?? "";
|
|
762
812
|
emit("update:modelValue", text);
|
|
763
813
|
};
|
|
@@ -786,6 +836,8 @@ var VInput = (0, import_runtime_core5.defineComponent)({
|
|
|
786
836
|
accessibilityHint: props.accessibilityHint,
|
|
787
837
|
accessibilityState: props.accessibilityState,
|
|
788
838
|
onChangetext,
|
|
839
|
+
onCompositionstart,
|
|
840
|
+
onCompositionend,
|
|
789
841
|
onFocus,
|
|
790
842
|
onBlur,
|
|
791
843
|
onSubmit
|
|
@@ -898,6 +950,11 @@ var VScrollView = (0, import_runtime_core8.defineComponent)({
|
|
|
898
950
|
default: false
|
|
899
951
|
},
|
|
900
952
|
contentContainerStyle: Object,
|
|
953
|
+
/** Minimum interval in ms between scroll event emissions. Default: 16 (~60fps) */
|
|
954
|
+
scrollEventThrottle: {
|
|
955
|
+
type: Number,
|
|
956
|
+
default: 16
|
|
957
|
+
},
|
|
901
958
|
/** Whether the pull-to-refresh indicator is active */
|
|
902
959
|
refreshing: {
|
|
903
960
|
type: Boolean,
|
|
@@ -911,8 +968,13 @@ var VScrollView = (0, import_runtime_core8.defineComponent)({
|
|
|
911
968
|
},
|
|
912
969
|
emits: ["scroll", "refresh"],
|
|
913
970
|
setup(props, { slots, emit }) {
|
|
971
|
+
let lastScrollEmit = 0;
|
|
914
972
|
const onScroll = (payload) => {
|
|
915
|
-
|
|
973
|
+
const now = Date.now();
|
|
974
|
+
if (now - lastScrollEmit >= props.scrollEventThrottle) {
|
|
975
|
+
lastScrollEmit = now;
|
|
976
|
+
emit("scroll", payload);
|
|
977
|
+
}
|
|
916
978
|
};
|
|
917
979
|
const onRefresh = () => {
|
|
918
980
|
emit("refresh");
|
|
@@ -959,13 +1021,29 @@ var VImage = (0, import_runtime_core9.defineComponent)({
|
|
|
959
1021
|
accessibilityState: Object
|
|
960
1022
|
},
|
|
961
1023
|
emits: ["load", "error"],
|
|
962
|
-
setup(props, { emit }) {
|
|
1024
|
+
setup(props, { emit, expose }) {
|
|
1025
|
+
const loading = (0, import_runtime_core9.ref)(true);
|
|
1026
|
+
(0, import_runtime_core9.watch)(
|
|
1027
|
+
() => props.source?.uri,
|
|
1028
|
+
() => {
|
|
1029
|
+
loading.value = true;
|
|
1030
|
+
}
|
|
1031
|
+
);
|
|
1032
|
+
const onLoad = () => {
|
|
1033
|
+
loading.value = false;
|
|
1034
|
+
emit("load");
|
|
1035
|
+
};
|
|
1036
|
+
const onError = (e) => {
|
|
1037
|
+
loading.value = false;
|
|
1038
|
+
emit("error", e);
|
|
1039
|
+
};
|
|
1040
|
+
expose({ loading });
|
|
963
1041
|
return () => (0, import_runtime_core9.h)(
|
|
964
1042
|
"VImage",
|
|
965
1043
|
{
|
|
966
1044
|
...props,
|
|
967
|
-
onLoad
|
|
968
|
-
onError
|
|
1045
|
+
onLoad,
|
|
1046
|
+
onError
|
|
969
1047
|
}
|
|
970
1048
|
);
|
|
971
1049
|
}
|
|
@@ -1071,8 +1149,29 @@ var VList = (0, import_runtime_core13.defineComponent)({
|
|
|
1071
1149
|
},
|
|
1072
1150
|
emits: ["scroll", "endReached"],
|
|
1073
1151
|
setup(props, { slots, emit }) {
|
|
1152
|
+
let lastScrollEmit = 0;
|
|
1153
|
+
const onScroll = (e) => {
|
|
1154
|
+
const now = Date.now();
|
|
1155
|
+
if (now - lastScrollEmit >= 16) {
|
|
1156
|
+
lastScrollEmit = now;
|
|
1157
|
+
emit("scroll", e);
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1074
1160
|
return () => {
|
|
1075
1161
|
const items = props.data ?? [];
|
|
1162
|
+
if (typeof __DEV__ !== "undefined" && __DEV__ && items.length > 0) {
|
|
1163
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1164
|
+
for (let index = 0; index < items.length; index++) {
|
|
1165
|
+
const key = props.keyExtractor(items[index], index);
|
|
1166
|
+
if (keys.has(key)) {
|
|
1167
|
+
console.warn(
|
|
1168
|
+
`[VueNative] VList: Duplicate key "${key}" at index ${index}. Each item must have a unique key for correct reconciliation.`
|
|
1169
|
+
);
|
|
1170
|
+
break;
|
|
1171
|
+
}
|
|
1172
|
+
keys.add(key);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1076
1175
|
const children = [];
|
|
1077
1176
|
if (slots.header) {
|
|
1078
1177
|
children.push(
|
|
@@ -1110,7 +1209,7 @@ var VList = (0, import_runtime_core13.defineComponent)({
|
|
|
1110
1209
|
showsScrollIndicator: props.showsScrollIndicator,
|
|
1111
1210
|
bounces: props.bounces,
|
|
1112
1211
|
horizontal: props.horizontal,
|
|
1113
|
-
onScroll
|
|
1212
|
+
onScroll,
|
|
1114
1213
|
onEndReached: () => emit("endReached")
|
|
1115
1214
|
},
|
|
1116
1215
|
children
|
|
@@ -1135,10 +1234,24 @@ var VModal = (0, import_runtime_core14.defineComponent)({
|
|
|
1135
1234
|
},
|
|
1136
1235
|
emits: ["dismiss"],
|
|
1137
1236
|
setup(props, { slots, emit }) {
|
|
1237
|
+
const debouncedVisible = (0, import_runtime_core14.ref)(props.visible);
|
|
1238
|
+
let visibleTimer;
|
|
1239
|
+
(0, import_runtime_core14.watch)(
|
|
1240
|
+
() => props.visible,
|
|
1241
|
+
(val) => {
|
|
1242
|
+
if (visibleTimer) clearTimeout(visibleTimer);
|
|
1243
|
+
visibleTimer = setTimeout(() => {
|
|
1244
|
+
debouncedVisible.value = val;
|
|
1245
|
+
}, 50);
|
|
1246
|
+
}
|
|
1247
|
+
);
|
|
1248
|
+
(0, import_runtime_core14.onUnmounted)(() => {
|
|
1249
|
+
if (visibleTimer) clearTimeout(visibleTimer);
|
|
1250
|
+
});
|
|
1138
1251
|
return () => (0, import_runtime_core14.h)(
|
|
1139
1252
|
"VModal",
|
|
1140
1253
|
{
|
|
1141
|
-
visible:
|
|
1254
|
+
visible: debouncedVisible.value,
|
|
1142
1255
|
style: props.style,
|
|
1143
1256
|
onDismiss: () => emit("dismiss")
|
|
1144
1257
|
},
|
|
@@ -1159,8 +1272,22 @@ var VAlertDialog = (0, import_runtime_core15.defineComponent)({
|
|
|
1159
1272
|
},
|
|
1160
1273
|
emits: ["confirm", "cancel", "action"],
|
|
1161
1274
|
setup(props, { emit }) {
|
|
1275
|
+
const debouncedVisible = (0, import_runtime_core15.ref)(props.visible);
|
|
1276
|
+
let visibleTimer;
|
|
1277
|
+
(0, import_runtime_core15.watch)(
|
|
1278
|
+
() => props.visible,
|
|
1279
|
+
(val) => {
|
|
1280
|
+
if (visibleTimer) clearTimeout(visibleTimer);
|
|
1281
|
+
visibleTimer = setTimeout(() => {
|
|
1282
|
+
debouncedVisible.value = val;
|
|
1283
|
+
}, 50);
|
|
1284
|
+
}
|
|
1285
|
+
);
|
|
1286
|
+
(0, import_runtime_core15.onUnmounted)(() => {
|
|
1287
|
+
if (visibleTimer) clearTimeout(visibleTimer);
|
|
1288
|
+
});
|
|
1162
1289
|
return () => (0, import_runtime_core15.h)("VAlertDialog", {
|
|
1163
|
-
visible:
|
|
1290
|
+
visible: debouncedVisible.value,
|
|
1164
1291
|
title: props.title,
|
|
1165
1292
|
message: props.message,
|
|
1166
1293
|
buttons: props.buttons,
|
|
@@ -1200,8 +1327,18 @@ var VWebView = (0, import_runtime_core17.defineComponent)({
|
|
|
1200
1327
|
},
|
|
1201
1328
|
emits: ["load", "error", "message"],
|
|
1202
1329
|
setup(props, { emit }) {
|
|
1330
|
+
const sanitizedSource = (0, import_runtime_core17.computed)(() => {
|
|
1331
|
+
const source = props.source;
|
|
1332
|
+
if (!source?.uri) return source;
|
|
1333
|
+
const lower = source.uri.toLowerCase().trim();
|
|
1334
|
+
if (lower.startsWith("javascript:") || lower.startsWith("data:text/html")) {
|
|
1335
|
+
console.warn("[VueNative] VWebView: Blocked potentially unsafe URI scheme");
|
|
1336
|
+
return { ...source, uri: void 0 };
|
|
1337
|
+
}
|
|
1338
|
+
return source;
|
|
1339
|
+
});
|
|
1203
1340
|
return () => (0, import_runtime_core17.h)("VWebView", {
|
|
1204
|
-
source:
|
|
1341
|
+
source: sanitizedSource.value,
|
|
1205
1342
|
style: props.style,
|
|
1206
1343
|
javaScriptEnabled: props.javaScriptEnabled,
|
|
1207
1344
|
onLoad: (e) => emit("load", e),
|
|
@@ -1847,15 +1984,33 @@ function useHaptics() {
|
|
|
1847
1984
|
}
|
|
1848
1985
|
|
|
1849
1986
|
// src/composables/useAsyncStorage.ts
|
|
1987
|
+
var writeQueues = /* @__PURE__ */ new Map();
|
|
1988
|
+
function queueWrite(key, fn) {
|
|
1989
|
+
const prev = writeQueues.get(key) ?? Promise.resolve();
|
|
1990
|
+
const next = prev.then(fn, fn);
|
|
1991
|
+
writeQueues.set(key, next);
|
|
1992
|
+
next.then(() => {
|
|
1993
|
+
if (writeQueues.get(key) === next) {
|
|
1994
|
+
writeQueues.delete(key);
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
return next;
|
|
1998
|
+
}
|
|
1850
1999
|
function useAsyncStorage() {
|
|
1851
2000
|
function getItem(key) {
|
|
1852
2001
|
return NativeBridge.invokeNativeModule("AsyncStorage", "getItem", [key]);
|
|
1853
2002
|
}
|
|
1854
2003
|
function setItem(key, value) {
|
|
1855
|
-
return
|
|
2004
|
+
return queueWrite(
|
|
2005
|
+
key,
|
|
2006
|
+
() => NativeBridge.invokeNativeModule("AsyncStorage", "setItem", [key, value]).then(() => void 0)
|
|
2007
|
+
);
|
|
1856
2008
|
}
|
|
1857
2009
|
function removeItem(key) {
|
|
1858
|
-
return
|
|
2010
|
+
return queueWrite(
|
|
2011
|
+
key,
|
|
2012
|
+
() => NativeBridge.invokeNativeModule("AsyncStorage", "removeItem", [key]).then(() => void 0)
|
|
2013
|
+
);
|
|
1859
2014
|
}
|
|
1860
2015
|
function getAllKeys() {
|
|
1861
2016
|
return NativeBridge.invokeNativeModule("AsyncStorage", "getAllKeys", []);
|
|
@@ -1992,15 +2147,20 @@ var import_runtime_core33 = require("@vue/runtime-core");
|
|
|
1992
2147
|
function useNetwork() {
|
|
1993
2148
|
const isConnected = (0, import_runtime_core33.ref)(true);
|
|
1994
2149
|
const connectionType = (0, import_runtime_core33.ref)("unknown");
|
|
1995
|
-
|
|
1996
|
-
isConnected.value = status.isConnected;
|
|
1997
|
-
connectionType.value = status.connectionType;
|
|
1998
|
-
}).catch(() => {
|
|
1999
|
-
});
|
|
2150
|
+
let lastEventTime = 0;
|
|
2000
2151
|
const unsubscribe = NativeBridge.onGlobalEvent("network:change", (payload) => {
|
|
2152
|
+
lastEventTime = Date.now();
|
|
2001
2153
|
isConnected.value = payload.isConnected;
|
|
2002
2154
|
connectionType.value = payload.connectionType;
|
|
2003
2155
|
});
|
|
2156
|
+
const initTime = Date.now();
|
|
2157
|
+
NativeBridge.invokeNativeModule("Network", "getStatus").then((status) => {
|
|
2158
|
+
if (lastEventTime <= initTime) {
|
|
2159
|
+
isConnected.value = status.isConnected;
|
|
2160
|
+
connectionType.value = status.connectionType;
|
|
2161
|
+
}
|
|
2162
|
+
}).catch(() => {
|
|
2163
|
+
});
|
|
2004
2164
|
(0, import_runtime_core33.onUnmounted)(unsubscribe);
|
|
2005
2165
|
return { isConnected, connectionType };
|
|
2006
2166
|
}
|
|
@@ -2057,21 +2217,39 @@ function useGeolocation() {
|
|
|
2057
2217
|
const error = (0, import_runtime_core35.ref)(null);
|
|
2058
2218
|
let watchId = null;
|
|
2059
2219
|
async function getCurrentPosition() {
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2220
|
+
try {
|
|
2221
|
+
error.value = null;
|
|
2222
|
+
const result = await NativeBridge.invokeNativeModule("Geolocation", "getCurrentPosition");
|
|
2223
|
+
coords.value = result;
|
|
2224
|
+
return result;
|
|
2225
|
+
} catch (e) {
|
|
2226
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2227
|
+
error.value = msg;
|
|
2228
|
+
throw e;
|
|
2229
|
+
}
|
|
2063
2230
|
}
|
|
2064
2231
|
async function watchPosition() {
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2232
|
+
try {
|
|
2233
|
+
error.value = null;
|
|
2234
|
+
const id = await NativeBridge.invokeNativeModule("Geolocation", "watchPosition");
|
|
2235
|
+
watchId = id;
|
|
2236
|
+
const unsubscribe = NativeBridge.onGlobalEvent("location:update", (payload) => {
|
|
2237
|
+
coords.value = payload;
|
|
2238
|
+
});
|
|
2239
|
+
const unsubscribeError = NativeBridge.onGlobalEvent("location:error", (payload) => {
|
|
2240
|
+
error.value = payload.message;
|
|
2241
|
+
});
|
|
2242
|
+
(0, import_runtime_core35.onUnmounted)(() => {
|
|
2243
|
+
unsubscribe();
|
|
2244
|
+
unsubscribeError();
|
|
2245
|
+
if (watchId !== null) clearWatch(watchId);
|
|
2246
|
+
});
|
|
2247
|
+
return id;
|
|
2248
|
+
} catch (e) {
|
|
2249
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2250
|
+
error.value = msg;
|
|
2251
|
+
throw e;
|
|
2252
|
+
}
|
|
2075
2253
|
}
|
|
2076
2254
|
async function clearWatch(id) {
|
|
2077
2255
|
await NativeBridge.invokeNativeModule("Geolocation", "clearWatch", [id]);
|
|
@@ -2204,6 +2382,10 @@ function useHttp(config = {}) {
|
|
|
2204
2382
|
}
|
|
2205
2383
|
const loading = (0, import_runtime_core38.ref)(false);
|
|
2206
2384
|
const error = (0, import_runtime_core38.ref)(null);
|
|
2385
|
+
let isMounted = true;
|
|
2386
|
+
(0, import_runtime_core38.onUnmounted)(() => {
|
|
2387
|
+
isMounted = false;
|
|
2388
|
+
});
|
|
2207
2389
|
async function request(method, url, options = {}) {
|
|
2208
2390
|
const fullUrl = config.baseURL ? `${config.baseURL}${url}` : url;
|
|
2209
2391
|
loading.value = true;
|
|
@@ -2223,6 +2405,9 @@ function useHttp(config = {}) {
|
|
|
2223
2405
|
}
|
|
2224
2406
|
const response = await fetch(fullUrl, fetchOptions);
|
|
2225
2407
|
const data = await response.json();
|
|
2408
|
+
if (!isMounted) {
|
|
2409
|
+
return { data, status: response.status, ok: response.ok, headers: {} };
|
|
2410
|
+
}
|
|
2226
2411
|
return {
|
|
2227
2412
|
data,
|
|
2228
2413
|
status: response.status,
|
|
@@ -2231,10 +2416,14 @@ function useHttp(config = {}) {
|
|
|
2231
2416
|
};
|
|
2232
2417
|
} catch (e) {
|
|
2233
2418
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2234
|
-
|
|
2419
|
+
if (isMounted) {
|
|
2420
|
+
error.value = msg;
|
|
2421
|
+
}
|
|
2235
2422
|
throw e;
|
|
2236
2423
|
} finally {
|
|
2237
|
-
|
|
2424
|
+
if (isMounted) {
|
|
2425
|
+
loading.value = false;
|
|
2426
|
+
}
|
|
2238
2427
|
}
|
|
2239
2428
|
}
|
|
2240
2429
|
return {
|
|
@@ -2270,7 +2459,11 @@ function useBackHandler(handler) {
|
|
|
2270
2459
|
let unsubscribe = null;
|
|
2271
2460
|
(0, import_runtime_core40.onMounted)(() => {
|
|
2272
2461
|
unsubscribe = NativeBridge.onGlobalEvent("hardware:backPress", () => {
|
|
2273
|
-
handler();
|
|
2462
|
+
const handled = handler();
|
|
2463
|
+
if (!handled) {
|
|
2464
|
+
NativeBridge.invokeNativeModule("BackHandler", "exitApp", []).catch(() => {
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2274
2467
|
});
|
|
2275
2468
|
});
|
|
2276
2469
|
(0, import_runtime_core40.onUnmounted)(() => {
|
|
@@ -2360,6 +2553,8 @@ function useWebSocket(url, options = {}) {
|
|
|
2360
2553
|
const error = (0, import_runtime_core43.ref)(null);
|
|
2361
2554
|
let reconnectAttempts = 0;
|
|
2362
2555
|
let reconnectTimer = null;
|
|
2556
|
+
const MAX_PENDING_MESSAGES = 100;
|
|
2557
|
+
const pendingMessages = [];
|
|
2363
2558
|
const unsubscribers = [];
|
|
2364
2559
|
unsubscribers.push(
|
|
2365
2560
|
NativeBridge.onGlobalEvent("websocket:open", (payload) => {
|
|
@@ -2367,6 +2562,12 @@ function useWebSocket(url, options = {}) {
|
|
|
2367
2562
|
status.value = "OPEN";
|
|
2368
2563
|
error.value = null;
|
|
2369
2564
|
reconnectAttempts = 0;
|
|
2565
|
+
while (pendingMessages.length > 0) {
|
|
2566
|
+
const msg = pendingMessages.shift();
|
|
2567
|
+
NativeBridge.invokeNativeModule("WebSocket", "send", [connectionId, msg]).catch((err) => {
|
|
2568
|
+
error.value = err.message;
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2370
2571
|
})
|
|
2371
2572
|
);
|
|
2372
2573
|
unsubscribers.push(
|
|
@@ -2381,9 +2582,10 @@ function useWebSocket(url, options = {}) {
|
|
|
2381
2582
|
status.value = "CLOSED";
|
|
2382
2583
|
if (autoReconnect && reconnectAttempts < maxReconnectAttempts && payload.code !== 1e3) {
|
|
2383
2584
|
reconnectAttempts++;
|
|
2585
|
+
const backoffMs = reconnectInterval * Math.pow(2, reconnectAttempts - 1);
|
|
2384
2586
|
reconnectTimer = setTimeout(() => {
|
|
2385
2587
|
open();
|
|
2386
|
-
},
|
|
2588
|
+
}, backoffMs);
|
|
2387
2589
|
}
|
|
2388
2590
|
})
|
|
2389
2591
|
);
|
|
@@ -2403,8 +2605,17 @@ function useWebSocket(url, options = {}) {
|
|
|
2403
2605
|
});
|
|
2404
2606
|
}
|
|
2405
2607
|
function send(data) {
|
|
2406
|
-
if (status.value !== "OPEN") return;
|
|
2407
2608
|
const message = typeof data === "string" ? data : JSON.stringify(data);
|
|
2609
|
+
if (status.value !== "OPEN") {
|
|
2610
|
+
if (pendingMessages.length >= MAX_PENDING_MESSAGES) {
|
|
2611
|
+
pendingMessages.shift();
|
|
2612
|
+
if (__DEV__) {
|
|
2613
|
+
console.warn("[VueNative] WebSocket pending message queue full, dropping oldest message");
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
pendingMessages.push(message);
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2408
2619
|
NativeBridge.invokeNativeModule("WebSocket", "send", [connectionId, message]).catch((err) => {
|
|
2409
2620
|
error.value = err.message;
|
|
2410
2621
|
});
|
|
@@ -2688,22 +2899,22 @@ function useDatabase(name = "default") {
|
|
|
2688
2899
|
}
|
|
2689
2900
|
async function transaction(callback) {
|
|
2690
2901
|
await ensureOpen();
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
await NativeBridge.invokeNativeModule("Database", "executeTransaction", [name, statements.splice(0)]);
|
|
2902
|
+
await NativeBridge.invokeNativeModule("Database", "execute", [name, "BEGIN TRANSACTION", []]);
|
|
2903
|
+
try {
|
|
2904
|
+
const ctx = {
|
|
2905
|
+
execute: async (sql, params) => {
|
|
2906
|
+
return NativeBridge.invokeNativeModule("Database", "execute", [name, sql, params ?? []]);
|
|
2907
|
+
},
|
|
2908
|
+
query: async (sql, params) => {
|
|
2909
|
+
return NativeBridge.invokeNativeModule("Database", "query", [name, sql, params ?? []]);
|
|
2700
2910
|
}
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2911
|
+
};
|
|
2912
|
+
await callback(ctx);
|
|
2913
|
+
await NativeBridge.invokeNativeModule("Database", "execute", [name, "COMMIT", []]);
|
|
2914
|
+
} catch (err) {
|
|
2915
|
+
await NativeBridge.invokeNativeModule("Database", "execute", [name, "ROLLBACK", []]).catch(() => {
|
|
2916
|
+
});
|
|
2917
|
+
throw err;
|
|
2707
2918
|
}
|
|
2708
2919
|
}
|
|
2709
2920
|
async function close() {
|
|
@@ -3092,6 +3303,8 @@ function useOTAUpdate(serverUrl) {
|
|
|
3092
3303
|
await NativeBridge.invokeNativeModule("OTA", "downloadUpdate", [downloadUrl, expectedHash || ""]);
|
|
3093
3304
|
status.value = "ready";
|
|
3094
3305
|
} catch (err) {
|
|
3306
|
+
await NativeBridge.invokeNativeModule("OTA", "cleanupPartialDownload", []).catch(() => {
|
|
3307
|
+
});
|
|
3095
3308
|
error.value = err?.message || String(err);
|
|
3096
3309
|
status.value = "error";
|
|
3097
3310
|
throw err;
|
|
@@ -3100,7 +3313,17 @@ function useOTAUpdate(serverUrl) {
|
|
|
3100
3313
|
}
|
|
3101
3314
|
}
|
|
3102
3315
|
async function applyUpdate() {
|
|
3316
|
+
if (status.value !== "ready") {
|
|
3317
|
+
throw new Error("No update ready to apply. Call downloadUpdate() first.");
|
|
3318
|
+
}
|
|
3103
3319
|
error.value = null;
|
|
3320
|
+
try {
|
|
3321
|
+
await NativeBridge.invokeNativeModule("OTA", "verifyBundle", []);
|
|
3322
|
+
} catch (err) {
|
|
3323
|
+
status.value = "error";
|
|
3324
|
+
error.value = "Bundle verification failed: " + (err?.message || String(err));
|
|
3325
|
+
throw err;
|
|
3326
|
+
}
|
|
3104
3327
|
try {
|
|
3105
3328
|
await NativeBridge.invokeNativeModule("OTA", "applyUpdate", []);
|
|
3106
3329
|
const info = await NativeBridge.invokeNativeModule("OTA", "getCurrentVersion", []);
|