fix(calls): sandbox/prod VoIP-push failover + foreground CallKit-UI suppress
- voip-push: build both APNs Provider (production+sandbox) and try each per token with memoization. Fixes BadDeviceToken on Xcode-Dev-Builds where the token is Sandbox-only. - stores/call: only call callkit.displayIncomingCall when app NOT in foreground \u2014 in foreground the /call route handles ringing UI, otherwise double UI (system banner + fullscreen). - patch react-native-callkeep: New-Arch TurboModule compatibility (no overloads, no Bundle params in @ReactMethod). - pushTokenRegistration: more verbose [voip] diagnostics.
This commit is contained in:
parent
fb2d90b947
commit
6a907cf89b
@ -35,26 +35,36 @@ const lastRegisteredVoipToken: { current: string | null } = { current: null };
|
||||
|
||||
/** Holt iOS-VoIP-Push-Token via PushKit. Resolve mit `null` wenn nicht verfügbar. */
|
||||
async function fetchVoipToken(): Promise<string | null> {
|
||||
if (Platform.OS !== 'ios' || !RNVoipPushNotification) return null;
|
||||
if (Platform.OS !== 'ios') {
|
||||
if (__DEV__) console.log('[voip] skip (not iOS)');
|
||||
return null;
|
||||
}
|
||||
if (!RNVoipPushNotification) {
|
||||
console.warn('[voip] react-native-voip-push-notification NOT linked — PushKit token cannot be fetched. Did the with-voip-pushkit-ios.js plugin run + prebuild?');
|
||||
return null;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
let resolved = false;
|
||||
const onToken = (token: string) => {
|
||||
if (resolved) return;
|
||||
resolved = true;
|
||||
if (__DEV__) console.log('[voip] register event fired, token:', token ? token.slice(0, 20) + '…' : '(empty)');
|
||||
resolve(token || null);
|
||||
};
|
||||
try {
|
||||
// Listener registrieren BEVOR registerVoipToken — sonst race.
|
||||
RNVoipPushNotification.addEventListener('register', onToken);
|
||||
RNVoipPushNotification.registerVoipToken();
|
||||
if (__DEV__) console.log('[voip] registerVoipToken() called, waiting for callback…');
|
||||
// Safety-Timeout: nach 4s aufgeben (Cert/Provisioning fehlt etc.).
|
||||
setTimeout(() => {
|
||||
if (resolved) return;
|
||||
resolved = true;
|
||||
console.warn('[voip] timeout after 4s — NO PushKit token received. Check: Push-Notifications-Cap + Background-Modes(voip) in Xcode entitlements, physical device, VoIP-services-certificate in Apple Portal.');
|
||||
resolve(null);
|
||||
}, 4000);
|
||||
} catch (err) {
|
||||
if (__DEV__) console.warn('[voip] register failed:', err);
|
||||
console.warn('[voip] register failed:', err);
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
@ -141,7 +151,11 @@ export async function registerPushTokenWithBackend(): Promise<string | null> {
|
||||
lastRegisteredVoipToken.current = voipToken;
|
||||
if (__DEV__) {
|
||||
console.log('[push] token registered:', token.slice(0, 30) + '…');
|
||||
if (voipToken) console.log('[voip] token registered:', voipToken.slice(0, 30) + '…');
|
||||
if (voipToken) {
|
||||
console.log('[voip] token registered:', voipToken.slice(0, 30) + '…');
|
||||
} else if (Platform.OS === 'ios') {
|
||||
console.warn('[voip] iOS without VoIP-token — incoming calls in background/killed will NOT wake the app. Backend will fall back to silent Expo-push.');
|
||||
}
|
||||
}
|
||||
return token;
|
||||
} catch (err) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { create } from 'zustand';
|
||||
import { NativeModules } from 'react-native';
|
||||
import { AppState, NativeModules } from 'react-native';
|
||||
import type { RealtimeChannel } from '@supabase/supabase-js';
|
||||
import { supabase } from '../lib/supabase';
|
||||
import { apiFetch } from '../lib/api';
|
||||
@ -382,11 +382,16 @@ export const useCallStore = create<CallState>((set, get) => {
|
||||
currentRole = 'callee';
|
||||
loggedCallId = null;
|
||||
set({ status: 'incoming', peer: from, callId, muted: false, speaker: false, startedAt: null, endReason: null });
|
||||
// CallKit-/ConnectionService-UI hochziehen — das zeigt nativen Call-Screen
|
||||
// über Lockscreen, sogar wenn die App im Background ist. RNCallKeep
|
||||
// dedupliziert intern via UUID, also safe wenn AppDelegate's
|
||||
// reportNewIncomingCall (VoIP-Push-Pfad) schon dieselbe UUID gemeldet hat.
|
||||
try { callkit.displayIncomingCall(callId, from.nickname || 'ReBreak'); } catch {}
|
||||
// CallKit-/ConnectionService-UI hochziehen — ABER nur wenn App NICHT im
|
||||
// Vordergrund. Im Foreground kümmert sich der In-App /call-Screen darum,
|
||||
// sonst gibt es Doppel-UI (System-Banner + Fullscreen). VoIP-Push-Pfad
|
||||
// (AppDelegate.reportNewIncomingCall) läuft eh getrennt, das deduliziert
|
||||
// CallKit intern via UUID.
|
||||
if (AppState.currentState !== 'active') {
|
||||
try { callkit.displayIncomingCall(callId, from.nickname || 'ReBreak'); } catch {}
|
||||
} else {
|
||||
clog('receiveIncoming: app foreground — skipping CallKit UI (using in-app /call screen)');
|
||||
}
|
||||
// CallKit (iOS) + ConnectionService (Android) spielen ihren eigenen
|
||||
// Ringtone — KEIN InCallManager.startRingtone() hier, sonst doppeltes
|
||||
// Klingeln. InCallManager.start() bleibt aus demselben Grund weg; der
|
||||
|
||||
@ -28,47 +28,56 @@ type ApnProvider = InstanceType<ApnModule["Provider"]>;
|
||||
type ApnNotification = InstanceType<ApnModule["Notification"]>;
|
||||
|
||||
let apnMod: ApnModule | null = null;
|
||||
let provider: ApnProvider | null = null;
|
||||
let providerProd: ApnProvider | null = null;
|
||||
let providerSandbox: ApnProvider | null = null;
|
||||
let initialized = false;
|
||||
let topic: string | null = null;
|
||||
/** Token → "prod" | "sandbox" Memoization. Vermeidet 2-Round-Trip pro Push. */
|
||||
const tokenEnvCache = new Map<string, "prod" | "sandbox">();
|
||||
|
||||
async function getProvider(): Promise<ApnProvider | null> {
|
||||
if (initialized) return provider;
|
||||
async function ensureInit(): Promise<boolean> {
|
||||
if (initialized) return providerProd !== null || providerSandbox !== null;
|
||||
initialized = true;
|
||||
|
||||
const p12Path = process.env.APNS_VOIP_P12_PATH;
|
||||
const p12Pass = process.env.APNS_VOIP_P12_PASSWORD;
|
||||
const tpc = process.env.APNS_VOIP_TOPIC;
|
||||
const production = process.env.APNS_VOIP_PRODUCTION === "true";
|
||||
|
||||
if (!p12Path || !p12Pass || !tpc) {
|
||||
console.warn(
|
||||
"[voip-push] disabled — missing env (APNS_VOIP_P12_PATH/PASSWORD/TOPIC)",
|
||||
);
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
if (!fs.existsSync(p12Path)) {
|
||||
console.warn(`[voip-push] disabled — p12 file not found at ${p12Path}`);
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Dynamic import — vermeidet bundler statisches Tracing.
|
||||
const mod = (await import("@parse/node-apn")) as unknown as ApnModule & {
|
||||
default?: ApnModule;
|
||||
};
|
||||
apnMod = mod.default ?? mod;
|
||||
topic = tpc;
|
||||
provider = new apnMod.Provider({
|
||||
// VoIP-Services-Cert ist Universal — wir bauen BEIDE Provider (Production +
|
||||
// Sandbox) und wählen pro Token via Memoization. So funktioniert es für
|
||||
// Xcode-Dev-Builds (Sandbox) UND TestFlight/AppStore (Production) parallel.
|
||||
providerProd = new apnMod.Provider({
|
||||
pfx: p12Path,
|
||||
passphrase: p12Pass,
|
||||
production,
|
||||
production: true,
|
||||
});
|
||||
console.log(`[voip-push] initialized (topic=${tpc}, production=${production})`);
|
||||
return provider;
|
||||
providerSandbox = new apnMod.Provider({
|
||||
pfx: p12Path,
|
||||
passphrase: p12Pass,
|
||||
production: false,
|
||||
});
|
||||
console.log(`[voip-push] initialized (topic=${tpc}, prod+sandbox providers ready)`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("[voip-push] init failed:", err);
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,8 +106,8 @@ export interface VoIPCallPayload {
|
||||
* @returns true bei Erfolg (oder no-op wenn Service disabled), false bei Fehler.
|
||||
*/
|
||||
export async function sendVoIPPush(payload: VoIPCallPayload): Promise<boolean> {
|
||||
const p = await getProvider();
|
||||
if (!p || !topic || !apnMod) return true; // no-op, regulärer Push übernimmt
|
||||
const ok = await ensureInit();
|
||||
if (!ok || !topic || !apnMod) return true; // no-op, regulärer Push übernimmt
|
||||
|
||||
const note: ApnNotification = new apnMod.Notification();
|
||||
note.topic = topic;
|
||||
@ -116,28 +125,56 @@ export async function sendVoIPPush(payload: VoIPCallPayload): Promise<boolean> {
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await p.send(note, payload.voipToken);
|
||||
if (result.failed.length > 0) {
|
||||
// Reihenfolge: zuerst memoized Env (falls bekannt), sonst Production first →
|
||||
// Sandbox als Fallback.
|
||||
const cached = tokenEnvCache.get(payload.voipToken);
|
||||
const order: Array<"prod" | "sandbox"> =
|
||||
cached === "sandbox" ? ["sandbox", "prod"] : ["prod", "sandbox"];
|
||||
|
||||
for (const env of order) {
|
||||
const prov = env === "prod" ? providerProd : providerSandbox;
|
||||
if (!prov) continue;
|
||||
try {
|
||||
const result = await prov.send(note, payload.voipToken);
|
||||
if (result.failed.length === 0) {
|
||||
tokenEnvCache.set(payload.voipToken, env);
|
||||
return true;
|
||||
}
|
||||
const f = result.failed[0];
|
||||
const reason = (f.response as { reason?: string })?.reason;
|
||||
// BadDeviceToken → nur dann Failover, sonst Abbruch (z.B. Unregistered).
|
||||
if (reason === "BadDeviceToken") {
|
||||
if (env === order[order.length - 1]) {
|
||||
console.warn(
|
||||
`[voip-push] failed token=${payload.voipToken.slice(0, 8)}… BadDeviceToken on both envs`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// try next env
|
||||
continue;
|
||||
}
|
||||
console.warn(
|
||||
`[voip-push] failed token=${payload.voipToken.slice(0, 8)}… reason=`,
|
||||
`[voip-push] failed token=${payload.voipToken.slice(0, 8)}… env=${env} reason=`,
|
||||
f.response ?? f.error,
|
||||
);
|
||||
return false;
|
||||
} catch (err) {
|
||||
console.error("[voip-push] send threw:", err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("[voip-push] send threw:", err);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Cleanup bei Server-Shutdown — wichtig wegen HTTP/2-Verbindung an Apple. */
|
||||
export function shutdownVoIPProvider(): void {
|
||||
if (provider) {
|
||||
provider.shutdown();
|
||||
provider = null;
|
||||
initialized = false;
|
||||
if (providerProd) {
|
||||
providerProd.shutdown();
|
||||
providerProd = null;
|
||||
}
|
||||
if (providerSandbox) {
|
||||
providerSandbox.shutdown();
|
||||
providerSandbox = null;
|
||||
}
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
@ -29,6 +29,10 @@
|
||||
"metro-symbolicate": "0.83.3",
|
||||
"metro-transform-plugins": "0.83.3",
|
||||
"metro-transform-worker": "0.83.3"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"metro-core@0.83.3": "patches/metro-core@0.83.3.patch",
|
||||
"react-native-callkeep": "patches/react-native-callkeep.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
72
patches/react-native-callkeep.patch
Normal file
72
patches/react-native-callkeep.patch
Normal file
@ -0,0 +1,72 @@
|
||||
diff --git a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java
|
||||
index 025480ac97460cc45775ea11aa43af36522d5df6..106fbf0081b21ea02029add0aca86f55e8a55c07 100644
|
||||
--- a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java
|
||||
+++ b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java
|
||||
@@ -189,7 +189,7 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule implements Life
|
||||
public void reportNewIncomingCall(String uuid, String number, String callerName, boolean hasVideo, String payload) {
|
||||
Log.d(TAG, "[RNCallKeepModule] reportNewIncomingCall, uuid: " + uuid + ", number: " + number + ", callerName: " + callerName);
|
||||
|
||||
- this.displayIncomingCall(uuid, number, callerName, hasVideo);
|
||||
+ this.displayIncomingCall(uuid, number, callerName, hasVideo, null);
|
||||
|
||||
// Send event to JS
|
||||
WritableMap args = Arguments.createMap();
|
||||
@@ -434,17 +434,26 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule implements Life
|
||||
this.hasListeners = false;
|
||||
}
|
||||
|
||||
- @ReactMethod
|
||||
- public void displayIncomingCall(String uuid, String number, String callerName) {
|
||||
+ // REBREAK_PATCH: @ReactMethod entfernt — RN New Architecture TurboModule
|
||||
+ // erlaubt keine overloaded methods mit gleichem JS-Namen. JS ruft immer den
|
||||
+ // 5-arg-Overload (siehe RNCallKeep.js displayIncomingCall implementation).
|
||||
+ public void displayIncomingCall3args(String uuid, String number, String callerName) {
|
||||
this.displayIncomingCall(uuid, number, callerName, false, null);
|
||||
}
|
||||
|
||||
+ // REBREAK_PATCH: TurboModule-Interop unterstützt android.os.Bundle nicht als
|
||||
+ // @ReactMethod-Parameter. Daher: 4-arg-Variante (ohne Bundle) ist die einzige
|
||||
+ // exposed @ReactMethod. JS-Wrapper ruft nie mit payload-Bundle.
|
||||
@ReactMethod
|
||||
public void displayIncomingCall(String uuid, String number, String callerName, boolean hasVideo) {
|
||||
- this.displayIncomingCall(uuid, number, callerName, hasVideo, null);
|
||||
+ this.displayIncomingCallInternal(uuid, number, callerName, hasVideo, null);
|
||||
}
|
||||
|
||||
public void displayIncomingCall(String uuid, String number, String callerName, boolean hasVideo, @Nullable Bundle payload) {
|
||||
+ this.displayIncomingCallInternal(uuid, number, callerName, hasVideo, payload);
|
||||
+ }
|
||||
+
|
||||
+ private void displayIncomingCallInternal(String uuid, String number, String callerName, boolean hasVideo, @Nullable Bundle payload) {
|
||||
if (!isConnectionServiceAvailable() || !hasPhoneAccount()) {
|
||||
Log.w(TAG, "[RNCallKeepModule] displayIncomingCall ignored due to no ConnectionService or no phone account");
|
||||
return;
|
||||
@@ -483,17 +492,25 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule implements Life
|
||||
conn.onAnswer();
|
||||
}
|
||||
|
||||
- @ReactMethod
|
||||
- public void startCall(String uuid, String number, String callerName) {
|
||||
+ // REBREAK_PATCH: @ReactMethod entfernt (siehe displayIncomingCall above).
|
||||
+ public void startCall3args(String uuid, String number, String callerName) {
|
||||
this.startCall(uuid, number, callerName, false, null);
|
||||
}
|
||||
|
||||
+ public void startCall4args(String uuid, String number, String callerName, boolean hasVideo) {
|
||||
+ this.startCall(uuid, number, callerName, hasVideo, null);
|
||||
+ }
|
||||
+
|
||||
@ReactMethod
|
||||
public void startCall(String uuid, String number, String callerName, boolean hasVideo) {
|
||||
- this.startCall(uuid, number, callerName, hasVideo, null);
|
||||
+ this.startCallInternal(uuid, number, callerName, hasVideo, null);
|
||||
}
|
||||
|
||||
public void startCall(String uuid, String number, String callerName, boolean hasVideo, @Nullable Bundle payload) {
|
||||
+ this.startCallInternal(uuid, number, callerName, hasVideo, payload);
|
||||
+ }
|
||||
+
|
||||
+ private void startCallInternal(String uuid, String number, String callerName, boolean hasVideo, @Nullable Bundle payload) {
|
||||
Log.d(TAG, "[RNCallKeepModule] startCall called, uuid: " + uuid + ", number: " + number + ", callerName: " + callerName + ", payload: " + payload);
|
||||
|
||||
if (!isConnectionServiceAvailable() || !hasPhoneAccount() || !hasPermissions() || number == null) {
|
||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@ -21,6 +21,9 @@ patchedDependencies:
|
||||
metro-core@0.83.3:
|
||||
hash: dbd76dee4e5497574765c5986b0e889264e7251ea7b5e849e2967d2eb2efb757
|
||||
path: patches/metro-core@0.83.3.patch
|
||||
react-native-callkeep:
|
||||
hash: cba8c2dd49745c7a4f62437ea38db7e1d457966cd037dc405c0dca06d84850dd
|
||||
path: patches/react-native-callkeep.patch
|
||||
|
||||
importers:
|
||||
|
||||
@ -42,7 +45,7 @@ importers:
|
||||
version: 14.3.0(vue@3.5.34(typescript@5.9.3))
|
||||
'@vueuse/nuxt':
|
||||
specifier: ^14.2.1
|
||||
version: 14.3.0(magicast@0.5.3)(nuxt@4.1.3(@electric-sql/pglite@0.4.1)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@parcel/watcher@2.5.6)(@types/node@22.19.17)(@vue/compiler-sfc@3.5.35)(cac@6.7.14)(db0@0.3.4(@electric-sql/pglite@0.4.1)(mysql2@3.15.3))(eslint@10.3.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(mysql2@3.15.3)(optionator@0.9.4)(rollup@4.60.3)(terser@5.46.2)(typescript@5.9.3)(yaml@2.8.4))(vue@3.5.34(typescript@5.9.3))
|
||||
version: 14.3.0(magicast@0.5.3)(nuxt@4.1.3(@electric-sql/pglite@0.4.1)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@parcel/watcher@2.5.6)(@types/node@22.19.17)(@vue/compiler-sfc@3.5.35)(cac@6.7.14)(db0@0.3.4(@electric-sql/pglite@0.4.1)(mysql2@3.15.3))(eslint@10.3.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(mysql2@3.15.3)(optionator@0.9.4)(rollup@4.60.3)(terser@5.46.2)(typescript@5.9.3)(vite@7.3.3(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)(yaml@2.8.4))(yaml@2.8.4))(vue@3.5.34(typescript@5.9.3))
|
||||
nuxt:
|
||||
specifier: 4.1.3
|
||||
version: 4.1.3(@electric-sql/pglite@0.4.1)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@parcel/watcher@2.5.6)(@types/node@22.19.17)(@vue/compiler-sfc@3.5.35)(cac@6.7.14)(db0@0.3.4(@electric-sql/pglite@0.4.1)(mysql2@3.15.3))(eslint@10.3.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(mysql2@3.15.3)(optionator@0.9.4)(rollup@4.60.3)(terser@5.46.2)(typescript@5.9.3)(vite@7.3.3(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)(yaml@2.8.4))(yaml@2.8.4)
|
||||
@ -73,7 +76,7 @@ importers:
|
||||
version: 1.2.3
|
||||
'@nuxt/fonts':
|
||||
specifier: ^0.11.4
|
||||
version: 0.11.4(db0@0.3.4(@electric-sql/pglite@0.4.1)(mysql2@3.15.3))(ioredis@5.10.1)(magicast@0.5.3)
|
||||
version: 0.11.4(db0@0.3.4(@electric-sql/pglite@0.4.1)(mysql2@3.15.3))(ioredis@5.10.1)(magicast@0.5.3)(vite@7.3.3(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)(yaml@2.8.4))
|
||||
'@nuxt/icon':
|
||||
specifier: ^1.10.0
|
||||
version: 1.15.0(magicast@0.5.3)(vite@7.3.3(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)(yaml@2.8.4))(vue@3.5.34(typescript@5.9.3))
|
||||
@ -91,7 +94,7 @@ importers:
|
||||
version: 3.0.3(magicast@0.5.3)(vue@3.5.34(typescript@5.9.3))
|
||||
'@vueuse/nuxt':
|
||||
specifier: ^14.2.1
|
||||
version: 14.3.0(magicast@0.5.3)(nuxt@4.1.3(@electric-sql/pglite@0.4.1)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@parcel/watcher@2.5.6)(@types/node@22.19.17)(@vue/compiler-sfc@3.5.35)(cac@6.7.14)(db0@0.3.4(@electric-sql/pglite@0.4.1)(mysql2@3.15.3))(eslint@10.3.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(mysql2@3.15.3)(optionator@0.9.4)(rollup@4.60.3)(terser@5.46.2)(typescript@5.9.3)(yaml@2.8.4))(vue@3.5.34(typescript@5.9.3))
|
||||
version: 14.3.0(magicast@0.5.3)(nuxt@4.1.3(@electric-sql/pglite@0.4.1)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@parcel/watcher@2.5.6)(@types/node@22.19.17)(@vue/compiler-sfc@3.5.35)(cac@6.7.14)(db0@0.3.4(@electric-sql/pglite@0.4.1)(mysql2@3.15.3))(eslint@10.3.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(mysql2@3.15.3)(optionator@0.9.4)(rollup@4.60.3)(terser@5.46.2)(typescript@5.9.3)(vite@7.3.3(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)(yaml@2.8.4))(yaml@2.8.4))(vue@3.5.34(typescript@5.9.3))
|
||||
chart.js:
|
||||
specifier: ^4.5.1
|
||||
version: 4.5.1
|
||||
@ -275,7 +278,7 @@ importers:
|
||||
version: 1.2.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0)
|
||||
react-native-callkeep:
|
||||
specifier: ^4.3.16
|
||||
version: 4.3.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0))
|
||||
version: 4.3.16(patch_hash=cba8c2dd49745c7a4f62437ea38db7e1d457966cd037dc405c0dca06d84850dd)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0))
|
||||
react-native-gesture-handler:
|
||||
specifier: ~2.28.0
|
||||
version: 2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0)
|
||||
@ -11716,7 +11719,7 @@ snapshots:
|
||||
- utf-8-validate
|
||||
- vue
|
||||
|
||||
'@nuxt/fonts@0.11.4(db0@0.3.4(@electric-sql/pglite@0.4.1)(mysql2@3.15.3))(ioredis@5.10.1)(magicast@0.5.3)':
|
||||
'@nuxt/fonts@0.11.4(db0@0.3.4(@electric-sql/pglite@0.4.1)(mysql2@3.15.3))(ioredis@5.10.1)(magicast@0.5.3)(vite@7.3.3(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)(yaml@2.8.4))':
|
||||
dependencies:
|
||||
'@nuxt/devtools-kit': 2.7.0(magicast@0.5.3)(vite@7.3.3(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)(yaml@2.8.4))
|
||||
'@nuxt/kit': 3.21.4(magicast@0.5.3)
|
||||
@ -14329,7 +14332,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
|
||||
'@vueuse/nuxt@14.3.0(magicast@0.5.3)(nuxt@4.1.3(@electric-sql/pglite@0.4.1)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@parcel/watcher@2.5.6)(@types/node@22.19.17)(@vue/compiler-sfc@3.5.35)(cac@6.7.14)(db0@0.3.4(@electric-sql/pglite@0.4.1)(mysql2@3.15.3))(eslint@10.3.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(mysql2@3.15.3)(optionator@0.9.4)(rollup@4.60.3)(terser@5.46.2)(typescript@5.9.3)(yaml@2.8.4))(vue@3.5.34(typescript@5.9.3))':
|
||||
'@vueuse/nuxt@14.3.0(magicast@0.5.3)(nuxt@4.1.3(@electric-sql/pglite@0.4.1)(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@parcel/watcher@2.5.6)(@types/node@22.19.17)(@vue/compiler-sfc@3.5.35)(cac@6.7.14)(db0@0.3.4(@electric-sql/pglite@0.4.1)(mysql2@3.15.3))(eslint@10.3.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(mysql2@3.15.3)(optionator@0.9.4)(rollup@4.60.3)(terser@5.46.2)(typescript@5.9.3)(vite@7.3.3(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)(yaml@2.8.4))(yaml@2.8.4))(vue@3.5.34(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@nuxt/kit': 4.4.4(magicast@0.5.3)
|
||||
'@vueuse/core': 14.3.0(vue@3.5.34(typescript@5.9.3))
|
||||
@ -18874,7 +18877,7 @@ snapshots:
|
||||
sf-symbols-typescript: 2.2.0
|
||||
use-latest-callback: 0.2.6(react@19.1.0)
|
||||
|
||||
react-native-callkeep@4.3.16(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0)):
|
||||
react-native-callkeep@4.3.16(patch_hash=cba8c2dd49745c7a4f62437ea38db7e1d457966cd037dc405c0dca06d84850dd)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0)):
|
||||
dependencies:
|
||||
react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user