@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.cjs +247 -53
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +380 -186
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -384,6 +384,17 @@ var _NativeBridgeImpl = class _NativeBridgeImpl {
|
|
|
384
384
|
));
|
|
385
385
|
}
|
|
386
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
|
+
}
|
|
387
398
|
this.pendingCallbacks.set(callbackId, { resolve, reject, timeoutId });
|
|
388
399
|
this.enqueue("invokeNativeModule", [moduleName, methodName, args, callbackId]);
|
|
389
400
|
});
|
|
@@ -403,11 +414,9 @@ var _NativeBridgeImpl = class _NativeBridgeImpl {
|
|
|
403
414
|
resolveCallback(callbackId, result, error) {
|
|
404
415
|
const pending = this.pendingCallbacks.get(callbackId);
|
|
405
416
|
if (!pending) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
);
|
|
410
|
-
}
|
|
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
|
+
);
|
|
411
420
|
return;
|
|
412
421
|
}
|
|
413
422
|
clearTimeout(pending.timeoutId);
|
|
@@ -472,6 +481,8 @@ var _NativeBridgeImpl = class _NativeBridgeImpl {
|
|
|
472
481
|
};
|
|
473
482
|
/** Maximum callback ID before wraparound (safe for 32-bit signed int) */
|
|
474
483
|
_NativeBridgeImpl.MAX_CALLBACK_ID = 2147483647;
|
|
484
|
+
/** Maximum number of pending callbacks before evicting the oldest */
|
|
485
|
+
_NativeBridgeImpl.MAX_PENDING_CALLBACKS = 1e3;
|
|
475
486
|
var NativeBridgeImpl = _NativeBridgeImpl;
|
|
476
487
|
if (typeof globalThis.__DEV__ === "undefined") {
|
|
477
488
|
;
|
|
@@ -786,7 +797,17 @@ var VInput = (0, import_runtime_core5.defineComponent)({
|
|
|
786
797
|
},
|
|
787
798
|
emits: ["update:modelValue", "focus", "blur", "submit"],
|
|
788
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
|
+
};
|
|
789
809
|
const onChangetext = (payload) => {
|
|
810
|
+
if (isComposing.value) return;
|
|
790
811
|
const text = typeof payload === "string" ? payload : payload?.text ?? "";
|
|
791
812
|
emit("update:modelValue", text);
|
|
792
813
|
};
|
|
@@ -815,6 +836,8 @@ var VInput = (0, import_runtime_core5.defineComponent)({
|
|
|
815
836
|
accessibilityHint: props.accessibilityHint,
|
|
816
837
|
accessibilityState: props.accessibilityState,
|
|
817
838
|
onChangetext,
|
|
839
|
+
onCompositionstart,
|
|
840
|
+
onCompositionend,
|
|
818
841
|
onFocus,
|
|
819
842
|
onBlur,
|
|
820
843
|
onSubmit
|
|
@@ -927,6 +950,11 @@ var VScrollView = (0, import_runtime_core8.defineComponent)({
|
|
|
927
950
|
default: false
|
|
928
951
|
},
|
|
929
952
|
contentContainerStyle: Object,
|
|
953
|
+
/** Minimum interval in ms between scroll event emissions. Default: 16 (~60fps) */
|
|
954
|
+
scrollEventThrottle: {
|
|
955
|
+
type: Number,
|
|
956
|
+
default: 16
|
|
957
|
+
},
|
|
930
958
|
/** Whether the pull-to-refresh indicator is active */
|
|
931
959
|
refreshing: {
|
|
932
960
|
type: Boolean,
|
|
@@ -940,8 +968,13 @@ var VScrollView = (0, import_runtime_core8.defineComponent)({
|
|
|
940
968
|
},
|
|
941
969
|
emits: ["scroll", "refresh"],
|
|
942
970
|
setup(props, { slots, emit }) {
|
|
971
|
+
let lastScrollEmit = 0;
|
|
943
972
|
const onScroll = (payload) => {
|
|
944
|
-
|
|
973
|
+
const now = Date.now();
|
|
974
|
+
if (now - lastScrollEmit >= props.scrollEventThrottle) {
|
|
975
|
+
lastScrollEmit = now;
|
|
976
|
+
emit("scroll", payload);
|
|
977
|
+
}
|
|
945
978
|
};
|
|
946
979
|
const onRefresh = () => {
|
|
947
980
|
emit("refresh");
|
|
@@ -988,13 +1021,29 @@ var VImage = (0, import_runtime_core9.defineComponent)({
|
|
|
988
1021
|
accessibilityState: Object
|
|
989
1022
|
},
|
|
990
1023
|
emits: ["load", "error"],
|
|
991
|
-
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 });
|
|
992
1041
|
return () => (0, import_runtime_core9.h)(
|
|
993
1042
|
"VImage",
|
|
994
1043
|
{
|
|
995
1044
|
...props,
|
|
996
|
-
onLoad
|
|
997
|
-
onError
|
|
1045
|
+
onLoad,
|
|
1046
|
+
onError
|
|
998
1047
|
}
|
|
999
1048
|
);
|
|
1000
1049
|
}
|
|
@@ -1100,8 +1149,29 @@ var VList = (0, import_runtime_core13.defineComponent)({
|
|
|
1100
1149
|
},
|
|
1101
1150
|
emits: ["scroll", "endReached"],
|
|
1102
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
|
+
};
|
|
1103
1160
|
return () => {
|
|
1104
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
|
+
}
|
|
1105
1175
|
const children = [];
|
|
1106
1176
|
if (slots.header) {
|
|
1107
1177
|
children.push(
|
|
@@ -1139,7 +1209,7 @@ var VList = (0, import_runtime_core13.defineComponent)({
|
|
|
1139
1209
|
showsScrollIndicator: props.showsScrollIndicator,
|
|
1140
1210
|
bounces: props.bounces,
|
|
1141
1211
|
horizontal: props.horizontal,
|
|
1142
|
-
onScroll
|
|
1212
|
+
onScroll,
|
|
1143
1213
|
onEndReached: () => emit("endReached")
|
|
1144
1214
|
},
|
|
1145
1215
|
children
|
|
@@ -1164,10 +1234,24 @@ var VModal = (0, import_runtime_core14.defineComponent)({
|
|
|
1164
1234
|
},
|
|
1165
1235
|
emits: ["dismiss"],
|
|
1166
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
|
+
});
|
|
1167
1251
|
return () => (0, import_runtime_core14.h)(
|
|
1168
1252
|
"VModal",
|
|
1169
1253
|
{
|
|
1170
|
-
visible:
|
|
1254
|
+
visible: debouncedVisible.value,
|
|
1171
1255
|
style: props.style,
|
|
1172
1256
|
onDismiss: () => emit("dismiss")
|
|
1173
1257
|
},
|
|
@@ -1188,8 +1272,22 @@ var VAlertDialog = (0, import_runtime_core15.defineComponent)({
|
|
|
1188
1272
|
},
|
|
1189
1273
|
emits: ["confirm", "cancel", "action"],
|
|
1190
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
|
+
});
|
|
1191
1289
|
return () => (0, import_runtime_core15.h)("VAlertDialog", {
|
|
1192
|
-
visible:
|
|
1290
|
+
visible: debouncedVisible.value,
|
|
1193
1291
|
title: props.title,
|
|
1194
1292
|
message: props.message,
|
|
1195
1293
|
buttons: props.buttons,
|
|
@@ -1229,8 +1327,18 @@ var VWebView = (0, import_runtime_core17.defineComponent)({
|
|
|
1229
1327
|
},
|
|
1230
1328
|
emits: ["load", "error", "message"],
|
|
1231
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
|
+
});
|
|
1232
1340
|
return () => (0, import_runtime_core17.h)("VWebView", {
|
|
1233
|
-
source:
|
|
1341
|
+
source: sanitizedSource.value,
|
|
1234
1342
|
style: props.style,
|
|
1235
1343
|
javaScriptEnabled: props.javaScriptEnabled,
|
|
1236
1344
|
onLoad: (e) => emit("load", e),
|
|
@@ -1876,15 +1984,33 @@ function useHaptics() {
|
|
|
1876
1984
|
}
|
|
1877
1985
|
|
|
1878
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
|
+
}
|
|
1879
1999
|
function useAsyncStorage() {
|
|
1880
2000
|
function getItem(key) {
|
|
1881
2001
|
return NativeBridge.invokeNativeModule("AsyncStorage", "getItem", [key]);
|
|
1882
2002
|
}
|
|
1883
2003
|
function setItem(key, value) {
|
|
1884
|
-
return
|
|
2004
|
+
return queueWrite(
|
|
2005
|
+
key,
|
|
2006
|
+
() => NativeBridge.invokeNativeModule("AsyncStorage", "setItem", [key, value]).then(() => void 0)
|
|
2007
|
+
);
|
|
1885
2008
|
}
|
|
1886
2009
|
function removeItem(key) {
|
|
1887
|
-
return
|
|
2010
|
+
return queueWrite(
|
|
2011
|
+
key,
|
|
2012
|
+
() => NativeBridge.invokeNativeModule("AsyncStorage", "removeItem", [key]).then(() => void 0)
|
|
2013
|
+
);
|
|
1888
2014
|
}
|
|
1889
2015
|
function getAllKeys() {
|
|
1890
2016
|
return NativeBridge.invokeNativeModule("AsyncStorage", "getAllKeys", []);
|
|
@@ -2021,15 +2147,20 @@ var import_runtime_core33 = require("@vue/runtime-core");
|
|
|
2021
2147
|
function useNetwork() {
|
|
2022
2148
|
const isConnected = (0, import_runtime_core33.ref)(true);
|
|
2023
2149
|
const connectionType = (0, import_runtime_core33.ref)("unknown");
|
|
2024
|
-
|
|
2025
|
-
isConnected.value = status.isConnected;
|
|
2026
|
-
connectionType.value = status.connectionType;
|
|
2027
|
-
}).catch(() => {
|
|
2028
|
-
});
|
|
2150
|
+
let lastEventTime = 0;
|
|
2029
2151
|
const unsubscribe = NativeBridge.onGlobalEvent("network:change", (payload) => {
|
|
2152
|
+
lastEventTime = Date.now();
|
|
2030
2153
|
isConnected.value = payload.isConnected;
|
|
2031
2154
|
connectionType.value = payload.connectionType;
|
|
2032
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
|
+
});
|
|
2033
2164
|
(0, import_runtime_core33.onUnmounted)(unsubscribe);
|
|
2034
2165
|
return { isConnected, connectionType };
|
|
2035
2166
|
}
|
|
@@ -2086,21 +2217,39 @@ function useGeolocation() {
|
|
|
2086
2217
|
const error = (0, import_runtime_core35.ref)(null);
|
|
2087
2218
|
let watchId = null;
|
|
2088
2219
|
async function getCurrentPosition() {
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
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
|
+
}
|
|
2092
2230
|
}
|
|
2093
2231
|
async function watchPosition() {
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
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
|
+
}
|
|
2104
2253
|
}
|
|
2105
2254
|
async function clearWatch(id) {
|
|
2106
2255
|
await NativeBridge.invokeNativeModule("Geolocation", "clearWatch", [id]);
|
|
@@ -2233,6 +2382,10 @@ function useHttp(config = {}) {
|
|
|
2233
2382
|
}
|
|
2234
2383
|
const loading = (0, import_runtime_core38.ref)(false);
|
|
2235
2384
|
const error = (0, import_runtime_core38.ref)(null);
|
|
2385
|
+
let isMounted = true;
|
|
2386
|
+
(0, import_runtime_core38.onUnmounted)(() => {
|
|
2387
|
+
isMounted = false;
|
|
2388
|
+
});
|
|
2236
2389
|
async function request(method, url, options = {}) {
|
|
2237
2390
|
const fullUrl = config.baseURL ? `${config.baseURL}${url}` : url;
|
|
2238
2391
|
loading.value = true;
|
|
@@ -2252,6 +2405,9 @@ function useHttp(config = {}) {
|
|
|
2252
2405
|
}
|
|
2253
2406
|
const response = await fetch(fullUrl, fetchOptions);
|
|
2254
2407
|
const data = await response.json();
|
|
2408
|
+
if (!isMounted) {
|
|
2409
|
+
return { data, status: response.status, ok: response.ok, headers: {} };
|
|
2410
|
+
}
|
|
2255
2411
|
return {
|
|
2256
2412
|
data,
|
|
2257
2413
|
status: response.status,
|
|
@@ -2260,10 +2416,14 @@ function useHttp(config = {}) {
|
|
|
2260
2416
|
};
|
|
2261
2417
|
} catch (e) {
|
|
2262
2418
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2263
|
-
|
|
2419
|
+
if (isMounted) {
|
|
2420
|
+
error.value = msg;
|
|
2421
|
+
}
|
|
2264
2422
|
throw e;
|
|
2265
2423
|
} finally {
|
|
2266
|
-
|
|
2424
|
+
if (isMounted) {
|
|
2425
|
+
loading.value = false;
|
|
2426
|
+
}
|
|
2267
2427
|
}
|
|
2268
2428
|
}
|
|
2269
2429
|
return {
|
|
@@ -2299,7 +2459,11 @@ function useBackHandler(handler) {
|
|
|
2299
2459
|
let unsubscribe = null;
|
|
2300
2460
|
(0, import_runtime_core40.onMounted)(() => {
|
|
2301
2461
|
unsubscribe = NativeBridge.onGlobalEvent("hardware:backPress", () => {
|
|
2302
|
-
handler();
|
|
2462
|
+
const handled = handler();
|
|
2463
|
+
if (!handled) {
|
|
2464
|
+
NativeBridge.invokeNativeModule("BackHandler", "exitApp", []).catch(() => {
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2303
2467
|
});
|
|
2304
2468
|
});
|
|
2305
2469
|
(0, import_runtime_core40.onUnmounted)(() => {
|
|
@@ -2389,6 +2553,8 @@ function useWebSocket(url, options = {}) {
|
|
|
2389
2553
|
const error = (0, import_runtime_core43.ref)(null);
|
|
2390
2554
|
let reconnectAttempts = 0;
|
|
2391
2555
|
let reconnectTimer = null;
|
|
2556
|
+
const MAX_PENDING_MESSAGES = 100;
|
|
2557
|
+
const pendingMessages = [];
|
|
2392
2558
|
const unsubscribers = [];
|
|
2393
2559
|
unsubscribers.push(
|
|
2394
2560
|
NativeBridge.onGlobalEvent("websocket:open", (payload) => {
|
|
@@ -2396,6 +2562,12 @@ function useWebSocket(url, options = {}) {
|
|
|
2396
2562
|
status.value = "OPEN";
|
|
2397
2563
|
error.value = null;
|
|
2398
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
|
+
}
|
|
2399
2571
|
})
|
|
2400
2572
|
);
|
|
2401
2573
|
unsubscribers.push(
|
|
@@ -2410,9 +2582,10 @@ function useWebSocket(url, options = {}) {
|
|
|
2410
2582
|
status.value = "CLOSED";
|
|
2411
2583
|
if (autoReconnect && reconnectAttempts < maxReconnectAttempts && payload.code !== 1e3) {
|
|
2412
2584
|
reconnectAttempts++;
|
|
2585
|
+
const backoffMs = reconnectInterval * Math.pow(2, reconnectAttempts - 1);
|
|
2413
2586
|
reconnectTimer = setTimeout(() => {
|
|
2414
2587
|
open();
|
|
2415
|
-
},
|
|
2588
|
+
}, backoffMs);
|
|
2416
2589
|
}
|
|
2417
2590
|
})
|
|
2418
2591
|
);
|
|
@@ -2432,8 +2605,17 @@ function useWebSocket(url, options = {}) {
|
|
|
2432
2605
|
});
|
|
2433
2606
|
}
|
|
2434
2607
|
function send(data) {
|
|
2435
|
-
if (status.value !== "OPEN") return;
|
|
2436
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
|
+
}
|
|
2437
2619
|
NativeBridge.invokeNativeModule("WebSocket", "send", [connectionId, message]).catch((err) => {
|
|
2438
2620
|
error.value = err.message;
|
|
2439
2621
|
});
|
|
@@ -2717,22 +2899,22 @@ function useDatabase(name = "default") {
|
|
|
2717
2899
|
}
|
|
2718
2900
|
async function transaction(callback) {
|
|
2719
2901
|
await ensureOpen();
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
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 ?? []]);
|
|
2729
2910
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
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;
|
|
2736
2918
|
}
|
|
2737
2919
|
}
|
|
2738
2920
|
async function close() {
|
|
@@ -3121,6 +3303,8 @@ function useOTAUpdate(serverUrl) {
|
|
|
3121
3303
|
await NativeBridge.invokeNativeModule("OTA", "downloadUpdate", [downloadUrl, expectedHash || ""]);
|
|
3122
3304
|
status.value = "ready";
|
|
3123
3305
|
} catch (err) {
|
|
3306
|
+
await NativeBridge.invokeNativeModule("OTA", "cleanupPartialDownload", []).catch(() => {
|
|
3307
|
+
});
|
|
3124
3308
|
error.value = err?.message || String(err);
|
|
3125
3309
|
status.value = "error";
|
|
3126
3310
|
throw err;
|
|
@@ -3129,7 +3313,17 @@ function useOTAUpdate(serverUrl) {
|
|
|
3129
3313
|
}
|
|
3130
3314
|
}
|
|
3131
3315
|
async function applyUpdate() {
|
|
3316
|
+
if (status.value !== "ready") {
|
|
3317
|
+
throw new Error("No update ready to apply. Call downloadUpdate() first.");
|
|
3318
|
+
}
|
|
3132
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
|
+
}
|
|
3133
3327
|
try {
|
|
3134
3328
|
await NativeBridge.invokeNativeModule("OTA", "applyUpdate", []);
|
|
3135
3329
|
const info = await NativeBridge.invokeNativeModule("OTA", "getCurrentVersion", []);
|
package/dist/index.d.cts
CHANGED
|
@@ -602,6 +602,10 @@ declare const VButton: _vue_runtime_core.DefineComponent<_vue_runtime_core.Extra
|
|
|
602
602
|
* The component maps `modelValue` to the native `text` prop and listens
|
|
603
603
|
* for `changetext` events from the native side to update the model.
|
|
604
604
|
*
|
|
605
|
+
* Handles CJK IME composition correctly: during composition, model updates
|
|
606
|
+
* are deferred until the user commits the composed character to avoid
|
|
607
|
+
* v-model desync.
|
|
608
|
+
*
|
|
605
609
|
* @example
|
|
606
610
|
* ```vue
|
|
607
611
|
* <VInput
|
|
@@ -845,6 +849,11 @@ declare const VScrollView: _vue_runtime_core.DefineComponent<_vue_runtime_core.E
|
|
|
845
849
|
default: boolean;
|
|
846
850
|
};
|
|
847
851
|
contentContainerStyle: PropType<ViewStyle>;
|
|
852
|
+
/** Minimum interval in ms between scroll event emissions. Default: 16 (~60fps) */
|
|
853
|
+
scrollEventThrottle: {
|
|
854
|
+
type: NumberConstructor;
|
|
855
|
+
default: number;
|
|
856
|
+
};
|
|
848
857
|
/** Whether the pull-to-refresh indicator is active */
|
|
849
858
|
refreshing: {
|
|
850
859
|
type: BooleanConstructor;
|
|
@@ -883,6 +892,11 @@ declare const VScrollView: _vue_runtime_core.DefineComponent<_vue_runtime_core.E
|
|
|
883
892
|
default: boolean;
|
|
884
893
|
};
|
|
885
894
|
contentContainerStyle: PropType<ViewStyle>;
|
|
895
|
+
/** Minimum interval in ms between scroll event emissions. Default: 16 (~60fps) */
|
|
896
|
+
scrollEventThrottle: {
|
|
897
|
+
type: NumberConstructor;
|
|
898
|
+
default: number;
|
|
899
|
+
};
|
|
886
900
|
/** Whether the pull-to-refresh indicator is active */
|
|
887
901
|
refreshing: {
|
|
888
902
|
type: BooleanConstructor;
|
|
@@ -903,6 +917,7 @@ declare const VScrollView: _vue_runtime_core.DefineComponent<_vue_runtime_core.E
|
|
|
903
917
|
scrollEnabled: boolean;
|
|
904
918
|
bounces: boolean;
|
|
905
919
|
pagingEnabled: boolean;
|
|
920
|
+
scrollEventThrottle: number;
|
|
906
921
|
refreshing: boolean;
|
|
907
922
|
}, {}, {}, {}, string, _vue_runtime_core.ComponentProvideOptions, true, {}, any>;
|
|
908
923
|
|
|
@@ -912,6 +927,9 @@ declare const VScrollView: _vue_runtime_core.DefineComponent<_vue_runtime_core.E
|
|
|
912
927
|
* Maps to UIImageView on iOS. Loads images from URIs asynchronously
|
|
913
928
|
* with built-in caching. Supports various resize modes.
|
|
914
929
|
*
|
|
930
|
+
* Exposes a reactive `loading` ref (via template ref) that is `true`
|
|
931
|
+
* while the image is being fetched and `false` once it loads or errors.
|
|
932
|
+
*
|
|
915
933
|
* @example
|
|
916
934
|
* ```vue
|
|
917
935
|
* <VImage
|
|
@@ -1306,6 +1324,9 @@ interface WebViewSource {
|
|
|
1306
1324
|
/**
|
|
1307
1325
|
* Embedded web view component backed by WKWebView.
|
|
1308
1326
|
*
|
|
1327
|
+
* URI sources are validated to block dangerous schemes such as `javascript:`
|
|
1328
|
+
* and `data:text/html` which could lead to XSS.
|
|
1329
|
+
*
|
|
1309
1330
|
* @example
|
|
1310
1331
|
* <VWebView :source="{ uri: 'https://example.com' }" style="flex: 1" @load="onLoad" />
|
|
1311
1332
|
*/
|
|
@@ -2179,6 +2200,8 @@ declare function useHaptics(): {
|
|
|
2179
2200
|
* Async key-value storage composable backed by UserDefaults.
|
|
2180
2201
|
*
|
|
2181
2202
|
* All operations are Promise-based and run on a background thread.
|
|
2203
|
+
* Write operations (setItem, removeItem) are serialized per key to
|
|
2204
|
+
* prevent race conditions from concurrent access.
|
|
2182
2205
|
*
|
|
2183
2206
|
* @example
|
|
2184
2207
|
* ```ts
|
|
@@ -3479,6 +3502,8 @@ declare class NativeBridgeImpl {
|
|
|
3479
3502
|
private nextCallbackId;
|
|
3480
3503
|
/** Maximum callback ID before wraparound (safe for 32-bit signed int) */
|
|
3481
3504
|
private static readonly MAX_CALLBACK_ID;
|
|
3505
|
+
/** Maximum number of pending callbacks before evicting the oldest */
|
|
3506
|
+
private static readonly MAX_PENDING_CALLBACKS;
|
|
3482
3507
|
/** Global event listeners: eventName -> Set of callbacks */
|
|
3483
3508
|
private globalEventHandlers;
|
|
3484
3509
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -602,6 +602,10 @@ declare const VButton: _vue_runtime_core.DefineComponent<_vue_runtime_core.Extra
|
|
|
602
602
|
* The component maps `modelValue` to the native `text` prop and listens
|
|
603
603
|
* for `changetext` events from the native side to update the model.
|
|
604
604
|
*
|
|
605
|
+
* Handles CJK IME composition correctly: during composition, model updates
|
|
606
|
+
* are deferred until the user commits the composed character to avoid
|
|
607
|
+
* v-model desync.
|
|
608
|
+
*
|
|
605
609
|
* @example
|
|
606
610
|
* ```vue
|
|
607
611
|
* <VInput
|
|
@@ -845,6 +849,11 @@ declare const VScrollView: _vue_runtime_core.DefineComponent<_vue_runtime_core.E
|
|
|
845
849
|
default: boolean;
|
|
846
850
|
};
|
|
847
851
|
contentContainerStyle: PropType<ViewStyle>;
|
|
852
|
+
/** Minimum interval in ms between scroll event emissions. Default: 16 (~60fps) */
|
|
853
|
+
scrollEventThrottle: {
|
|
854
|
+
type: NumberConstructor;
|
|
855
|
+
default: number;
|
|
856
|
+
};
|
|
848
857
|
/** Whether the pull-to-refresh indicator is active */
|
|
849
858
|
refreshing: {
|
|
850
859
|
type: BooleanConstructor;
|
|
@@ -883,6 +892,11 @@ declare const VScrollView: _vue_runtime_core.DefineComponent<_vue_runtime_core.E
|
|
|
883
892
|
default: boolean;
|
|
884
893
|
};
|
|
885
894
|
contentContainerStyle: PropType<ViewStyle>;
|
|
895
|
+
/** Minimum interval in ms between scroll event emissions. Default: 16 (~60fps) */
|
|
896
|
+
scrollEventThrottle: {
|
|
897
|
+
type: NumberConstructor;
|
|
898
|
+
default: number;
|
|
899
|
+
};
|
|
886
900
|
/** Whether the pull-to-refresh indicator is active */
|
|
887
901
|
refreshing: {
|
|
888
902
|
type: BooleanConstructor;
|
|
@@ -903,6 +917,7 @@ declare const VScrollView: _vue_runtime_core.DefineComponent<_vue_runtime_core.E
|
|
|
903
917
|
scrollEnabled: boolean;
|
|
904
918
|
bounces: boolean;
|
|
905
919
|
pagingEnabled: boolean;
|
|
920
|
+
scrollEventThrottle: number;
|
|
906
921
|
refreshing: boolean;
|
|
907
922
|
}, {}, {}, {}, string, _vue_runtime_core.ComponentProvideOptions, true, {}, any>;
|
|
908
923
|
|
|
@@ -912,6 +927,9 @@ declare const VScrollView: _vue_runtime_core.DefineComponent<_vue_runtime_core.E
|
|
|
912
927
|
* Maps to UIImageView on iOS. Loads images from URIs asynchronously
|
|
913
928
|
* with built-in caching. Supports various resize modes.
|
|
914
929
|
*
|
|
930
|
+
* Exposes a reactive `loading` ref (via template ref) that is `true`
|
|
931
|
+
* while the image is being fetched and `false` once it loads or errors.
|
|
932
|
+
*
|
|
915
933
|
* @example
|
|
916
934
|
* ```vue
|
|
917
935
|
* <VImage
|
|
@@ -1306,6 +1324,9 @@ interface WebViewSource {
|
|
|
1306
1324
|
/**
|
|
1307
1325
|
* Embedded web view component backed by WKWebView.
|
|
1308
1326
|
*
|
|
1327
|
+
* URI sources are validated to block dangerous schemes such as `javascript:`
|
|
1328
|
+
* and `data:text/html` which could lead to XSS.
|
|
1329
|
+
*
|
|
1309
1330
|
* @example
|
|
1310
1331
|
* <VWebView :source="{ uri: 'https://example.com' }" style="flex: 1" @load="onLoad" />
|
|
1311
1332
|
*/
|
|
@@ -2179,6 +2200,8 @@ declare function useHaptics(): {
|
|
|
2179
2200
|
* Async key-value storage composable backed by UserDefaults.
|
|
2180
2201
|
*
|
|
2181
2202
|
* All operations are Promise-based and run on a background thread.
|
|
2203
|
+
* Write operations (setItem, removeItem) are serialized per key to
|
|
2204
|
+
* prevent race conditions from concurrent access.
|
|
2182
2205
|
*
|
|
2183
2206
|
* @example
|
|
2184
2207
|
* ```ts
|
|
@@ -3479,6 +3502,8 @@ declare class NativeBridgeImpl {
|
|
|
3479
3502
|
private nextCallbackId;
|
|
3480
3503
|
/** Maximum callback ID before wraparound (safe for 32-bit signed int) */
|
|
3481
3504
|
private static readonly MAX_CALLBACK_ID;
|
|
3505
|
+
/** Maximum number of pending callbacks before evicting the oldest */
|
|
3506
|
+
private static readonly MAX_PENDING_CALLBACKS;
|
|
3482
3507
|
/** Global event listeners: eventName -> Set of callbacks */
|
|
3483
3508
|
private globalEventHandlers;
|
|
3484
3509
|
/**
|