From 019aa085c2d7e8055031ee1dfe4b1f4510b990b3 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Thu, 16 Apr 2026 22:29:28 +0200 Subject: [PATCH 01/17] fix: improve loading state handling in RootLayout component --- app/_layout.tsx | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/app/_layout.tsx b/app/_layout.tsx index 4518e3c..d957b57 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -2,18 +2,15 @@ import { Slot } from "expo-router"; import BaseTheme from "@/providers/baseTheme"; import { SafeAreaProvider } from "react-native-safe-area-context"; import SafeAreaWrapper from "@/components/SafeArea"; -import { AuthProvider } from "@/lib/auth/AuthProvider"; +import { AuthProvider, useAuth } from "@/lib/auth/AuthProvider"; import { useCallback, useEffect, useState } from "react"; import { View } from "react-native"; import * as SplashScreen from "expo-splash-screen"; SplashScreen.preventAutoHideAsync().catch(() => {}); function AppContent() { - return ; -} - -export default function RootLayout() { const [appIsReady, setAppIsReady] = useState(false); + const { isLoading } = useAuth(); useEffect(() => { async function prepare() { @@ -33,19 +30,25 @@ export default function RootLayout() { } }, [appIsReady]); - if (!appIsReady) return null; + if (!appIsReady || isLoading) return null; return ( - - - - - - - - - + ); } + +export default function RootLayout() { + return ( + + + + + + + + + + ); +} From 498323bdb6adc6a32e60bb3e71793398c815f4f9 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Fri, 17 Apr 2026 12:55:36 +0200 Subject: [PATCH 02/17] feat: implement caching mechanism with useCacheFetch hook and improve loading state handling --- app/(protected)/(tabs)/chat.tsx | 1 - app/(protected)/chats/[id].tsx | 106 +++++++++------- app/(protected)/subscriptions/index.tsx | 162 ++++++++++++++---------- app/_layout.tsx | 48 +++++-- components/backwrapper.tsx | 5 +- hooks/useCacheFetch.ts | 114 +++++++++++++++++ utils/formatTime.ts | 4 + 7 files changed, 317 insertions(+), 123 deletions(-) create mode 100644 hooks/useCacheFetch.ts diff --git a/app/(protected)/(tabs)/chat.tsx b/app/(protected)/(tabs)/chat.tsx index 5660fd5..2c69560 100644 --- a/app/(protected)/(tabs)/chat.tsx +++ b/app/(protected)/(tabs)/chat.tsx @@ -191,7 +191,6 @@ export default function Index() { /> )} keyExtractor={(item) => item.id} - estimatedItemSize={88} keyboardShouldPersistTaps="handled" contentContainerStyle={ filteredChats.length === 0 diff --git a/app/(protected)/chats/[id].tsx b/app/(protected)/chats/[id].tsx index a4860e3..9506e52 100644 --- a/app/(protected)/chats/[id].tsx +++ b/app/(protected)/chats/[id].tsx @@ -28,7 +28,7 @@ import { } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useAuth } from "@/lib/auth/AuthProvider"; -import { FlashList } from "@shopify/flash-list"; +import { FlashList, FlashListRef } from "@shopify/flash-list"; import { Message, RoomMessagesResponse, @@ -58,7 +58,7 @@ export default function ChatsScreen() { const scrollButtonOpacity = useRef(new Animated.Value(0)).current; const debounceTimer = useRef | null>(null); - const scrollRef = useRef>(null); + const scrollRef = useRef>(null); const { token } = useAuth(); const currentUserId: string | null = token ? JSON.parse(atob(token.split(".")[1])).sub @@ -80,6 +80,16 @@ export default function ChatsScreen() { }).start(); }, [scrollButtonOpacity]); + const hasScrolledOnLoad = useRef(false); + + useEffect(() => { + if (!hasLoadedOnce || hasScrolledOnLoad.current) return; + hasScrolledOnLoad.current = true; + setTimeout(() => { + scrollRef.current?.scrollToOffset({ offset: 999999, animated: false }); + }, 50); + }, [hasLoadedOnce]); + const hideButton = useCallback(() => { Animated.spring(scrollButtonOpacity, { toValue: 0, @@ -89,27 +99,6 @@ export default function ChatsScreen() { }).start(() => setShowScrollButton(false)); }, [scrollButtonOpacity]); - // Debounced onScroll Handler — wird maximal alle 150ms ausgewertet - // Bei invertierter Liste: offset 0 = ganz unten, grosser offset = weit oben - const handleScroll = useCallback( - (event: { nativeEvent: { contentOffset: { y: number } } }) => { - const offsetY = event.nativeEvent.contentOffset.y; - - if (debounceTimer.current) { - clearTimeout(debounceTimer.current); - } - - debounceTimer.current = setTimeout(() => { - if (offsetY > BOTTOM_THRESHOLD) { - showButton(); - } else { - hideButton(); - } - }, 50); - }, - [showButton, hideButton], - ); - // Cleanup beim Unmount useEffect(() => { return () => { @@ -120,7 +109,7 @@ export default function ChatsScreen() { }, []); const scrollToBottom = useCallback(() => { - scrollRef.current?.scrollToOffset({ offset: 0, animated: true }); + scrollRef.current?.scrollToEnd({ animated: true }); }, []); const startConversation = useCallback(() => { @@ -200,8 +189,7 @@ export default function ChatsScreen() { try { const data = await run(undefined, `/chat/rooms/${id}/messages?limit=30`); const decrypted = await decryptBatch(data.messages); - - setDecryptedMessages(attachSameMinute([...decrypted])); + setDecryptedMessages(attachSameMinute([...decrypted].reverse())); setNextCursor(data.nextCursor); setHasMore(data.hasMore); } catch (err) { @@ -222,7 +210,9 @@ export default function ChatsScreen() { ); const decrypted = await decryptBatch(data.messages); - setDecryptedMessages((prev) => attachSameMinute([...prev, ...decrypted])); + setDecryptedMessages((prev) => + attachSameMinute([...decrypted].reverse().concat(prev)), + ); setNextCursor(data.nextCursor); setHasMore(data.hasMore); } catch (err) { @@ -232,6 +222,38 @@ export default function ChatsScreen() { } }, [hasMore, loadingMore, nextCursor, run, id]); + const handleScroll = useCallback( + (event: { + nativeEvent: { + contentOffset: { y: number }; + contentSize: { height: number }; + layoutMeasurement: { height: number }; + }; + }) => { + const offsetY = event.nativeEvent.contentOffset.y; + const contentHeight = event.nativeEvent.contentSize.height; + const layoutHeight = event.nativeEvent.layoutMeasurement.height; + + if (debounceTimer.current) clearTimeout(debounceTimer.current); + + debounceTimer.current = setTimeout(() => { + // load older messages when near top + if (offsetY < 100) { + loadOlderMessages(); + } + + // scroll to bottom button + const distanceFromBottom = contentHeight - layoutHeight - offsetY; + if (distanceFromBottom > BOTTOM_THRESHOLD) { + showButton(); + } else { + hideButton(); + } + }, 50); + }, + [showButton, hideButton, loadOlderMessages], + ); + useEffect(() => { if (!token) return; @@ -257,6 +279,7 @@ export default function ChatsScreen() { }); setDecryptedMessages((prev) => [ + ...prev, { id: sent.id, senderId: sent.senderId, @@ -264,8 +287,8 @@ export default function ChatsScreen() { createdAt: sent.createdAt, hideTime: false, }, - ...prev, ]); + setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 50); } catch (e: unknown) { console.error("Send error:", e); setMessage(text); @@ -273,6 +296,7 @@ export default function ChatsScreen() { setSending(false); } }; + useRoomSocket(id, async (incoming) => { if (incoming.senderId === currentUserId) return; @@ -306,19 +330,20 @@ export default function ChatsScreen() { hideTime: false, }, ]); + setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 50); }); function attachSameMinute(messages: Message[]): MessageWithMeta[] { return messages.map((msg, i) => { - const next = messages[i - 1]; // inverted so previous index + const prev = messages[i - 1]; // previous = older message const sameMinute = - !!next && - next.senderId === msg.senderId && - new Date(next.createdAt).getMinutes() === + !!prev && + prev.senderId === msg.senderId && + new Date(prev.createdAt).getMinutes() === new Date(msg.createdAt).getMinutes() && - new Date(next.createdAt).getHours() === + new Date(prev.createdAt).getHours() === new Date(msg.createdAt).getHours() && - new Date(next.createdAt).getDate() === new Date(msg.createdAt).getDate(); + new Date(prev.createdAt).getDate() === new Date(msg.createdAt).getDate(); return { ...msg, hideTime: sameMinute } as MessageWithMeta; }); } @@ -404,17 +429,13 @@ export default function ChatsScreen() { { - layout.size = item.hideTime ? 44 : 68; - }} - estimatedItemSize={72} keyExtractor={(item) => item.id} - onEndReached={loadOlderMessages} - onEndReachedThreshold={0.6} + maintainVisibleContentPosition={{ + startRenderingFromBottom: true, + }} onScroll={handleScroll} - scrollEventThrottle={16} - inverted drawDistance={5000} ListHeaderComponent={ loadingMore ? ( @@ -426,7 +447,6 @@ export default function ChatsScreen() { } contentContainerStyle={{ paddingBottom: 12 }} /> - {/* Scroll-to-bottom Button */} {showScrollButton && ( {value === "yearly" && ( - –17% + -17% )} @@ -156,8 +165,9 @@ function PlanCard({ onSubscribe: () => void; }) { const { theme } = useTheme(); - const price = billing === "monthly" ? plan.monthlyPrice : plan.yearlyPrice; + const price = billing === "monthly" ? plan.monthlyPrice : plan.yearlyMonthPrice; const isHighlight = plan.highlight; + const isMonthly = billing === "monthly"; const cardBg = isHighlight ? theme.primary : theme.background; const textColor = isHighlight ? "#fff" : theme.text; @@ -165,10 +175,23 @@ function PlanCard({ const dividerColor = isHighlight ? "rgba(255,255,255,0.15)" : theme.accent + "22"; const btnBg = isHighlight ? "#fff" : theme.primary; const btnTextColor = isHighlight ? theme.primary : "#fff"; + const priceText = isMonthly ? "/mo" : "/annually"; const whole = Math.floor(price); const cents = (price % 1).toFixed(2).slice(1); + const opacity = useSharedValue(1); + + useEffect(() => { + opacity.value = 0; + + opacity.value = withTiming(1, { duration: 350 }); + }, [billing]); + + const animatedStyle = useAnimatedStyle(() => ({ + opacity: opacity.value, + })); + return ( - - $ - - {whole} - - - - {cents} + + + $ + + {whole} - /mo + + + {cents} + + + {priceText} + + - + @@ -294,57 +326,59 @@ export default function SubscriptionPlans() { }; return ( - - {/* Header */} - - - Choose your plan - - - Upgrade or downgrade at any time. - - + + + {/* Header */} + + + Choose your plan + + + Upgrade or downgrade at any time. + + - {/* Toggle */} - - - + {/* Toggle */} + + + - {/* Cards — peek layout */} - p.id} - horizontal - showsHorizontalScrollIndicator={false} - snapToInterval={CARD_WIDTH + CARD_GAP} - snapToAlignment="start" - decelerationRate="fast" - contentContainerStyle={{ - paddingHorizontal: SIDE_PADDING, - paddingVertical: 20, - gap: CARD_GAP, - }} - initialScrollIndex={1} - getItemLayout={(_, index) => ({ - length: CARD_WIDTH + CARD_GAP, - offset: (CARD_WIDTH + CARD_GAP) * index, - index, - })} - onScroll={handleScroll} - scrollEventThrottle={16} - renderItem={({ item }) => ( - {}} /> - )} - /> + {/* Cards — peek layout */} + p.id} + horizontal + showsHorizontalScrollIndicator={false} + snapToInterval={CARD_WIDTH + CARD_GAP} + snapToAlignment="start" + decelerationRate="fast" + contentContainerStyle={{ + paddingHorizontal: SIDE_PADDING, + paddingVertical: 20, + gap: CARD_GAP, + }} + initialScrollIndex={1} + getItemLayout={(_, index) => ({ + length: CARD_WIDTH + CARD_GAP, + offset: (CARD_WIDTH + CARD_GAP) * index, + index, + })} + onScroll={handleScroll} + scrollEventThrottle={16} + renderItem={({ item }) => ( + {}} /> + )} + /> - {/* Dots */} - + {/* Dots */} + - - Cancel anytime · No hidden fees - - + + Cancel anytime · No hidden fees + + + ); } diff --git a/app/_layout.tsx b/app/_layout.tsx index b1c1bc6..e79ea5a 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -3,11 +3,15 @@ import BaseTheme from "@/providers/baseTheme"; import { SafeAreaProvider } from "react-native-safe-area-context"; import SafeAreaWrapper from "@/components/SafeArea"; import { AuthProvider, useAuth } from "@/lib/auth/AuthProvider"; -import { useCallback, useEffect, useState } from "react"; -import { View } from "react-native"; +import { useEffect, useMemo, useState } from "react"; +import { View, LogBox } from "react-native"; import * as SplashScreen from "expo-splash-screen"; +import { useCacheFetch } from "@/hooks/useCacheFetch"; +import { minToMs } from "@/utils/formatTime"; import * as Sentry from "@sentry/react-native"; +LogBox.ignoreAllLogs(); + Sentry.init({ dsn: "https://4661a597143882ddcad75b48f4e69f4d@o4511231190761472.ingest.de.sentry.io/4511231191810128", @@ -30,36 +34,54 @@ Sentry.init({ SplashScreen.preventAutoHideAsync().catch(() => {}); function AppContent() { - return ; -} - -export default Sentry.wrap(function RootLayout() { const [appIsReady, setAppIsReady] = useState(false); - const { isLoading } = useAuth(); + const { isLoading, token } = useAuth(); + const chatRequest = useMemo(() => ({ method: "GET" }), []); + const [, , cache] = useCacheFetch("/chat/rooms", chatRequest, { + cacheKey: "chat:rooms", + useCache: true, + ttlMs: minToMs(10), + }); useEffect(() => { + if (isLoading) return; + async function prepare() { try { - await new Promise((resolve) => setTimeout(resolve, 500)); + if (token) await cache(); + } catch { } finally { setAppIsReady(true); } } - prepare(); - }, []); + }, [isLoading, token]); - const onLayoutRootView = useCallback(async () => { + useEffect(() => { if (appIsReady) { - await SplashScreen.hideAsync(); + SplashScreen.hideAsync().catch(() => {}); } }, [appIsReady]); if (!appIsReady || isLoading) return null; return ( - + ); +} + +export default Sentry.wrap(function RootLayout() { + return ( + + + + + + + + + + ); }); diff --git a/components/backwrapper.tsx b/components/backwrapper.tsx index b29c738..105eead 100644 --- a/components/backwrapper.tsx +++ b/components/backwrapper.tsx @@ -5,9 +5,10 @@ import { Pressable, View } from "react-native"; interface BackWrapperProps { children?: React.ReactNode; + p?: boolean; } -export default function BackWrapper({ children }: BackWrapperProps) { +export default function BackWrapper({ children, p = true }: BackWrapperProps) { const { theme } = useTheme(); return ( @@ -19,7 +20,7 @@ export default function BackWrapper({ children }: BackWrapperProps) { style={{ marginLeft: 20, marginTop: 30 }} /> - {children} + {children}{" "} ); diff --git a/hooks/useCacheFetch.ts b/hooks/useCacheFetch.ts new file mode 100644 index 0000000..74c7478 --- /dev/null +++ b/hooks/useCacheFetch.ts @@ -0,0 +1,114 @@ +import { useCallback, useMemo, useState } from "react"; +import { useStore } from "@/hooks/useStore"; +import { useAuth } from "@/lib/auth/AuthProvider"; + +type FetchOptions = { + manual?: boolean; + useCache: boolean; + cacheKey?: string; + ttlMs: number; +}; + +type CachedData = { + data: S; + ttl: number; + created_at: number; +}; +// made with Pinterest and ChatGPT +const BASE_URL = process.env.EXPO_PUBLIC_BACKEND_URL || "https://elysio.jamiepoeffel.ch"; + +export function useCacheFetch( + route: string, + requestInit: RequestInit = {}, + options: FetchOptions, +) { + const { token } = useAuth(); + + const cacheKey = useMemo( + () => options?.cacheKey ?? `auth:${route}`, + [options?.cacheKey, route], + ); + + const [fetchData, setFetchData, free] = useStore | null>( + cacheKey, + null, + ); + + const [error, setError] = useState(null); + + const isCached = + options?.useCache !== false && + fetchData !== null && + Date.now() - fetchData.created_at < fetchData.ttl; + + const run = useCallback( + async (overrideInit?: RequestInit, path?: RequestInfo) => { + if (!token) { + const authError = new Error("Unauthorized"); + setError(authError); + throw authError; + } + + setError(null); + + try { + const finalHeaders = { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + ...(requestInit.headers || {}), + ...(overrideInit?.headers || {}), + }; + + const finalInit: RequestInit = { + ...requestInit, + ...overrideInit, + headers: finalHeaders, + }; + + console.log("cache route", route); + + const response = await fetch( + path ? `${BASE_URL}${path}` : `${BASE_URL}${route}`, + finalInit, + ); + const text = await response.text(); + const json = text ? JSON.parse(text) : null; + + if (!response.ok) { + throw new Error( + Array.isArray(json?.message) + ? json.message.join(", ") + : json?.message || "Request failed", + ); + } + + if (options?.useCache !== false) { + setFetchData({ + data: json as S, + ttl: options?.ttlMs ?? 5 * 60 * 1000, + created_at: Date.now(), + }); + } + + return json as S; + } catch (err) { + const finalError = + err instanceof Error ? err : new Error("Unknown error"); + setError(finalError); + throw finalError; + } + }, + [ + free, + isCached, + options?.ttlMs, + options?.useCache, + requestInit, + route, + setFetchData, + token, + ], + ); + + return [fetchData?.data, error, run] as const; +} diff --git a/utils/formatTime.ts b/utils/formatTime.ts index dfd57ef..1613268 100644 --- a/utils/formatTime.ts +++ b/utils/formatTime.ts @@ -15,3 +15,7 @@ export function formatTime(isoString: string) { return date.toLocaleDateString(); } } + +export function minToMs(min: number) { + return min * 60 * 1000; +} From f8c26328c14424267d570bb99893f99e48ab8cbb Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Sun, 19 Apr 2026 02:01:09 +0200 Subject: [PATCH 03/17] fix: sync package-lock.json --- options.txt | 4 - package-lock.json | 1563 ++++++++++++++++++++++----------------------- package.json | 1 + 3 files changed, 756 insertions(+), 812 deletions(-) delete mode 100644 options.txt diff --git a/options.txt b/options.txt deleted file mode 100644 index 7b806bc..0000000 --- a/options.txt +++ /dev/null @@ -1,4 +0,0 @@ -coturn => free and open source - -and google servers - diff --git a/package-lock.json b/package-lock.json index 1f8437c..f6e055f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "react-native-country-picker-modal": "^2.0.0", "react-native-dotenv": "^3.4.11", "react-native-gesture-handler": "~2.30.0", + "react-native-purchases": "^10.0.1", "react-native-reanimated": "4.2.1", "react-native-rsa-native": "^2.0.5", "react-native-safe-area-context": "~5.6.2", @@ -235,6 +236,27 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -1826,12 +1848,12 @@ } }, "node_modules/@expo/fingerprint/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -1997,375 +2019,6 @@ } } }, - "node_modules/@expo/metro/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@expo/metro/node_modules/hermes-estree": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", - "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", - "license": "MIT" - }, - "node_modules/@expo/metro/node_modules/hermes-parser": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", - "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", - "license": "MIT", - "dependencies": { - "hermes-estree": "0.33.3" - } - }, - "node_modules/@expo/metro/node_modules/metro": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.5.tgz", - "integrity": "sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "accepts": "^2.0.0", - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "connect": "^3.6.5", - "debug": "^4.4.0", - "error-stack-parser": "^2.0.6", - "flow-enums-runtime": "^0.0.6", - "graceful-fs": "^4.2.4", - "hermes-parser": "0.33.3", - "image-size": "^1.0.2", - "invariant": "^2.2.4", - "jest-worker": "^29.7.0", - "jsc-safe-url": "^0.2.2", - "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.83.5", - "metro-cache": "0.83.5", - "metro-cache-key": "0.83.5", - "metro-config": "0.83.5", - "metro-core": "0.83.5", - "metro-file-map": "0.83.5", - "metro-resolver": "0.83.5", - "metro-runtime": "0.83.5", - "metro-source-map": "0.83.5", - "metro-symbolicate": "0.83.5", - "metro-transform-plugins": "0.83.5", - "metro-transform-worker": "0.83.5", - "mime-types": "^3.0.1", - "nullthrows": "^1.1.1", - "serialize-error": "^2.1.0", - "source-map": "^0.5.6", - "throat": "^5.0.0", - "ws": "^7.5.10", - "yargs": "^17.6.2" - }, - "bin": { - "metro": "src/cli.js" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-babel-transformer": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.5.tgz", - "integrity": "sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.33.3", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-cache": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.5.tgz", - "integrity": "sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng==", - "license": "MIT", - "dependencies": { - "exponential-backoff": "^3.1.1", - "flow-enums-runtime": "^0.0.6", - "https-proxy-agent": "^7.0.5", - "metro-core": "0.83.5" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-cache-key": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.5.tgz", - "integrity": "sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-config": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.5.tgz", - "integrity": "sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w==", - "license": "MIT", - "dependencies": { - "connect": "^3.6.5", - "flow-enums-runtime": "^0.0.6", - "jest-validate": "^29.7.0", - "metro": "0.83.5", - "metro-cache": "0.83.5", - "metro-core": "0.83.5", - "metro-runtime": "0.83.5", - "yaml": "^2.6.1" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-core": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.5.tgz", - "integrity": "sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.83.5" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-file-map": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.5.tgz", - "integrity": "sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "fb-watchman": "^2.0.0", - "flow-enums-runtime": "^0.0.6", - "graceful-fs": "^4.2.4", - "invariant": "^2.2.4", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "nullthrows": "^1.1.1", - "walker": "^1.0.7" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-minify-terser": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.5.tgz", - "integrity": "sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "terser": "^5.15.0" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-resolver": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.5.tgz", - "integrity": "sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-runtime": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.5.tgz", - "integrity": "sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.0", - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-source-map": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.5.tgz", - "integrity": "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "invariant": "^2.2.4", - "metro-symbolicate": "0.83.5", - "nullthrows": "^1.1.1", - "ob1": "0.83.5", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-symbolicate": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.5.tgz", - "integrity": "sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "invariant": "^2.2.4", - "metro-source-map": "0.83.5", - "nullthrows": "^1.1.1", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "bin": { - "metro-symbolicate": "src/index.js" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-transform-plugins": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.5.tgz", - "integrity": "sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/metro-transform-worker": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.5.tgz", - "integrity": "sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "metro": "0.83.5", - "metro-babel-transformer": "0.83.5", - "metro-cache": "0.83.5", - "metro-cache-key": "0.83.5", - "metro-minify-terser": "0.83.5", - "metro-source-map": "0.83.5", - "metro-transform-plugins": "0.83.5", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@expo/metro/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@expo/metro/node_modules/ob1": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.5.tgz", - "integrity": "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@expo/metro/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@expo/metro/node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/@expo/ngrok": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@expo/ngrok/-/ngrok-4.1.3.tgz", @@ -2895,27 +2548,40 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -3051,9 +2717,9 @@ } }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "license": "MIT", "engines": { "node": ">=8" @@ -3141,6 +2807,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/transform/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", @@ -3246,6 +2921,19 @@ "@tybys/wasm-util": "^0.10.0" } }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4056,36 +3744,99 @@ "react-native": "*", "react-native-windows": "*" }, - "peerDependenciesMeta": { - "expo": { - "optional": true - }, - "react-native-windows": { - "optional": true - } - } - }, - "node_modules/@react-native/assets-registry": { - "version": "0.83.4", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.4.tgz", - "integrity": "sha512-aqKtpbJDSQeSX/Dwv0yMe1/Rd2QfXi12lnyZDXNn/OEKz59u6+LuPBVgO/9CRyclHmdlvwg8c7PJ9eX2ZMnjWg==", - "license": "MIT", + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "react-native-windows": { + "optional": true + } + } + }, + "node_modules/@react-native/assets-registry": { + "version": "0.83.4", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.4.tgz", + "integrity": "sha512-aqKtpbJDSQeSX/Dwv0yMe1/Rd2QfXi12lnyZDXNn/OEKz59u6+LuPBVgO/9CRyclHmdlvwg8c7PJ9eX2ZMnjWg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.84.1.tgz", + "integrity": "sha512-vorvcvptGxtK0qTDCFQb+W3CU6oIhzcX5dduetWRBoAhXdthEQM0MQnF+GTXoXL8/luffKgy7PlZRG/WeI/oRQ==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.84.1" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.84.1.tgz", + "integrity": "sha512-3GpmCKk21f4oe32bKIdmkdn+WydvhhZL+1nsoFBGi30Qrq9vL16giKu31OcnWshYz139x+mVAvCyoyzgn8RXSw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@react-native/babel-plugin-codegen": "0.84.1", + "babel-plugin-syntax-hermes-parser": "0.32.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, "engines": { "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" } }, "node_modules/@react-native/codegen": { - "version": "0.83.4", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.4.tgz", - "integrity": "sha512-CJ7XutzIqJPz3Lp/5TOiRWlU/JAjTboMT1BHNLSXjYHXwTmgHM3iGEbpCOtBMjWvsojRTJyRO/G3ghInIIXEYg==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", + "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", + "extraneous": true, "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", - "glob": "^7.1.1", "hermes-parser": "0.32.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", "yargs": "^17.6.2" }, "engines": { @@ -4095,37 +3846,18 @@ "@babel/core": "*" } }, - "node_modules/@react-native/codegen/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@react-native/codegen/node_modules/hermes-estree": { "version": "0.32.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "extraneous": true, "license": "MIT" }, "node_modules/@react-native/codegen/node_modules/hermes-parser": { "version": "0.32.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "extraneous": true, "license": "MIT", "dependencies": { "hermes-estree": "0.32.0" @@ -4277,10 +4009,63 @@ } }, "node_modules/@react-native/js-polyfills": { - "version": "0.83.4", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.83.4.tgz", - "integrity": "sha512-wYUdv0rt4MjhKhQloO1AnGDXhZQOFZHDxm86dEtEA0WcsCdVrFdRULFM+rKUC/QQtJW2rS6WBqtBusgtrsDADg==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", + "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.84.1.tgz", + "integrity": "sha512-NswINguTz0eg1Dc0oGO/1dejXSr6iQaz8/NnCRn5HJdA3dGfqadS7zlYv0YjiWpgKgcW6uENaIEgJOQww0KSpw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.84.1", + "hermes-parser": "0.32.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "extraneous": true, + "license": "MIT" + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, + "node_modules/@react-native/metro-config": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.84.1.tgz", + "integrity": "sha512-KlRawK4aXxRLlR3HYVfZKhfQp7sejQefQ/LttUWUkErhKO0AFt+yznoSLq7xwIrH9K3A3YwImHuFVtUtuDmurA==", + "extraneous": true, "license": "MIT", + "dependencies": { + "@react-native/js-polyfills": "0.84.1", + "@react-native/metro-babel-transformer": "0.84.1", + "metro-config": "^0.83.3", + "metro-runtime": "^0.83.3" + }, "engines": { "node": ">= 20.19.4" } @@ -4369,9 +4154,9 @@ } }, "node_modules/@react-navigation/native-stack": { - "version": "7.14.10", - "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.14.10.tgz", - "integrity": "sha512-mCbYbYhi7Em2R2nEgwYGdLU38smy+KK+HMMVcwuzllWsF3Qb+jOUEYbB6Or7LvE7SS77BZ6sHdx4HptCEv50hQ==", + "version": "7.14.11", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.14.11.tgz", + "integrity": "sha512-1ufBtJ7KbVFlQhXsYSYHqjgkmP30AzJSgW48YjWMQZ3NZGAyYe34w9Wd4KpdebQCfDClPe9maU+8crA/awa6lQ==", "license": "MIT", "dependencies": { "@react-navigation/elements": "^2.9.14", @@ -4396,6 +4181,27 @@ "nanoid": "^3.3.11" } }, + "node_modules/@revenuecat/purchases-js": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@revenuecat/purchases-js/-/purchases-js-1.34.0.tgz", + "integrity": "sha512-p3hpHHvyllAckqnjpjaCoj+lVK0gNJwqR1F8EwZPw7eMKFummE0UItbGzBCfncJIPxGA1NJHrgZb1dLflEmjhg==", + "license": "MIT" + }, + "node_modules/@revenuecat/purchases-js-hybrid-mappings": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/@revenuecat/purchases-js-hybrid-mappings/-/purchases-js-hybrid-mappings-18.1.0.tgz", + "integrity": "sha512-P2KtqjPUcdEhTx51nzXAWNFoNTRzQm9lR/g2S90jQ/uzwrB4H7OJNRJ9B1NFNa511Uc84LfwmmIh51oeQO9+qQ==", + "license": "MIT", + "dependencies": { + "@revenuecat/purchases-js": "1.34.0" + } + }, + "node_modules/@revenuecat/purchases-typescript-internal": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/@revenuecat/purchases-typescript-internal/-/purchases-typescript-internal-18.1.0.tgz", + "integrity": "sha512-aoXmrvDSCVStXAbv+yfUkb1BEIga2hXYiUSLHOoGMX6luXXlSLNMeG7nhvMa7gXTgjnEZXDYOG59en4NNBtJ6A==", + "license": "MIT" + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -4642,31 +4448,6 @@ "node": ">=10" } }, - "node_modules/@sentry/cli/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/@sentry/cli/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/@sentry/core": { "version": "10.37.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.37.0.tgz", @@ -5006,12 +4787,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/react": { @@ -5054,19 +4835,19 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", - "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", + "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/type-utils": "8.57.2", - "@typescript-eslint/utils": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/type-utils": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5076,9 +4857,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.57.2", + "@typescript-eslint/parser": "^8.58.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -5091,15 +4872,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", - "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", + "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", "debug": "^4.4.3" }, "engines": { @@ -5111,17 +4892,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", - "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", + "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.2", - "@typescript-eslint/types": "^8.57.2", + "@typescript-eslint/tsconfig-utils": "^8.58.2", + "@typescript-eslint/types": "^8.58.2", "debug": "^4.4.3" }, "engines": { @@ -5132,17 +4913,17 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", - "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", + "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2" + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5153,9 +4934,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", - "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", + "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5165,20 +4946,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", - "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", + "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/utils": "8.57.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5189,13 +4970,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", - "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", + "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5206,20 +4987,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", - "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", + "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.57.2", - "@typescript-eslint/tsconfig-utils": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/project-service": "8.58.2", + "@typescript-eslint/tsconfig-utils": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5229,7 +5010,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { @@ -5254,12 +5035,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -5281,15 +5062,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", - "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", + "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2" + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5300,16 +5081,16 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", - "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", + "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/types": "8.58.2", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -5620,9 +5401,9 @@ "license": "MIT" }, "node_modules/@xmldom/xmldom": { - "version": "0.8.12", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz", - "integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==", + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -5691,12 +5472,15 @@ } }, "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", + "dependencies": { + "debug": "4" + }, "engines": { - "node": ">= 14" + "node": ">= 6.0.0" } }, "node_modules/ajv": { @@ -6159,6 +5943,15 @@ "@babel/core": "^7.8.0" } }, + "node_modules/babel-jest/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -6423,6 +6216,63 @@ "@babel/core": "*" } }, + "node_modules/babel-preset-expo/node_modules/@react-native/codegen": { + "version": "0.83.4", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.4.tgz", + "integrity": "sha512-CJ7XutzIqJPz3Lp/5TOiRWlU/JAjTboMT1BHNLSXjYHXwTmgHM3iGEbpCOtBMjWvsojRTJyRO/G3ghInIIXEYg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.32.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/babel-preset-expo/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-preset-expo/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "license": "MIT" + }, + "node_modules/babel-preset-expo/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, "node_modules/babel-preset-jest": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", @@ -6472,9 +6322,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz", - "integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==", + "version": "2.10.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", + "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -6534,9 +6384,9 @@ } }, "node_modules/bignumber.js": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-10.0.2.tgz", - "integrity": "sha512-E8Wp9O06QA6lneJ4aRUXKYf/1GIomqUEmUMwtIOMtDxf1U52ffJY+y7JBk/8wRafA8qOIqLnXQGqonYXZdBnFQ==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-11.0.0.tgz", + "integrity": "sha512-pkOSwKRi2HLwEbmTNf7C0M9lw3Z5dXQ1irbanJvySh/nJQBcFsMxF+g5o2JEn5BPFmfAqYalrhOwoFdLJXohEg==", "license": "MIT" }, "node_modules/bl": { @@ -6604,9 +6454,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -6626,9 +6476,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "funding": [ { "type": "opencollective", @@ -6645,11 +6495,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -6750,14 +6600,14 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -6818,9 +6668,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001782", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001782.tgz", - "integrity": "sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==", + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", "funding": [ { "type": "opencollective", @@ -7694,9 +7544,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.328", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", - "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", + "version": "1.5.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", + "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -7851,9 +7701,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -7937,15 +7787,15 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.1.tgz", - "integrity": "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.24.1", + "es-abstract": "^1.24.2", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", @@ -7957,8 +7807,7 @@ "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", - "math-intrinsics": "^1.1.0", - "safe-array-concat": "^1.1.3" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -8138,14 +7987,14 @@ } }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", "license": "MIT", "dependencies": { "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -8320,29 +8169,6 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", - "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "node-exports-info": "^1.6.0", - "object-keys": "^1.1.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -8687,32 +8513,6 @@ "expo": "*" } }, - "node_modules/expo-device/node_modules/ua-parser-js": { - "version": "0.7.41", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", - "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "license": "MIT", - "bin": { - "ua-parser-js": "script/cli.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/expo-file-system": { "version": "55.0.16", "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-55.0.16.tgz", @@ -9395,6 +9195,32 @@ "node": ">=4" } }, + "node_modules/expo/node_modules/react-native-worklets": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.8.1.tgz", + "integrity": "sha512-oWP/lStsAHU6oYCaWDXrda/wOHVdhusQJz1e6x9gPnXdFf4ndNDAOtWCmk2zGrAnlapfyA3rM6PCQq94mPg9cw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-classes": "^7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", + "convert-source-map": "^2.0.0", + "semver": "^7.7.3" + }, + "peerDependencies": { + "@babel/core": "*", + "@react-native/metro-config": "*", + "react": "*", + "react-native": "0.81 - 0.85" + } + }, "node_modules/expo/node_modules/restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -9614,9 +9440,9 @@ "license": "MIT" }, "node_modules/fast-xml-builder": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", - "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", + "integrity": "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==", "devOptional": true, "funding": [ { @@ -9630,9 +9456,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "5.5.9", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz", - "integrity": "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.1.tgz", + "integrity": "sha512-8Cc3f8GUGUULg34pBch/KGyPLglS+OFs05deyOlY7fL2MTagYPKrVQNmR1fLF/yJ9PH5ZSTd3YDF6pnmeZU+zA==", "devOptional": true, "funding": [ { @@ -9642,9 +9468,10 @@ ], "license": "MIT", "dependencies": { - "fast-xml-builder": "^1.1.4", - "path-expression-matcher": "^1.2.0", - "strnum": "^2.2.2" + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.5", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" @@ -9711,6 +9538,32 @@ "asap": "~2.0.3" } }, + "node_modules/fbjs/node_modules/ua-parser-js": { + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", + "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -10101,9 +9954,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.7", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", - "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -10172,12 +10025,12 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -10349,9 +10202,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -10463,16 +10316,16 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", + "agent-base": "6", "debug": "4" }, "engines": { - "node": ">= 14" + "node": ">= 6" } }, "node_modules/human-signals": { @@ -11269,6 +11122,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", @@ -12358,12 +12220,12 @@ } }, "node_modules/mediasoup-client": { - "version": "3.18.7", - "resolved": "https://registry.npmjs.org/mediasoup-client/-/mediasoup-client-3.18.7.tgz", - "integrity": "sha512-110f+zYEvSllYoF6didbIznIvSrTwMY9CqDhdWpq1M0goX1HWLoBdguiYB/MqcA858R2dOKDarP1R4vv5RAg1w==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/mediasoup-client/-/mediasoup-client-3.19.0.tgz", + "integrity": "sha512-XU4Y7yOe0FqUXwu/Mf8Wgrp5AOde1l60FEFNvSSrR2nNzbw3+jAcHQV35Ii9bXb21PT5XaW4SkI4dhxhwFXR3Q==", "license": "ISC", "dependencies": { - "@types/debug": "^4.1.12", + "@types/debug": "^4.1.13", "@types/events-alias": "npm:@types/events@^3.0.3", "awaitqueue": "^3.3.0", "debug": "^4.4.3", @@ -12374,7 +12236,7 @@ "supports-color": "^10.2.2" }, "engines": { - "node": ">=20" + "node": ">=22" }, "funding": { "type": "opencollective", @@ -12428,9 +12290,9 @@ } }, "node_modules/metro": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.6.tgz", - "integrity": "sha512-pbdndsAZ2F/ceopDdhVbttpa/hfLzXPJ/husc+QvQ33R0D9UXJKzTn5+OzOXx4bpQNtAKF2bY88cCI3Zl44xDQ==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.5.tgz", + "integrity": "sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", @@ -12448,24 +12310,24 @@ "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.35.0", + "hermes-parser": "0.33.3", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.83.6", - "metro-cache": "0.83.6", - "metro-cache-key": "0.83.6", - "metro-config": "0.83.6", - "metro-core": "0.83.6", - "metro-file-map": "0.83.6", - "metro-resolver": "0.83.6", - "metro-runtime": "0.83.6", - "metro-source-map": "0.83.6", - "metro-symbolicate": "0.83.6", - "metro-transform-plugins": "0.83.6", - "metro-transform-worker": "0.83.6", + "metro-babel-transformer": "0.83.5", + "metro-cache": "0.83.5", + "metro-cache-key": "0.83.5", + "metro-config": "0.83.5", + "metro-core": "0.83.5", + "metro-file-map": "0.83.5", + "metro-resolver": "0.83.5", + "metro-runtime": "0.83.5", + "metro-source-map": "0.83.5", + "metro-symbolicate": "0.83.5", + "metro-transform-plugins": "0.83.5", + "metro-transform-worker": "0.83.5", "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", @@ -12482,15 +12344,14 @@ } }, "node_modules/metro-babel-transformer": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.6.tgz", - "integrity": "sha512-1AnuazBpzY3meRMr04WUw14kRBkV0W3Ez+AA75FAeNpRyWNN5S3M3PHLUbZw7IXq7ZeOzceyRsHStaFrnWd+8w==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.5.tgz", + "integrity": "sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.35.0", - "metro-cache-key": "0.83.6", + "hermes-parser": "0.33.3", "nullthrows": "^1.1.1" }, "engines": { @@ -12498,39 +12359,39 @@ } }, "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", - "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", + "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", "license": "MIT" }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", - "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", + "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", "license": "MIT", "dependencies": { - "hermes-estree": "0.35.0" + "hermes-estree": "0.33.3" } }, "node_modules/metro-cache": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.6.tgz", - "integrity": "sha512-DpvZE32feNkqfZkI4Fic7YI/Kw8QP9wdl1rC4YKPrA77wQbI9vXbxjmfkCT/EGwBTFOPKqvIXo+H3BNe93YyiQ==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.5.tgz", + "integrity": "sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng==", "license": "MIT", "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", - "metro-core": "0.83.6" + "metro-core": "0.83.5" }, "engines": { "node": ">=20.19.4" } }, "node_modules/metro-cache-key": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.6.tgz", - "integrity": "sha512-5gdK4PVpgNOHi7xCGrgesNP1AuOA2TiPqpcirGXZi4RLLzX1VMowpkgTVtBfpQQCqWoosQF9yrSo9/KDQg1eBg==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.5.tgz", + "integrity": "sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -12539,19 +12400,41 @@ "node": ">=20.19.4" } }, + "node_modules/metro-cache/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/metro-cache/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/metro-config": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.6.tgz", - "integrity": "sha512-G5622400uNtnAMlppEA5zkFAZltEf7DSGhOu09BkisCxOlVMWfdosD/oPyh4f2YVQsc1MBYyp4w6OzbExTYarg==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.5.tgz", + "integrity": "sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w==", "license": "MIT", "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", - "metro": "0.83.6", - "metro-cache": "0.83.6", - "metro-core": "0.83.6", - "metro-runtime": "0.83.6", + "metro": "0.83.5", + "metro-cache": "0.83.5", + "metro-core": "0.83.5", + "metro-runtime": "0.83.5", "yaml": "^2.6.1" }, "engines": { @@ -12574,23 +12457,23 @@ } }, "node_modules/metro-core": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.6.tgz", - "integrity": "sha512-l+yQ2fuIgR//wszUlMrrAa9+Z+kbKazd0QOh0VQY7jC4ghb7yZBBSla/UMYRBZZ6fPg9IM+wD3+h+37a5f9etw==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.5.tgz", + "integrity": "sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.83.6" + "metro-resolver": "0.83.5" }, "engines": { "node": ">=20.19.4" } }, "node_modules/metro-file-map": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.6.tgz", - "integrity": "sha512-Jg3oN604C7GWbQwFAUXt8KsbMXeKfsxbZ5HFy4XFM3ggTS+ja9QgUmq9B613kgXv3G4M6rwiI6cvh9TRly4x3w==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.5.tgz", + "integrity": "sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -12608,9 +12491,9 @@ } }, "node_modules/metro-minify-terser": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.6.tgz", - "integrity": "sha512-Vx3/Ne9Q+EIEDLfKzZUOtn/rxSNa/QjlYxc42nvK4Mg8mB6XUgd3LXX5ZZVq7lzQgehgEqLrbgShJPGfeF8PnQ==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.5.tgz", + "integrity": "sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", @@ -12621,9 +12504,9 @@ } }, "node_modules/metro-resolver": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.6.tgz", - "integrity": "sha512-lAwR/FsT1uJ5iCt4AIsN3boKfJ88aN8bjvDT5FwBS0tKeKw4/sbdSTWlFxc7W/MUTN5RekJ3nQkJRIWsvs28tA==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.5.tgz", + "integrity": "sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -12633,9 +12516,9 @@ } }, "node_modules/metro-runtime": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.6.tgz", - "integrity": "sha512-WQPua1G2VgYbwRn6vSKxOhTX7CFbSf/JdUu6Nd8bZnPXckOf7HQ2y51NXNQHoEsiuawathrkzL8pBhv+zgZFmg==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.5.tgz", + "integrity": "sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", @@ -12646,18 +12529,18 @@ } }, "node_modules/metro-source-map": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.6.tgz", - "integrity": "sha512-AqJbOMMpeyyM4iNI91pchqDIszzNuuHApEhg6OABqZ+9mjLEqzcIEQ/fboZ7x74fNU5DBd2K36FdUQYPqlGClA==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.5.tgz", + "integrity": "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.83.6", + "metro-symbolicate": "0.83.5", "nullthrows": "^1.1.1", - "ob1": "0.83.6", + "ob1": "0.83.5", "source-map": "^0.5.6", "vlq": "^1.0.0" }, @@ -12666,14 +12549,14 @@ } }, "node_modules/metro-symbolicate": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.6.tgz", - "integrity": "sha512-4nvkmv9T7ozhprlPwk/+xm0SVPsxly5kYyMHdNaOlFemFz4df9BanvD46Ac6OISu/4Idinzfk2KVb++6OfzPAQ==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.5.tgz", + "integrity": "sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.83.6", + "metro-source-map": "0.83.5", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" @@ -12686,9 +12569,9 @@ } }, "node_modules/metro-transform-plugins": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.6.tgz", - "integrity": "sha512-V+zoY2Ul0v0BW6IokJkTud3raXmDdbdwkUQ/5eiSoy0jKuKMhrDjdH+H5buCS5iiJdNbykOn69Eip+Sqymkodg==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.5.tgz", + "integrity": "sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -12703,9 +12586,9 @@ } }, "node_modules/metro-transform-worker": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.6.tgz", - "integrity": "sha512-G5kDJ/P0ZTIf57t3iyAd5qIXbj2Wb1j7WtIDh82uTFQHe2Mq2SO9aXG9j1wI+kxZlIe58Z22XEXIKMl89z0ibQ==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.5.tgz", + "integrity": "sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -12713,13 +12596,13 @@ "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", - "metro": "0.83.6", - "metro-babel-transformer": "0.83.6", - "metro-cache": "0.83.6", - "metro-cache-key": "0.83.6", - "metro-minify-terser": "0.83.6", - "metro-source-map": "0.83.6", - "metro-transform-plugins": "0.83.6", + "metro": "0.83.5", + "metro-babel-transformer": "0.83.5", + "metro-cache": "0.83.5", + "metro-cache-key": "0.83.5", + "metro-minify-terser": "0.83.5", + "metro-source-map": "0.83.5", + "metro-transform-plugins": "0.83.5", "nullthrows": "^1.1.1" }, "engines": { @@ -12740,18 +12623,18 @@ } }, "node_modules/metro/node_modules/hermes-estree": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", - "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", + "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", "license": "MIT" }, "node_modules/metro/node_modules/hermes-parser": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", - "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", + "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", "license": "MIT", "dependencies": { - "hermes-estree": "0.35.0" + "hermes-estree": "0.33.3" } }, "node_modules/metro/node_modules/mime-types": { @@ -13075,9 +12958,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "license": "MIT" }, "node_modules/node-stream-zip": { @@ -13174,9 +13057,9 @@ "license": "MIT" }, "node_modules/ob1": { - "version": "0.83.6", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.6.tgz", - "integrity": "sha512-m/xZYkwcjo6UqLMrUICEB3iHk7Bjt3RSR7KXMi6Y1MO/kGkPhoRmfUDF6KAan3rLAZ7ABRqnQyKUTwaqZgUV4w==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.5.tgz", + "integrity": "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -13674,16 +13557,6 @@ "node": ">=10" } }, - "node_modules/patch-package/node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/patch-package/node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -13720,9 +13593,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", - "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", "devOptional": true, "funding": [ { @@ -13776,9 +13649,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -13887,9 +13760,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -14017,9 +13890,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "devOptional": true, "license": "BSD-3-Clause", "dependencies": { @@ -14201,9 +14074,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.72.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.72.0.tgz", - "integrity": "sha512-V4v6jubaf6JAurEaVnT9aUPKFbNtDgohj5CIgVGyPHvT9wRx5OZHVjz31GsxnPNI278XMu+ruFz+wGOscHaLKw==", + "version": "7.72.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.72.1.tgz", + "integrity": "sha512-RhwBoy2ygeVZje+C+bwJ8g0NjTdBmDlJvAUHTxRjTmSUKPYsKfMphkS2sgEMotsY03bP358yEYlnUeZy//D9Ig==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -14217,9 +14090,9 @@ } }, "node_modules/react-is": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", - "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", + "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==", "license": "MIT" }, "node_modules/react-native": { @@ -14452,6 +14325,31 @@ "react-native": "*" } }, + "node_modules/react-native-purchases": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/react-native-purchases/-/react-native-purchases-10.0.1.tgz", + "integrity": "sha512-FyJgOLuGo2TqR/sswzgkUebiYu30FPAsB9N2goyv4q2pr5ixPL0v6QrETrYDpnPI8kz/wg+aMv8/HsAU9b6ajw==", + "license": "MIT", + "workspaces": [ + "examples/purchaseTesterTypescript", + "react-native-purchases-ui", + "e2e-tests/MaestroTestApp" + ], + "dependencies": { + "@revenuecat/purchases-js-hybrid-mappings": "18.1.0", + "@revenuecat/purchases-typescript-internal": "18.1.0" + }, + "peerDependencies": { + "react": ">= 16.6.3", + "react-native": ">= 0.73.0", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } + } + }, "node_modules/react-native-reanimated": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.2.1.tgz", @@ -14737,6 +14635,36 @@ "node": ">=10" } }, + "node_modules/react-native/node_modules/@react-native/codegen": { + "version": "0.83.4", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.4.tgz", + "integrity": "sha512-CJ7XutzIqJPz3Lp/5TOiRWlU/JAjTboMT1BHNLSXjYHXwTmgHM3iGEbpCOtBMjWvsojRTJyRO/G3ghInIIXEYg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.32.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/react-native/node_modules/@react-native/js-polyfills": { + "version": "0.83.4", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.83.4.tgz", + "integrity": "sha512-wYUdv0rt4MjhKhQloO1AnGDXhZQOFZHDxm86dEtEA0WcsCdVrFdRULFM+rKUC/QQtJW2rS6WBqtBusgtrsDADg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + } + }, "node_modules/react-native/node_modules/@react-native/virtualized-lists": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.83.4.tgz", @@ -14790,6 +14718,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/react-native/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "license": "MIT" + }, + "node_modules/react-native/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, "node_modules/react-native/node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -15006,9 +14949,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.1.0" @@ -15034,12 +14977,15 @@ "license": "ISC" }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -15579,13 +15525,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -15670,12 +15616,13 @@ "license": "MIT" }, "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/slice-ansi": { @@ -15709,9 +15656,9 @@ } }, "node_modules/slugify": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.8.tgz", - "integrity": "sha512-HVk9X1E0gz3mSpoi60h/saazLKXKaZThMLU3u/aNwoYn8/xQyX2MGxL0ui2eaokkD7tF+Zo+cKTHUbe1mmmGzA==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz", + "integrity": "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==", "license": "MIT", "engines": { "node": ">=8.0.0" @@ -16080,9 +16027,9 @@ } }, "node_modules/strnum": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", - "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", "devOptional": true, "funding": [ { @@ -16250,9 +16197,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", "dev": true, "license": "MIT", "engines": { @@ -16260,13 +16207,13 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -16516,9 +16463,9 @@ } }, "node_modules/ua-parser-js": { - "version": "1.0.41", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", - "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", "funding": [ { "type": "opencollective", @@ -16560,9 +16507,9 @@ } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { diff --git a/package.json b/package.json index 6096009..f15bfc4 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "react-native-country-picker-modal": "^2.0.0", "react-native-dotenv": "^3.4.11", "react-native-gesture-handler": "~2.30.0", + "react-native-purchases": "^10.0.1", "react-native-reanimated": "4.2.1", "react-native-rsa-native": "^2.0.5", "react-native-safe-area-context": "~5.6.2", From c2fd2830d7d54dd49410f0ac94d96dbcbfb5584c Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Sun, 19 Apr 2026 02:11:26 +0200 Subject: [PATCH 04/17] fix: replace react-native-aes-gcm-crypto with expo-crypto AES-GCM --- package-lock.json | 200 --------------------------------- package.json | 1 - services/chat-crypto.client.ts | 70 ++++++------ 3 files changed, 36 insertions(+), 235 deletions(-) diff --git a/package-lock.json b/package-lock.json index f6e055f..9f335a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,6 @@ "react-hook-form": "^7.71.2", "react-native": "0.83.4", "react-native-aes-crypto": "^3.3.0", - "react-native-aes-gcm-crypto": "^0.2.2", "react-native-async-storage": "^0.0.1", "react-native-country-code-picker": "^1.3.5", "react-native-country-picker-modal": "^2.0.0", @@ -3762,107 +3761,6 @@ "node": ">= 20.19.4" } }, - "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.84.1.tgz", - "integrity": "sha512-vorvcvptGxtK0qTDCFQb+W3CU6oIhzcX5dduetWRBoAhXdthEQM0MQnF+GTXoXL8/luffKgy7PlZRG/WeI/oRQ==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.84.1" - }, - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/babel-preset": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.84.1.tgz", - "integrity": "sha512-3GpmCKk21f4oe32bKIdmkdn+WydvhhZL+1nsoFBGi30Qrq9vL16giKu31OcnWshYz139x+mVAvCyoyzgn8RXSw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/plugin-transform-typescript": "^7.25.2", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@react-native/babel-plugin-codegen": "0.84.1", - "babel-plugin-syntax-hermes-parser": "0.32.0", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/codegen": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", - "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.25.3", - "hermes-parser": "0.32.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "tinyglobby": "^0.2.15", - "yargs": "^17.6.2" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/codegen/node_modules/hermes-estree": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", - "extraneous": true, - "license": "MIT" - }, - "node_modules/@react-native/codegen/node_modules/hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.32.0" - } - }, "node_modules/@react-native/community-cli-plugin": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.4.tgz", @@ -4008,68 +3906,6 @@ "node": ">= 20.19.4" } }, - "node_modules/@react-native/js-polyfills": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", - "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", - "extraneous": true, - "license": "MIT", - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.84.1.tgz", - "integrity": "sha512-NswINguTz0eg1Dc0oGO/1dejXSr6iQaz8/NnCRn5HJdA3dGfqadS7zlYv0YjiWpgKgcW6uENaIEgJOQww0KSpw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.84.1", - "hermes-parser": "0.32.0", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", - "extraneous": true, - "license": "MIT" - }, - "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.32.0" - } - }, - "node_modules/@react-native/metro-config": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.84.1.tgz", - "integrity": "sha512-KlRawK4aXxRLlR3HYVfZKhfQp7sejQefQ/LttUWUkErhKO0AFt+yznoSLq7xwIrH9K3A3YwImHuFVtUtuDmurA==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@react-native/js-polyfills": "0.84.1", - "@react-native/metro-babel-transformer": "0.84.1", - "metro-config": "^0.83.3", - "metro-runtime": "^0.83.3" - }, - "engines": { - "node": ">= 20.19.4" - } - }, "node_modules/@react-native/normalize-colors": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.4.tgz", @@ -9195,32 +9031,6 @@ "node": ">=4" } }, - "node_modules/expo/node_modules/react-native-worklets": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.8.1.tgz", - "integrity": "sha512-oWP/lStsAHU6oYCaWDXrda/wOHVdhusQJz1e6x9gPnXdFf4ndNDAOtWCmk2zGrAnlapfyA3rM6PCQq94mPg9cw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-classes": "^7.28.4", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/preset-typescript": "^7.27.1", - "convert-source-map": "^2.0.0", - "semver": "^7.7.3" - }, - "peerDependencies": { - "@babel/core": "*", - "@react-native/metro-config": "*", - "react": "*", - "react-native": "0.81 - 0.85" - } - }, "node_modules/expo/node_modules/restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -14159,16 +13969,6 @@ "integrity": "sha512-8QW/1HAxecVOHzMwWc+f9wXnBKDs0Q2tIIlLy2EZjcmf4kgPLIX19Fxq+c69KeNWwzHJwW3NYAWP+ETbql6PxQ==", "license": "MIT" }, - "node_modules/react-native-aes-gcm-crypto": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/react-native-aes-gcm-crypto/-/react-native-aes-gcm-crypto-0.2.2.tgz", - "integrity": "sha512-vUwkh2zBiIQMRY191IfZhDmhHVT+nV4sxet/A0V8J35lVShCA4kuFzBL+QVB06RM2EF8oZSnNMt/uvFkKAx6QQ==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, "node_modules/react-native-async-storage": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/react-native-async-storage/-/react-native-async-storage-0.0.1.tgz", diff --git a/package.json b/package.json index f15bfc4..f6ac8a1 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "react-hook-form": "^7.71.2", "react-native": "0.83.4", "react-native-aes-crypto": "^3.3.0", - "react-native-aes-gcm-crypto": "^0.2.2", "react-native-async-storage": "^0.0.1", "react-native-country-code-picker": "^1.3.5", "react-native-country-picker-modal": "^2.0.0", diff --git a/services/chat-crypto.client.ts b/services/chat-crypto.client.ts index babda40..c73d7dd 100644 --- a/services/chat-crypto.client.ts +++ b/services/chat-crypto.client.ts @@ -1,14 +1,18 @@ import * as SecureStore from "expo-secure-store"; import { RSA } from "react-native-rsa-native"; -import AesGcmCrypto from "react-native-aes-gcm-crypto"; -import * as Crypto from "expo-crypto"; +import { + AESEncryptionKey, + AESSealedData, + aesEncryptAsync, + aesDecryptAsync, +} from "expo-crypto"; import { Message, RoomMessagesResponse } from "@/types/messages"; const PRIVATE_KEY_STORE_KEY = "chat_private_key"; const PUBLIC_KEY_STORE_KEY = "chat_public_key"; let cachedPrivateKey: string | null = null; -const aesKeyCache = new Map(); // encryptedKey → decryptedAesKey +const aesKeyCache = new Map(); export interface EncryptedPayload { type: "text"; @@ -51,10 +55,19 @@ export async function encryptMessage( plainText: string, recipients: { userId: string; publicKey: string }[], ): Promise { - const keyBytes = Crypto.getRandomValues(new Uint8Array(32)); - const aesKeyBase64 = bytesToBase64(keyBytes); + const aesKey = await AESEncryptionKey.generate(256); + const aesKeyBase64 = await aesKey.encoded("base64"); - const encrypted = await AesGcmCrypto.encrypt(plainText, false, aesKeyBase64); + const plaintextBase64 = btoa(unescape(encodeURIComponent(plainText))); + + const sealedData = await aesEncryptAsync(plaintextBase64, aesKey); + + const iv = (await sealedData.iv("base64")) as string; + const authTag = (await sealedData.tag("base64")) as string; + const ciphertext = (await sealedData.ciphertext({ + encoding: "base64", + includeTag: false, + })) as string; const encryptedKeys = await Promise.all( recipients.map(async (r) => ({ @@ -65,42 +78,38 @@ export async function encryptMessage( return { type: "text", - ciphertext: encrypted.content, - iv: encrypted.iv, - authTag: encrypted.tag, + ciphertext, + iv, + authTag, encryptedKeys, }; } -// ─── 3. Nachricht entschlüsseln ────────────────────────────────────────────── - export async function decryptMessage(msg: { ciphertext: string; iv: string; authTag: string; encryptedKey: string; }): Promise { - // Cache private key — only 1 disk read ever if (!cachedPrivateKey) { cachedPrivateKey = await SecureStore.getItemAsync(PRIVATE_KEY_STORE_KEY); } if (!cachedPrivateKey) throw new Error("Kein Private Key gefunden"); - // Cache AES key — RSA decrypt only once per unique encrypted key - let aesKeyBase64 = aesKeyCache.get(msg.encryptedKey); - if (!aesKeyBase64) { - aesKeyBase64 = await RSA.decrypt(msg.encryptedKey, cachedPrivateKey); - aesKeyCache.set(msg.encryptedKey, aesKeyBase64); + let aesKey = aesKeyCache.get(msg.encryptedKey); + if (!aesKey) { + const aesKeyBase64 = await RSA.decrypt(msg.encryptedKey, cachedPrivateKey); + aesKey = await AESEncryptionKey.import(aesKeyBase64, "base64"); + aesKeyCache.set(msg.encryptedKey, aesKey); } - const plainText = await AesGcmCrypto.decrypt( - msg.ciphertext, - aesKeyBase64, - msg.iv, - msg.authTag, - false, - ); - return plainText; + const sealedData = AESSealedData.fromParts(msg.iv, msg.ciphertext, msg.authTag); + + const decryptedBase64 = (await aesDecryptAsync(sealedData, aesKey, { + output: "base64", + })) as string; + + return decodeURIComponent(escape(atob(decryptedBase64))); } async function prewarmAesCache(messages: RoomMessagesResponse["messages"]) { @@ -118,7 +127,8 @@ async function prewarmAesCache(messages: RoomMessagesResponse["messages"]) { await Promise.all( uniqueKeys.map(async (encKey) => { if (encKey && !aesKeyCache.has(encKey)) { - const aesKey = await RSA.decrypt(encKey, privateKey); + const aesKeyBase64 = await RSA.decrypt(encKey, privateKey); + const aesKey = await AESEncryptionKey.import(aesKeyBase64, "base64"); aesKeyCache.set(encKey, aesKey); } }), @@ -155,11 +165,3 @@ export async function decryptBatch( }), ); } - -function bytesToBase64(bytes: Uint8Array): string { - let binary = ""; - for (let i = 0; i < bytes.length; i++) { - binary += String.fromCharCode(bytes[i]); - } - return btoa(binary); -} From 66664c8c7336a258f2bc6ef2cc5e78b1a6b52029 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Sun, 19 Apr 2026 02:23:27 +0200 Subject: [PATCH 05/17] fix: sync package-lock.json --- package-lock.json | 189 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/package-lock.json b/package-lock.json index 9f335a7..6e5c039 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3761,6 +3761,107 @@ "node": ">= 20.19.4" } }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.84.1.tgz", + "integrity": "sha512-vorvcvptGxtK0qTDCFQb+W3CU6oIhzcX5dduetWRBoAhXdthEQM0MQnF+GTXoXL8/luffKgy7PlZRG/WeI/oRQ==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.84.1" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.84.1.tgz", + "integrity": "sha512-3GpmCKk21f4oe32bKIdmkdn+WydvhhZL+1nsoFBGi30Qrq9vL16giKu31OcnWshYz139x+mVAvCyoyzgn8RXSw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@react-native/babel-plugin-codegen": "0.84.1", + "babel-plugin-syntax-hermes-parser": "0.32.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", + "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", + "hermes-parser": "0.32.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "extraneous": true, + "license": "MIT" + }, + "node_modules/@react-native/codegen/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, "node_modules/@react-native/community-cli-plugin": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.4.tgz", @@ -3906,6 +4007,68 @@ "node": ">= 20.19.4" } }, + "node_modules/@react-native/js-polyfills": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", + "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.84.1.tgz", + "integrity": "sha512-NswINguTz0eg1Dc0oGO/1dejXSr6iQaz8/NnCRn5HJdA3dGfqadS7zlYv0YjiWpgKgcW6uENaIEgJOQww0KSpw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.84.1", + "hermes-parser": "0.32.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "extraneous": true, + "license": "MIT" + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, + "node_modules/@react-native/metro-config": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.84.1.tgz", + "integrity": "sha512-KlRawK4aXxRLlR3HYVfZKhfQp7sejQefQ/LttUWUkErhKO0AFt+yznoSLq7xwIrH9K3A3YwImHuFVtUtuDmurA==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@react-native/js-polyfills": "0.84.1", + "@react-native/metro-babel-transformer": "0.84.1", + "metro-config": "^0.83.3", + "metro-runtime": "^0.83.3" + }, + "engines": { + "node": ">= 20.19.4" + } + }, "node_modules/@react-native/normalize-colors": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.4.tgz", @@ -9031,6 +9194,32 @@ "node": ">=4" } }, + "node_modules/expo/node_modules/react-native-worklets": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.8.1.tgz", + "integrity": "sha512-oWP/lStsAHU6oYCaWDXrda/wOHVdhusQJz1e6x9gPnXdFf4ndNDAOtWCmk2zGrAnlapfyA3rM6PCQq94mPg9cw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-classes": "^7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", + "convert-source-map": "^2.0.0", + "semver": "^7.7.3" + }, + "peerDependencies": { + "@babel/core": "*", + "@react-native/metro-config": "*", + "react": "*", + "react-native": "0.81 - 0.85" + } + }, "node_modules/expo/node_modules/restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", From 6fef20b98d0573078f31e7d40b4444b4cc1b79e2 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Sun, 19 Apr 2026 02:42:24 +0200 Subject: [PATCH 06/17] fix: update cryptographic library to react-native-fast-rsa and adjust key generation methods --- package-lock.json | 223 ++++----------------------------- package.json | 2 +- services/chat-crypto.client.ts | 34 +++-- 3 files changed, 51 insertions(+), 208 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6e5c039..077d320 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,10 +58,10 @@ "react-native-country-code-picker": "^1.3.5", "react-native-country-picker-modal": "^2.0.0", "react-native-dotenv": "^3.4.11", + "react-native-fast-rsa": "^2.6.3", "react-native-gesture-handler": "~2.30.0", "react-native-purchases": "^10.0.1", "react-native-reanimated": "4.2.1", - "react-native-rsa-native": "^2.0.5", "react-native-safe-area-context": "~5.6.2", "react-native-screens": "~4.23.0", "react-native-svg": "15.15.3", @@ -3761,107 +3761,6 @@ "node": ">= 20.19.4" } }, - "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.84.1.tgz", - "integrity": "sha512-vorvcvptGxtK0qTDCFQb+W3CU6oIhzcX5dduetWRBoAhXdthEQM0MQnF+GTXoXL8/luffKgy7PlZRG/WeI/oRQ==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.84.1" - }, - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/babel-preset": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.84.1.tgz", - "integrity": "sha512-3GpmCKk21f4oe32bKIdmkdn+WydvhhZL+1nsoFBGi30Qrq9vL16giKu31OcnWshYz139x+mVAvCyoyzgn8RXSw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/plugin-transform-typescript": "^7.25.2", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@react-native/babel-plugin-codegen": "0.84.1", - "babel-plugin-syntax-hermes-parser": "0.32.0", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/codegen": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", - "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.25.3", - "hermes-parser": "0.32.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "tinyglobby": "^0.2.15", - "yargs": "^17.6.2" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/codegen/node_modules/hermes-estree": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", - "extraneous": true, - "license": "MIT" - }, - "node_modules/@react-native/codegen/node_modules/hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.32.0" - } - }, "node_modules/@react-native/community-cli-plugin": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.4.tgz", @@ -4007,68 +3906,6 @@ "node": ">= 20.19.4" } }, - "node_modules/@react-native/js-polyfills": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", - "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", - "extraneous": true, - "license": "MIT", - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.84.1.tgz", - "integrity": "sha512-NswINguTz0eg1Dc0oGO/1dejXSr6iQaz8/NnCRn5HJdA3dGfqadS7zlYv0YjiWpgKgcW6uENaIEgJOQww0KSpw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.84.1", - "hermes-parser": "0.32.0", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", - "extraneous": true, - "license": "MIT" - }, - "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.32.0" - } - }, - "node_modules/@react-native/metro-config": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.84.1.tgz", - "integrity": "sha512-KlRawK4aXxRLlR3HYVfZKhfQp7sejQefQ/LttUWUkErhKO0AFt+yznoSLq7xwIrH9K3A3YwImHuFVtUtuDmurA==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@react-native/js-polyfills": "0.84.1", - "@react-native/metro-babel-transformer": "0.84.1", - "metro-config": "^0.83.3", - "metro-runtime": "^0.83.3" - }, - "engines": { - "node": ">= 20.19.4" - } - }, "node_modules/@react-native/normalize-colors": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.4.tgz", @@ -9194,32 +9031,6 @@ "node": ">=4" } }, - "node_modules/expo/node_modules/react-native-worklets": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.8.1.tgz", - "integrity": "sha512-oWP/lStsAHU6oYCaWDXrda/wOHVdhusQJz1e6x9gPnXdFf4ndNDAOtWCmk2zGrAnlapfyA3rM6PCQq94mPg9cw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-classes": "^7.28.4", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/preset-typescript": "^7.27.1", - "convert-source-map": "^2.0.0", - "semver": "^7.7.3" - }, - "peerDependencies": { - "@babel/core": "*", - "@react-native/metro-config": "*", - "react": "*", - "react-native": "0.81 - 0.85" - } - }, "node_modules/expo/node_modules/restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -9703,6 +9514,12 @@ "node": ">=16" } }, + "node_modules/flatbuffers": { + "version": "24.3.25", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-24.3.25.tgz", + "integrity": "sha512-3HDgPbgiwWMI9zVB7VYBHaMrbOO7Gm0v+yD2FV/sCKj+9NDeVL7BOBYUuhWAQGKWOzBo8S9WdMvV0eixO233XQ==", + "license": "Apache-2.0" + }, "node_modules/flatted": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", @@ -14289,6 +14106,26 @@ "@babel/runtime": "^7.20.6" } }, + "node_modules/react-native-fast-rsa": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/react-native-fast-rsa/-/react-native-fast-rsa-2.6.3.tgz", + "integrity": "sha512-lJX/6fcEY8+GVz7xvuEtnoZ37WIm8K+YYhO9Bx20rFBIpPvGd8gkd5t6a540RLpbmTN86y0bA50ewalrAyHYMQ==", + "license": "MIT", + "workspaces": [ + "example" + ], + "dependencies": { + "big-integer": "^1.6.52", + "flatbuffers": "24.3.25" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-gesture-handler": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.30.1.tgz", @@ -14376,12 +14213,6 @@ "node": ">=10" } }, - "node_modules/react-native-rsa-native": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/react-native-rsa-native/-/react-native-rsa-native-2.0.5.tgz", - "integrity": "sha512-gwwvFSwGW5WKrpDyBQ/eTf1UrVABeAvMcT4YWemzPSUo6aHZs1kbBm2rXmwN5okhUzJsry5zjjz/qdx5GXRugQ==", - "license": "MIT" - }, "node_modules/react-native-safe-area-context": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", diff --git a/package.json b/package.json index f6ac8a1..6f51177 100644 --- a/package.json +++ b/package.json @@ -63,10 +63,10 @@ "react-native-country-code-picker": "^1.3.5", "react-native-country-picker-modal": "^2.0.0", "react-native-dotenv": "^3.4.11", + "react-native-fast-rsa": "^2.6.3", "react-native-gesture-handler": "~2.30.0", "react-native-purchases": "^10.0.1", "react-native-reanimated": "4.2.1", - "react-native-rsa-native": "^2.0.5", "react-native-safe-area-context": "~5.6.2", "react-native-screens": "~4.23.0", "react-native-svg": "15.15.3", diff --git a/services/chat-crypto.client.ts b/services/chat-crypto.client.ts index c73d7dd..7600626 100644 --- a/services/chat-crypto.client.ts +++ b/services/chat-crypto.client.ts @@ -1,5 +1,5 @@ import * as SecureStore from "expo-secure-store"; -import { RSA } from "react-native-rsa-native"; +import RSA, { Hash } from "react-native-fast-rsa"; import { AESEncryptionKey, AESSealedData, @@ -31,10 +31,10 @@ export async function initCrypto(apiBaseUrl: string, authToken: string): Promise if (existingPrivate && existingPublic) { publicKey = existingPublic; } else { - const keys = await RSA.generateKeys(2048); - await SecureStore.setItemAsync(PRIVATE_KEY_STORE_KEY, keys.private); - await SecureStore.setItemAsync(PUBLIC_KEY_STORE_KEY, keys.public); - publicKey = keys.public; + const keys = await RSA.generate(2048); + await SecureStore.setItemAsync(PRIVATE_KEY_STORE_KEY, keys.privateKey); + await SecureStore.setItemAsync(PUBLIC_KEY_STORE_KEY, keys.publicKey); + publicKey = keys.publicKey; } const res = await fetch(`${apiBaseUrl}/users/me/public-key`, { @@ -57,9 +57,7 @@ export async function encryptMessage( ): Promise { const aesKey = await AESEncryptionKey.generate(256); const aesKeyBase64 = await aesKey.encoded("base64"); - const plaintextBase64 = btoa(unescape(encodeURIComponent(plainText))); - const sealedData = await aesEncryptAsync(plaintextBase64, aesKey); const iv = (await sealedData.iv("base64")) as string; @@ -72,7 +70,12 @@ export async function encryptMessage( const encryptedKeys = await Promise.all( recipients.map(async (r) => ({ userId: r.userId, - encryptedKey: await RSA.encrypt(aesKeyBase64, r.publicKey), + encryptedKey: await RSA.encryptOAEP( + aesKeyBase64, + "", + Hash.SHA256, + r.publicKey, + ), })), ); @@ -98,13 +101,17 @@ export async function decryptMessage(msg: { let aesKey = aesKeyCache.get(msg.encryptedKey); if (!aesKey) { - const aesKeyBase64 = await RSA.decrypt(msg.encryptedKey, cachedPrivateKey); + const aesKeyBase64 = await RSA.decryptOAEP( + msg.encryptedKey, + "", + Hash.SHA256, + cachedPrivateKey, + ); aesKey = await AESEncryptionKey.import(aesKeyBase64, "base64"); aesKeyCache.set(msg.encryptedKey, aesKey); } const sealedData = AESSealedData.fromParts(msg.iv, msg.ciphertext, msg.authTag); - const decryptedBase64 = (await aesDecryptAsync(sealedData, aesKey, { output: "base64", })) as string; @@ -127,7 +134,12 @@ async function prewarmAesCache(messages: RoomMessagesResponse["messages"]) { await Promise.all( uniqueKeys.map(async (encKey) => { if (encKey && !aesKeyCache.has(encKey)) { - const aesKeyBase64 = await RSA.decrypt(encKey, privateKey); + const aesKeyBase64 = await RSA.decryptOAEP( + encKey, + "", + Hash.SHA256, + privateKey, + ); const aesKey = await AESEncryptionKey.import(aesKeyBase64, "base64"); aesKeyCache.set(encKey, aesKey); } From 540e5725d2de53f5a8957a2f58386ce170f4fbaf Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Sun, 19 Apr 2026 03:08:14 +0200 Subject: [PATCH 07/17] [fix] sync package-lock.json --- .gitignore | 8 +- package-lock.json | 189 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index d45b108..68f501e 100644 --- a/.gitignore +++ b/.gitignore @@ -50,10 +50,4 @@ app-example /ios /android -.env.local - -.env.local - -.env.local - -.env.local +.env.local \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 077d320..c21ed4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3761,6 +3761,107 @@ "node": ">= 20.19.4" } }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.84.1.tgz", + "integrity": "sha512-vorvcvptGxtK0qTDCFQb+W3CU6oIhzcX5dduetWRBoAhXdthEQM0MQnF+GTXoXL8/luffKgy7PlZRG/WeI/oRQ==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.84.1" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.84.1.tgz", + "integrity": "sha512-3GpmCKk21f4oe32bKIdmkdn+WydvhhZL+1nsoFBGi30Qrq9vL16giKu31OcnWshYz139x+mVAvCyoyzgn8RXSw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@react-native/babel-plugin-codegen": "0.84.1", + "babel-plugin-syntax-hermes-parser": "0.32.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", + "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", + "hermes-parser": "0.32.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "extraneous": true, + "license": "MIT" + }, + "node_modules/@react-native/codegen/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, "node_modules/@react-native/community-cli-plugin": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.4.tgz", @@ -3906,6 +4007,68 @@ "node": ">= 20.19.4" } }, + "node_modules/@react-native/js-polyfills": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", + "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.84.1.tgz", + "integrity": "sha512-NswINguTz0eg1Dc0oGO/1dejXSr6iQaz8/NnCRn5HJdA3dGfqadS7zlYv0YjiWpgKgcW6uENaIEgJOQww0KSpw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.84.1", + "hermes-parser": "0.32.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "extraneous": true, + "license": "MIT" + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, + "node_modules/@react-native/metro-config": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.84.1.tgz", + "integrity": "sha512-KlRawK4aXxRLlR3HYVfZKhfQp7sejQefQ/LttUWUkErhKO0AFt+yznoSLq7xwIrH9K3A3YwImHuFVtUtuDmurA==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@react-native/js-polyfills": "0.84.1", + "@react-native/metro-babel-transformer": "0.84.1", + "metro-config": "^0.83.3", + "metro-runtime": "^0.83.3" + }, + "engines": { + "node": ">= 20.19.4" + } + }, "node_modules/@react-native/normalize-colors": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.4.tgz", @@ -9031,6 +9194,32 @@ "node": ">=4" } }, + "node_modules/expo/node_modules/react-native-worklets": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.8.1.tgz", + "integrity": "sha512-oWP/lStsAHU6oYCaWDXrda/wOHVdhusQJz1e6x9gPnXdFf4ndNDAOtWCmk2zGrAnlapfyA3rM6PCQq94mPg9cw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-classes": "^7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", + "convert-source-map": "^2.0.0", + "semver": "^7.7.3" + }, + "peerDependencies": { + "@babel/core": "*", + "@react-native/metro-config": "*", + "react": "*", + "react-native": "0.81 - 0.85" + } + }, "node_modules/expo/node_modules/restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", From 16b09cad1df4c2a099b09a3902a8c6fd70a86580 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Sun, 19 Apr 2026 03:26:19 +0200 Subject: [PATCH 08/17] [fix] encryption fix --- services/chat-crypto.client.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/services/chat-crypto.client.ts b/services/chat-crypto.client.ts index 7600626..8672539 100644 --- a/services/chat-crypto.client.ts +++ b/services/chat-crypto.client.ts @@ -62,10 +62,11 @@ export async function encryptMessage( const iv = (await sealedData.iv("base64")) as string; const authTag = (await sealedData.tag("base64")) as string; - const ciphertext = (await sealedData.ciphertext({ - encoding: "base64", + const ciphertextBytes = (await sealedData.ciphertext({ + encoding: "bytes", includeTag: false, - })) as string; + })) as Uint8Array; + const ciphertext = bytesToBase64(ciphertextBytes); const encryptedKeys = await Promise.all( recipients.map(async (r) => ({ @@ -177,3 +178,11 @@ export async function decryptBatch( }), ); } + +function bytesToBase64(bytes: Uint8Array): string { + let binary = ""; + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]); + } + return btoa(binary); +} From bc33811fc05f3b5d49f8c3698599cc38cc160123 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Sun, 19 Apr 2026 03:31:56 +0200 Subject: [PATCH 09/17] fix: pass Uint8Array to AESSealedData.fromParts --- services/chat-crypto.client.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/services/chat-crypto.client.ts b/services/chat-crypto.client.ts index 8672539..946759b 100644 --- a/services/chat-crypto.client.ts +++ b/services/chat-crypto.client.ts @@ -112,7 +112,11 @@ export async function decryptMessage(msg: { aesKeyCache.set(msg.encryptedKey, aesKey); } - const sealedData = AESSealedData.fromParts(msg.iv, msg.ciphertext, msg.authTag); + const sealedData = AESSealedData.fromParts( + base64ToBytes(msg.iv), + base64ToBytes(msg.ciphertext), + base64ToBytes(msg.authTag), + ); const decryptedBase64 = (await aesDecryptAsync(sealedData, aesKey, { output: "base64", })) as string; @@ -186,3 +190,12 @@ function bytesToBase64(bytes: Uint8Array): string { } return btoa(binary); } + +function base64ToBytes(base64: string): Uint8Array { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes; +} From b21f49f94c48e91c4e02d6bfb996ba2966ba7ab2 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Sun, 19 Apr 2026 03:53:31 +0200 Subject: [PATCH 10/17] feat: add checkout screen and update subscription plans --- app/(protected)/subscriptions/checkout.tsx | 415 +++++++++++++++++++++ app/(protected)/subscriptions/index.tsx | 43 ++- 2 files changed, 440 insertions(+), 18 deletions(-) create mode 100644 app/(protected)/subscriptions/checkout.tsx diff --git a/app/(protected)/subscriptions/checkout.tsx b/app/(protected)/subscriptions/checkout.tsx new file mode 100644 index 0000000..09b0c66 --- /dev/null +++ b/app/(protected)/subscriptions/checkout.tsx @@ -0,0 +1,415 @@ +import { useState } from "react"; +import * as WebBrowser from "expo-web-browser"; +import { + View, + Text, + StyleSheet, + TouchableOpacity, + ScrollView, + ActivityIndicator, +} from "react-native"; +import { useLocalSearchParams } from "expo-router"; +import { useTheme } from "@/lib/theme/context"; +import BackWrapper from "@/components/backwrapper"; +import { ShieldCheck, CreditCard, Lock } from "lucide-react-native"; + +type BillingCycle = "monthly" | "yearly"; + +interface Plan { + id: string; + name: string; + monthlyPrice: number; + yearlyPrice: number; + yearlyMonthPrice: number; + features: string[]; +} + +const PLANS: Record = { + basic: { + id: "basic", + name: "Basic", + monthlyPrice: 5.99, + yearlyMonthPrice: 4.99, + yearlyPrice: 59.99, + features: [ + "Up to 10 video calls per day", + "Basic profile customization", + "Text chat after matching", + "Standard support", + ], + }, + premium: { + id: "premium", + name: "Premium", + monthlyPrice: 12.99, + yearlyMonthPrice: 10.49, + yearlyPrice: 125.99, + features: [ + "Everything in Basic", + "Up to 30 video calls per day", + "See who liked you", + "Priority in discovery feed", + "Read receipts in chat", + "Standard support", + ], + }, + gold: { + id: "gold", + name: "Gold", + monthlyPrice: 29.99, + yearlyMonthPrice: 24.99, + yearlyPrice: 299.99, + features: [ + "Everything in Premium", + "Up to 100 video calls per day", + "Profile boost every week", + "Standard support", + ], + }, +}; + +// ─── Order Summary ───────────────────────────────────────────────────────────── + +function OrderSummary({ plan, billing }: { plan: Plan; billing: BillingCycle }) { + const { theme } = useTheme(); + const isYearly = billing === "yearly"; + const price = isYearly ? plan.yearlyPrice : plan.monthlyPrice; + const perMonth = isYearly ? plan.yearlyMonthPrice : plan.monthlyPrice; + + return ( + + + Order summary + + + + + + + {plan.name} plan · {isYearly ? "Yearly" : "Monthly"} + + {isYearly && ( + + Save 17% vs monthly + + )} + + + ${price.toFixed(2)} + + + + + + + + Total today + + + ${price.toFixed(2)} + + + + {isYearly && ( + + ${perMonth.toFixed(2)}/mo · billed annually + + )} + + ); +} + +// ─── Feature List ────────────────────────────────────────────────────────────── + +function IncludedFeatures({ plan }: { plan: Plan }) { + const { theme } = useTheme(); + return ( + + + What{"'"}s included + + + {plan.features.map((f, i) => ( + + + ✓ + + + {f} + + + ))} + + + ); +} + +// ─── Trust Badges ────────────────────────────────────────────────────────────── + +function TrustBadges() { + const { theme } = useTheme(); + const items = [ + { Icon: Lock, label: "Secure payment" }, + { Icon: ShieldCheck, label: "Cancel anytime" }, + { Icon: CreditCard, label: "No hidden fees" }, + ]; + return ( + + {items.map(({ Icon, label }) => ( + + + + {label} + + + ))} + + ); +} + +// ─── Main Screen ─────────────────────────────────────────────────────────────── + +export default function Checkout() { + const { theme } = useTheme(); + const { planId, billing } = useLocalSearchParams<{ + planId: string; + billing: BillingCycle; + }>(); + const [loading, setLoading] = useState(false); + + const plan = PLANS[planId ?? "premium"]; + const billingCycle: BillingCycle = billing === "yearly" ? "yearly" : "monthly"; + + const handleSubscribe = () => { + setLoading(true); + // TODO: integrate payment provider + setTimeout(() => setLoading(false), 2000); + }; + + return ( + + + + Checkout + + Review your order before confirming + + + + + + + + + {/* Sticky CTA */} + + + {loading ? ( + + ) : ( + Confirm & Subscribe + )} + + + By subscribing you agree to our{" "} + + WebBrowser.openBrowserAsync( + "https://mindsofd23b.github.io/Landing-Elysio/termsandconditions/", + { + presentationStyle: + WebBrowser.WebBrowserPresentationStyle.FORM_SHEET, + controlsColor: theme.primary, + toolbarColor: theme.background, + enableBarCollapsing: true, + }, + ) + } + style={{ color: theme.primary }} + > + Terms of Service + {" "} + and{" "} + + WebBrowser.openBrowserAsync( + "https://mindsofd23b.github.io/Landing-Elysio/privacypolicy/", + { + presentationStyle: + WebBrowser.WebBrowserPresentationStyle.FORM_SHEET, + controlsColor: theme.primary, + toolbarColor: theme.background, + enableBarCollapsing: true, + }, + ) + } + style={{ color: theme.primary }} + > + Privacy Policy + + . + + + + ); +} + +// ─── Styles ──────────────────────────────────────────────────────────────────── + +const styles = StyleSheet.create({ + scroll: { + paddingHorizontal: 20, + paddingBottom: 24, + gap: 14, + }, + header: { + paddingTop: 8, + paddingBottom: 4, + }, + title: { + fontSize: 26, + fontWeight: "800", + letterSpacing: -0.5, + }, + subtitle: { + fontSize: 14, + marginTop: 4, + }, + + card: { + borderRadius: 16, + padding: 18, + }, + sectionTitle: { + fontSize: 13, + fontWeight: "700", + letterSpacing: 0.4, + textTransform: "uppercase", + marginBottom: 14, + }, + + planRow: { + flexDirection: "row", + alignItems: "center", + gap: 12, + }, + planDot: { + width: 10, + height: 10, + borderRadius: 5, + }, + planLabel: { + fontSize: 15, + fontWeight: "600", + }, + savingNote: { + fontSize: 12, + marginTop: 2, + fontWeight: "500", + }, + planPrice: { + fontSize: 15, + fontWeight: "700", + }, + divider: { + height: 1, + marginVertical: 14, + }, + totalRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + }, + totalLabel: { + fontSize: 16, + fontWeight: "700", + }, + totalPrice: { + fontSize: 20, + fontWeight: "800", + letterSpacing: -0.5, + }, + perMonthNote: { + fontSize: 12, + marginTop: 6, + textAlign: "right", + }, + + featureList: { gap: 10 }, + featureRow: { + flexDirection: "row", + alignItems: "flex-start", + gap: 10, + }, + checkMark: { + fontSize: 13, + fontWeight: "700", + marginTop: 2, + }, + featureText: { + fontSize: 14, + lineHeight: 20, + flex: 1, + }, + + trustRow: { + flexDirection: "row", + justifyContent: "space-around", + paddingVertical: 8, + }, + trustItem: { + alignItems: "center", + gap: 6, + }, + trustText: { + fontSize: 11, + fontWeight: "500", + }, + + footer: { + paddingHorizontal: 20, + paddingTop: 14, + paddingBottom: 32, + borderTopWidth: 1, + }, + ctaBtn: { + borderRadius: 50, + paddingVertical: 16, + alignItems: "center", + justifyContent: "center", + }, + ctaBtnDisabled: { + opacity: 0.7, + }, + ctaBtnText: { + color: "#fff", + fontSize: 16, + fontWeight: "700", + }, + legalNote: { + fontSize: 11, + textAlign: "center", + marginTop: 10, + lineHeight: 16, + }, +}); diff --git a/app/(protected)/subscriptions/index.tsx b/app/(protected)/subscriptions/index.tsx index 52b4b23..4159bef 100644 --- a/app/(protected)/subscriptions/index.tsx +++ b/app/(protected)/subscriptions/index.tsx @@ -16,6 +16,7 @@ import Animated, { withTiming, } from "react-native-reanimated"; import BackWrapper from "@/components/backwrapper"; +import { router } from "expo-router"; const { width: SCREEN_WIDTH } = Dimensions.get("window"); @@ -51,11 +52,10 @@ const PLANS: Plan[] = [ yearlyMonthPrice: 4.99, yearlyPrice: 59.99, features: [ - "Access to all features", - "Bi-weekly sync calls", - "Available 3 days each week", - "Easy-to-manage dashboard", - "Immediate start", + "Up to 10 video calls per day", + "Basic profile customization", + "Text chat after matching", + "Standard support", ], }, { @@ -70,13 +70,12 @@ const PLANS: Plan[] = [ badge: "BEST VALUE", highlight: true, features: [ - "All from Basic plan", - "Direct comms + weekly sync", - "Available 5 days each week", - "Delivery in avg. 48 hours", - "No-code add-ons for free", - "Easy-to-manage dashboard", - "Immediate start", + "Everything in Basic", + "Up to 30 video calls per day", + "See who liked you", + "Priority in discovery feed", + "Read receipts in chat", + "Standard support", ], }, { @@ -89,11 +88,10 @@ const PLANS: Plan[] = [ yearlyMonthPrice: 24.99, yearlyPrice: 299.99, features: [ - "Custom scope", - "Dedicated team", - "Fixed deadlines", - "Strategic consulting bi-weekly", - "Payment plan on milestones", + "Everything in Premium", + "Up to 100 video calls per day", + "Profile boost every week", + "Standard support", ], }, ]; @@ -367,7 +365,16 @@ export default function SubscriptionPlans() { onScroll={handleScroll} scrollEventThrottle={16} renderItem={({ item }) => ( - {}} /> + { + router.push({ + pathname: "/subscriptions/checkout", + params: { planId: item.id, billing }, + }); + }} + /> )} /> From 051e42db275543432885c270465e4986fb7cccb3 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Sun, 19 Apr 2026 04:04:46 +0200 Subject: [PATCH 11/17] fix: update MenuRow component to support onPress prop and replace href with WebBrowser links in SettingsScreen --- app/(protected)/(tabs)/settings.tsx | 40 ++++++++++++++++++++++++++--- components/menuRow.tsx | 20 ++++++++------- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/app/(protected)/(tabs)/settings.tsx b/app/(protected)/(tabs)/settings.tsx index fee4857..1b9345d 100644 --- a/app/(protected)/(tabs)/settings.tsx +++ b/app/(protected)/(tabs)/settings.tsx @@ -7,6 +7,7 @@ import { useAuth } from "@/lib/auth/AuthProvider"; import { createT } from "@/i18n"; import Constants from "expo-constants"; import { MenuRow } from "@/components/menuRow"; +import * as WebBrowser from "expo-web-browser"; const t = createT("auth.settings"); const version = Constants.expoConfig?.version; @@ -74,7 +75,18 @@ export default function SettingsScreen() { divider={divider} iconColor={iconColor} textColor={theme.text} - href="/settings/apperance" + onPress={() => + WebBrowser.openBrowserAsync( + "https://mindsofd23b.github.io/Landing-Elysio/termsandconditions/", + { + presentationStyle: + WebBrowser.WebBrowserPresentationStyle.FORM_SHEET, + controlsColor: theme.primary, + toolbarColor: theme.background, + enableBarCollapsing: true, + }, + ) + } /> + WebBrowser.openBrowserAsync( + "https://mindsofd23b.github.io/Landing-Elysio/privacypolicy/", + { + presentationStyle: + WebBrowser.WebBrowserPresentationStyle.FORM_SHEET, + controlsColor: theme.primary, + toolbarColor: theme.background, + enableBarCollapsing: true, + }, + ) + } /> { + WebBrowser.openBrowserAsync( + "https://mindsofd23b.github.io/Landing-Elysio/aboutus/", + { + presentationStyle: + WebBrowser.WebBrowserPresentationStyle.FORM_SHEET, + controlsColor: theme.primary, + toolbarColor: theme.background, + enableBarCollapsing: true, + }, + ); + }} /> void; href?: never } | { href: Href; onPress?: never }); + export function MenuRow({ icon: Icon, label, divider, iconColor, textColor, + onPress, href, -}: { - icon: React.ElementType; - label: string; - divider: string; - iconColor: string; - textColor: string; - href: Href; -}) { +}: MenuRowProps) { const { theme } = useTheme(); return ( router.push(href)} + onPress={onPress || (() => router.push(href))} style={({ pressed }) => [ styles.row, { borderBottomColor: divider, width: "100%" }, From 954dcd451d63cae1894c220eb40de291aa7a41a5 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Mon, 20 Apr 2026 09:52:45 +0200 Subject: [PATCH 12/17] feat: enhance app layout and add server status info screen --- app.json | 2 +- app/(protected)/(tabs)/index.tsx | 71 ++++++- app/(protected)/_layout.tsx | 26 ++- app/(protected)/info.tsx | 136 ++++++++++++++ app/(protected)/subscriptions/index.tsx | 22 ++- app/(protected)/videocall/index.tsx | 57 +++++- app/_layout.tsx | 38 +++- components/ServerStatusSheet.tsx | 238 ++++++++++++++++++++++++ lib/theme/theme.ts | 4 + 9 files changed, 572 insertions(+), 22 deletions(-) create mode 100644 app/(protected)/info.tsx create mode 100644 components/ServerStatusSheet.tsx diff --git a/app.json b/app.json index e088df4..b67edd2 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,6 @@ { "expo": { - "name": "Elysio", + "name": "Elysio - Dating", "slug": "elysio", "version": "0.0.1", "orientation": "portrait", diff --git a/app/(protected)/(tabs)/index.tsx b/app/(protected)/(tabs)/index.tsx index 2794f2c..46a4a5f 100644 --- a/app/(protected)/(tabs)/index.tsx +++ b/app/(protected)/(tabs)/index.tsx @@ -4,16 +4,16 @@ import { Animated, Dimensions, Easing, - Pressable, StyleSheet, View, Text, + Pressable, } from "react-native"; import { useTheme } from "@/lib/theme/context"; import { Theme } from "@/lib/theme/theme"; import { router } from "expo-router"; import { CONSTANTS, PlanType } from "@/lib/constants"; -import { Heart } from "lucide-react-native"; +import { CloudOff, Heart } from "lucide-react-native"; import { useAuthFetch } from "@/hooks/useAuthFetch"; import * as StoreReview from "expo-store-review"; @@ -80,7 +80,7 @@ const images = [ { id: "l9", col: "left", - uri: "https://images.unsplash.com/photo-1583185136875-8ac7cae3ef13?q=80&w=687&auto=format&fit=crop", + uri: "https://images.unsplash.com/photo-1591711696773-c4b7fe4d3d74?q=80&w=2342&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", h: 155, }, { @@ -406,6 +406,53 @@ export default function Index() { /> + router.push("/(protected)/info")} + > + + + + + + Offline + + + + + + @@ -501,6 +548,14 @@ const makeStyles = (theme: Theme) => ring2: { width: 260, height: 260, borderWidth: 6, opacity: 0.9 }, ring3: { width: 320, height: 320, borderWidth: 4, opacity: 0.75 }, + topInidicator: { + position: "absolute", + top: 8, + left: 0, + right: 0, + alignItems: "center", + }, + bottomIndicator: { position: "absolute", bottom: 8, @@ -515,6 +570,16 @@ const makeStyles = (theme: Theme) => backgroundColor: theme.base + "80", elevation: 0.9, }, + indicatorPillOrange: { + flexDirection: "row", + alignItems: "center", + gap: 4, + backgroundColor: theme.orange, + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 99, + elevation: 0.9, + }, indicatorText: { color: theme.white, fontWeight: "600", diff --git a/app/(protected)/_layout.tsx b/app/(protected)/_layout.tsx index 0fc95dd..1e9ab54 100644 --- a/app/(protected)/_layout.tsx +++ b/app/(protected)/_layout.tsx @@ -1,10 +1,15 @@ -import { Redirect, Stack } from "expo-router"; +import { Redirect, router, Stack } from "expo-router"; import { useAuth } from "@/lib/auth/AuthProvider"; import { usePushNotifications } from "@/hooks/usePushNotifications"; -import { ActivityIndicator, View } from "react-native"; +import { ActivityIndicator, Pressable, StyleSheet, Text, View } from "react-native"; +import { GlassView } from "expo-glass-effect"; +import { X } from "lucide-react-native"; +import { useTheme } from "@/lib/theme/context"; export default function ProtectedLayout() { const { isAuthenticated, isLoading } = useAuth(); + const { theme } = useTheme(); + usePushNotifications(); if (isLoading) { @@ -19,5 +24,20 @@ export default function ProtectedLayout() { return ; } - return ; + return ( + + + + ); } diff --git a/app/(protected)/info.tsx b/app/(protected)/info.tsx new file mode 100644 index 0000000..f5eefea --- /dev/null +++ b/app/(protected)/info.tsx @@ -0,0 +1,136 @@ +import { + AlertBanner, + DetailCard, + ACCENT, + RecheckPill, + SectionHeader, + SheetHeader, + StatusCard, + type DetailItem, + type StatusItem, +} from "@/components/ServerStatusSheet"; +import { + BarChart3, + BookMarked, + CloudOff, + Eye, + Pencil, + RefreshCw, + Shield, + X, +} from "lucide-react-native"; +import { useEffect, useState } from "react"; +import { Pressable, ScrollView, StyleSheet } from "react-native"; +import { router } from "expo-router"; +import { useTheme } from "@/lib/theme/context"; + +const RECHECK_SECONDS = 20; + +const DETAILS: DetailItem[] = [ + { + icon: , + title: "Your data is safe", + description: "All your messages are stored locally on this device.", + }, + { + icon: , + title: "Automatic sync", + description: "Any changes you make will sync automatically when servers return.", + }, +]; + +const WORKS: StatusItem[] = [ + { + icon: , + label: "Viewing messages", + works: true, + }, + { icon: , label: "Reading old chats", works: true }, + { + icon: , + label: "Tracking activity", + works: true, + }, + { + icon: , + label: "Saved content", + works: true, + }, +]; + +const LIMITED: StatusItem[] = [ + { + icon: , + label: "Cross-device sync", + works: false, + }, + { + icon: , + label: "Sending messages", + works: false, + }, +]; + +export default function InfoScreen() { + const [seconds, setSeconds] = useState(RECHECK_SECONDS); + const { theme } = useTheme(); + + useEffect(() => { + const interval = setInterval(() => { + setSeconds((s) => (s <= 1 ? RECHECK_SECONDS : s - 1)); + }, 1000); + return () => clearInterval(interval); + }, []); + + return ( + + } + title="Server Status" + right={ + router.back()} style={styles.closeButton}> + + + } + /> + + + + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + scroll: { + flex: 1, + }, + content: { + padding: 16, + paddingBottom: 48, + }, + closeButton: { + width: 32, + height: 32, + borderRadius: 999, + backgroundColor: "rgba(0,0,0,0.08)", + alignItems: "center", + justifyContent: "center", + }, +}); diff --git a/app/(protected)/subscriptions/index.tsx b/app/(protected)/subscriptions/index.tsx index 4159bef..1799c42 100644 --- a/app/(protected)/subscriptions/index.tsx +++ b/app/(protected)/subscriptions/index.tsx @@ -48,9 +48,9 @@ const PLANS: Plan[] = [ name: "Basic", nameItalic: "plan", tagline: "One request at a time", - monthlyPrice: 5.99, - yearlyMonthPrice: 4.99, - yearlyPrice: 59.99, + monthlyPrice: 5.9, + yearlyMonthPrice: 4.9, + yearlyPrice: 58.8, features: [ "Up to 10 video calls per day", "Basic profile customization", @@ -64,9 +64,9 @@ const PLANS: Plan[] = [ name: "Premium", nameItalic: "plan", tagline: "Double your output 2×", - monthlyPrice: 12.99, - yearlyMonthPrice: 10.49, - yearlyPrice: 125.99, + monthlyPrice: 12.9, + yearlyMonthPrice: 10.9, + yearlyPrice: 130.8, badge: "BEST VALUE", highlight: true, features: [ @@ -84,9 +84,9 @@ const PLANS: Plan[] = [ name: "Gold", nameItalic: "plan", tagline: "Fitting your individual needs", - monthlyPrice: 29.99, - yearlyMonthPrice: 24.99, - yearlyPrice: 299.99, + monthlyPrice: 29.9, + yearlyMonthPrice: 24.9, + yearlyPrice: 298.8, features: [ "Everything in Premium", "Up to 100 video calls per day", @@ -242,7 +242,9 @@ function PlanCard({ - $ + + CHF + {whole} diff --git a/app/(protected)/videocall/index.tsx b/app/(protected)/videocall/index.tsx index f4466e7..f5ef4ef 100644 --- a/app/(protected)/videocall/index.tsx +++ b/app/(protected)/videocall/index.tsx @@ -18,6 +18,25 @@ registerGlobals(); const BASE_URL = process.env.EXPO_PUBLIC_BACKEND_URL || "https://elysio.jamiepoeffel.ch"; export default function VideoCall() { + const ICEBREAKERS = [ + "What's the weirdest thing you've ever eaten?", + "If you could live in any movie universe, which would you pick?", + "What's a skill you're secretly proud of?", + "What's the most spontaneous thing you've ever done?", + "If you had to eat one meal forever, what would it be?", + "What's your most controversial food opinion?", + "Would you rather explore space or the deep ocean?", + "What's the best trip you've ever been on?", + "What did you want to be as a kid?", + "What's your go-to karaoke song?", + "What's a hobby you've always wanted to try?", + "What's the last thing that made you laugh out loud?", + ]; + + const [icebreakerIndex, setIcebreakerIndex] = useState(() => + Math.floor(Math.random() * ICEBREAKERS.length), + ); + const { token } = useAuth(); const [started, setStarted] = useState(false); const [localUrl, setLocalUrl] = useState(null); @@ -560,10 +579,13 @@ export default function VideoCall() { } function handleIcebreaker() { - Alert.alert( - "Icebreaker", - "Hier kannst du Tipps oder einen kurzen Gesprächsstarter anzeigen.", - ); + setIcebreakerIndex((prev) => { + let next; + do { + next = Math.floor(Math.random() * ICEBREAKERS.length); + } while (next === prev && ICEBREAKERS.length > 1); + return next; + }); } useEffect(() => { @@ -687,6 +709,12 @@ export default function VideoCall() { )} + + + {ICEBREAKERS[icebreakerIndex]} + + + @@ -905,4 +933,25 @@ const styles = StyleSheet.create({ fontSize: 14, marginBottom: 6, }, + icebreakerBubble: { + position: "absolute", + left: 14, + bottom: 110, + maxWidth: 200, + backgroundColor: "rgba(255,255,255,0.93)", + borderRadius: 14, + paddingHorizontal: 13, + paddingVertical: 10, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.18, + shadowRadius: 6, + elevation: 4, + }, + icebreakerText: { + color: "#111", + fontSize: 13, + fontWeight: "500", + lineHeight: 18, + }, }); diff --git a/app/_layout.tsx b/app/_layout.tsx index e79ea5a..86647bd 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -4,11 +4,12 @@ import { SafeAreaProvider } from "react-native-safe-area-context"; import SafeAreaWrapper from "@/components/SafeArea"; import { AuthProvider, useAuth } from "@/lib/auth/AuthProvider"; import { useEffect, useMemo, useState } from "react"; -import { View, LogBox } from "react-native"; +import { View, LogBox, Platform } from "react-native"; import * as SplashScreen from "expo-splash-screen"; import { useCacheFetch } from "@/hooks/useCacheFetch"; import { minToMs } from "@/utils/formatTime"; import * as Sentry from "@sentry/react-native"; +import Purchases, { LOG_LEVEL } from "react-native-purchases"; LogBox.ignoreAllLogs(); @@ -43,9 +44,44 @@ function AppContent() { ttlMs: minToMs(10), }); + async function getCustomerInfo() { + try { + const customerInfo = await Purchases.getCustomerInfo(); + console.log("Customer Info:", customerInfo); + } catch (error) { + console.error("Error fetching customer info:", error); + } + } + + async function getOfferings() { + const offerings = await Purchases.getOfferings(); + + if ( + offerings.current !== null && + offerings.current.availablePackages.length !== 0 + ) { + console.log("Offerings:", JSON.stringify(offerings, null, 2)); + } + } + useEffect(() => { if (isLoading) return; + Purchases.setLogLevel(LOG_LEVEL.DEBUG); + + if (Platform.OS === "ios") { + Purchases.configure({ + apiKey: "appl_HHPNNqzuCyRKMvuLLtZFloXfaIA", + }); + } else if (Platform.OS === "android") { + Purchases.configure({ + apiKey: "appl_9c8b1cbdcbb849d0a1e7cdd7f", + }); + } + + getCustomerInfo(); + getOfferings(); + async function prepare() { try { if (token) await cache(); diff --git a/components/ServerStatusSheet.tsx b/components/ServerStatusSheet.tsx new file mode 100644 index 0000000..469dc5f --- /dev/null +++ b/components/ServerStatusSheet.tsx @@ -0,0 +1,238 @@ +import { AlertTriangle, CheckCircle2, CloudOff } from "lucide-react-native"; +import { ActivityIndicator, StyleSheet, Text, View, type ViewStyle } from "react-native"; + +export const ACCENT = "#d97706"; + +// ─── Types ─────────────────────────────────────────────────────────────────── + +export type DetailItem = { + icon: React.ReactNode; + title: string; + description: string; +}; + +export type StatusItem = { + icon: React.ReactNode; + label: string; + works: boolean; +}; + +export type StatusSection = { + title: string; + items: StatusItem[]; +}; + +export type DetailSection = { + title: string; + items: DetailItem[]; +}; + +// ─── RecheckPill ───────────────────────────────────────────────────────────── + +export function RecheckPill({ seconds }: { seconds: number }) { + return ( + + + + + Rechecking in {seconds}s + + + + ); +} + +// ─── AlertBanner ───────────────────────────────────────────────────────────── + +export function AlertBanner({ title, subtitle }: { title: string; subtitle: string }) { + return ( + + + + {title} + {subtitle} + + + ); +} + +// ─── SectionHeader ─────────────────────────────────────────────────────────── + +export function SectionHeader({ title }: { title: string }) { + return {title}; +} + +// ─── DetailCard ────────────────────────────────────────────────────────────── + +export function DetailCard({ items }: { items: DetailItem[] }) { + return ( + + {items.map((item, i) => ( + + ))} + + ); +} + +function DetailRow({ icon, title, description, last }: DetailItem & { last: boolean }) { + return ( + + {icon} + + {title} + {description} + + + ); +} + +// ─── StatusCard ────────────────────────────────────────────────────────────── + +export function StatusCard({ items }: { items: StatusItem[] }) { + return ( + + {items.map((item, i) => ( + + ))} + + ); +} + +function StatusRow({ icon, label, works, last }: StatusItem & { last: boolean }) { + return ( + + {icon} + {label} + {works ? ( + + ) : ( + + )} + + ); +} + +// ─── SheetHeader ───────────────────────────────────────────────────────────── + +export function SheetHeader({ + icon, + title, + right, +}: { + icon: React.ReactNode; + title: string; + right?: React.ReactNode; +}) { + return ( + + {icon} + {title} + {right} + + ); +} + +// ─── Styles ────────────────────────────────────────────────────────────────── + +const styles = StyleSheet.create({ + pillRow: { + alignItems: "center", + marginBottom: 16, + }, + pill: { + flexDirection: "row", + alignItems: "center", + gap: 6, + paddingHorizontal: 14, + paddingVertical: 7, + borderRadius: 20, + backgroundColor: ACCENT + "30", + }, + pillText: { + fontSize: 13, + fontWeight: "500", + }, + banner: { + flexDirection: "row", + gap: 10, + alignItems: "flex-start", + padding: 14, + borderRadius: 14, + borderWidth: 1, + marginBottom: 24, + }, + bannerTitle: { + fontWeight: "600", + fontSize: 14, + marginBottom: 2, + }, + bannerSub: { + fontSize: 13, + opacity: 0.75, + }, + sectionHeader: { + fontSize: 14, + color: "rgba(100,100,100,0.9)", + marginBottom: 8, + marginLeft: 2, + fontWeight: "400", + }, + card: { + backgroundColor: "rgba(255,255,255,0.6)", + borderRadius: 16, + marginBottom: 24, + overflow: "hidden", + }, + row: { + flexDirection: "row", + alignItems: "center", + gap: 12, + padding: 14, + }, + rowDivider: { + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: "rgba(0,0,0,0.08)", + }, + rowIcon: { + width: 28, + alignItems: "center", + }, + rowTitle: { + fontSize: 14, + fontWeight: "600", + color: "#1a1a1a", + marginBottom: 1, + }, + rowDesc: { + fontSize: 13, + color: "#555", + lineHeight: 18, + marginTop: 2, + }, + header: { + flexDirection: "row", + alignItems: "center", + marginBottom: 20, + paddingHorizontal: 4, + }, + headerIcon: { + width: 36, + height: 36, + borderRadius: 999, + backgroundColor: ACCENT + "20", + alignItems: "center", + justifyContent: "center", + }, + headerTitle: { + flex: 1, + marginLeft: 10, + fontSize: 17, + fontWeight: "700", + color: "#1a1a1a", + }, +}); diff --git a/lib/theme/theme.ts b/lib/theme/theme.ts index 92a1151..7982899 100644 --- a/lib/theme/theme.ts +++ b/lib/theme/theme.ts @@ -12,6 +12,8 @@ export const colors = { card: "#000000" + "15", cardAccent: "#000000" + "2A", grayscale: "#9CA3AF", + gray: "#ced4da", + orange: "#f3722c", }, dark: { background: "#1E1E1E", @@ -26,6 +28,8 @@ export const colors = { card: "#FFFFFF" + "15", cardAccent: "#FFFFFF" + "2A", grayscale: "#9CA3AF", + gray: "#ced4da", + orange: "#f3722c", }, }; From 9b91ab6d55ded07719541dfdf643f8cdff408442 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Mon, 20 Apr 2026 10:55:46 +0200 Subject: [PATCH 13/17] fix: refactor subscription plans and update checkout component to use new plans structure --- app/(protected)/subscriptions/checkout.tsx | 64 ++----------------- app/(protected)/subscriptions/index.tsx | 72 +-------------------- app/(protected)/subscriptions/plans.ts | 74 ++++++++++++++++++++++ components/ChatComponent.tsx | 3 +- 4 files changed, 82 insertions(+), 131 deletions(-) create mode 100644 app/(protected)/subscriptions/plans.ts diff --git a/app/(protected)/subscriptions/checkout.tsx b/app/(protected)/subscriptions/checkout.tsx index 09b0c66..9a5e370 100644 --- a/app/(protected)/subscriptions/checkout.tsx +++ b/app/(protected)/subscriptions/checkout.tsx @@ -12,61 +12,7 @@ import { useLocalSearchParams } from "expo-router"; import { useTheme } from "@/lib/theme/context"; import BackWrapper from "@/components/backwrapper"; import { ShieldCheck, CreditCard, Lock } from "lucide-react-native"; - -type BillingCycle = "monthly" | "yearly"; - -interface Plan { - id: string; - name: string; - monthlyPrice: number; - yearlyPrice: number; - yearlyMonthPrice: number; - features: string[]; -} - -const PLANS: Record = { - basic: { - id: "basic", - name: "Basic", - monthlyPrice: 5.99, - yearlyMonthPrice: 4.99, - yearlyPrice: 59.99, - features: [ - "Up to 10 video calls per day", - "Basic profile customization", - "Text chat after matching", - "Standard support", - ], - }, - premium: { - id: "premium", - name: "Premium", - monthlyPrice: 12.99, - yearlyMonthPrice: 10.49, - yearlyPrice: 125.99, - features: [ - "Everything in Basic", - "Up to 30 video calls per day", - "See who liked you", - "Priority in discovery feed", - "Read receipts in chat", - "Standard support", - ], - }, - gold: { - id: "gold", - name: "Gold", - monthlyPrice: 29.99, - yearlyMonthPrice: 24.99, - yearlyPrice: 299.99, - features: [ - "Everything in Premium", - "Up to 100 video calls per day", - "Profile boost every week", - "Standard support", - ], - }, -}; +import { type BillingCycle, type Plan, PLANS_MAP as PLANS } from "./plans"; // ─── Order Summary ───────────────────────────────────────────────────────────── @@ -95,7 +41,7 @@ function OrderSummary({ plan, billing }: { plan: Plan; billing: BillingCycle }) )} - ${price.toFixed(2)} + CHF {price.toFixed(2)} @@ -106,13 +52,13 @@ function OrderSummary({ plan, billing }: { plan: Plan; billing: BillingCycle }) Total today - ${price.toFixed(2)} + CHF {price.toFixed(2)} {isYearly && ( - ${perMonth.toFixed(2)}/mo · billed annually + CHF {perMonth.toFixed(2)}/mo · billed annually )} @@ -247,7 +193,7 @@ export default function Checkout() { } style={{ color: theme.primary }} > - Terms of Service + Terms and Conditions {" "} and{" "} = Object.fromEntries( + PLANS.map((p) => [p.id, p]), +); diff --git a/components/ChatComponent.tsx b/components/ChatComponent.tsx index 4cb30bd..9d29c7f 100644 --- a/components/ChatComponent.tsx +++ b/components/ChatComponent.tsx @@ -9,6 +9,7 @@ interface ChatComponentProps { chat: Chat; onPress: () => void; } + // Design made with Pinterest and ChatGPT export default function ChatComponent({ chat, onPress }: ChatComponentProps) { const { theme } = useTheme(); @@ -51,7 +52,7 @@ export default function ChatComponent({ chat, onPress }: ChatComponentProps) { {formatTime(chat.updatedAt)} - + {chat.lastMessage ? chat.lastMessage : "No messages yet"} From 3bac3522b5133c1d627a25a1ba12c6afac59582b Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Mon, 20 Apr 2026 11:31:23 +0200 Subject: [PATCH 14/17] [feat] adding payment --- app/(protected)/subscriptions/checkout.tsx | 86 +++++++++++++++++---- assets/crown.png | Bin 0 -> 11787 bytes assets/heart.png | Bin 0 -> 12302 bytes assets/zap.png | Bin 0 -> 10843 bytes 4 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 assets/crown.png create mode 100644 assets/heart.png create mode 100644 assets/zap.png diff --git a/app/(protected)/subscriptions/checkout.tsx b/app/(protected)/subscriptions/checkout.tsx index 9a5e370..8575060 100644 --- a/app/(protected)/subscriptions/checkout.tsx +++ b/app/(protected)/subscriptions/checkout.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import * as WebBrowser from "expo-web-browser"; import { View, @@ -13,14 +13,27 @@ import { useTheme } from "@/lib/theme/context"; import BackWrapper from "@/components/backwrapper"; import { ShieldCheck, CreditCard, Lock } from "lucide-react-native"; import { type BillingCycle, type Plan, PLANS_MAP as PLANS } from "./plans"; +import Purchases, { PurchasesPackage } from "react-native-purchases"; // ─── Order Summary ───────────────────────────────────────────────────────────── -function OrderSummary({ plan, billing }: { plan: Plan; billing: BillingCycle }) { +function OrderSummary({ + plan, + billing, + rcPriceString, + rcPerMonthString, +}: { + plan: Plan; + billing: BillingCycle; + rcPriceString?: string; + rcPerMonthString?: string; +}) { const { theme } = useTheme(); const isYearly = billing === "yearly"; - const price = isYearly ? plan.yearlyPrice : plan.monthlyPrice; - const perMonth = isYearly ? plan.yearlyMonthPrice : plan.monthlyPrice; + const fallbackPrice = isYearly ? plan.yearlyPrice : plan.monthlyPrice; + const fallbackPerMonth = isYearly ? plan.yearlyMonthPrice : plan.monthlyPrice; + const displayPrice = rcPriceString ?? `CHF ${fallbackPrice.toFixed(2)}`; + const displayPerMonth = rcPerMonthString ?? `CHF ${fallbackPerMonth.toFixed(2)}`; return ( @@ -41,7 +54,7 @@ function OrderSummary({ plan, billing }: { plan: Plan; billing: BillingCycle }) )} - CHF {price.toFixed(2)} + {displayPrice} @@ -52,13 +65,13 @@ function OrderSummary({ plan, billing }: { plan: Plan; billing: BillingCycle }) Total today - CHF {price.toFixed(2)} + {displayPrice} {isYearly && ( - CHF {perMonth.toFixed(2)}/mo · billed annually + {displayPerMonth}/mo · billed annually )} @@ -122,16 +135,54 @@ export default function Checkout() { billing: BillingCycle; }>(); const [loading, setLoading] = useState(false); + const [pkg, setPkg] = useState(null); + const [offeringLoading, setOfferingLoading] = useState(true); const plan = PLANS[planId ?? "premium"]; const billingCycle: BillingCycle = billing === "yearly" ? "yearly" : "monthly"; - const handleSubscribe = () => { + useEffect(() => { + async function loadPackage() { + try { + const offerings = await Purchases.getOfferings(); + const offering = offerings.all[planId ?? "premium"] ?? offerings.current; + if (offering) { + const selected = + billingCycle === "yearly" + ? (offering.annual ?? offering.availablePackages[0]) + : (offering.monthly ?? offering.availablePackages[0]); + setPkg(selected ?? null); + } + } catch (error) { + console.error("Failed to load offerings:", error); + } finally { + setOfferingLoading(false); + } + } + loadPackage(); + }, [planId, billingCycle]); + + const handleSubscribe = async () => { + if (!pkg) return; setLoading(true); - // TODO: integrate payment provider - setTimeout(() => setLoading(false), 2000); + try { + const { customerInfo } = await Purchases.purchasePackage(pkg); + if (typeof customerInfo.entitlements.active[planId] !== "undefined") { + console.log("successfully subscribed to", planId); + } + } catch (error) { + console.error("Subscription error:", error); + } finally { + setLoading(false); + } }; + const rcPriceString = pkg?.product.priceString; + const rcPerMonthString = + billingCycle === "yearly" && pkg + ? `${pkg.product.currencyCode} ${(pkg.product.price / 12).toFixed(2)}` + : undefined; + return ( - + @@ -164,13 +220,13 @@ export default function Checkout() { style={[ styles.ctaBtn, { backgroundColor: theme.primary }, - loading && styles.ctaBtnDisabled, + (loading || offeringLoading || !pkg) && styles.ctaBtnDisabled, ]} onPress={handleSubscribe} activeOpacity={0.85} - disabled={loading} + disabled={loading || offeringLoading || !pkg} > - {loading ? ( + {loading || offeringLoading ? ( ) : ( Confirm & Subscribe @@ -220,8 +276,6 @@ export default function Checkout() { ); } -// ─── Styles ──────────────────────────────────────────────────────────────────── - const styles = StyleSheet.create({ scroll: { paddingHorizontal: 20, diff --git a/assets/crown.png b/assets/crown.png new file mode 100644 index 0000000000000000000000000000000000000000..857319a673d5879abf1789043addb71f93dce78e GIT binary patch literal 11787 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 L8EQ7FBclHYy!P>n literal 0 HcmV?d00001 diff --git a/assets/zap.png b/assets/zap.png new file mode 100644 index 0000000000000000000000000000000000000000..5f40a07e1aa05bd5fbc8c91701914b167e1a3d9d GIT binary patch literal 10843 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 literal 0 HcmV?d00001 From b763f7d27e66db44715965204e05c629a9500d6d Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Mon, 20 Apr 2026 21:36:56 +0200 Subject: [PATCH 15/17] feat: implement server status checks and add outage/update screens --- app.json | 4 + app/(auth)/outage.tsx | 311 ++++++++++++++++++++++++++++++++++++++ app/(auth)/update.tsx | 110 ++++++++++++++ app/_layout.tsx | 58 ++++++- lib/auth/AuthProvider.tsx | 5 - package-lock.json | 189 ----------------------- 6 files changed, 478 insertions(+), 199 deletions(-) create mode 100644 app/(auth)/outage.tsx create mode 100644 app/(auth)/update.tsx diff --git a/app.json b/app.json index b67edd2..4d08e97 100644 --- a/app.json +++ b/app.json @@ -14,6 +14,9 @@ "NSMicrophoneUsageDescription": "Allow access to microphone for calls", "ITSAppUsesNonExemptEncryption": false }, + "entitlements": { + "com.apple.developer.in-app-payments": [] + }, "bundleIdentifier": "com.mindsofd23b.elysio", "usesAppleSignIn": true }, @@ -59,6 +62,7 @@ "expo-secure-store", "expo-localization", "expo-image", + "react-native-purchases", [ "@sentry/react-native/expo", { diff --git a/app/(auth)/outage.tsx b/app/(auth)/outage.tsx new file mode 100644 index 0000000..7226bff --- /dev/null +++ b/app/(auth)/outage.tsx @@ -0,0 +1,311 @@ +import { useEffect, useRef, useState } from "react"; +import { View, Text, StyleSheet, Animated, Easing } from "react-native"; +import Svg, { Path } from "react-native-svg"; +import * as Haptics from "expo-haptics"; +import { useTheme } from "@/lib/theme/context"; + +const RETRY_INTERVAL = 20; +const CRACK_LENGTH = 50; + +// viewBox "0 0 64 60" — symmetric heart, bottom point (32,57), top-center dip (32,14) +// Left bump peak (19,4), right bump peak (45,4), left/right extremes x=3/x=61 +const LEFT_PATH = "M 32 57 C 9 44 3 30 3 20 C 3 10 11 4 19 4 C 25 4 29 8 32 14"; +const RIGHT_PATH = "M 32 14 C 35 8 39 4 45 4 C 53 4 61 10 61 20 C 61 30 55 44 32 57"; +const CRACK_PATH = "M 32 15 L 29 26 L 35 31 L 28 43 L 33 49 L 32 57"; + +const AnimatedPath = Animated.createAnimatedComponent(Path); + +function BrokenHeart({ color }: { color: string }) { + const pulse = useRef(new Animated.Value(1)).current; + const splitLeft = useRef(new Animated.Value(0)).current; + const splitRight = useRef(new Animated.Value(0)).current; + const splitY = useRef(new Animated.Value(0)).current; + const rotateLeft = useRef(new Animated.Value(0)).current; + const rotateRight = useRef(new Animated.Value(0)).current; + const crackDash = useRef(new Animated.Value(CRACK_LENGTH)).current; + const crackOpacity = useRef(new Animated.Value(0)).current; + + useEffect(() => { + function run() { + pulse.setValue(1); + splitLeft.setValue(0); + splitRight.setValue(0); + splitY.setValue(0); + rotateLeft.setValue(0); + rotateRight.setValue(0); + crackDash.setValue(CRACK_LENGTH); + crackOpacity.setValue(0); + + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + setTimeout(() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light), 310); + setTimeout( + () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy), + 1120, + ); + + Animated.sequence([ + // Two heartbeats + Animated.sequence([ + Animated.timing(pulse, { + toValue: 1.2, + duration: 180, + easing: Easing.out(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(pulse, { + toValue: 0.92, + duration: 130, + easing: Easing.in(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(pulse, { + toValue: 1.15, + duration: 160, + easing: Easing.out(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(pulse, { + toValue: 1, + duration: 180, + easing: Easing.in(Easing.quad), + useNativeDriver: true, + }), + ]), + Animated.delay(250), + // Crack draws itself on + Animated.parallel([ + Animated.timing(crackOpacity, { + toValue: 1, + duration: 120, + useNativeDriver: false, + }), + Animated.timing(crackDash, { + toValue: 0, + duration: 380, + easing: Easing.out(Easing.cubic), + useNativeDriver: false, + }), + ]), + Animated.delay(120), + // Break apart — halves fly open + crack fades + Animated.parallel([ + Animated.timing(splitLeft, { + toValue: -12, + duration: 520, + easing: Easing.out(Easing.back(1.2)), + useNativeDriver: true, + }), + Animated.timing(splitRight, { + toValue: 12, + duration: 520, + easing: Easing.out(Easing.back(1.2)), + useNativeDriver: true, + }), + Animated.timing(rotateLeft, { + toValue: -14, + duration: 520, + easing: Easing.out(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(rotateRight, { + toValue: 14, + duration: 520, + easing: Easing.out(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(splitY, { + toValue: 6, + duration: 520, + easing: Easing.out(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(crackOpacity, { + toValue: 0, + duration: 300, + easing: Easing.in(Easing.quad), + useNativeDriver: false, + }), + ]), + // Hold broken + Animated.delay(1400), + // Reassemble + Animated.parallel([ + Animated.timing(splitLeft, { + toValue: 0, + duration: 420, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(splitRight, { + toValue: 0, + duration: 420, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(rotateLeft, { + toValue: 0, + duration: 420, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(rotateRight, { + toValue: 0, + duration: 420, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true, + }), + Animated.timing(splitY, { + toValue: 0, + duration: 420, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true, + }), + ]), + Animated.delay(900), + ]).start(() => run()); + } + + run(); + }, []); + + const leftTransform = [ + { translateX: splitLeft }, + { translateY: splitY }, + { + rotate: rotateLeft.interpolate({ + inputRange: [-14, 0], + outputRange: ["-14deg", "0deg"], + }), + }, + ]; + const rightTransform = [ + { translateX: splitRight }, + { translateY: splitY }, + { + rotate: rotateRight.interpolate({ + inputRange: [0, 14], + outputRange: ["0deg", "14deg"], + }), + }, + ]; + + return ( + + + + + + + + + + + + + + + + + + ); +} + +export default function OutageScreen() { + const { theme } = useTheme(); + const [countdown, setCountdown] = useState(RETRY_INTERVAL); + + useEffect(() => { + setCountdown(RETRY_INTERVAL); + const interval = setInterval(() => { + setCountdown((prev) => (prev <= 1 ? RETRY_INTERVAL : prev - 1)); + }, 1000); + return () => clearInterval(interval); + }, []); + + const s = styles(theme); + + return ( + + + We{"'"}re experiencing issues + + Our team is on it and working as fast as possible to get everything back + up. We apologize for the inconvenience. + + + Retrying in + {countdown}s + + + ); +} + +const styles = (theme: any) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: theme.background, + justifyContent: "center", + alignItems: "center", + paddingHorizontal: 32, + gap: 20, + }, + title: { + fontSize: 22, + fontWeight: "700", + color: theme.text, + textAlign: "center", + }, + body: { + fontSize: 15, + color: theme.grayscale, + textAlign: "center", + lineHeight: 22, + }, + retryBox: { + marginTop: 12, + flexDirection: "row", + alignItems: "center", + gap: 8, + backgroundColor: theme.card, + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 12, + }, + retryLabel: { + fontSize: 14, + color: theme.grayscale, + }, + retryCount: { + fontSize: 18, + fontWeight: "700", + color: theme.primary, + }, + }); diff --git a/app/(auth)/update.tsx b/app/(auth)/update.tsx new file mode 100644 index 0000000..a4cace6 --- /dev/null +++ b/app/(auth)/update.tsx @@ -0,0 +1,110 @@ +import { View, Text, StyleSheet, Animated, Easing } from "react-native"; +import { useEffect, useRef } from "react"; +import { useTheme } from "@/lib/theme/context"; + +interface UpdateScreenProps { + duration?: string; +} + +export default function UpdateScreen({ duration }: UpdateScreenProps) { + const { theme } = useTheme(); + const progressAnim = useRef(new Animated.Value(0)).current; + + useEffect(() => { + const pulse = Animated.loop( + Animated.sequence([ + Animated.timing(progressAnim, { + toValue: 1, + duration: 1200, + easing: Easing.inOut(Easing.ease), + useNativeDriver: true, + }), + Animated.timing(progressAnim, { + toValue: 0, + duration: 1200, + easing: Easing.inOut(Easing.ease), + useNativeDriver: true, + }), + ]), + ); + pulse.start(); + return () => pulse.stop(); + }, []); + + const opacity = progressAnim.interpolate({ + inputRange: [0, 1], + outputRange: [0.4, 1], + }); + + const s = styles(theme); + + return ( + + + ⚙️ + + Update in progress + + We{"'"}re updating Elysio to bring you new features and improvements. The + app will be back shortly. + + + Estimated time + {duration ?? "Unknown"} + + + ); +} + +const styles = (theme: any) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: theme.background, + justifyContent: "center", + alignItems: "center", + paddingHorizontal: 32, + gap: 20, + }, + icon: { + width: 64, + height: 64, + justifyContent: "center", + alignItems: "center", + marginBottom: 8, + }, + iconText: { + fontSize: 48, + }, + title: { + fontSize: 22, + fontWeight: "700", + color: theme.text, + textAlign: "center", + }, + body: { + fontSize: 15, + color: theme.grayscale, + textAlign: "center", + lineHeight: 22, + }, + durationBox: { + marginTop: 12, + flexDirection: "row", + alignItems: "center", + gap: 8, + backgroundColor: theme.card, + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 12, + }, + durationLabel: { + fontSize: 14, + color: theme.grayscale, + }, + durationValue: { + fontSize: 18, + fontWeight: "700", + color: theme.primary, + }, + }); diff --git a/app/_layout.tsx b/app/_layout.tsx index 86647bd..c946ed2 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -3,13 +3,19 @@ import BaseTheme from "@/providers/baseTheme"; import { SafeAreaProvider } from "react-native-safe-area-context"; import SafeAreaWrapper from "@/components/SafeArea"; import { AuthProvider, useAuth } from "@/lib/auth/AuthProvider"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { View, LogBox, Platform } from "react-native"; import * as SplashScreen from "expo-splash-screen"; import { useCacheFetch } from "@/hooks/useCacheFetch"; import { minToMs } from "@/utils/formatTime"; import * as Sentry from "@sentry/react-native"; import Purchases, { LOG_LEVEL } from "react-native-purchases"; +import { initCrypto } from "@/services/chat-crypto.client"; +import OutageScreen from "@/app/(auth)/outage"; +import UpdateScreen from "@/app/(auth)/update"; + +const SERVER_URL = "https://elysio.jamiepoeffel.ch"; +const SERVER_CHECK_INTERVAL = 20_000; LogBox.ignoreAllLogs(); @@ -36,7 +42,40 @@ SplashScreen.preventAutoHideAsync().catch(() => {}); function AppContent() { const [appIsReady, setAppIsReady] = useState(false); + const [serverStatus, setServerStatus] = useState< + "pending" | "ok" | "down" | "update" + >("pending"); + const [updateDuration, setUpdateDuration] = useState(undefined); const { isLoading, token } = useAuth(); + const serverCheckRef = useRef | null>(null); + + async function checkServer() { + try { + const res = await fetch(SERVER_URL, { method: "GET", cache: "no-store" }); + const text = await res.text(); + const trimmed = text.trim(); + if (res.ok && trimmed === "Hello World!") { + setServerStatus("ok"); + } else if (trimmed.startsWith("update")) { + const parts = trimmed.split(" "); + setUpdateDuration(parts[1] ?? undefined); + setServerStatus("update"); + } else { + setServerStatus("down"); + } + } catch { + setServerStatus("down"); + } + } + + useEffect(() => { + checkServer(); + serverCheckRef.current = setInterval(checkServer, SERVER_CHECK_INTERVAL); + return () => { + if (serverCheckRef.current) clearInterval(serverCheckRef.current); + }; + }, []); + const chatRequest = useMemo(() => ({ method: "GET" }), []); const [, , cache] = useCacheFetch("/chat/rooms", chatRequest, { cacheKey: "chat:rooms", @@ -65,7 +104,7 @@ function AppContent() { } useEffect(() => { - if (isLoading) return; + if (isLoading || serverStatus !== "ok") return; Purchases.setLogLevel(LOG_LEVEL.DEBUG); @@ -75,7 +114,7 @@ function AppContent() { }); } else if (Platform.OS === "android") { Purchases.configure({ - apiKey: "appl_9c8b1cbdcbb849d0a1e7cdd7f", + apiKey: "goog_vuioAmQKQpPnwGqxktfBuiqAKfz", }); } @@ -84,14 +123,17 @@ function AppContent() { async function prepare() { try { - if (token) await cache(); + if (token) { + await initCrypto(SERVER_URL, token); + await cache(); + } } catch { } finally { setAppIsReady(true); } } prepare(); - }, [isLoading, token]); + }, [isLoading, token, serverStatus]); useEffect(() => { if (appIsReady) { @@ -99,6 +141,12 @@ function AppContent() { } }, [appIsReady]); + if (serverStatus === "pending") return null; + + if (serverStatus === "down") return ; + + if (serverStatus === "update") return ; + if (!appIsReady || isLoading) return null; return ( diff --git a/lib/auth/AuthProvider.tsx b/lib/auth/AuthProvider.tsx index 4836157..b54ed3d 100644 --- a/lib/auth/AuthProvider.tsx +++ b/lib/auth/AuthProvider.tsx @@ -1,6 +1,5 @@ import { createContext, useContext, useEffect, useMemo, useState } from "react"; import AsyncStorage from "@react-native-async-storage/async-storage"; -import { initCrypto } from "@/services/chat-crypto.client"; import { get } from "@/utils/store"; import { initDeviceToken } from "@/services/notifications"; @@ -25,10 +24,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { try { const storedToken = await AsyncStorage.getItem(TOKEN_KEY); setToken(storedToken); - - if (storedToken) { - await initCrypto(BASE_URL, storedToken); - } } catch (error) { console.error("Failed to load auth token", error); setToken(null); diff --git a/package-lock.json b/package-lock.json index c21ed4c..077d320 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3761,107 +3761,6 @@ "node": ">= 20.19.4" } }, - "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.84.1.tgz", - "integrity": "sha512-vorvcvptGxtK0qTDCFQb+W3CU6oIhzcX5dduetWRBoAhXdthEQM0MQnF+GTXoXL8/luffKgy7PlZRG/WeI/oRQ==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.84.1" - }, - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/babel-preset": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.84.1.tgz", - "integrity": "sha512-3GpmCKk21f4oe32bKIdmkdn+WydvhhZL+1nsoFBGi30Qrq9vL16giKu31OcnWshYz139x+mVAvCyoyzgn8RXSw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/plugin-transform-typescript": "^7.25.2", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@react-native/babel-plugin-codegen": "0.84.1", - "babel-plugin-syntax-hermes-parser": "0.32.0", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/codegen": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", - "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.25.3", - "hermes-parser": "0.32.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "tinyglobby": "^0.2.15", - "yargs": "^17.6.2" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/codegen/node_modules/hermes-estree": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", - "extraneous": true, - "license": "MIT" - }, - "node_modules/@react-native/codegen/node_modules/hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.32.0" - } - }, "node_modules/@react-native/community-cli-plugin": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.4.tgz", @@ -4007,68 +3906,6 @@ "node": ">= 20.19.4" } }, - "node_modules/@react-native/js-polyfills": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", - "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", - "extraneous": true, - "license": "MIT", - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.84.1.tgz", - "integrity": "sha512-NswINguTz0eg1Dc0oGO/1dejXSr6iQaz8/NnCRn5HJdA3dGfqadS7zlYv0YjiWpgKgcW6uENaIEgJOQww0KSpw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.84.1", - "hermes-parser": "0.32.0", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", - "extraneous": true, - "license": "MIT" - }, - "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.32.0" - } - }, - "node_modules/@react-native/metro-config": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.84.1.tgz", - "integrity": "sha512-KlRawK4aXxRLlR3HYVfZKhfQp7sejQefQ/LttUWUkErhKO0AFt+yznoSLq7xwIrH9K3A3YwImHuFVtUtuDmurA==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@react-native/js-polyfills": "0.84.1", - "@react-native/metro-babel-transformer": "0.84.1", - "metro-config": "^0.83.3", - "metro-runtime": "^0.83.3" - }, - "engines": { - "node": ">= 20.19.4" - } - }, "node_modules/@react-native/normalize-colors": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.4.tgz", @@ -9194,32 +9031,6 @@ "node": ">=4" } }, - "node_modules/expo/node_modules/react-native-worklets": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.8.1.tgz", - "integrity": "sha512-oWP/lStsAHU6oYCaWDXrda/wOHVdhusQJz1e6x9gPnXdFf4ndNDAOtWCmk2zGrAnlapfyA3rM6PCQq94mPg9cw==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-classes": "^7.28.4", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/preset-typescript": "^7.27.1", - "convert-source-map": "^2.0.0", - "semver": "^7.7.3" - }, - "peerDependencies": { - "@babel/core": "*", - "@react-native/metro-config": "*", - "react": "*", - "react-native": "0.81 - 0.85" - } - }, "node_modules/expo/node_modules/restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", From 41c3e2188c64c878b5dc856e5b61538565e2d3c6 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Mon, 20 Apr 2026 21:53:00 +0200 Subject: [PATCH 16/17] [fix] package-lock.json sync --- package-lock.json | 339 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 274 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index aba4ff6..882b473 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "expo-font": "~55.0.4", "expo-haptics": "~55.0.14", "expo-image": "~55.0.8", + "expo-image-picker": "~55.0.18", "expo-linear-gradient": "~55.0.13", "expo-linking": "~55.0.13", "expo-localization": "~55.0.13", @@ -3761,6 +3762,107 @@ "node": ">= 20.19.4" } }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.84.1.tgz", + "integrity": "sha512-vorvcvptGxtK0qTDCFQb+W3CU6oIhzcX5dduetWRBoAhXdthEQM0MQnF+GTXoXL8/luffKgy7PlZRG/WeI/oRQ==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.84.1" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.84.1.tgz", + "integrity": "sha512-3GpmCKk21f4oe32bKIdmkdn+WydvhhZL+1nsoFBGi30Qrq9vL16giKu31OcnWshYz139x+mVAvCyoyzgn8RXSw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@react-native/babel-plugin-codegen": "0.84.1", + "babel-plugin-syntax-hermes-parser": "0.32.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", + "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", + "hermes-parser": "0.32.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "extraneous": true, + "license": "MIT" + }, + "node_modules/@react-native/codegen/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, "node_modules/@react-native/community-cli-plugin": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.4.tgz", @@ -3906,6 +4008,68 @@ "node": ">= 20.19.4" } }, + "node_modules/@react-native/js-polyfills": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", + "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.84.1.tgz", + "integrity": "sha512-NswINguTz0eg1Dc0oGO/1dejXSr6iQaz8/NnCRn5HJdA3dGfqadS7zlYv0YjiWpgKgcW6uENaIEgJOQww0KSpw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.84.1", + "hermes-parser": "0.32.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "extraneous": true, + "license": "MIT" + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, + "node_modules/@react-native/metro-config": { + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.84.1.tgz", + "integrity": "sha512-KlRawK4aXxRLlR3HYVfZKhfQp7sejQefQ/LttUWUkErhKO0AFt+yznoSLq7xwIrH9K3A3YwImHuFVtUtuDmurA==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@react-native/js-polyfills": "0.84.1", + "@react-native/metro-babel-transformer": "0.84.1", + "metro-config": "^0.83.3", + "metro-runtime": "^0.83.3" + }, + "engines": { + "node": ">= 20.19.4" + } + }, "node_modules/@react-native/normalize-colors": { "version": "0.83.4", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.4.tgz", @@ -4671,16 +4835,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", - "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.0.tgz", + "integrity": "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/type-utils": "8.58.2", - "@typescript-eslint/utils": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/type-utils": "8.59.0", + "@typescript-eslint/utils": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -4693,7 +4857,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.58.2", + "@typescript-eslint/parser": "^8.59.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -4708,15 +4872,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", - "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.0.tgz", + "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3" }, "engines": { @@ -4732,13 +4896,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", - "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.0.tgz", + "integrity": "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.2", - "@typescript-eslint/types": "^8.58.2", + "@typescript-eslint/tsconfig-utils": "^8.59.0", + "@typescript-eslint/types": "^8.59.0", "debug": "^4.4.3" }, "engines": { @@ -4753,13 +4917,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", - "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.0.tgz", + "integrity": "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2" + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4770,9 +4934,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", - "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.0.tgz", + "integrity": "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4786,14 +4950,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", - "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.0.tgz", + "integrity": "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/utils": "8.59.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -4810,9 +4974,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", - "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.0.tgz", + "integrity": "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4823,15 +4987,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", - "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.0.tgz", + "integrity": "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.58.2", - "@typescript-eslint/tsconfig-utils": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", + "@typescript-eslint/project-service": "8.59.0", + "@typescript-eslint/tsconfig-utils": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -4898,15 +5062,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", - "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.0.tgz", + "integrity": "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2" + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4921,12 +5085,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", - "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.0.tgz", + "integrity": "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/types": "8.59.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -8413,6 +8577,27 @@ } } }, + "node_modules/expo-image-loader": { + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-55.0.0.tgz", + "integrity": "sha512-NOjp56wDrfuA5aiNAybBIjqIn1IxKeGJ8CECWZncQ/GzjZfyTYAHTCyeApYkdKkMBLHINzI4BbTGSlbCa0fXXQ==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-image-picker": { + "version": "55.0.18", + "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-55.0.18.tgz", + "integrity": "sha512-lGpPGRu+7mE8qN0ma2boRsCmfOGbdHZ2bXTpWVeWly0JCZdogGlTrYFnhTqgS8+lmiRb/UCOs7iTm2P5Rra6kw==", + "license": "MIT", + "dependencies": { + "expo-image-loader": "~55.0.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-json-utils": { "version": "55.0.2", "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-55.0.2.tgz", @@ -9031,6 +9216,32 @@ "node": ">=4" } }, + "node_modules/expo/node_modules/react-native-worklets": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.8.1.tgz", + "integrity": "sha512-oWP/lStsAHU6oYCaWDXrda/wOHVdhusQJz1e6x9gPnXdFf4ndNDAOtWCmk2zGrAnlapfyA3rM6PCQq94mPg9cw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-classes": "^7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", + "convert-source-map": "^2.0.0", + "semver": "^7.7.3" + }, + "peerDependencies": { + "@babel/core": "*", + "@react-native/metro-config": "*", + "react": "*", + "react-native": "0.81 - 0.85" + } + }, "node_modules/expo/node_modules/restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -13331,9 +13542,9 @@ } }, "node_modules/patch-package/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -14993,14 +15204,14 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, @@ -17047,7 +17258,5 @@ } } } - } } - } } From 3c86566f575e1454dfa164364efa6af5ef6249c8 Mon Sep 17 00:00:00 2001 From: Jamie-Poeffel Date: Mon, 20 Apr 2026 21:55:51 +0200 Subject: [PATCH 17/17] fix: remove unused react-native-purchases dependency from app.json --- app.json | 1 - 1 file changed, 1 deletion(-) diff --git a/app.json b/app.json index 4d08e97..dfc7dee 100644 --- a/app.json +++ b/app.json @@ -62,7 +62,6 @@ "expo-secure-store", "expo-localization", "expo-image", - "react-native-purchases", [ "@sentry/react-native/expo", {