From 230f0e0c78be658b8f5c00a3667713e81582e333 Mon Sep 17 00:00:00 2001 From: Jose Felix Date: Fri, 3 Jan 2025 01:11:53 -0400 Subject: [PATCH] feat: support rtc connection between mobile and browser --- packages/mobile/app.json | 1 + packages/mobile/app/_layout.tsx | 15 +- .../mobile/app/onboarding/camera-scan.tsx | 132 ++++--- packages/mobile/hooks/use-state-ref.ts | 39 ++ packages/mobile/package.json | 11 +- packages/trpc/src/web-rtc.ts | 78 ++-- packages/web/pages/mobile-testing.tsx | 6 +- yarn.lock | 364 +++++++++++------- 8 files changed, 385 insertions(+), 261 deletions(-) create mode 100644 packages/mobile/hooks/use-state-ref.ts diff --git a/packages/mobile/app.json b/packages/mobile/app.json index 7b7cd51c09..1ac8eb8703 100644 --- a/packages/mobile/app.json +++ b/packages/mobile/app.json @@ -64,6 +64,7 @@ "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera" } ] + // "@config-plugins/react-native-webrtc" ], "experiments": { "typedRoutes": true diff --git a/packages/mobile/app/_layout.tsx b/packages/mobile/app/_layout.tsx index ff741a6b65..5fae4c9007 100644 --- a/packages/mobile/app/_layout.tsx +++ b/packages/mobile/app/_layout.tsx @@ -95,11 +95,11 @@ export default function RootLayout() { api.createClient({ transformer: superjson, links: [ - loggerLink({ - enabled: (opts) => - process.env.NODE_ENV === "development" || - (opts.direction === "down" && opts.result instanceof Error), - }), + // loggerLink({ + // enabled: (opts) => + // process.env.NODE_ENV === "development" || + // (opts.direction === "down" && opts.result instanceof Error), + // }), (runtime) => { const removeLastSlash = (url: string) => url.replace(/\/$/, ""); const servers = { @@ -123,9 +123,7 @@ export default function RootLayout() { [constructEdgeRouterKey("main")]: makeSkipBatchLink( removeLastSlash( process.env.EXPO_PUBLIC_OSMOSIS_BE_BASE_URL ?? "" - ) + - "/" + - constructEdgeUrlPathname("main") + ) + constructEdgeUrlPathname("main") )(runtime), }; @@ -141,6 +139,7 @@ export default function RootLayout() { const possibleOsmosisFePath = pathParts.join("."); if (basePath === "osmosisFe") { + console.log("possibleOsmosisFePath", possibleOsmosisFePath); return servers[constructEdgeRouterKey("main")]({ ...ctx, op: { diff --git a/packages/mobile/app/onboarding/camera-scan.tsx b/packages/mobile/app/onboarding/camera-scan.tsx index 4f65546499..65ae4d3cda 100644 --- a/packages/mobile/app/onboarding/camera-scan.tsx +++ b/packages/mobile/app/onboarding/camera-scan.tsx @@ -33,7 +33,7 @@ type WebRTCStatus = | "AwaitingConnection" | "Error"; -const useWebRTC = () => { +const useWebRTC = ({ sessionToken }: { sessionToken: string }) => { const [status, setStatus] = useState("Init"); const [connected, setConnected] = useState(false); @@ -44,11 +44,19 @@ const useWebRTC = () => { // Keep a reference to the peer connection const peerConnectionRef = useRef(null); - const intervalIdRef = useRef(null); - const handleSession = useCallback( - async (sessionToken: string) => { + // Add a ref to store the data channel + const dataChannelRef = useRef(null); + + useEffect(() => { + if (!sessionToken) return; + let intervalId: NodeJS.Timeout | null = null; + let isCleanedUp = false; + + const startSession = async () => { try { + if (isCleanedUp) return; + setStatus("FetchingOffer"); // 1) Fetch the offer const offerRes = await apiUtils.osmosisFe.webRTC.fetchOffer.ensureData({ @@ -64,104 +72,112 @@ const useWebRTC = () => { const pc = new RTCPeerConnection({ iceServers: [STUN_SERVER] }); peerConnectionRef.current = pc; - // 3) When we get local ICE candidates, post them to the server - pc.onicecandidate = async (event) => { + // 3) Set remote description (the desktop’s offer) + await pc.setRemoteDescription({ + type: "offer", + sdp: offerRes.offerSDP, + }); + + // 4) When we get local ICE candidates, post them to the server + pc.addEventListener("icecandidate", async (event) => { if (event.candidate) { - await postCandidateMutation.mutate({ + await postCandidateMutation.mutateAsync({ sessionToken, candidate: JSON.stringify(event.candidate), }); } - }; + }); - // 4) If the desktop created a data channel, handle it - pc.ondatachannel = (ev) => { + // 5) If the desktop created a data channel, handle it + pc.addEventListener("datachannel", (ev) => { const channel = ev.channel; - channel.onopen = () => { + // @ts-ignore + dataChannelRef.current = channel; // Store the channel reference + + channel.addEventListener("open", () => { console.log("[Mobile] Data channel open, can receive data."); setStatus("ChannelOpen"); setConnected(true); - }; - channel.onmessage = (msgEvent) => { - console.log("[Mobile] Received data:", msgEvent.data); - // If the desktop sends the private key, you'd handle it here - }; - }; - - // 5) Set remote description (the desktop’s offer) - await pc.setRemoteDescription({ - type: "offer", - sdp: offerRes.offerSDP, + + // Send initial message when channel opens + channel.send("Hello from mobile device!"); + }); + + channel.addEventListener("message", (msgEvent) => { + console.log("[Mobile] Received from desktop:", msgEvent.data); + // Handle incoming messages here + }); + + channel.addEventListener("close", () => { + console.log("[Mobile] Data channel closed"); + setConnected(false); + }); }); setStatus("CreatingAnswer"); // 6) Create answer + if (isCleanedUp) return; const answer = await pc.createAnswer(); + if (isCleanedUp) return; await pc.setLocalDescription(answer); setStatus("PostingAnswer"); - await postAnswerMutation.mutate({ + await postAnswerMutation.mutateAsync({ sessionToken, answerSDP: answer.sdp ?? "", }); // 7) Start polling for desktop ICE candidates - const intervalId = setInterval(async () => { + intervalId = setInterval(async () => { const candRes = await apiUtils.osmosisFe.webRTC.fetchCandidates.ensureData({ sessionToken, }); const candidates = candRes.candidates || []; - for (const cStr of candidates) { + for (const c of candidates) { try { - const candidate = new RTCIceCandidate(JSON.parse(cStr)); + const candidate = new RTCIceCandidate(c); await pc.addIceCandidate(candidate); } catch (err) { console.warn("[Mobile] Failed to add ICE candidate:", err); } } }, 3000); - intervalIdRef.current = intervalId; + setStatus("AwaitingConnection"); } catch (err: any) { console.error(err); - setStatus("Error"); + if (!isCleanedUp) setStatus("Error"); } - }, - [ - apiUtils.osmosisFe.webRTC.fetchCandidates, - apiUtils.osmosisFe.webRTC.fetchOffer, - postAnswerMutation, - postCandidateMutation, - ] - ); + }; + startSession(); - useEffect(() => { return () => { - peerConnectionRef.current?.close(); - if (intervalIdRef.current) { - clearInterval(intervalIdRef.current); - intervalIdRef.current = null; + isCleanedUp = true; + if (intervalId) { + clearInterval(intervalId); + } + if (peerConnectionRef.current) { + peerConnectionRef.current.close(); + peerConnectionRef.current = null; } }; - }, []); + }, [sessionToken]); const handleSendTest = () => { - // If we created our own data channel, you could send from here - // Or if the desktop created the channel, we handle ondatachannel - // This is optional, depending on your flow - const pc = peerConnectionRef.current; - if (!pc) return; - const sendChannel = pc.createDataChannel("mobileChannel"); - sendChannel.onopen = () => { - sendChannel.send("Hello from Mobile!"); - }; + if ( + dataChannelRef.current && + dataChannelRef.current.readyState === "open" + ) { + dataChannelRef.current.send("Test message from mobile!"); + } else { + console.log("[Mobile] Data channel not ready"); + } }; return { status, handleSendTest, - handleSession, connected, }; }; @@ -169,9 +185,13 @@ const useWebRTC = () => { export default function Welcome() { const { top, bottom } = useSafeAreaInsets(); const [autoFocus, setAutoFocus] = useState("off"); - const [shouldFreezeCamera, setShouldFreezeCamera] = useState(false); + const [sessionToken, setSessionToken] = useState(""); + + const shouldFreezeCamera = sessionToken !== ""; - const { status, handleSendTest, handleSession, connected } = useWebRTC(); + const { status, handleSendTest, connected } = useWebRTC({ + sessionToken, + }); const resetCameraAutoFocus = () => { const abortController = new AbortController(); @@ -189,10 +209,8 @@ export default function Welcome() { return; } - setShouldFreezeCamera(true); const parsedData = JSON.parse(data); - await handleSession(parsedData.sessionToken); - setShouldFreezeCamera(false); + setSessionToken(parsedData.sessionToken); }; const overlayWidth = Dimensions.get("window").height / CAMERA_ASPECT_RATIO; diff --git a/packages/mobile/hooks/use-state-ref.ts b/packages/mobile/hooks/use-state-ref.ts new file mode 100644 index 0000000000..1c6e782c1a --- /dev/null +++ b/packages/mobile/hooks/use-state-ref.ts @@ -0,0 +1,39 @@ +import { isFunction } from "@osmosis-labs/utils"; +import { Dispatch, SetStateAction, useCallback, useRef, useState } from "react"; + +type ReadOnlyRefObject = { + readonly current: T; +}; + +type UseStateRef = { + (initialState: S | (() => S)): [ + S, + Dispatch>, + ReadOnlyRefObject + ]; + (): [ + S | undefined, + Dispatch>, + ReadOnlyRefObject + ]; +}; + +/** + * useState and useRef together. This is useful to get the state value + * inside an async callback instead of that value at the time the + * callback was created from. + */ +export const useStateRef: UseStateRef = (initialState?: S | (() => S)) => { + const [state, setState] = useState(initialState); + const ref = useRef(state); + + const dispatch: typeof setState = useCallback((setStateAction) => { + ref.current = isFunction(setStateAction) + ? setStateAction(ref.current) + : setStateAction; + + setState(ref.current); + }, []); + + return [state, dispatch, ref]; +}; diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 83844f8a70..28a403867b 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -18,6 +18,7 @@ "preset": "jest-expo" }, "dependencies": { + "@config-plugins/react-native-webrtc": "^10.0.0", "@expo/vector-icons": "^14.0.2", "@gorhom/bottom-sheet": "^5", "@osmosis-labs/server": "^1.0.0", @@ -39,13 +40,13 @@ "@trpc/react-query": "^10.45.1", "dayjs": "^1.10.7", "debounce": "^1.2.1", - "expo": "~52.0.8", + "expo": "~52.0.23", "expo-blur": "~14.0.1", "expo-camera": "~16.0.10", "expo-clipboard": "^7.0.0", "expo-constants": "~17.0.3", "expo-crypto": "~14.0.1", - "expo-dev-client": "~5.0.4", + "expo-dev-client": "~5.0.8", "expo-font": "~13.0.1", "expo-haptics": "~14.0.0", "expo-linear-gradient": "~14.0.1", @@ -61,11 +62,11 @@ "polished": "^4.3.1", "react": "18.3.1", "react-dom": "18.3.1", - "react-native": "0.76.2", + "react-native": "0.76.5", "react-native-gesture-handler": "~2.20.2", "react-native-haptic-feedback": "^2.3.3", - "react-native-ios-context-menu": "^2.5.2", - "react-native-ios-utilities": "^4.5.1", + "react-native-ios-context-menu": "3.0.0-23", + "react-native-ios-utilities": "5.0.0-58", "react-native-markdown-display": "^7.0.2", "react-native-mmkv": "^3.2.0", "react-native-reanimated": "~3.16.1", diff --git a/packages/trpc/src/web-rtc.ts b/packages/trpc/src/web-rtc.ts index 0f81485c65..6daf8338c3 100644 --- a/packages/trpc/src/web-rtc.ts +++ b/packages/trpc/src/web-rtc.ts @@ -4,9 +4,7 @@ import { z } from "zod"; import { createTRPCRouter, rateLimitedProcedure } from "./api"; export const WebRTCSessionDataSchema = z.object({ - offerSDP: z.string().optional(), - answerSDP: z.string().optional(), - candidates: z.array(z.string()).optional(), + offerSDP: z.string(), createdAt: z.number(), }); @@ -16,7 +14,15 @@ export type WebRTCSessionData = z.infer; const TTL_FIVE_MINUTES = 5 * 60; // 5 minutes function getSessionKey(sessionToken: string) { - return `session:${sessionToken}`; + return `session:${sessionToken}:offer`; +} + +function getAnswerKey(sessionToken: string) { + return `session:${sessionToken}:answer`; +} + +function getCandidatesKey(sessionToken: string) { + return `session:${sessionToken}:candidates`; } export const webRTCRouter = createTRPCRouter({ @@ -32,7 +38,6 @@ export const webRTCRouter = createTRPCRouter({ const sessionKey = getSessionKey(input.sessionToken); const sessionData = WebRTCSessionDataSchema.parse({ offerSDP: input.offerSDP, - candidates: [], createdAt: Date.now(), }); @@ -67,21 +72,8 @@ export const webRTCRouter = createTRPCRouter({ }) ) .mutation(async ({ input }) => { - const sessionKey = getSessionKey(input.sessionToken); - const existingData = await getRedisClient().get( - sessionKey - ); - if (!existingData) { - return { success: false, error: "Session not found" }; - } - - const validatedExistingData = WebRTCSessionDataSchema.parse(existingData); - const updatedData = WebRTCSessionDataSchema.parse({ - ...validatedExistingData, - answerSDP: input.answerSDP, - }); - - await getRedisClient().set(sessionKey, JSON.stringify(updatedData), { + const answerKey = getAnswerKey(input.sessionToken); + await getRedisClient().set(answerKey, input.answerSDP, { ex: TTL_FIVE_MINUTES, }); return { success: true }; @@ -95,12 +87,9 @@ export const webRTCRouter = createTRPCRouter({ }) ) .query(async ({ input }) => { - const sessionKey = getSessionKey(input.sessionToken); - const data = await getRedisClient().get(sessionKey); - if (!data) return { answerSDP: null }; - - const validatedData = WebRTCSessionDataSchema.parse(data); - return { answerSDP: validatedData.answerSDP ?? null }; + const answerKey = getAnswerKey(input.sessionToken); + const answerSDP = await getRedisClient().get(answerKey); + return { answerSDP: answerSDP ?? null }; }), // 5. Either side: post ICE candidate @@ -112,26 +101,9 @@ export const webRTCRouter = createTRPCRouter({ }) ) .mutation(async ({ input }) => { - const sessionKey = getSessionKey(input.sessionToken); - const existingData = await getRedisClient().get( - sessionKey - ); - if (!existingData) { - return { success: false, error: "Session not found" }; - } - - const validatedExistingData = WebRTCSessionDataSchema.parse(existingData); - const updatedData = WebRTCSessionDataSchema.parse({ - ...validatedExistingData, - candidates: [ - ...(validatedExistingData.candidates ?? []), - input.candidate, - ], - }); - - await getRedisClient().set(sessionKey, JSON.stringify(updatedData), { - ex: TTL_FIVE_MINUTES, - }); + const candidatesKey = getCandidatesKey(input.sessionToken); + await getRedisClient().rpush(candidatesKey, input.candidate); + await getRedisClient().expire(candidatesKey, TTL_FIVE_MINUTES); return { success: true }; }), @@ -143,11 +115,13 @@ export const webRTCRouter = createTRPCRouter({ }) ) .query(async ({ input }) => { - const sessionKey = getSessionKey(input.sessionToken); - const data = await getRedisClient().get(sessionKey); - if (!data) return { candidates: [] }; - - const validatedData = WebRTCSessionDataSchema.parse(data); - return { candidates: validatedData.candidates ?? [] }; + const candidatesKey = getCandidatesKey(input.sessionToken); + const candidates = await getRedisClient().lrange<{ + candidate: string; + sdpMid: string; + sdpMLineIndex: number; + usernameFragment: string; + }>(candidatesKey, 0, -1); + return { candidates: candidates ?? [] }; }), }); diff --git a/packages/web/pages/mobile-testing.tsx b/packages/web/pages/mobile-testing.tsx index 0bdd9c827c..c38201e81e 100644 --- a/packages/web/pages/mobile-testing.tsx +++ b/packages/web/pages/mobile-testing.tsx @@ -107,10 +107,10 @@ export default function DesktopPage() { */ useEffect(() => { const candidateList = fetchCandidatesQuery.data?.candidates ?? []; - if (candidateList && pc) { - candidateList.forEach(async (cStr) => { + if (candidateList && pc && fetchAnswerQuery.data?.answerSDP) { + candidateList.forEach(async (c) => { try { - const candidate = new RTCIceCandidate(JSON.parse(cStr)); + const candidate = new RTCIceCandidate(c); await pc.addIceCandidate(candidate); } catch (e) { console.error("Failed to add ICE candidate:", e); diff --git a/yarn.lock b/yarn.lock index f4fa48fff3..4d493b6e53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2918,6 +2918,11 @@ preact "^10.16.0" sha.js "^2.4.11" +"@config-plugins/react-native-webrtc@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@config-plugins/react-native-webrtc/-/react-native-webrtc-10.0.0.tgz#66c7d34bb783b26bc7ebcec9ccb021fa1a55433c" + integrity sha512-q6owBOwQo3HRx4/b0FteE06Ymlcx7pK5bw+Stg77wgTWyxWAJ90yfVvvdMckzxuxMwDd78o9yCLKIONTulHD4A== + "@confio/ics23@^0.6.8": version "0.6.8" resolved "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz" @@ -4456,29 +4461,29 @@ dependencies: uuid "^8.0.0" -"@expo/cli@0.21.6": - version "0.21.6" - resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.21.6.tgz#57e09ab5d901dbf443f9978d052eaa37b163c635" - integrity sha512-xGeQUK/IX5Wzg2ngXV89wL2s1tWaMJR+sxF8HgGwJqFshC0ikQSV0TRCtz4SCdDjfM7dTPkf4UvOSA45zUMXmA== +"@expo/cli@0.22.7": + version "0.22.7" + resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.22.7.tgz#c6501e6ff5162eacba9d027c0e09c91b8db32cee" + integrity sha512-aNrUPVFPdIX42Q6UM6qygrN4DUqnXMDS1CnkTfNFVIZWRiJ1TUA05Zk6aF35M674CKd/c/dWHFjmbgjsyN/hEA== dependencies: "@0no-co/graphql.web" "^1.0.8" "@babel/runtime" "^7.20.0" "@expo/code-signing-certificates" "^0.0.5" "@expo/config" "~10.0.4" - "@expo/config-plugins" "~9.0.3" + "@expo/config-plugins" "~9.0.10" "@expo/devcert" "^1.1.2" "@expo/env" "~0.4.0" "@expo/image-utils" "^0.6.0" "@expo/json-file" "^9.0.0" - "@expo/metro-config" "~0.19.0" + "@expo/metro-config" "~0.19.8" "@expo/osascript" "^2.0.31" "@expo/package-manager" "^1.5.0" "@expo/plist" "^0.2.0" - "@expo/prebuild-config" "^8.0.16" + "@expo/prebuild-config" "^8.0.23" "@expo/rudder-sdk-node" "^1.1.1" "@expo/spawn-async" "^1.7.2" "@expo/xcpretty" "^4.3.0" - "@react-native/dev-middleware" "0.76.2" + "@react-native/dev-middleware" "0.76.5" "@urql/core" "^5.0.6" "@urql/exchange-retry" "^1.3.0" accepts "^1.3.8" @@ -4541,7 +4546,7 @@ node-forge "^1.2.1" nullthrows "^1.1.1" -"@expo/config-plugins@9.0.9", "@expo/config-plugins@~9.0.0", "@expo/config-plugins@~9.0.3": +"@expo/config-plugins@~9.0.0": version "9.0.9" resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-9.0.9.tgz#3765c310c112e200c6715365d6023f6a05b001ed" integrity sha512-pbgbY3SwCMwkijhfe163J05BrTx4MqzeaV+nVgUMs7vRcjHY1tfM57Pdv6SPtgeDvZ8fvdXFXXzkJva+a7C9Bw== @@ -4561,6 +4566,26 @@ xcode "^3.0.1" xml2js "0.6.0" +"@expo/config-plugins@~9.0.10", "@expo/config-plugins@~9.0.12": + version "9.0.12" + resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-9.0.12.tgz#f122b2dca22e135eadf6e73442da3ced0ce8aa0a" + integrity sha512-/Ko/NM+GzvJyRkq8PITm8ms0KY5v0wmN1OQFYRMkcJqOi3PjlhndW+G6bHpJI9mkQXBaUnHwAiGLqIC3+MQ5Wg== + dependencies: + "@expo/config-types" "^52.0.0" + "@expo/json-file" "~9.0.0" + "@expo/plist" "^0.2.0" + "@expo/sdk-runtime-versions" "^1.0.0" + chalk "^4.1.2" + debug "^4.3.5" + getenv "^1.0.0" + glob "^10.4.2" + resolve-from "^5.0.0" + semver "^7.5.4" + slash "^3.0.0" + slugify "^1.6.6" + xcode "^3.0.1" + xml2js "0.6.0" + "@expo/config-types@^52.0.0": version "52.0.1" resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-52.0.1.tgz#327af1b72a3a9d4556f41e083e0e284dd8198b96" @@ -4585,6 +4610,25 @@ slugify "^1.3.4" sucrase "3.35.0" +"@expo/config@~10.0.6": + version "10.0.6" + resolved "https://registry.yarnpkg.com/@expo/config/-/config-10.0.6.tgz#85830491bc8cce2af3f19276922a13f5578d2aa8" + integrity sha512-xXkfPElrtxznkOZxFASJ7OPa6E9IHSjcZwj5BQ6XUF2dz5M7AFa2h5sXM8AalSaDU5tEBSgoUOjTh5957TlR8g== + dependencies: + "@babel/code-frame" "~7.10.4" + "@expo/config-plugins" "~9.0.10" + "@expo/config-types" "^52.0.0" + "@expo/json-file" "^9.0.0" + deepmerge "^4.3.1" + getenv "^1.0.0" + glob "^10.4.2" + require-from-string "^2.0.2" + resolve-from "^5.0.0" + resolve-workspace-root "^2.0.0" + semver "^7.6.0" + slugify "^1.3.4" + sucrase "3.35.0" + "@expo/devcert@^1.1.2": version "1.1.4" resolved "https://registry.yarnpkg.com/@expo/devcert/-/devcert-1.1.4.tgz#d98807802a541847cc42791a606bfdc26e641277" @@ -4614,10 +4658,10 @@ dotenv-expand "~11.0.6" getenv "^1.0.0" -"@expo/fingerprint@0.11.2": - version "0.11.2" - resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.11.2.tgz#94de978e94e2e2773c6a5554d53d04f7a7c710d2" - integrity sha512-WPibADqymGSKkNNnrGfw4dRipz7F8DwMSv7zb6T9oTGtdRiObrUpGmtBXmvo6z9MqWkNRprEJNxPjvkkvMvwhQ== +"@expo/fingerprint@0.11.6": + version "0.11.6" + resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.11.6.tgz#7e01d436c1610c7dc1fc6898b2d90adaa19a39a0" + integrity sha512-hlVIfMEJYZIqIFMjeGRN5GhK/h6vJ3M4QVc1ZD8F0Bh7gMeI+jZkEyZdL5XT29jergQrksP638e2qFwgrGTw/w== dependencies: "@expo/spawn-async" "^1.7.2" arg "^5.0.2" @@ -4655,10 +4699,10 @@ json5 "^2.2.3" write-file-atomic "^2.3.0" -"@expo/metro-config@0.19.4", "@expo/metro-config@~0.19.0": - version "0.19.4" - resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.19.4.tgz#940b6fad7809a92a8ffdb1bbe87aa805f5822c6b" - integrity sha512-2SWwYN8MZvMIRawWEr+1RBYncitPwu2VMACRYig+wBycJ9fsPb6BMVmBYi+3MHDUlJHNy/Bqfw++jn1eqBFETQ== +"@expo/metro-config@0.19.8", "@expo/metro-config@~0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.19.8.tgz#f1ea552b6fa5217093fe364ff5ca78a7e261a28b" + integrity sha512-dVAOetouQYuOTEJ2zR0OTLNPOH6zPkeEt5fY53TK0Wxi1QmtsmH6vEWg05U4zkSJ6f1aXmQ0Za77R8QxuukESA== dependencies: "@babel/core" "^7.20.0" "@babel/generator" "^7.20.5" @@ -4736,6 +4780,23 @@ semver "^7.6.0" xml2js "0.6.0" +"@expo/prebuild-config@^8.0.23": + version "8.0.23" + resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-8.0.23.tgz#2ec6d5464f35d308bdb94ba75b7e6aba0ebb507d" + integrity sha512-Zf01kFiN2PISmLb0DhIAJh76v3J2oYUKSjiAtGZLOH0HUz59by/qdyU4mGHWdeyRdCCrLUA21Rct2MBykvRMsg== + dependencies: + "@expo/config" "~10.0.4" + "@expo/config-plugins" "~9.0.10" + "@expo/config-types" "^52.0.0" + "@expo/image-utils" "^0.6.0" + "@expo/json-file" "^9.0.0" + "@react-native/normalize-colors" "0.76.5" + debug "^4.3.1" + fs-extra "^9.0.0" + resolve-from "^5.0.0" + semver "^7.6.0" + xml2js "0.6.0" + "@expo/rudder-sdk-node@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@expo/rudder-sdk-node/-/rudder-sdk-node-1.1.1.tgz#6aa575f346833eb6290282118766d4919c808c6a" @@ -7661,22 +7722,22 @@ resolved "https://registry.yarnpkg.com/@react-native-menu/menu/-/menu-1.1.6.tgz#df6b4bf46a8ac5718605203f7fcd6fd3684715f5" integrity sha512-KRPBqa9jmYDFoacUxw8z1ucpbvmdlPuRO8tsFt2jM8JMC2s+YQwTtISG73PeqH9KD7BV+8igD/nizPfcipOmhQ== -"@react-native/assets-registry@0.76.2": - version "0.76.2" - resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.76.2.tgz#c3cf689b336e008bec3a80976ae6a92aa34d616b" - integrity sha512-0CTWv/FqJzU1vsyx2JpCkyLSUOePU7DdKgFvtHdwOxFpOw3aBecszqZDGJADYV9WSZQlq6RV0HmIaWycGYCOMA== +"@react-native/assets-registry@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.76.5.tgz#3343338813aa6354df9fec52af50d0b5f7f3d013" + integrity sha512-MN5dasWo37MirVcKWuysRkRr4BjNc81SXwUtJYstwbn8oEkfnwR9DaqdDTo/hHOnTdhafffLIa2xOOHcjDIGEw== -"@react-native/babel-plugin-codegen@0.76.2": - version "0.76.2" - resolved "https://registry.yarnpkg.com/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.76.2.tgz#9a18112abbc9e4b8f6bcb2f45521f009c462984a" - integrity sha512-a1IfRho/ZUVbvzSu3JWkxsvqyEI7IXApPQikhGWw4e24QYsIYHdlIULs3rb0840lqpO1dbbuudfO7lmkpkbkMg== +"@react-native/babel-plugin-codegen@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.76.5.tgz#a7c32274351e51db9c0a7849ce8059940448c087" + integrity sha512-xe7HSQGop4bnOLMaXt0aU+rIatMNEQbz242SDl8V9vx5oOTI0VbZV9yLy6yBc6poUlYbcboF20YVjoRsxX4yww== dependencies: - "@react-native/codegen" "0.76.2" + "@react-native/codegen" "0.76.5" -"@react-native/babel-preset@0.76.2": - version "0.76.2" - resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.76.2.tgz#3c4555012c612f8a849d407b3c91e63afe085c66" - integrity sha512-/kbxZqy70mGONv23uZg7lm7ZCE4dO5dgMzVPz6QsveXIRHQBRLsSC+9w2iZEnYWpLayoWFmTbq8ZG+4W32D3bA== +"@react-native/babel-preset@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.76.5.tgz#794ca17e1107e46712153f296a4930de2048e20e" + integrity sha512-1Nu5Um4EogOdppBLI4pfupkteTjWfmI0hqW8ezWTg7Bezw0FtBj8yS8UYVd3wTnDFT9A5mA2VNoNUqomJnvj2A== dependencies: "@babel/core" "^7.25.2" "@babel/plugin-proposal-export-default-from" "^7.24.7" @@ -7719,15 +7780,15 @@ "@babel/plugin-transform-typescript" "^7.25.2" "@babel/plugin-transform-unicode-regex" "^7.24.7" "@babel/template" "^7.25.0" - "@react-native/babel-plugin-codegen" "0.76.2" + "@react-native/babel-plugin-codegen" "0.76.5" babel-plugin-syntax-hermes-parser "^0.25.1" babel-plugin-transform-flow-enums "^0.0.2" react-refresh "^0.14.0" -"@react-native/codegen@0.76.2": - version "0.76.2" - resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.76.2.tgz#b770d3522275717c4c2894a1f519749d5fd16733" - integrity sha512-rIgdI5mHHnNTzAeDYH+ivKMIcv6vr04Ol+TmX77n1HjJkzMhQqSHWcX+Pq9oiu7l2zKkymadrw6OPD8VPgre8g== +"@react-native/codegen@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.76.5.tgz#4d89ec14a023d6946dbc44537c39b03bd006db7b" + integrity sha512-FoZ9VRQ5MpgtDAnVo1rT9nNRfjnWpE40o1GeJSDlpUMttd36bVXvsDm8W/NhX8BKTWXSX+CPQJsRcvN1UPYGKg== dependencies: "@babel/parser" "^7.25.3" glob "^7.1.1" @@ -7738,13 +7799,13 @@ nullthrows "^1.1.1" yargs "^17.6.2" -"@react-native/community-cli-plugin@0.76.2": - version "0.76.2" - resolved "https://registry.yarnpkg.com/@react-native/community-cli-plugin/-/community-cli-plugin-0.76.2.tgz#c12b42918eb5f8f85a08cf4d1f40ca1158dfc2ca" - integrity sha512-ZRL8oTGSMwXqTsVkRL9AVW8C/AZRnxCcFfhestsx//SrQt3J/hbtDOHTIGkkt5AEA0zEvb/UAAyIAN/wuN4llw== +"@react-native/community-cli-plugin@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/community-cli-plugin/-/community-cli-plugin-0.76.5.tgz#e701a9f99565504a2510d1b54713b1c5dd0f1bb4" + integrity sha512-3MKMnlU0cZOWlMhz5UG6WqACJiWUrE3XwBEumzbMmZw3Iw3h+fIsn+7kLLE5EhzqLt0hg5Y4cgYFi4kOaNgq+g== dependencies: - "@react-native/dev-middleware" "0.76.2" - "@react-native/metro-babel-transformer" "0.76.2" + "@react-native/dev-middleware" "0.76.5" + "@react-native/metro-babel-transformer" "0.76.5" chalk "^4.0.0" execa "^5.1.1" invariant "^2.2.4" @@ -7755,18 +7816,18 @@ readline "^1.3.0" semver "^7.1.3" -"@react-native/debugger-frontend@0.76.2": - version "0.76.2" - resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.76.2.tgz#6fed871e1c87f2b5992c56625b7088f3cf477af4" - integrity sha512-FIcz24Oya2wIO7rZD3dxVyK8t5ZD6Fojl9o7lrjnTWqMedcevRTtdSOIAf4ypksYH/x7HypovE2Zp8U65Xv0Mw== +"@react-native/debugger-frontend@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.76.5.tgz#0e89940543fb5029506690b83f12547d0bf42cc4" + integrity sha512-5gtsLfBaSoa9WP8ToDb/8NnDBLZjv4sybQQj7rDKytKOdsXm3Pr2y4D7x7GQQtP1ZQRqzU0X0OZrhRz9xNnOqA== -"@react-native/dev-middleware@0.76.2": - version "0.76.2" - resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.76.2.tgz#f73dd4032e6acaeb30a672c778420e3d4eb9e76f" - integrity sha512-qiowXpxofLk0lpIZps7fyyp9NiKlqBwh0R0yVub5l4EJcqjLonjsznYAHbusnPW9kb9MQSdovGPNv5b8RadJww== +"@react-native/dev-middleware@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.76.5.tgz#10d02fcc6c3c9d24f6dc147c2ef95d6fa6bd3787" + integrity sha512-f8eimsxpkvMgJia7POKoUu9uqjGF6KgkxX4zqr/a6eoR1qdEAWUd6PonSAqtag3PAqvEaJpB99gLH2ZJI1nDGg== dependencies: "@isaacs/ttlcache" "^1.4.1" - "@react-native/debugger-frontend" "0.76.2" + "@react-native/debugger-frontend" "0.76.5" chrome-launcher "^0.15.2" chromium-edge-launcher "^0.2.0" connect "^3.6.5" @@ -7777,23 +7838,23 @@ serve-static "^1.13.1" ws "^6.2.3" -"@react-native/gradle-plugin@0.76.2": - version "0.76.2" - resolved "https://registry.yarnpkg.com/@react-native/gradle-plugin/-/gradle-plugin-0.76.2.tgz#a06be606fadf9896f4302684aa5c1f2ae95f912b" - integrity sha512-KC5/uAeLoeD1dOjymx6gnNFHGGLB22xNYjrjrJNK5r0bw2O2KXp4rpB5VCT/2H5B48cVC0xPB7RIKOFrDHr5bQ== +"@react-native/gradle-plugin@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/gradle-plugin/-/gradle-plugin-0.76.5.tgz#90d55ec3a99c609358db97b2d7444b28fdc35bc0" + integrity sha512-7KSyD0g0KhbngITduC8OABn0MAlJfwjIdze7nA4Oe1q3R7qmAv+wQzW+UEXvPah8m1WqFjYTkQwz/4mK3XrQGw== -"@react-native/js-polyfills@0.76.2": - version "0.76.2" - resolved "https://registry.yarnpkg.com/@react-native/js-polyfills/-/js-polyfills-0.76.2.tgz#2ae509d2cafba8291baf3f8b54a761e08d6fa97d" - integrity sha512-OXunyNn33fa7gQ6iU5rQcYZQsO7OkJIAr/TgVdoHxpOB4i+ZGsfv6df3JKriBVT1ZZm6ZTlKyIa4QpLq3p0dmw== +"@react-native/js-polyfills@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/js-polyfills/-/js-polyfills-0.76.5.tgz#8f35696d96f804de589cd38382c4f0ffbbc248d5" + integrity sha512-ggM8tcKTcaqyKQcXMIvcB0vVfqr9ZRhWVxWIdiFO1mPvJyS6n+a+lLGkgQAyO8pfH0R1qw6K9D0nqbbDo865WQ== -"@react-native/metro-babel-transformer@0.76.2": - version "0.76.2" - resolved "https://registry.yarnpkg.com/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.76.2.tgz#7645512527cae774f6c34d60718870178e9833df" - integrity sha512-OIYhmWfN+HDyQLzoEg+2P0h7OopYk4djggg0M+k5e1a+g2dFNJILO/BsDobM8uLA8hAzClAJyJLZbPo5jeqdMA== +"@react-native/metro-babel-transformer@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.76.5.tgz#ed5a05fff34c47af43eba4069e50be7c486f77bd" + integrity sha512-Cm9G5Sg5BDty3/MKa3vbCAJtT3YHhlEaPlQALLykju7qBS+pHZV9bE9hocfyyvc5N/osTIGWxG5YOfqTeMu1oQ== dependencies: "@babel/core" "^7.25.2" - "@react-native/babel-preset" "0.76.2" + "@react-native/babel-preset" "0.76.5" hermes-parser "0.23.1" nullthrows "^1.1.1" @@ -7802,15 +7863,20 @@ resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.76.2.tgz#015fa1d454ec605153c8af05666185026e682e66" integrity sha512-ICoOpaTLPsFQjNLSM00NgQr6wal300cZZonHVSDXKntX+BfkLeuCHRtr/Mn+klTtW+/1v2/2FRm9dXjvyGf9Dw== +"@react-native/normalize-colors@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.76.5.tgz#a33560736311aefcf1d3cb594597befe81a9a53c" + integrity sha512-6QRLEok1r55gLqj+94mEWUENuU5A6wsr2OoXpyq/CgQ7THWowbHtru/kRGRr6o3AQXrVnZheR60JNgFcpNYIug== + "@react-native/normalize-colors@^0.74.1": version "0.74.88" resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.74.88.tgz#46f4c7270c8e6853281d7dd966e0eb362068e41a" integrity sha512-He5oTwPBxvXrxJ91dZzpxR7P+VYmc9IkJfhuH8zUiU50ckrt+xWNjtVugPdUv4LuVjmZ36Vk2EX8bl1gVn2dVA== -"@react-native/virtualized-lists@0.76.2": - version "0.76.2" - resolved "https://registry.yarnpkg.com/@react-native/virtualized-lists/-/virtualized-lists-0.76.2.tgz#2b9f720323d828cdd0f1ef5d26f700cfd44b5e89" - integrity sha512-FzXvkHgKvJGf0pSuLy6878cxJ6mxWKgZsH9s2kO4LWJocI8Bi3ViDx7IGAWYuvN+Fnue5TKaqGPhfD+4XrKtYQ== +"@react-native/virtualized-lists@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/virtualized-lists/-/virtualized-lists-0.76.5.tgz#394c2d48687db30c79278d183fda8a129a2d24d3" + integrity sha512-M/fW1fTwxrHbcx0OiVOIxzG6rKC0j9cR9Csf80o77y1Xry0yrNPpAlf8D1ev3LvHsiAUiRNFlauoPtodrs2J1A== dependencies: invariant "^2.2.4" nullthrows "^1.1.1" @@ -7843,12 +7909,19 @@ dependencies: color "^4.2.3" +"@react-navigation/elements@^2.2.5": + version "2.2.5" + resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.2.5.tgz#0e2ca76e2003e96b417a3d7c2829bf1afd69193f" + integrity sha512-sDhE+W14P7MNWLMxXg1MEVXwkLUpMZJGflE6nQNzLmolJQIHgcia0Mrm8uRa3bQovhxYu1UzEojLZ+caoZt7Fg== + dependencies: + color "^4.2.3" + "@react-navigation/native-stack@^7.0.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.1.0.tgz#7e4df1dc25daa9832f9677ca4c7c065287e5118f" - integrity sha512-9wp5YLFbT1TbIVCGN1B20TRRrA79UR3urhdNljbyHLxBHCB0DXCrY8asDC/l2ecTJCYVqNFLbRgPgSHYTBblfw== + version "7.2.0" + resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.2.0.tgz#8aa489f88d662b3543a931b9cb934bb2e09a4893" + integrity sha512-mw7Nq9qQrGsmJmCTwIIWB7yY/3tWYXvQswx+HJScGAadIjemvytJXm1fcl3+YZ9T9Ym0aERcVe5kDs+ny3X4vA== dependencies: - "@react-navigation/elements" "^2.1.0" + "@react-navigation/elements" "^2.2.5" warn-once "^0.1.1" "@react-navigation/native@^7.0.0": @@ -9284,6 +9357,11 @@ dependencies: "@upstash/redis" "^1.28.3" +"@upstash/lock@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@upstash/lock/-/lock-0.2.1.tgz#11eabbc6436726277beec68ad452d2e4254069c1" + integrity sha512-7f/yeicYfNLwDwXm5fAoKbobcdHpNWPbXewmdDFfOgAB4gVWaHXT63iOc09qq6DVFTB5YHvmm0jVIFV8eQnJ+g== + "@upstash/ratelimit@^2.0.5": version "2.0.5" resolved "https://registry.yarnpkg.com/@upstash/ratelimit/-/ratelimit-2.0.5.tgz#0e8e693b79bdcf5d8643c38bebe8b76e7b79b54a" @@ -10953,10 +11031,10 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-expo@~12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-12.0.1.tgz#933b6fc00e2634977e6e270f6b2c775b98c64099" - integrity sha512-9T2o+aeKnHOtQhk/undQbibJv02bdCgfs68ZwgAdueljDBcs2oVfq41qG9XThYwa6Dn7CdfnoEUsIyFqBwjcVw== +babel-preset-expo@~12.0.4: + version "12.0.4" + resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-12.0.4.tgz#ec965530d866c8905aac1fa478562cb08ab32a55" + integrity sha512-SAzAwqpyjA+/OFrU95OOioj6oTeCv4+rRfrNmBTy5S/gJswrZKBSPJioFudIaJBy43W+BL7HA5AspBIF6tO/aA== dependencies: "@babel/plugin-proposal-decorators" "^7.12.9" "@babel/plugin-transform-export-namespace-from" "^7.22.11" @@ -10964,7 +11042,7 @@ babel-preset-expo@~12.0.1: "@babel/plugin-transform-parameters" "^7.22.15" "@babel/preset-react" "^7.22.15" "@babel/preset-typescript" "^7.23.0" - "@react-native/babel-preset" "0.76.2" + "@react-native/babel-preset" "0.76.5" babel-plugin-react-native-web "~0.19.13" react-refresh "^0.14.2" @@ -14619,24 +14697,24 @@ expo-crypto@~14.0.1: dependencies: base64-js "^1.3.0" -expo-dev-client@~5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/expo-dev-client/-/expo-dev-client-5.0.4.tgz#54c0d942dcb7aa0ef80a4bd5ef6e102f26a76bb4" - integrity sha512-07OSxCkrn+IIDEDnI153YDHHtAr/3GvYdJH8HWYnVBCKzkgOxsEszyE5MHKMOou8+NZNVwGWjW5UZXoLqANcGw== +expo-dev-client@~5.0.8: + version "5.0.8" + resolved "https://registry.yarnpkg.com/expo-dev-client/-/expo-dev-client-5.0.8.tgz#abfe5b2a0983672a5333c418918dd287620c2237" + integrity sha512-b9vEIoPBYNqCegxx1D/aZdXkLABa+JA7eFn/dygiU0HnFsr0YBFx3Lj2kzMukayy5x0u9EjMps3l/fznC1EMQg== dependencies: - expo-dev-launcher "5.0.16" - expo-dev-menu "6.0.11" + expo-dev-launcher "5.0.21" + expo-dev-menu "6.0.15" expo-dev-menu-interface "1.9.2" expo-manifests "~0.15.0" expo-updates-interface "~1.0.0" -expo-dev-launcher@5.0.16: - version "5.0.16" - resolved "https://registry.yarnpkg.com/expo-dev-launcher/-/expo-dev-launcher-5.0.16.tgz#7405ed75e9197be6cf7b3668afe248479ba21e44" - integrity sha512-/rRKc5v4Qdo/TgLM7syW90zek5/Bu8NFxm05PJgUHxBiVy6YmasugsfYJ8+bxDoiO+5U9f9RN77uXpZc1By1NQ== +expo-dev-launcher@5.0.21: + version "5.0.21" + resolved "https://registry.yarnpkg.com/expo-dev-launcher/-/expo-dev-launcher-5.0.21.tgz#22c5e24929e2ee769552644615ea6ccc1fb23077" + integrity sha512-ZH/PB6COzxQMl9vvJB84hLNqU2X4gcoj+P6QgpWoANdoMLjl9Cm4u14XlEghZ7W3EHkesZUHl8dT+p/5QIiaNA== dependencies: ajv "8.11.0" - expo-dev-menu "6.0.11" + expo-dev-menu "6.0.14" expo-manifests "~0.15.0" resolve-from "^5.0.0" @@ -14645,17 +14723,24 @@ expo-dev-menu-interface@1.9.2: resolved "https://registry.yarnpkg.com/expo-dev-menu-interface/-/expo-dev-menu-interface-1.9.2.tgz#3515d1365df965f1ad56607cddd133a3c764e23d" integrity sha512-9piGiHZYnNjoO9oQFWlVsndQ1jhTdGCKf81WfCMHbQBamna/zucC1A+jbGpyzE4icXZZ29CpsSd4uVR+tB2Rfw== -expo-dev-menu@6.0.11: - version "6.0.11" - resolved "https://registry.yarnpkg.com/expo-dev-menu/-/expo-dev-menu-6.0.11.tgz#0fed39a854b5b671cc715d3d24c3a2e47be7063c" - integrity sha512-NqyenTw3GdwXsIaC7RrUlxQIL4rpUOJjhRlg9mfiVjE4d69U6nyZoaeyF+Rs2pTQwxbgHqQ17Vpm+q0gOuENkA== +expo-dev-menu@6.0.14: + version "6.0.14" + resolved "https://registry.yarnpkg.com/expo-dev-menu/-/expo-dev-menu-6.0.14.tgz#808393d5a96ad87b36c128a5b690bea7eade37d6" + integrity sha512-bG4NXm4epHSmZdWW9hZCS7dTcKO82CyVfYMbNSOw5o/FKO9cY/AgfSj6ERPE9zBqYEJJEJaoJ0mzv0d47/h23g== dependencies: expo-dev-menu-interface "1.9.2" -expo-file-system@~18.0.4: - version "18.0.4" - resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-18.0.4.tgz#eecf8dc0b3b545e9ac5cd00352665afe2d57732f" - integrity sha512-aAWEDwnu0XHOBYvQ9Q0+QIa+483vYJaC4IDsXyWQ73Rtsg273NZh5kYowY+cAocvoSmA99G6htrLBn11ax2bTQ== +expo-dev-menu@6.0.15: + version "6.0.15" + resolved "https://registry.yarnpkg.com/expo-dev-menu/-/expo-dev-menu-6.0.15.tgz#1e9d9b2ad0335bfe379317b6576165179c0a0b09" + integrity sha512-GMMOSKq9Sjv6uZJt0pSHLHAi33ZHj6mdC+ostvz1A02+U86+mujcMUozYIBTzpiL4Vzj4v7D+sJB+oW6CmDxgg== + dependencies: + expo-dev-menu-interface "1.9.2" + +expo-file-system@~18.0.6: + version "18.0.6" + resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-18.0.6.tgz#43f7718530d0e2aa1f49bca7ccb721007acabf2c" + integrity sha512-gGEwIJCXV3/wpIJ/wRyhmieLOSAY7HeFFjb+wEfHs04aE63JYR+rXXV4b7rBpEh1ZgNV9U91zfet/iQG7J8HBQ== dependencies: web-streams-polyfill "^3.3.2" @@ -14666,6 +14751,13 @@ expo-font@~13.0.1: dependencies: fontfaceobserver "^2.1.0" +expo-font@~13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-13.0.2.tgz#efd3e91714c040a0cf38db91920bce2e0331ff6e" + integrity sha512-H9FaXM7ZW5+EfV38w80BgJG3H17kB7CuVXwHoiszIYyoPfWz9bWesFe4QwNZjTq3pzKes28sSd8irFuflIrSIA== + dependencies: + fontfaceobserver "^2.1.0" + expo-haptics@~14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/expo-haptics/-/expo-haptics-14.0.0.tgz#b3ccea2ed5c7f4c2505e2e8cbfa799091b185303" @@ -14709,10 +14801,10 @@ expo-manifests@~0.15.0: "@expo/config" "~10.0.4" expo-json-utils "~0.14.0" -expo-modules-autolinking@2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-2.0.2.tgz#cf946317e22e120dfe97440f225c8033c6bced42" - integrity sha512-n3jC7VoJLfOLGk8NWhEAvM5zSjbLh1kMUSo76nJupx5/vASxDdzihppYebrKrNXPHq5mcw8Jr+r7YB+8xHx7QQ== +expo-modules-autolinking@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-2.0.4.tgz#28fcd12fb0d066a2933cca3bf3b597da0f6b2f2a" + integrity sha512-e0p+19NhmD50U7s7BV7kWIypWmTNC9n/VlJKlXS05hM/zX7pe6JKmXyb+BFnXJq3SLBalLCUY0tu2gEUF3XeVg== dependencies: "@expo/spawn-async" "^1.7.2" chalk "^4.1.0" @@ -14723,10 +14815,10 @@ expo-modules-autolinking@2.0.2: require-from-string "^2.0.2" resolve-from "^5.0.0" -expo-modules-core@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-2.0.4.tgz#a65700e2c8b9055c4420286acf2dff3d2076da99" - integrity sha512-nNS40KYh1d7tWXCcEKBrSigIKCVfJwkPLhR/mniAoPzqevUDLVJNJjIgKfQL6kPlsViC3hwwgrUpKSlmWv2DFg== +expo-modules-core@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-2.1.2.tgz#258be4fbd162b69eb4ad2789131ac2dc7e85fc08" + integrity sha512-0OhMU5S8zf9c/CRh1MwiXfOInI9wzz6yiIh5RuR/9J7N6xHRum68hInsPbaSc1UQpo08ZZLM4MPsbpoNRUoqIg== dependencies: invariant "^2.2.4" @@ -14791,26 +14883,26 @@ expo-web-browser@~14.0.1: resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-14.0.1.tgz#97f3f141b0897364bc8364d90d6e29df0beec8aa" integrity sha512-QM9F3ie+UyIOoBvqFmT6CZojb1vMc2H+7ZlMT5dEu1PL2jtYyOeK2hLfbt/EMt7CBm/w+P29H9W9Y9gdebOkuQ== -expo@~52.0.8: - version "52.0.8" - resolved "https://registry.yarnpkg.com/expo/-/expo-52.0.8.tgz#b252eb5355879505bd91e77f7612ab7075704737" - integrity sha512-EQwJFRS2UUTGTe++bcLN3b7TeSpSWhQzIPt4jIh+0iannbYxgXLE6oKApTJhh0dsMWDnZzU5a8DIdadJY8VZJA== +expo@~52.0.23: + version "52.0.23" + resolved "https://registry.yarnpkg.com/expo/-/expo-52.0.23.tgz#68b79da2206afdce9c68f45f519f812bab9a2cfe" + integrity sha512-DR36Vkpz/ZLPci4fxDBG/pLk26nGK63vcZ+X4RZJfNBzi14DXZ939loP8YzWGV78Qp23qdPINczpo2727tqLxg== dependencies: "@babel/runtime" "^7.20.0" - "@expo/cli" "0.21.6" - "@expo/config" "~10.0.4" - "@expo/config-plugins" "9.0.9" - "@expo/fingerprint" "0.11.2" - "@expo/metro-config" "0.19.4" + "@expo/cli" "0.22.7" + "@expo/config" "~10.0.6" + "@expo/config-plugins" "~9.0.12" + "@expo/fingerprint" "0.11.6" + "@expo/metro-config" "0.19.8" "@expo/vector-icons" "^14.0.0" - babel-preset-expo "~12.0.1" + babel-preset-expo "~12.0.4" expo-asset "~11.0.1" expo-constants "~17.0.3" - expo-file-system "~18.0.4" - expo-font "~13.0.1" + expo-file-system "~18.0.6" + expo-font "~13.0.2" expo-keep-awake "~14.0.1" - expo-modules-autolinking "2.0.2" - expo-modules-core "2.0.4" + expo-modules-autolinking "2.0.4" + expo-modules-core "2.1.2" fbemitter "^3.0.0" web-streams-polyfill "^3.3.2" whatwg-url-without-unicode "8.0.0-3" @@ -22119,17 +22211,17 @@ react-native-helmet-async@2.0.4: react-fast-compare "^3.2.2" shallowequal "^1.1.0" -react-native-ios-context-menu@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/react-native-ios-context-menu/-/react-native-ios-context-menu-2.5.2.tgz#a54a5a9848619f7a43e673ba4e6d119a76c99459" - integrity sha512-DlG/4uTaNqq2Gnz2bN2seLPyvZTptQ6UhftImvcVdsQyNqG9R9Bnmh4cD/JZVAyK732cyFgtFIE/EUXo0AstoQ== +react-native-ios-context-menu@3.0.0-23: + version "3.0.0-23" + resolved "https://registry.yarnpkg.com/react-native-ios-context-menu/-/react-native-ios-context-menu-3.0.0-23.tgz#f97e432697652057b9caa81d658c63f8242e3841" + integrity sha512-gpjys1y59uU4MYuNTTQv9npTKnjafn7UJxV67984xSWL1OWSoKmhLvLJsAEj+271+ccZSA/T4qxA3MScZY7E6g== dependencies: "@dominicstop/ts-event-emitter" "^1.1.0" -react-native-ios-utilities@^4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/react-native-ios-utilities/-/react-native-ios-utilities-4.5.1.tgz#3d43f4fd3add5f0f6a2ff6c53f35c850226e2ead" - integrity sha512-A8PMME94PXv1Ui5WrTBcz69zVPAshvvIuYxJUkE09Nl1Ca6/ds6wCxys/A+KkNPtgeVCN7diBR/M5BTVnJbKDQ== +react-native-ios-utilities@5.0.0-58: + version "5.0.0-58" + resolved "https://registry.yarnpkg.com/react-native-ios-utilities/-/react-native-ios-utilities-5.0.0-58.tgz#f4572522e1313eb6160358c9b807df58503665f8" + integrity sha512-/9iD5eNvFdvN4vlFJLzctKS9I1MAoaIET0p2pdnj6ZKUe66/OGgfYrpZTaix+Cx1JGXo+zrEqru/5oh/AcDHvw== react-native-is-edge-to-edge@^1.1.6: version "1.1.6" @@ -22251,19 +22343,19 @@ react-native-webview@^11.26.0: escape-string-regexp "2.0.0" invariant "2.2.4" -react-native@0.76.2: - version "0.76.2" - resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.76.2.tgz#966c7c61bd089002540ce668d9fbe544f2f9ea96" - integrity sha512-mkEBKGOmJxhfq8IOsvmk0QuTzlBt9vS+uo0gwbqfUmEDqoC359v80zhUf94WimYBrBkpRQWFbEu5iqMDHrYzlQ== +react-native@0.76.5: + version "0.76.5" + resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.76.5.tgz#3ce43d3c31f46cfd98e56ef2dfc70866c04ad185" + integrity sha512-op2p2kB+lqMF1D7AdX4+wvaR0OPFbvWYs+VBE7bwsb99Cn9xISrLRLAgFflZedQsa5HvnOGrULhtnmItbIKVVw== dependencies: "@jest/create-cache-key-function" "^29.6.3" - "@react-native/assets-registry" "0.76.2" - "@react-native/codegen" "0.76.2" - "@react-native/community-cli-plugin" "0.76.2" - "@react-native/gradle-plugin" "0.76.2" - "@react-native/js-polyfills" "0.76.2" - "@react-native/normalize-colors" "0.76.2" - "@react-native/virtualized-lists" "0.76.2" + "@react-native/assets-registry" "0.76.5" + "@react-native/codegen" "0.76.5" + "@react-native/community-cli-plugin" "0.76.5" + "@react-native/gradle-plugin" "0.76.5" + "@react-native/js-polyfills" "0.76.5" + "@react-native/normalize-colors" "0.76.5" + "@react-native/virtualized-lists" "0.76.5" abort-controller "^3.0.0" anser "^1.4.9" ansi-regex "^5.0.0"