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. */
|
/** Holt iOS-VoIP-Push-Token via PushKit. Resolve mit `null` wenn nicht verfügbar. */
|
||||||
async function fetchVoipToken(): Promise<string | null> {
|
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) => {
|
return new Promise((resolve) => {
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
const onToken = (token: string) => {
|
const onToken = (token: string) => {
|
||||||
if (resolved) return;
|
if (resolved) return;
|
||||||
resolved = true;
|
resolved = true;
|
||||||
|
if (__DEV__) console.log('[voip] register event fired, token:', token ? token.slice(0, 20) + '…' : '(empty)');
|
||||||
resolve(token || null);
|
resolve(token || null);
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
// Listener registrieren BEVOR registerVoipToken — sonst race.
|
// Listener registrieren BEVOR registerVoipToken — sonst race.
|
||||||
RNVoipPushNotification.addEventListener('register', onToken);
|
RNVoipPushNotification.addEventListener('register', onToken);
|
||||||
RNVoipPushNotification.registerVoipToken();
|
RNVoipPushNotification.registerVoipToken();
|
||||||
|
if (__DEV__) console.log('[voip] registerVoipToken() called, waiting for callback…');
|
||||||
// Safety-Timeout: nach 4s aufgeben (Cert/Provisioning fehlt etc.).
|
// Safety-Timeout: nach 4s aufgeben (Cert/Provisioning fehlt etc.).
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (resolved) return;
|
if (resolved) return;
|
||||||
resolved = true;
|
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);
|
resolve(null);
|
||||||
}, 4000);
|
}, 4000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (__DEV__) console.warn('[voip] register failed:', err);
|
console.warn('[voip] register failed:', err);
|
||||||
resolve(null);
|
resolve(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -141,7 +151,11 @@ export async function registerPushTokenWithBackend(): Promise<string | null> {
|
|||||||
lastRegisteredVoipToken.current = voipToken;
|
lastRegisteredVoipToken.current = voipToken;
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
console.log('[push] token registered:', token.slice(0, 30) + '…');
|
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;
|
return token;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { NativeModules } from 'react-native';
|
import { AppState, NativeModules } from 'react-native';
|
||||||
import type { RealtimeChannel } from '@supabase/supabase-js';
|
import type { RealtimeChannel } from '@supabase/supabase-js';
|
||||||
import { supabase } from '../lib/supabase';
|
import { supabase } from '../lib/supabase';
|
||||||
import { apiFetch } from '../lib/api';
|
import { apiFetch } from '../lib/api';
|
||||||
@ -382,11 +382,16 @@ export const useCallStore = create<CallState>((set, get) => {
|
|||||||
currentRole = 'callee';
|
currentRole = 'callee';
|
||||||
loggedCallId = null;
|
loggedCallId = null;
|
||||||
set({ status: 'incoming', peer: from, callId, muted: false, speaker: false, startedAt: null, endReason: null });
|
set({ status: 'incoming', peer: from, callId, muted: false, speaker: false, startedAt: null, endReason: null });
|
||||||
// CallKit-/ConnectionService-UI hochziehen — das zeigt nativen Call-Screen
|
// CallKit-/ConnectionService-UI hochziehen — ABER nur wenn App NICHT im
|
||||||
// über Lockscreen, sogar wenn die App im Background ist. RNCallKeep
|
// Vordergrund. Im Foreground kümmert sich der In-App /call-Screen darum,
|
||||||
// dedupliziert intern via UUID, also safe wenn AppDelegate's
|
// sonst gibt es Doppel-UI (System-Banner + Fullscreen). VoIP-Push-Pfad
|
||||||
// reportNewIncomingCall (VoIP-Push-Pfad) schon dieselbe UUID gemeldet hat.
|
// (AppDelegate.reportNewIncomingCall) läuft eh getrennt, das deduliziert
|
||||||
try { callkit.displayIncomingCall(callId, from.nickname || 'ReBreak'); } catch {}
|
// 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
|
// CallKit (iOS) + ConnectionService (Android) spielen ihren eigenen
|
||||||
// Ringtone — KEIN InCallManager.startRingtone() hier, sonst doppeltes
|
// Ringtone — KEIN InCallManager.startRingtone() hier, sonst doppeltes
|
||||||
// Klingeln. InCallManager.start() bleibt aus demselben Grund weg; der
|
// Klingeln. InCallManager.start() bleibt aus demselben Grund weg; der
|
||||||
|
|||||||
@ -28,47 +28,56 @@ type ApnProvider = InstanceType<ApnModule["Provider"]>;
|
|||||||
type ApnNotification = InstanceType<ApnModule["Notification"]>;
|
type ApnNotification = InstanceType<ApnModule["Notification"]>;
|
||||||
|
|
||||||
let apnMod: ApnModule | null = null;
|
let apnMod: ApnModule | null = null;
|
||||||
let provider: ApnProvider | null = null;
|
let providerProd: ApnProvider | null = null;
|
||||||
|
let providerSandbox: ApnProvider | null = null;
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
let topic: string | null = null;
|
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> {
|
async function ensureInit(): Promise<boolean> {
|
||||||
if (initialized) return provider;
|
if (initialized) return providerProd !== null || providerSandbox !== null;
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
const p12Path = process.env.APNS_VOIP_P12_PATH;
|
const p12Path = process.env.APNS_VOIP_P12_PATH;
|
||||||
const p12Pass = process.env.APNS_VOIP_P12_PASSWORD;
|
const p12Pass = process.env.APNS_VOIP_P12_PASSWORD;
|
||||||
const tpc = process.env.APNS_VOIP_TOPIC;
|
const tpc = process.env.APNS_VOIP_TOPIC;
|
||||||
const production = process.env.APNS_VOIP_PRODUCTION === "true";
|
|
||||||
|
|
||||||
if (!p12Path || !p12Pass || !tpc) {
|
if (!p12Path || !p12Pass || !tpc) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[voip-push] disabled — missing env (APNS_VOIP_P12_PATH/PASSWORD/TOPIC)",
|
"[voip-push] disabled — missing env (APNS_VOIP_P12_PATH/PASSWORD/TOPIC)",
|
||||||
);
|
);
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
if (!fs.existsSync(p12Path)) {
|
if (!fs.existsSync(p12Path)) {
|
||||||
console.warn(`[voip-push] disabled — p12 file not found at ${p12Path}`);
|
console.warn(`[voip-push] disabled — p12 file not found at ${p12Path}`);
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Dynamic import — vermeidet bundler statisches Tracing.
|
|
||||||
const mod = (await import("@parse/node-apn")) as unknown as ApnModule & {
|
const mod = (await import("@parse/node-apn")) as unknown as ApnModule & {
|
||||||
default?: ApnModule;
|
default?: ApnModule;
|
||||||
};
|
};
|
||||||
apnMod = mod.default ?? mod;
|
apnMod = mod.default ?? mod;
|
||||||
topic = tpc;
|
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,
|
pfx: p12Path,
|
||||||
passphrase: p12Pass,
|
passphrase: p12Pass,
|
||||||
production,
|
production: true,
|
||||||
});
|
});
|
||||||
console.log(`[voip-push] initialized (topic=${tpc}, production=${production})`);
|
providerSandbox = new apnMod.Provider({
|
||||||
return provider;
|
pfx: p12Path,
|
||||||
|
passphrase: p12Pass,
|
||||||
|
production: false,
|
||||||
|
});
|
||||||
|
console.log(`[voip-push] initialized (topic=${tpc}, prod+sandbox providers ready)`);
|
||||||
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[voip-push] init failed:", 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.
|
* @returns true bei Erfolg (oder no-op wenn Service disabled), false bei Fehler.
|
||||||
*/
|
*/
|
||||||
export async function sendVoIPPush(payload: VoIPCallPayload): Promise<boolean> {
|
export async function sendVoIPPush(payload: VoIPCallPayload): Promise<boolean> {
|
||||||
const p = await getProvider();
|
const ok = await ensureInit();
|
||||||
if (!p || !topic || !apnMod) return true; // no-op, regulärer Push übernimmt
|
if (!ok || !topic || !apnMod) return true; // no-op, regulärer Push übernimmt
|
||||||
|
|
||||||
const note: ApnNotification = new apnMod.Notification();
|
const note: ApnNotification = new apnMod.Notification();
|
||||||
note.topic = topic;
|
note.topic = topic;
|
||||||
@ -116,28 +125,56 @@ export async function sendVoIPPush(payload: VoIPCallPayload): Promise<boolean> {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
// Reihenfolge: zuerst memoized Env (falls bekannt), sonst Production first →
|
||||||
const result = await p.send(note, payload.voipToken);
|
// Sandbox als Fallback.
|
||||||
if (result.failed.length > 0) {
|
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 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(
|
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,
|
f.response ?? f.error,
|
||||||
);
|
);
|
||||||
return false;
|
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. */
|
/** Cleanup bei Server-Shutdown — wichtig wegen HTTP/2-Verbindung an Apple. */
|
||||||
export function shutdownVoIPProvider(): void {
|
export function shutdownVoIPProvider(): void {
|
||||||
if (provider) {
|
if (providerProd) {
|
||||||
provider.shutdown();
|
providerProd.shutdown();
|
||||||
provider = null;
|
providerProd = null;
|
||||||
initialized = false;
|
|
||||||
}
|
}
|
||||||
|
if (providerSandbox) {
|
||||||
|
providerSandbox.shutdown();
|
||||||
|
providerSandbox = null;
|
||||||
|
}
|
||||||
|
initialized = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,10 @@
|
|||||||
"metro-symbolicate": "0.83.3",
|
"metro-symbolicate": "0.83.3",
|
||||||
"metro-transform-plugins": "0.83.3",
|
"metro-transform-plugins": "0.83.3",
|
||||||
"metro-transform-worker": "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:
|
metro-core@0.83.3:
|
||||||
hash: dbd76dee4e5497574765c5986b0e889264e7251ea7b5e849e2967d2eb2efb757
|
hash: dbd76dee4e5497574765c5986b0e889264e7251ea7b5e849e2967d2eb2efb757
|
||||||
path: patches/metro-core@0.83.3.patch
|
path: patches/metro-core@0.83.3.patch
|
||||||
|
react-native-callkeep:
|
||||||
|
hash: cba8c2dd49745c7a4f62437ea38db7e1d457966cd037dc405c0dca06d84850dd
|
||||||
|
path: patches/react-native-callkeep.patch
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
@ -42,7 +45,7 @@ importers:
|
|||||||
version: 14.3.0(vue@3.5.34(typescript@5.9.3))
|
version: 14.3.0(vue@3.5.34(typescript@5.9.3))
|
||||||
'@vueuse/nuxt':
|
'@vueuse/nuxt':
|
||||||
specifier: ^14.2.1
|
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:
|
nuxt:
|
||||||
specifier: 4.1.3
|
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)
|
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
|
version: 1.2.3
|
||||||
'@nuxt/fonts':
|
'@nuxt/fonts':
|
||||||
specifier: ^0.11.4
|
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':
|
'@nuxt/icon':
|
||||||
specifier: ^1.10.0
|
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))
|
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))
|
version: 3.0.3(magicast@0.5.3)(vue@3.5.34(typescript@5.9.3))
|
||||||
'@vueuse/nuxt':
|
'@vueuse/nuxt':
|
||||||
specifier: ^14.2.1
|
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:
|
chart.js:
|
||||||
specifier: ^4.5.1
|
specifier: ^4.5.1
|
||||||
version: 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)
|
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:
|
react-native-callkeep:
|
||||||
specifier: ^4.3.16
|
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:
|
react-native-gesture-handler:
|
||||||
specifier: ~2.28.0
|
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)
|
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
|
- utf-8-validate
|
||||||
- vue
|
- 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:
|
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/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)
|
'@nuxt/kit': 3.21.4(magicast@0.5.3)
|
||||||
@ -14329,7 +14332,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- magicast
|
- 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:
|
dependencies:
|
||||||
'@nuxt/kit': 4.4.4(magicast@0.5.3)
|
'@nuxt/kit': 4.4.4(magicast@0.5.3)
|
||||||
'@vueuse/core': 14.3.0(vue@3.5.34(typescript@5.9.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
|
sf-symbols-typescript: 2.2.0
|
||||||
use-latest-callback: 0.2.6(react@19.1.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:
|
dependencies:
|
||||||
react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0)
|
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