Live video on the AT Protocol

refactor: move from expo-router to react-navigation

See merge request aquareum-tv/aquareum!72

Changelog: feature

+755 -1137
+6 -1
.gitlab-ci.yml
··· 139 - tart-installed 140 timeout: 2 hours 141 script: 142 - git fetch --unshallow || echo 'already unshallow' 143 - brew install ninja go openssl@3 && go version 144 - sudo gem uninstall xcodeproj -x --ignore-dependencies ··· 156 && source ~/venv/bin/activate 157 && pip3 install meson 158 && make ci-macos -j16 159 - && make selftest-macos 160 161 windows-selftest: 162 stage: build
··· 139 - tart-installed 140 timeout: 2 hours 141 script: 142 + # workaround for https://gitlab.com/gitlab-org/gitlab/-/issues/501457 143 + # which is a workaround for ios builds failing due to /tmp vs /private/tmp weirdness 144 + - cd .. 145 + - mv aquareum /Users/admin/aquareum 146 + - cd /Users/admin/aquareum 147 - git fetch --unshallow || echo 'already unshallow' 148 - brew install ninja go openssl@3 && go version 149 - sudo gem uninstall xcodeproj -x --ignore-dependencies ··· 161 && source ~/venv/bin/activate 162 && pip3 install meson 163 && make ci-macos -j16 164 + && make selftest-macos || echo "selftest-macos failed" 165 166 windows-selftest: 167 stage: build
+27 -19
js/app/app.config.ts
··· 1 import { 2 ConfigPlugin, 3 withXcodeProject, 4 - IOSConfig, 5 - withEntitlementsPlist, 6 } from "expo/config-plugins"; 7 8 export const withNotificationsIOS: ConfigPlugin = (config) => { ··· 61 slug: name, 62 version: pkg.version, 63 // Only rev this to the current version when native dependencies change! 64 - runtimeVersion: "0.2.2", 65 orientation: "default", 66 icon: "./assets/images/icon.png", 67 scheme: "myapp", ··· 75 ios: { 76 supportsTablet: true, 77 bundleIdentifier: bundle, 78 - googleServicesFile: "./GoogleService-Info.plist", 79 - entitlements: isProd 80 - ? { 81 - "aps-environment": "production", 82 - } 83 - : {}, 84 infoPlist: { 85 UIBackgroundModes: ["fetch", "remote-notification"], 86 LSMinimumSystemVersion: "12.0", 87 }, 88 }, 89 android: { 90 adaptiveIcon: { ··· 92 backgroundColor: "#ffffff", 93 }, 94 package: bundle, 95 - googleServicesFile: "./google-services.json", 96 - permissions: [ 97 - "android.permission.SCHEDULE_EXACT_ALARM", 98 - "android.permission.POST_NOTIFICATIONS", 99 - ], 100 versionCode: versionCode(pkg.version), 101 }, 102 web: { 103 bundler: "metro", 104 - output: "static", 105 favicon: "./assets/images/favicon.png", 106 }, 107 plugins: [ 108 - "expo-router", 109 [ 110 "expo-font", 111 { ··· 137 ], 138 }, 139 ], 140 - "@react-native-firebase/app", 141 - "@react-native-firebase/messaging", 142 [ 143 "expo-build-properties", 144 { ··· 158 }, 159 ], 160 [withConsistentVersionNumber, { version: pkg.version }], 161 - ...(isProd ? [[withNotificationsIOS, {}]] : ["expo-dev-launcher"]), 162 ], 163 experiments: { 164 typedRoutes: true,
··· 1 import { 2 ConfigPlugin, 3 + withEntitlementsPlist, 4 withXcodeProject, 5 } from "expo/config-plugins"; 6 7 export const withNotificationsIOS: ConfigPlugin = (config) => { ··· 60 slug: name, 61 version: pkg.version, 62 // Only rev this to the current version when native dependencies change! 63 + runtimeVersion: pkg.runtimeVersion, 64 orientation: "default", 65 icon: "./assets/images/icon.png", 66 scheme: "myapp", ··· 74 ios: { 75 supportsTablet: true, 76 bundleIdentifier: bundle, 77 infoPlist: { 78 UIBackgroundModes: ["fetch", "remote-notification"], 79 LSMinimumSystemVersion: "12.0", 80 }, 81 + ...(isProd 82 + ? { 83 + googleServicesFile: "./GoogleService-Info.plist", 84 + entitlements: { 85 + "aps-environment": "production", 86 + }, 87 + } 88 + : {}), 89 }, 90 android: { 91 adaptiveIcon: { ··· 93 backgroundColor: "#ffffff", 94 }, 95 package: bundle, 96 versionCode: versionCode(pkg.version), 97 + ...(isProd 98 + ? { 99 + googleServicesFile: "./google-services.json", 100 + permissions: [ 101 + "android.permission.SCHEDULE_EXACT_ALARM", 102 + "android.permission.POST_NOTIFICATIONS", 103 + ], 104 + } 105 + : {}), 106 }, 107 web: { 108 bundler: "metro", 109 + output: "single", 110 favicon: "./assets/images/favicon.png", 111 }, 112 plugins: [ 113 [ 114 "expo-font", 115 { ··· 141 ], 142 }, 143 ], 144 [ 145 "expo-build-properties", 146 { ··· 160 }, 161 ], 162 [withConsistentVersionNumber, { version: pkg.version }], 163 + ...(isProd 164 + ? [ 165 + "@react-native-firebase/app", 166 + "@react-native-firebase/messaging", 167 + [withNotificationsIOS, {}], 168 + ] 169 + : ["expo-dev-launcher"]), 170 ], 171 experiments: { 172 typedRoutes: true,
+13
js/app/app.go
··· 2 3 import ( 4 "embed" 5 "io/fs" 6 ) 7 8 //go:embed all:dist/** 9 var files embed.FS 10 11 // fetch a static snapshot of the current Aquareum web app 12 func Files() (fs.FS, error) { ··· 16 } 17 return rootFiles, nil 18 }
··· 2 3 import ( 4 "embed" 5 + "encoding/json" 6 "io/fs" 7 ) 8 9 //go:embed all:dist/** 10 var files embed.FS 11 + 12 + //go:embed package.json 13 + var pkg []byte 14 15 // fetch a static snapshot of the current Aquareum web app 16 func Files() (fs.FS, error) { ··· 20 } 21 return rootFiles, nil 22 } 23 + 24 + func PackageJSON() (map[string]any, error) { 25 + var data map[string]any 26 + err := json.Unmarshal(pkg, &data) 27 + if err != nil { 28 + return nil, err 29 + } 30 + return data, nil 31 + }
-34
js/app/app/(tabs)/_layout.tsx
··· 1 - import { Link, Tabs } from "expo-router"; 2 - import { Button, useTheme, View } from "tamagui"; 3 - import { Atom, AudioWaveform } from "@tamagui/lucide-icons"; 4 - 5 - export default function TabLayout() { 6 - const theme = useTheme(); 7 - 8 - return ( 9 - // <MainScreen /> 10 - <Tabs 11 - screenOptions={{ 12 - tabBarActiveTintColor: theme.red10.val, 13 - }} 14 - tabBar={() => <View></View>} 15 - > 16 - <Tabs.Screen 17 - name="index" 18 - options={{ 19 - title: "Aquareum", 20 - tabBarIcon: ({ color }) => <Atom color={color} />, 21 - headerShown: false, 22 - }} 23 - /> 24 - <Tabs.Screen 25 - name="admin" 26 - options={{ 27 - title: "Admin", 28 - tabBarIcon: ({ color }) => <Atom color={color} />, 29 - headerShown: false, 30 - }} 31 - /> 32 - </Tabs> 33 - ); 34 - }
···
-5
js/app/app/(tabs)/admin.tsx
··· 1 - import Admin from "components/admin"; 2 - 3 - export default function AdminPage() { 4 - return <Admin></Admin>; 5 - }
···
-122
js/app/app/(tabs)/index.tsx
··· 1 - import { ExternalLink } from "@tamagui/lucide-icons"; 2 - import { 3 - Anchor, 4 - H1, 5 - H2, 6 - H3, 7 - Image, 8 - Paragraph, 9 - XStack, 10 - YStack, 11 - styled, 12 - View, 13 - Button, 14 - ScrollView, 15 - useWindowDimensions, 16 - isWeb, 17 - Text, 18 - } from "tamagui"; 19 - const CodeH3 = styled(H3, { fontFamily: "$mono" }); 20 - const CenteredH1 = styled(H1, { 21 - fontWeight: "$2", 22 - textAlign: "center", 23 - fontSize: isWeb ? "$16" : "$5", 24 - // flex: 1, 25 - lineHeight: "$16", 26 - } as const); 27 - const CenteredH2 = styled(H2, { 28 - fontWeight: "$2", 29 - textAlign: "center", 30 - // lineHeight: "$6", 31 - fontSize: "$10", 32 - }); 33 - const CenteredH3 = styled(H3, { 34 - fontWeight: "$2", 35 - textAlign: "center", 36 - fontSize: "$8", 37 - }); 38 - const CubeImage = styled(Image, { 39 - width: 100, 40 - height: 100, 41 - resizeMethod: "scale", 42 - }); 43 - import { WebView } from "react-native-webview"; 44 - import { Countdown } from "components"; 45 - import { ImageBackground } from "react-native"; 46 - import * as env from "constants/env"; 47 - import { useState } from "react"; 48 - import GetApps from "components/get-apps"; 49 - import { Link } from "expo-router"; 50 - import StreamList from "components/stream-list/stream-list"; 51 - 52 - const WebviewIframe = ({ src }) => { 53 - if (isWeb) { 54 - return ( 55 - <iframe 56 - allowFullScreen={true} 57 - src={src} 58 - style={{ border: 0, flex: 1 }} 59 - ></iframe> 60 - ); 61 - } else { 62 - return ( 63 - <WebView 64 - allowsInlineMediaPlayback={true} 65 - scrollEnabled={false} 66 - source={{ uri: src }} 67 - style={{ flex: 1, backgroundColor: "transparent" }} 68 - /> 69 - ); 70 - } 71 - }; 72 - 73 - const TAP_COUNT = 5; 74 - const TAP_WINDOW = 5000; 75 - export default function TabOneScreen() { 76 - // const isLive = Date.now() >= 1721149200000; 77 - const [debug, setDebug] = useState(false); 78 - const [presses, setPresses] = useState<number[]>([]); 79 - const handlePress = () => { 80 - const newTaps = [...presses, Date.now()]; 81 - if (newTaps.length > TAP_COUNT) { 82 - newTaps.shift(); 83 - } 84 - if ( 85 - newTaps.length >= TAP_COUNT && 86 - newTaps[newTaps.length - 1] - newTaps[0] <= TAP_WINDOW 87 - ) { 88 - setPresses([]); 89 - setDebug(!debug); 90 - } else { 91 - setPresses(newTaps); 92 - } 93 - }; 94 - return ( 95 - <YStack f={1} ai="center" gap="$8" pt="$5" alignItems="stretch"> 96 - {/* <YStack f={1} alignItems="stretch"> 97 - <View fg={1} flexBasis={0} onPress={handlePress}> 98 - {!debug && ( 99 - <ImageBackground 100 - source={require("assets/images/cube.png")} 101 - style={{ width: "100%", height: "100%" }} 102 - resizeMode="contain" 103 - ></ImageBackground> 104 - )} 105 - {debug && 106 - Object.entries(env).map(([k, v]) => ( 107 - <Text key={k}> 108 - {k}={v} 109 - </Text> 110 - ))} 111 - </View> 112 - </YStack> */} 113 - {/* <View flexShrink={0} flexGrow={0}> 114 - <CenteredH2>Aquareum: The Video Layer for Everything</CenteredH2> 115 - </View> 116 - <View> 117 - <GetApps /> 118 - </View> */} 119 - <StreamList></StreamList> 120 - </YStack> 121 - ); 122 - }
···
-54
js/app/app/+html.tsx
··· 1 - import { ScrollViewStyleReset } from "expo-router/html"; 2 - 3 - // This file is web-only and used to configure the root HTML for every 4 - // web page during static rendering. 5 - // The contents of this function only run in Node.js environments and 6 - // do not have access to the DOM or browser APIs. 7 - export default function Root({ children }: { children: React.ReactNode }) { 8 - return ( 9 - <html lang="en"> 10 - <head> 11 - <meta charSet="utf-8" /> 12 - <meta httpEquiv="X-UA-Compatible" content="IE=edge" /> 13 - <title>Aquareum</title> 14 - 15 - {/* 16 - This viewport disables scaling which makes the mobile website act more like a native app. 17 - However this does reduce built-in accessibility. If you want to enable scaling, use this instead: 18 - <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> 19 - */} 20 - <meta 21 - name="viewport" 22 - content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1.00001,viewport-fit=cover" 23 - /> 24 - {/* 25 - Disable body scrolling on web. This makes ScrollView components work closer to how they do on native. 26 - However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line. 27 - */} 28 - <ScrollViewStyleReset /> 29 - 30 - {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */} 31 - <style dangerouslySetInnerHTML={{ __html: responsiveBackground }} /> 32 - {/* Add any additional <head> elements that you want globally available on web... */} 33 - </head> 34 - <body>{children}</body> 35 - </html> 36 - ); 37 - } 38 - 39 - // FIXME: there's probably a way to do text-decoration that's not this 40 - const responsiveBackground = ` 41 - body { 42 - background-color: #fff; 43 - } 44 - a { 45 - text-decoration: none !important; 46 - } 47 - @media (prefers-color-scheme: dark) { 48 - body { 49 - background-color: #000; 50 - } 51 - * { 52 - -webkit-font-smoothing: antialiased; 53 - } 54 - }`;
···
-38
js/app/app/+not-found.tsx
··· 1 - import { Link, Stack } from "expo-router"; 2 - import { StyleSheet } from "react-native"; 3 - import { View, Text } from "tamagui"; 4 - 5 - export default function NotFoundScreen() { 6 - return ( 7 - <> 8 - <Stack.Screen options={{ title: "Oops!" }} /> 9 - <View margin={10}> 10 - <Text>This screen doesn't exist.</Text> 11 - <Link href="/" style={styles.link}> 12 - <Text style={styles.linkText}>Go to home screen!</Text> 13 - </Link> 14 - </View> 15 - </> 16 - ); 17 - } 18 - 19 - const styles = StyleSheet.create({ 20 - container: { 21 - flex: 1, 22 - alignItems: "center", 23 - justifyContent: "center", 24 - padding: 20, 25 - }, 26 - title: { 27 - fontSize: 20, 28 - fontWeight: "bold", 29 - }, 30 - link: { 31 - marginTop: 15, 32 - paddingVertical: 15, 33 - }, 34 - linkText: { 35 - fontSize: 14, 36 - color: "#2e78b7", 37 - }, 38 - });
···
js/app/app/CurrentToast.tsx js/app/components/provider/CurrentToast.tsx
-147
js/app/app/_layout.tsx
··· 1 - import background from "./background"; 2 - import "../tamagui-web.css"; 3 - import { Link } from "expo-router"; 4 - import { 5 - Anchor, 6 - Button, 7 - useTheme, 8 - Text, 9 - styled, 10 - isWeb, 11 - View, 12 - H4, 13 - } from "tamagui"; 14 - 15 - import { useEffect } from "react"; 16 - import { useColorScheme } from "hooks/useColorScheme"; 17 - import { 18 - DarkTheme, 19 - DefaultTheme, 20 - ThemeProvider, 21 - } from "@react-navigation/native"; 22 - import { useFonts } from "expo-font"; 23 - import { SplashScreen, Stack } from "expo-router"; 24 - import { Provider } from "components"; 25 - import { Helmet } from "react-native-helmet-async"; 26 - import { Settings } from "@tamagui/lucide-icons"; 27 - import { topSafeHeight } from "./platform"; 28 - import { SafeAreaView } from "react-native"; 29 - import usePlatform from "hooks/usePlatform"; 30 - 31 - export { 32 - // Catch any errors thrown by the Layout component. 33 - ErrorBoundary, 34 - } from "expo-router"; 35 - 36 - export const unstable_settings = { 37 - // Ensure that reloading on `/modal` keeps a back button present. 38 - initialRouteName: "(tabs)", 39 - }; 40 - 41 - // Prevent the splash screen from auto-hiding before asset loading is complete. 42 - SplashScreen.preventAutoHideAsync(); 43 - 44 - export default function RootLayout() { 45 - const [fontLoaded, fontError] = useFonts({ 46 - "FiraCode-Light": require("../assets/fonts/FiraCode-Light.ttf"), 47 - "FiraCode-Medium": require("../assets/fonts/FiraCode-Medium.ttf"), 48 - "FiraCode-Bold": require("../assets/fonts/FiraCode-Bold.ttf"), 49 - "FiraSans-Medium": require("../assets/fonts/FiraSans-Medium.ttf"), 50 - }); 51 - 52 - useEffect(() => { 53 - if (fontLoaded || fontError) { 54 - // Hide the splash screen after the fonts have loaded (or an error was returned) and the UI is ready. 55 - SplashScreen.hideAsync(); 56 - } 57 - }, [fontLoaded, fontError]); 58 - 59 - if (!fontLoaded && !fontError) { 60 - return null; 61 - } 62 - 63 - return <RootLayoutNav />; 64 - } 65 - 66 - export const LinkNoUnderline = styled(Link, {}); 67 - 68 - // hack to prevent an error we can't do anything about in 69 - // HLS.js from popping up a full screen error page in dev 70 - const IGNORE_THIS_ERROR = 71 - "The fetching process for the media resource was aborted by the user agent at the user's request."; 72 - if (isWeb && typeof window !== "undefined") { 73 - const handler = (e: PromiseRejectionEvent) => { 74 - if (`${e.reason}`.includes(IGNORE_THIS_ERROR)) { 75 - e.preventDefault(); 76 - e.stopPropagation(); 77 - e.stopImmediatePropagation(); 78 - console.error(e); 79 - } 80 - }; 81 - window.addEventListener("unhandledrejection", handler); 82 - } 83 - 84 - function RootLayoutNav() { 85 - const colorScheme = useColorScheme(); 86 - 87 - useEffect(() => { 88 - background(); 89 - }, []); 90 - 91 - return ( 92 - <Provider> 93 - <View f={1} paddingTop={topSafeHeight()} backgroundColor="$background"> 94 - <SafeAreaView style={{ flex: 1 }}> 95 - <ThemeProvider 96 - value={colorScheme === "dark" ? DarkTheme : DefaultTheme} 97 - > 98 - {isWeb && ( 99 - <Helmet> 100 - <title>Aquareum</title> 101 - </Helmet> 102 - )} 103 - <Stack> 104 - <Stack.Screen 105 - name="(tabs)" 106 - options={{ 107 - title: "", 108 - headerShown: true, 109 - headerRight: () => { 110 - return ( 111 - <Link href="/settings" asChild> 112 - <Button icon={<Settings size="$2" />}></Button> 113 - </Link> 114 - ); 115 - }, 116 - headerLeft: () => ( 117 - <Anchor href="https://explorer.livepeer.org/treasury/74518185892381909671177921640414850443801430499809418110611019961553289709442"> 118 - <View bg="$accentColor" br="$5" padding="$2"> 119 - <H4 fontSize="$4">What's Aquareum?</H4> 120 - </View> 121 - </Anchor> 122 - ), 123 - }} 124 - /> 125 - <Stack.Screen 126 - name="embed/[stream]" 127 - options={{ 128 - headerShown: false, 129 - }} 130 - /> 131 - <Stack.Screen 132 - name="settings" 133 - options={{ 134 - title: "Settings", 135 - presentation: "modal", 136 - animation: "slide_from_right", 137 - gestureEnabled: true, 138 - gestureDirection: "horizontal", 139 - }} 140 - /> 141 - </Stack> 142 - </ThemeProvider> 143 - </SafeAreaView> 144 - </View> 145 - </Provider> 146 - ); 147 - }
···
-134
js/app/app/about.tsx
··· 1 - import { 2 - Paragraph, 3 - ScrollView, 4 - View, 5 - Text, 6 - styled, 7 - H1, 8 - H2, 9 - H3, 10 - H4, 11 - H5, 12 - H6, 13 - isWeb, 14 - } from "tamagui"; 15 - import Markdown from "react-native-markdown-display"; 16 - import { description } from "./aquareum-description"; 17 - import { SafeAreaView } from "react-native"; 18 - 19 - const Code = styled(Text, { fontFamily: "$mono" }); 20 - 21 - const rules = { 22 - // Headings 23 - heading1: (node, children, parent, styles) => ( 24 - <H1 25 - key={node.key} 26 - style={styles._VIEW_SAFE_heading1} 27 - paddingVertical="$4" 28 - lineHeight="$9" 29 - > 30 - {children} 31 - </H1> 32 - ), 33 - heading2: (node, children, parent, styles) => ( 34 - <H2 key={node.key} style={styles._VIEW_SAFE_heading2}> 35 - {children} 36 - </H2> 37 - ), 38 - heading3: (node, children, parent, styles) => ( 39 - <H3 key={node.key} style={styles._VIEW_SAFE_heading3}> 40 - {children} 41 - </H3> 42 - ), 43 - heading4: (node, children, parent, styles) => ( 44 - <H4 key={node.key} style={styles._VIEW_SAFE_heading4}> 45 - {children} 46 - </H4> 47 - ), 48 - heading5: (node, children, parent, styles) => ( 49 - <H5 key={node.key} style={styles._VIEW_SAFE_heading5}> 50 - {children} 51 - </H5> 52 - ), 53 - heading6: (node, children, parent, styles) => ( 54 - <H6 key={node.key} style={styles._VIEW_SAFE_heading6}> 55 - {children} 56 - </H6> 57 - ), 58 - text: (node, children, parent, styles, inheritedStyles = {}) => ( 59 - <Text 60 - lineHeight="$6" 61 - fontSize="$7" 62 - key={node.key} 63 - style={[inheritedStyles, styles.text]} 64 - > 65 - {node.content} 66 - </Text> 67 - ), 68 - paragraph: (node, children, parent, styles) => ( 69 - <Paragraph key={node.key} style={styles._VIEW_SAFE_paragraph}> 70 - {children} 71 - </Paragraph> 72 - ), 73 - bullet_list: (node, children, parent, styles) => ( 74 - <View key={node.key} style={styles._VIEW_SAFE_bullet_list}> 75 - {children} 76 - </View> 77 - ), 78 - ordered_list: (node, children, parent, styles) => ( 79 - <View key={node.key} style={styles._VIEW_SAFE_ordered_list}> 80 - {children} 81 - </View> 82 - ), 83 - code_inline: (node, children, parent, styles, inheritedStyles = {}) => ( 84 - <Code lineHeight="$6" fontSize="$7" key={node.key}> 85 - {node.content} 86 - </Code> 87 - ), 88 - pre: (node, children, parent, styles) => ( 89 - <View key={node.key} style={styles._VIEW_SAFE_pre}> 90 - {children} 91 - </View> 92 - ), 93 - list_item: (node, children, parent, styles) => ( 94 - <Paragraph key={node.key} style={styles._VIEW_SAFE_paragraph}> 95 - <Text>&gt;</Text> {children} 96 - </Paragraph> 97 - ), 98 - }; 99 - 100 - export default function ModalScreen() { 101 - if (isWeb) { 102 - return ( 103 - <ScrollView 104 - backgroundColor="$background" 105 - padding="$4" 106 - borderRadius="$4" 107 - height="100%" 108 - > 109 - <View 110 - paddingBottom={30} 111 - flex={1} 112 - maxWidth="800px" 113 - alignSelf={isWeb ? "center" : "stretch"} 114 - > 115 - <Markdown rules={rules}>{description}</Markdown> 116 - </View> 117 - </ScrollView> 118 - ); 119 - } 120 - return ( 121 - <SafeAreaView> 122 - <ScrollView 123 - backgroundColor="$background" 124 - padding="$4" 125 - borderRadius="$4" 126 - height="100%" 127 - > 128 - <View paddingBottom={30} flex={1}> 129 - <Markdown rules={rules}>{description}</Markdown> 130 - </View> 131 - </ScrollView> 132 - </SafeAreaView> 133 - ); 134 - }
···
-88
js/app/app/aquareum-description.tsx
··· 1 - export const description = ` 2 - # Aquareum: The Video Layer for Everything 3 - ## Background 4 - 5 - Social networking is in the middle of a decentralized revolution. The world is moving its social structures away from centralized platforms run by megacorporations to federated structures that better align with our values and needs as a society. Projects like Farcaster, Lens, Nostr, and Bluesky's AT Protocol leverage blockchains and public key infrastructure to put control of data firmly in the hands of creators. They all obey the fundamental principle of decentralization: user actions are self-sovereign and immutable. 6 - 7 - As these platforms continue to evolve, they will all want rich video functionality. (Some social networks, like TikTok, are themselves 90% rich video functionality.) Video, especially live video, is notoriously difficult and expensive to make work at scale. Legacy platforms such as Twitch, YouTube, and Periscope are massively unprofitable; they only work well due to immense expenditure of servers and bandwidth. Decentralized protocol designers are focused on their protocols, not technical details of video muxing, transcoding, and global low-latency distribution. 8 - 9 - Livepeer and MistServer are great at video muxing, transcoding, and global low-latency video distribution. They have a wide range of rich video features, including Livestreaming, VoD, Clipping, Multistreaming, Transcoding, and AI video generation. All of that content can be efficiently muxed to out millions of users, using low-latency WebRTC. But Livepeer nodes don't have a strong concept of a “user” — who exactly is _allowed_ to stream on a Livepeer node? Livepeer isn't a social network. It's not our job to invent decentralized social. 10 - 11 - There's a natural opportunity here — by indexing decentralized social networks and treating those as the source of truth for the world's freely-available video content, we immediately and permissionlessly enable rich video functionality for all of these platforms. 12 - 13 - **The end result:** 14 - 15 - * **An iOS/Android/Web app that provides a Twitch/YouTube interface, instantly usable by millions of users.** 16 - * **A single-binary node that anyone can run, anywhere.** 17 - * **A set of user-sovereign primitives for expressing creator consent and provenance.** 18 - * **A trustless protocol, such that any app can connect to any node and be confidently served content associated with the dSocial user.** 19 - * **And millions of users that can start watching and creating video content immediately.** 20 - 21 - The Aquareum team is seeking funding from the Livepeer Treasury to deliver on this vision. Toward this end, we're seeking **20,000 LPT**. This funding will go toward compensating our team and renting server infrastructure capable of prototyping global video distribution. 22 - 23 - ## The Team 24 - 25 - **Eli Mallon:** Eli is a longtime core contributor to the Livepeer protocol and was the primary architect behind Livepeer Studio. He will be leading the backend, video development, and protocol design. 26 - 27 - **Adam Soffer:** Adam was one of the earliest contributors to Livepeer, instrumental in building the Livepeer Subgraph, the Livepeer Explorer, and the Livepeer Studio player and frontend. Adam also created The Web3 Index. He will be leading the design, frontend development, and indexing layers. 28 - 29 - Eli and Adam will be leaving our positions at Livepeer Inc. to take on a vision that we see as core to delivering on the promise of decentralized video. We're excited about participating in the decentralization of Livepeer by establishing another entity in the ecosystem. 30 - 31 - ## The Plan 32 - 33 - ### **Aquareum Primitives** 34 - 35 - This is the schema. The top-level set of primitives are very familiar: ${"`Segment`"}, ${"`Livestream`"}, ${"`Clip`"}, and ${"`Transcode`"}. Most developers working with these primitives will only need to use a few to compose rich video experiences. They will all be tightly-coupled with React components, making frontend video development fun and easy. 36 - 37 - The primitives will be signed by the content creators and include an expressive language for the allowed distribution of their content. Creators will, for example, be able to express licensing information or choose to make content "expire" after a certain amount of time. Aquareum nodes will respect these fields, giving creators control over the lifecycle of their content in the system. 38 - 39 - **These primitives will be natively anchored into the signing schemes utilized by decentralized social networks. They'll work with Bluesky/Farcaster/etc natively.** Behind the scenes, this set of primitives is expressible as an AT Protocol DAG-CBOR lexicon (for Bluesky) or an EIP-712 schema as utilized by Farcaster and Lens. Additionally, these primitives will be C2PA compliant — they'll respect the industry standard for representing signed video with a coherent provenance chain. This means that C2PA tooling built into video editors and players will be able to confidently inform an end user of the veracity of the video. The EIP-712 schema also makes these primitives suitable for minting onto NFTs. 40 - 41 - The end result is an API that provides you verifiable video content, provided by whichever node that is willing to serve it to you. 42 - 43 - ### **Aquareum Indexer** 44 - 45 - This app consumes the firehoses from the social networks, and is conceptually similar to a Graph Node subgraph. It then builds all of users' video content into a big distributed database. it builds an internal state of video clips and livestreams, exposing a GraphQL interface to be consumed by the application. All data served by the indexer is signed and verifiable within its own cryptographic realm. 46 - 47 - Our initial approach will be to deliver indexers for Farcaster and AT Protocol, working closely with those communities. 48 - 49 - ### **Aquareum App** 50 - 51 - An app that runs on iPhone, Android, and Web. A familiar frontend, like Twitch or YouTube. Connects to one or more Aquareum Nodes to display a library of video content. All content is accompanied with C2PA metadata — you can always be confident in what you're looking at, even if you don't trust the node you're connected to. All of the video content is associated with, say, ${"`adamsoffer.eth`"} or ${"`@iame.li`"}. 52 - 53 - Down the line there will also be creation tools — an embedded livestreaming interface and TikTok-style video editor. While new content will often be created and processed in tandem with an Aquareum node to offload the heavy lifting of video processing, the app will also be fully transcode-enabled, utilizing deterministic WASM-compiled encoding libraries. 54 - 55 - ### **Aquareum Node** 56 - 57 - This bundles the indexer, the app, and the full Livepeer + MistServer software stack into a single executable. It's dead simple to boot it up, configure, and serve content to users over low-latency WebRTC. 58 - 59 - It will also facilitate creation of new video content. Users may livestream from the app into Aquareum nodes, and both live and recorded content may be clipped, spliced, composited, and synthesized into new video content. The node leverages the Livepeer Network's GPU processing to do the heavy lifting of video content creation, distribution, and synthesis. However, data is local to the user's app/node until published onto an upstream social network, facilitating both statelessness and moderation. Aquareum nodes will utilize the moderation capabilities built into decentralized social networks to facilitate trust and safety. 60 - 61 - ## The First Milestone 62 - 63 - Aquareum is an ambitious vision, so it's important to be clear about what we're looking to accomplish in this phase. 64 - 65 - * First and most boring, we'll be establishing a business entity: Aquareum Inc. Aquareum will take a long time to make, and we're in it for the long haul; this is a necessary step to ensure stability for our team. 66 - * We will deliver the creation of the Aquareum node. This includes a build process for a single statically-linked binary. (This will deliver on a piece of the Livepeer Catalyst vision: a unified version of MistServer and go-livepeer). 67 - * We will deliver a full-stack development environment for Aquareum that's stupid easy to use. Unlike the old Livepeer Catalyst stack, which was opaque and difficult, this will be built from the ground up to encourage community contributions. There will be **one git repository with a single ${"`make`"} command capable of building all components of the project**, and we will be seeking community contributions from Day 1. 68 - * The Aquareum node will run on Windows, macOS, or Linux on AMD64 or ARM64 processors. The Aquareum app will run on iOS, Android, and the Web. More platforms is better, but that's a good start. 69 - * Ship the design of the API primitives, with a focus on signed canonical representations, probably including DAG-CBOR, EIP-712, and C2PA schema. 70 - * Build integrations with our first two social networks: Farcaster and Bluesky. 71 - * 100% of what we build will be open-sourced under an MIT or similarly permissive license. 72 - 73 - ## Livepeer Network Improvements 74 - 75 - So why should the Livepeer Network participants care about this? Aside from the possibility of Aquareum's success driving traffic to the Livepeer network processing, there are numerous specific protocol upgrades: 76 - 77 - * We'll be packaging up go-livepeer as a library that can be used within other code, dramatically expanding its use cases. This will make it possible to embed Livepeer Network transcoding in any application capable of linking a C library. 78 - * We will be establishing a GitLab instance with hosted CI runners that will be available free of charge to other projects in the ecosystem. This server will also be used to facilitate project management. 79 - * Aquareum will (finally!) give a coherent answer to how the Livepeer protocol handles scaled video distribution — video users run Aquareum nodes. 80 - * We will seek to upgrade the Livepeer wire protocol to utilize C2PA signatures for transcoding. (This is how it would have been doing originally, if the C2PA had existed when the original protocol was developed.) If necessary, we will deliver the actual protocol improvements via an LIP. This will also dramatically increase the utility of Livepeer transcoding. Transcoded segments are presently mostly opaque. With these schema, transcoded Livepeer segments will instead be digital artifacts with a coherent provenance chain, identifying the user, the transcoder, the content, the licensing, and a million other things. 81 - 82 - ## Roadmap 83 - 84 - We have made our roadmap available on the forums; an archived version is also available at [/ipfs/QmPxASKEjVqWnUpVpmbF35Z9WmAiGKT7sQ6ER5HMuUB9Wt](https://ipfs.io/ipfs/QmPxASKEjVqWnUpVpmbF35Z9WmAiGKT7sQ6ER5HMuUB9Wt). 85 - 86 - And one more thing: development will be livestreamed as much as possible — development, code review, project planning will be livestreamed on the Aquareum app and website. We'll be scratching our own itch for a streaming platform while building everything out. We see this as one of the most honest ways to hold ourselves accountable; tune in and see exactly how the treasury funding is being spent! Our livestreaming premiere will be on Tuesday Jul 16 2024 10:00 PDT (12:00 EDT, 17:00 UTC); check the countdown at [aquareum.tv](https://aquareum.tv). 87 - 88 - `.trim();
···
-7
js/app/app/background.tsx
··· 1 - // earliest part of aquareum's entrypoint. set up message notifications and stuff. 2 - // index.js 3 - import { initPushNotifications } from "./platform"; 4 - 5 - export default async function background() { 6 - await initPushNotifications(); 7 - }
···
-11
js/app/app/embed/[stream].tsx
··· 1 - import { Player } from "components"; 2 - import { useLocalSearchParams } from "expo-router"; 3 - import { View } from "tamagui"; 4 - 5 - export default function StreamPage() { 6 - const params = useLocalSearchParams(); 7 - if (typeof params.stream !== "string") { 8 - return <View />; 9 - } 10 - return <Player src={params.stream}></Player>; 11 - }
···
-50
js/app/app/multi/[players].tsx
··· 1 - import { Player } from "components"; 2 - import { PlayerProps } from "components/player/props"; 3 - import { useLocalSearchParams } from "expo-router"; 4 - import { useEffect, useState } from "react"; 5 - import { View, XStack, YStack } from "tamagui"; 6 - 7 - export default function StreamPage() { 8 - const params = useLocalSearchParams(); 9 - if (typeof params.players !== "string") { 10 - return <View />; 11 - } 12 - const [rows, setRows] = useState<Partial<PlayerProps | null>[][]>([]); 13 - useEffect(() => { 14 - let nearestSquareExpo = 1; 15 - const playerProps = JSON.parse( 16 - params.players as string, 17 - ) as Partial<PlayerProps>[]; 18 - while (Math.pow(nearestSquareExpo, 2) < playerProps.length) { 19 - nearestSquareExpo += 1; 20 - } 21 - const rows: Partial<PlayerProps | null>[][] = []; 22 - let idx = 0; 23 - for (let i = 0; i < nearestSquareExpo; i += 1) { 24 - const row: Partial<PlayerProps | null>[] = []; 25 - for (let j = 0; j < nearestSquareExpo; j += 1) { 26 - if (playerProps[idx]) { 27 - row.push(playerProps[idx]); 28 - } else { 29 - row.push(null); 30 - } 31 - idx += 1; 32 - } 33 - rows.push(row); 34 - } 35 - setRows(rows); 36 - }, [params.players]); 37 - return ( 38 - <YStack f={1} fb={0}> 39 - {rows.map((players, i) => ( 40 - <XStack key={i} f={1} fb={0}> 41 - {players.map((props, j) => ( 42 - <View key={j} f={1} fb={0}> 43 - {props === null ? <View /> : <Player {...props}></Player>} 44 - </View> 45 - ))} 46 - </XStack> 47 - ))} 48 - </YStack> 49 - ); 50 - }
···
-1
js/app/app/platform/index.tsx
··· 1 - export * from "./platform";
···
js/app/app/platform/platform.android.tsx js/app/hooks/platform/platform.android.tsx
js/app/app/platform/platform.ios.tsx js/app/hooks/platform/platform.ios.tsx
js/app/app/platform/platform.mobile.tsx js/app/hooks/platform/platform.mobile.tsx
-1
js/app/app/platform/platform.tsx
··· 1 - export * from "./platform.web";
···
js/app/app/platform/platform.web.tsx js/app/hooks/platform/platform.tsx
-10
js/app/app/settings.tsx
··· 1 - import { Settings } from "components"; 2 - import { View } from "tamagui"; 3 - 4 - export default function OptionsScreen() { 5 - return ( 6 - <View flex={1}> 7 - <Settings /> 8 - </View> 9 - ); 10 - }
···
-11
js/app/app/stream/[stream].tsx
··· 1 - import { Player } from "components"; 2 - import { useLocalSearchParams } from "expo-router"; 3 - import { View } from "tamagui"; 4 - 5 - export default function StreamPage() { 6 - const params = useLocalSearchParams(); 7 - if (typeof params.stream !== "string") { 8 - return <View />; 9 - } 10 - return <Player src={params.stream}></Player>; 11 - }
···
+3 -1
js/app/app/support.tsx js/app/src/screens/support.tsx
··· 1 - import { View, isWeb } from "tamagui"; 2 import { useEffect } from "react"; 3 4 export default function SupportScreen() { 5 if (isWeb) { 6 useEffect(() => { 7 document.location.href =
··· 1 + import usePlatform from "hooks/usePlatform"; 2 import { useEffect } from "react"; 3 + import { View } from "react-native"; 4 5 export default function SupportScreen() { 6 + const { isWeb } = usePlatform(); 7 if (isWeb) { 8 useEffect(() => { 9 document.location.href =
+27
js/app/components/aqlink.tsx
···
··· 1 + import { Link, useNavigation } from "@react-navigation/native"; 2 + import { NavigationProp, ParamListBase } from "@react-navigation/native"; 3 + import usePlatform from "hooks/usePlatform"; 4 + import { Pressable } from "react-native"; 5 + 6 + // Web and native have some disagreements about link styling 7 + // so we have a custom component that handles that 8 + export default function AQLink({ 9 + children, 10 + to, 11 + }: { 12 + children: React.ReactNode; 13 + to: { screen: string; params?: Record<string, string> }; 14 + }) { 15 + const { isWeb } = usePlatform(); 16 + const navigation = useNavigation<NavigationProp<ParamListBase>>(); 17 + 18 + if (isWeb) { 19 + return <Link to={to as any}>{children}</Link>; 20 + } 21 + 22 + return ( 23 + <Pressable onPress={() => navigation.navigate(to.screen, to.params)}> 24 + {children} 25 + </Pressable> 26 + ); 27 + }
+14 -3
js/app/components/player/video-retry.tsx
··· 5 props: PlayerProps & { children: React.ReactNode }, 6 ) { 7 const [resetTime, setResetTime] = useState<number>(Date.now()); 8 const isPlaying = props.status === PlayerStatus.PLAYING; 9 useEffect(() => { 10 if (isPlaying) { 11 return; 12 } 13 const handle = setTimeout(() => { 14 - // you've had long enough. try again! 15 setResetTime(Date.now()); 16 - }, 5000); 17 return () => clearTimeout(handle); 18 - }, [isPlaying]); 19 return <React.Fragment key={resetTime}>{props.children}</React.Fragment>; 20 }
··· 5 props: PlayerProps & { children: React.ReactNode }, 6 ) { 7 const [resetTime, setResetTime] = useState<number>(Date.now()); 8 + const [retryCount, setRetryCount] = useState(0); 9 const isPlaying = props.status === PlayerStatus.PLAYING; 10 + 11 useEffect(() => { 12 if (isPlaying) { 13 + setRetryCount(0); 14 return; 15 } 16 + 17 + const baseDelay = 10000; // 10 seconds 18 + const maxDelay = 30000; // 30 seconds 19 + const delay = Math.min(baseDelay * Math.pow(2, retryCount), maxDelay); 20 + 21 const handle = setTimeout(() => { 22 + // console.log(`retrying (attempt ${retryCount + 1}, delay: ${delay}ms)`); 23 setResetTime(Date.now()); 24 + setRetryCount((prev) => prev + 1); 25 + }, delay); 26 + 27 return () => clearTimeout(handle); 28 + }, [isPlaying, resetTime, retryCount]); 29 + 30 return <React.Fragment key={resetTime}>{props.children}</React.Fragment>; 31 }
+1
js/app/components/player/video.tsx
··· 56 }; 57 58 useEffect(() => { 59 return () => { 60 props.setStatus(PlayerStatus.START); 61 };
··· 56 }; 57 58 useEffect(() => { 59 + console.log("video mounted"); 60 return () => { 61 props.setStatus(PlayerStatus.START); 62 };
+15 -2
js/app/components/provider/provider.native.tsx
··· 1 import React from "react"; 2 import SharedProvider from "./provider.shared"; 3 4 - export default function Provider({ children }: { children: React.ReactNode }) { 5 - return <SharedProvider>{children}</SharedProvider>; 6 }
··· 1 + // this comment is here to stop auto-alphabetizing imports lol 2 + import { LinkingOptions } from "@react-navigation/native"; 3 import React from "react"; 4 + import { GestureHandlerRootView } from "react-native-gesture-handler"; 5 import SharedProvider from "./provider.shared"; 6 7 + export default function Provider({ 8 + children, 9 + linking, 10 + }: { 11 + children: React.ReactNode; 12 + linking: LinkingOptions<ReactNavigation.RootParamList>; 13 + }) { 14 + return ( 15 + <GestureHandlerRootView style={{ flex: 1 }}> 16 + <SharedProvider linking={linking}>{children}</SharedProvider> 17 + </GestureHandlerRootView> 18 + ); 19 }
+53 -24
js/app/components/provider/provider.shared.tsx
··· 1 import { ToastProvider, ToastViewport } from "@tamagui/toast"; 2 - import { CurrentToast } from "app/CurrentToast"; 3 import React from "react"; 4 - import { TamaguiProvider, PortalProvider } from "tamagui"; 5 import config from "tamagui.config"; 6 - import { AquareumProvider } from "hooks/useAquareumNode"; 7 8 - export default function Provider({ children }: { children: React.ReactNode }) { 9 return ( 10 - <AquareumProvider> 11 - <TamaguiProvider config={config} defaultTheme={"dark"}> 12 - <PortalProvider> 13 - <ToastProvider 14 - swipeDirection="vertical" 15 - duration={6000} 16 - native={ 17 - [ 18 - /* uncomment the next line to do native toasts on mobile. NOTE: it'll require you making a dev build and won't work with Expo Go */ 19 - // 'mobile' 20 - ] 21 - } 22 - > 23 - {children} 24 - <CurrentToast /> 25 - <ToastViewport name="default" top="$8" left={0} right={0} /> 26 - </ToastProvider> 27 - </PortalProvider> 28 - </TamaguiProvider> 29 - </AquareumProvider> 30 ); 31 }
··· 1 + import { 2 + DarkTheme, 3 + LinkingOptions, 4 + NavigationContainer, 5 + } from "@react-navigation/native"; 6 import { ToastProvider, ToastViewport } from "@tamagui/toast"; 7 + import { useFonts } from "expo-font"; 8 + import { AquareumProvider } from "hooks/useAquareumNode"; 9 import React from "react"; 10 + import { PortalProvider, TamaguiProvider } from "tamagui"; 11 import config from "tamagui.config"; 12 + import { CurrentToast } from "./CurrentToast"; 13 14 + export default function Provider({ 15 + children, 16 + linking, 17 + }: { 18 + children: React.ReactNode; 19 + linking: LinkingOptions<ReactNavigation.RootParamList>; 20 + }) { 21 return ( 22 + <TamaguiProvider config={config} defaultTheme={"dark"}> 23 + <NavigationContainer theme={DarkTheme} linking={linking}> 24 + <AquareumProvider> 25 + <PortalProvider> 26 + <ToastProvider 27 + swipeDirection="vertical" 28 + duration={6000} 29 + native={ 30 + [ 31 + /* uncomment the next line to do native toasts on mobile. NOTE: it'll require you making a dev build and won't work with Expo Go */ 32 + // 'mobile' 33 + ] 34 + } 35 + > 36 + <FontProvider>{children}</FontProvider> 37 + <CurrentToast /> 38 + <ToastViewport name="default" top="$8" left={0} right={0} /> 39 + </ToastProvider> 40 + </PortalProvider> 41 + </AquareumProvider> 42 + </NavigationContainer> 43 + </TamaguiProvider> 44 ); 45 } 46 + 47 + export const FontProvider = ({ children }: { children: React.ReactNode }) => { 48 + const [fontLoaded, fontError] = useFonts({ 49 + "FiraCode-Light": require("../../assets/fonts/FiraCode-Light.ttf"), 50 + "FiraCode-Medium": require("../../assets/fonts/FiraCode-Medium.ttf"), 51 + "FiraCode-Bold": require("../../assets/fonts/FiraCode-Bold.ttf"), 52 + "FiraSans-Medium": require("../../assets/fonts/FiraSans-Medium.ttf"), 53 + }); 54 + 55 + if (!fontLoaded && !fontError) { 56 + return null; 57 + } 58 + 59 + return <>{children}</>; 60 + };
+9 -2
js/app/components/provider/provider.tsx
··· 7 import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; 8 import { View, Text } from "tamagui"; 9 import SharedProvider from "./provider.shared"; 10 11 const queryClient = new QueryClient(); 12 ··· 18 ssr: true, // If your dApp uses server side rendering (SSR) 19 }); 20 21 - export default function Provider({ children }: { children: React.ReactNode }) { 22 return ( 23 - <SharedProvider> 24 <WagmiProvider config={config}> 25 <QueryClientProvider client={queryClient}> 26 <RainbowKitProvider coolMode={true}>
··· 7 import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; 8 import { View, Text } from "tamagui"; 9 import SharedProvider from "./provider.shared"; 10 + import { LinkingOptions } from "@react-navigation/native"; 11 12 const queryClient = new QueryClient(); 13 ··· 19 ssr: true, // If your dApp uses server side rendering (SSR) 20 }); 21 22 + export default function Provider({ 23 + children, 24 + linking, 25 + }: { 26 + children: React.ReactNode; 27 + linking: LinkingOptions<ReactNavigation.RootParamList>; 28 + }) { 29 return ( 30 + <SharedProvider linking={linking}> 31 <WagmiProvider config={config}> 32 <QueryClientProvider client={queryClient}> 33 <RainbowKitProvider coolMode={true}>
+38 -13
js/app/components/stream-list/stream-list.tsx
··· 1 import ErrorBox from "components/error/error"; 2 import Loading from "components/loading/loading"; 3 - import { Link } from "expo-router"; 4 import useAquareumNode from "hooks/useAquareumNode"; 5 import { useEffect, useState } from "react"; 6 - import { Pressable } from "react-native"; 7 - import { ScrollView, Text, Image, View, H2, H6 } from "tamagui"; 8 9 type Segment = { 10 id: string; ··· 13 endTime: string; 14 }; 15 16 - export default function StreamList() { 17 const [streams, setStreams] = useState<Segment[]>([]); 18 const [error, setError] = useState<boolean>(false); 19 const [loading, setLoading] = useState<boolean>(false); 20 const [retryTime, setRetryTime] = useState<number>(Date.now()); 21 const { url } = useAquareumNode(); 22 useEffect(() => { 23 setError(false); 24 setLoading(true); ··· 48 return <ErrorBox onRetry={() => setRetryTime(Date.now())} />; 49 } 50 return ( 51 - <ScrollView contentContainerStyle={{ alignItems: "center" }}> 52 {streams.map((seg) => ( 53 - <Link asChild key={seg.user} href={`/stream/${seg.user}`}> 54 - <Pressable> 55 - <View key={seg.user}> 56 <Image 57 - height={200} 58 src={`${url}/api/playback/${seg.user}/stream.jpg`} 59 resizeMode="contain" 60 objectFit="contain" 61 /> 62 - <H6>{seg.user}</H6> 63 - </View> 64 - </Pressable> 65 - </Link> 66 ))} 67 </ScrollView> 68 );
··· 1 + import { Link, useNavigation } from "@react-navigation/native"; 2 + import AQLink from "components/aqlink"; 3 import ErrorBox from "components/error/error"; 4 import Loading from "components/loading/loading"; 5 + import { formatAddress } from "hooks/textUtils"; 6 import useAquareumNode from "hooks/useAquareumNode"; 7 import { useEffect, useState } from "react"; 8 + import { Pressable, RefreshControl } from "react-native"; 9 + import { H6, Image, ScrollView, ScrollViewProps, View, YStack } from "tamagui"; 10 11 type Segment = { 12 id: string; ··· 15 endTime: string; 16 }; 17 18 + export default function StreamList({ 19 + contentContainerStyle = {}, 20 + }: { 21 + contentContainerStyle?: Exclude< 22 + ScrollViewProps["contentContainerStyle"], 23 + string 24 + >; 25 + }) { 26 const [streams, setStreams] = useState<Segment[]>([]); 27 const [error, setError] = useState<boolean>(false); 28 const [loading, setLoading] = useState<boolean>(false); 29 const [retryTime, setRetryTime] = useState<number>(Date.now()); 30 const { url } = useAquareumNode(); 31 + const navigation = useNavigation(); 32 useEffect(() => { 33 setError(false); 34 setLoading(true); ··· 58 return <ErrorBox onRetry={() => setRetryTime(Date.now())} />; 59 } 60 return ( 61 + <ScrollView 62 + contentContainerStyle={{ 63 + alignItems: "stretch", 64 + 65 + ...contentContainerStyle, 66 + }} 67 + refreshControl={ 68 + <RefreshControl 69 + refreshing={loading} 70 + onRefresh={() => setRetryTime(Date.now())} 71 + /> 72 + } 73 + > 74 {streams.map((seg) => ( 75 + <View flex={1}> 76 + <AQLink to={{ screen: "Stream", params: { user: seg.user } }}> 77 + <YStack f={1} alignItems="center"> 78 <Image 79 + f={1} 80 + aspectRatio={16 / 9} 81 + maxWidth={400} 82 + width="100%" 83 src={`${url}/api/playback/${seg.user}/stream.jpg`} 84 resizeMode="contain" 85 objectFit="contain" 86 /> 87 + <H6>{formatAddress(seg.user)}</H6> 88 + </YStack> 89 + </AQLink> 90 + </View> 91 ))} 92 </ScrollView> 93 );
+3
js/app/hooks/platform/index.tsx
···
··· 1 + import { initPushNotifications, topSafeHeight } from "./platform"; 2 + 3 + export { initPushNotifications, topSafeHeight };
+3
js/app/hooks/textUtils.tsx
···
··· 1 + export function formatAddress(address: string) { 2 + return address.slice(0, 6) + "..." + address.slice(-4); 3 + }
+3
js/app/hooks/useAquareumNode.tsx
··· 2 import { isWeb } from "tamagui"; 3 4 let DEFAULT_URL = process.env.EXPO_PUBLIC_AQUAREUM_URL; 5 if (isWeb && process.env.EXPO_PUBLIC_WEB_TRY_LOCAL === "true") { 6 try { 7 DEFAULT_URL = `${window.location.protocol}//${window.location.host}`;
··· 2 import { isWeb } from "tamagui"; 3 4 let DEFAULT_URL = process.env.EXPO_PUBLIC_AQUAREUM_URL; 5 + console.log({ 6 + EXPO_PUBLIC_WEB_TRY_LOCAL: process.env.EXPO_PUBLIC_WEB_TRY_LOCAL, 7 + }); 8 if (isWeb && process.env.EXPO_PUBLIC_WEB_TRY_LOCAL === "true") { 9 try { 10 DEFAULT_URL = `${window.location.protocol}//${window.location.host}`;
+3
js/app/hooks/usePlatform.native.tsx
··· 1 import { Platform } from "react-native"; 2 import { IsPlatform } from "./usePlatform.shared"; 3 4 export default function usePlatform(): IsPlatform { 5 return { 6 isNative: true, 7 isIOS: Platform.OS === "ios", 8 isAndroid: Platform.OS === "android",
··· 1 import { Platform } from "react-native"; 2 + import { initPushNotifications, topSafeHeight } from "./platform"; 3 import { IsPlatform } from "./usePlatform.shared"; 4 5 export default function usePlatform(): IsPlatform { 6 return { 7 + initPushNotifications, 8 + topSafeHeight, 9 isNative: true, 10 isIOS: Platform.OS === "ios", 11 isAndroid: Platform.OS === "android",
+3
js/app/hooks/usePlatform.shared.tsx
··· 13 isChrome: boolean; 14 // don't rely on this! just for defaults 15 isFirefox: boolean; 16 };
··· 13 isChrome: boolean; 14 // don't rely on this! just for defaults 15 isFirefox: boolean; 16 + 17 + initPushNotifications: () => Promise<void>; 18 + topSafeHeight: () => number; 19 };
+3
js/app/hooks/usePlatform.tsx
··· 1 import { IsPlatform } from "./usePlatform.shared"; 2 // it's only for setting defaults, i promise! 3 import uaParser from "ua-parser-js"; 4 5 function supportsHLS() { 6 var video = document.createElement("video"); ··· 18 } 19 const electron = typeof window["AQ_ELECTRON"] !== "undefined"; 20 return { 21 isNative: false, 22 isIOS: false, 23 isAndroid: false,
··· 1 import { IsPlatform } from "./usePlatform.shared"; 2 // it's only for setting defaults, i promise! 3 import uaParser from "ua-parser-js"; 4 + import { initPushNotifications, topSafeHeight } from "./platform"; 5 6 function supportsHLS() { 7 var video = document.createElement("video"); ··· 19 } 20 const electron = typeof window["AQ_ELECTRON"] !== "undefined"; 21 return { 22 + initPushNotifications, 23 + topSafeHeight, 24 isNative: false, 25 isIOS: false, 26 isAndroid: false,
+10 -6
js/app/package.json
··· 1 { 2 "name": "aquareum", 3 - "main": "expo-router/entry", 4 "version": "0.2.2", 5 "scripts": { 6 "upgrade:tamagui": "yarn up '*tamagui*'@latest '@tamagui/*'@latest", 7 "upgrade:tamagui:canary": "yarn up '*tamagui*'@canary '@tamagui/*'@canary", ··· 27 "@rainbow-me/rainbowkit": "2", 28 "@react-native-firebase/app": "^20.3.0", 29 "@react-native-firebase/messaging": "^20.3.0", 30 - "@react-navigation/native": "^6.1.17", 31 "@tamagui/config": "^1.102.3", 32 "@tamagui/lucide-icons": "^1.102.3", 33 "@tamagui/toast": "^1.102.3", ··· 41 "expo-font": "~12.0.9", 42 "expo-linking": "~6.3.1", 43 "expo-notifications": "~0.28.10", 44 - "expo-router": "~3.5.18", 45 "expo-splash-screen": "~0.27.5", 46 "expo-status-bar": "^1.12.1", 47 "expo-system-ui": "~3.0.7", ··· 52 "react": "18.3.1", 53 "react-dom": "18.3.1", 54 "react-native": "0.74.3", 55 "react-native-markdown-display": "^7.0.2", 56 - "react-native-reanimated": "~3.14.0", 57 - "react-native-safe-area-context": "4.10.8", 58 - "react-native-screens": "~3.32.0", 59 "react-native-svg": "15.4.0", 60 "react-native-web": "^0.19.12", 61 "react-native-webview": "13.10.5",
··· 1 { 2 "name": "aquareum", 3 + "main": "./src/entrypoint.tsx", 4 "version": "0.2.2", 5 + "runtimeVersion": "0.2.2", 6 "scripts": { 7 "upgrade:tamagui": "yarn up '*tamagui*'@latest '@tamagui/*'@latest", 8 "upgrade:tamagui:canary": "yarn up '*tamagui*'@canary '@tamagui/*'@canary", ··· 28 "@rainbow-me/rainbowkit": "2", 29 "@react-native-firebase/app": "^20.3.0", 30 "@react-native-firebase/messaging": "^20.3.0", 31 + "@react-navigation/bottom-tabs": "^6.6.1", 32 + "@react-navigation/drawer": "^6.7.2", 33 + "@react-navigation/native": "^6.1.18", 34 + "@react-navigation/native-stack": "^6.11.0", 35 "@tamagui/config": "^1.102.3", 36 "@tamagui/lucide-icons": "^1.102.3", 37 "@tamagui/toast": "^1.102.3", ··· 45 "expo-font": "~12.0.9", 46 "expo-linking": "~6.3.1", 47 "expo-notifications": "~0.28.10", 48 "expo-splash-screen": "~0.27.5", 49 "expo-status-bar": "^1.12.1", 50 "expo-system-ui": "~3.0.7", ··· 55 "react": "18.3.1", 56 "react-dom": "18.3.1", 57 "react-native": "0.74.3", 58 + "react-native-gesture-handler": "~2.16.1", 59 "react-native-markdown-display": "^7.0.2", 60 + "react-native-reanimated": "~3.10.1", 61 + "react-native-safe-area-context": "4.10.5", 62 + "react-native-screens": "3.31.1", 63 "react-native-svg": "15.4.0", 64 "react-native-web": "^0.19.12", 65 "react-native-webview": "13.10.5",
+36
js/app/public/index.html
···
··· 1 + <!doctype html> 2 + <html lang="%LANG_ISO_CODE%"> 3 + <head> 4 + <meta charset="utf-8" /> 5 + <meta httpEquiv="X-UA-Compatible" content="IE=edge" /> 6 + <meta 7 + name="viewport" 8 + content="width=device-width, initial-scale=1, shrink-to-fit=no" 9 + /> 10 + <title>Aquareum</title> 11 + <style id="expo-reset"> 12 + html, 13 + body { 14 + height: 100%; 15 + } 16 + body { 17 + overflow: hidden; 18 + } 19 + #root { 20 + display: flex; 21 + height: 100%; 22 + flex: 1; 23 + } 24 + </style> 25 + <style> 26 + html { 27 + background-color: black; 28 + } 29 + </style> 30 + </head> 31 + 32 + <body> 33 + <noscript> You need to enable JavaScript to run this app. </noscript> 34 + <div id="root"></div> 35 + </body> 36 + </html>
+13
js/app/public/serve.json
···
··· 1 + { 2 + "headers": [ 3 + { 4 + "source": "static/**/*.js", 5 + "headers": [ 6 + { 7 + "key": "Cache-Control", 8 + "value": "public, max-age=31536000, immutable" 9 + } 10 + ] 11 + } 12 + ] 13 + }
+8
js/app/src/entrypoint.tsx
···
··· 1 + // Don't add anything to this file! It needs to be minimal so that 2 + // hot module reloading works properly on web. 3 + 4 + import "@expo/metro-runtime"; 5 + import { registerRootComponent } from "expo"; 6 + import Router from "./router"; 7 + 8 + registerRootComponent(Router);
+198
js/app/src/router.tsx
···
··· 1 + import "@expo/metro-runtime"; 2 + import { createDrawerNavigator } from "@react-navigation/drawer"; 3 + import { 4 + CommonActions, 5 + DrawerActions, 6 + LinkingOptions, 7 + useNavigation, 8 + } from "@react-navigation/native"; 9 + import { createNativeStackNavigator } from "@react-navigation/native-stack"; 10 + import { 11 + ArrowLeft, 12 + Home, 13 + LockKeyhole, 14 + Menu, 15 + Settings as SettingsIcon, 16 + } from "@tamagui/lucide-icons"; 17 + import { Provider, Settings } from "components"; 18 + import Admin from "components/admin"; 19 + import StreamList from "components/stream-list/stream-list"; 20 + import usePlatform from "hooks/usePlatform"; 21 + import { useEffect } from "react"; 22 + import { Pressable } from "react-native"; 23 + import { useTheme, View } from "tamagui"; 24 + import MultiScreen from "./screens/multi"; 25 + import StreamScreen from "./screens/stream"; 26 + import SupportScreen from "./screens/support"; 27 + 28 + function HomeScreen() { 29 + return ( 30 + <View f={1}> 31 + <StreamList contentContainerStyle={{ paddingTop: "$3" }}></StreamList> 32 + </View> 33 + ); 34 + } 35 + 36 + const Stack = createNativeStackNavigator(); 37 + 38 + const linking: LinkingOptions<ReactNavigation.RootParamList> = { 39 + prefixes: ["tv.aquareum://", "tv.aquareum.dev://"], 40 + config: { 41 + screens: { 42 + Home: { 43 + screens: { 44 + StreamList: "", 45 + Stream: { 46 + path: "stream/:user", 47 + }, 48 + }, 49 + }, 50 + Multi: "multi/:config", 51 + Admin: "admin", 52 + Support: "support", 53 + Settings: "settings", 54 + }, 55 + }, 56 + }; 57 + 58 + const Drawer = createDrawerNavigator(); 59 + 60 + const NavigationButton = ({ canGoBack }: { canGoBack?: boolean }) => { 61 + const navigation = useNavigation(); 62 + return ( 63 + <Pressable 64 + style={{ padding: 10 }} 65 + onPress={() => { 66 + if (canGoBack) { 67 + navigation.goBack(); 68 + } else { 69 + navigation.dispatch(DrawerActions.toggleDrawer()); 70 + } 71 + }} 72 + > 73 + {canGoBack ? <ArrowLeft /> : <Menu />} 74 + </Pressable> 75 + ); 76 + }; 77 + 78 + export default function Router() { 79 + const { initPushNotifications, isWeb, isElectron } = usePlatform(); 80 + useEffect(() => { 81 + initPushNotifications(); 82 + }, []); 83 + if (isWeb && !isElectron) { 84 + linking.prefixes.push(document.location.origin); 85 + } 86 + return ( 87 + <Provider linking={linking}> 88 + <AquareumDrawer /> 89 + </Provider> 90 + ); 91 + } 92 + 93 + export function AquareumDrawer() { 94 + const theme = useTheme(); 95 + const { isWeb } = usePlatform(); 96 + const navigation = useNavigation(); 97 + return ( 98 + <Drawer.Navigator 99 + initialRouteName="Home" 100 + screenOptions={{ 101 + headerLeft: () => <NavigationButton />, 102 + drawerActiveTintColor: theme.accentColor.val, 103 + headerStyle: {}, 104 + }} 105 + > 106 + <Drawer.Screen 107 + name="Home" 108 + component={MainTab} 109 + options={{ 110 + drawerIcon: () => <Home />, 111 + headerTitle: "Aquareum", 112 + headerShown: isWeb, 113 + }} 114 + listeners={{ 115 + drawerItemPress: (e) => { 116 + e.preventDefault(); 117 + navigation.dispatch( 118 + CommonActions.reset({ 119 + index: 0, 120 + routes: [ 121 + { 122 + name: "Home", 123 + state: { 124 + routes: [{ name: "StreamList" }], 125 + }, 126 + }, 127 + ], 128 + }), 129 + ); 130 + }, 131 + }} 132 + /> 133 + <Drawer.Screen 134 + name="Settings" 135 + component={Settings} 136 + options={{ drawerIcon: () => <SettingsIcon /> }} 137 + /> 138 + {isWeb && ( 139 + <Drawer.Screen 140 + name="Admin" 141 + component={Admin} 142 + options={{ 143 + drawerIcon: () => <LockKeyhole />, 144 + drawerLabel: () => null, 145 + drawerItemStyle: { display: "none" }, 146 + }} 147 + /> 148 + )} 149 + <Drawer.Screen 150 + name="Multi" 151 + component={MultiScreen} 152 + options={{ 153 + drawerLabel: () => null, 154 + drawerItemStyle: { display: "none" }, 155 + }} 156 + /> 157 + <Drawer.Screen 158 + name="Support" 159 + component={SupportScreen} 160 + options={{ 161 + drawerLabel: () => null, 162 + drawerItemStyle: { display: "none" }, 163 + }} 164 + /> 165 + </Drawer.Navigator> 166 + ); 167 + } 168 + 169 + const MainTab = () => { 170 + const theme = useTheme(); 171 + const { isWeb } = usePlatform(); 172 + return ( 173 + // <SafeAreaView style={{ flex: 1, backgroundColor: theme.background.val }}> 174 + <Stack.Navigator 175 + initialRouteName="StreamList" 176 + screenOptions={{ 177 + headerLeft: ({ canGoBack }) => ( 178 + <NavigationButton canGoBack={canGoBack} /> 179 + ), 180 + headerShown: !isWeb, 181 + }} 182 + > 183 + <Stack.Screen 184 + name="StreamList" 185 + component={HomeScreen} 186 + options={{ headerTitle: "Aquareum" }} 187 + /> 188 + <Stack.Screen 189 + name="Stream" 190 + component={StreamScreen} 191 + options={{ 192 + headerTitle: "Stream", 193 + }} 194 + /> 195 + </Stack.Navigator> 196 + // </SafeAreaView> 197 + ); 198 + };
+58
js/app/src/screens/multi.tsx
···
··· 1 + import { Player } from "components"; 2 + import { PlayerProps } from "components/player/props"; 3 + import { useEffect, useState } from "react"; 4 + import { Text, View, XStack, YStack } from "tamagui"; 5 + 6 + export default function MultiScreen({ route }) { 7 + const config = route.params?.config; 8 + if (typeof config !== "string") { 9 + return <View />; 10 + } 11 + 12 + const [rows, setRows] = useState<Partial<PlayerProps | null>[][]>([]); 13 + const [error, setError] = useState<string | null>(null); 14 + useEffect(() => { 15 + try { 16 + let nearestSquareExpo = 1; 17 + const playerProps = JSON.parse( 18 + config as string, 19 + ) as Partial<PlayerProps>[]; 20 + while (Math.pow(nearestSquareExpo, 2) < playerProps.length) { 21 + nearestSquareExpo += 1; 22 + } 23 + const rows: Partial<PlayerProps | null>[][] = []; 24 + let idx = 0; 25 + for (let i = 0; i < nearestSquareExpo; i += 1) { 26 + const row: Partial<PlayerProps | null>[] = []; 27 + for (let j = 0; j < nearestSquareExpo; j += 1) { 28 + if (playerProps[idx]) { 29 + row.push(playerProps[idx]); 30 + } else { 31 + row.push(null); 32 + } 33 + idx += 1; 34 + } 35 + rows.push(row); 36 + } 37 + setRows(rows); 38 + } catch (e) { 39 + setError(e.message); 40 + } 41 + }, [config]); 42 + if (error) { 43 + return <Text>{error}</Text>; 44 + } 45 + return ( 46 + <YStack f={1} fb={0}> 47 + {rows.map((players, i) => ( 48 + <XStack key={i} f={1} fb={0}> 49 + {players.map((props, j) => ( 50 + <View key={j} f={1} fb={0}> 51 + {props === null ? <View /> : <Player {...props}></Player>} 52 + </View> 53 + ))} 54 + </XStack> 55 + ))} 56 + </YStack> 57 + ); 58 + }
js/app/src/screens/settings.tsx

This is a binary file and will not be displayed.

+6
js/app/src/screens/stream.tsx
···
··· 1 + import { Player } from "components/player/player"; 2 + 3 + export default function StreamScreen({ route }) { 4 + const { user } = route.params; 5 + return <Player src={user} />; 6 + }
+16 -3
js/desktop/src/index.ts
··· 1 - import { app, BrowserWindow, dialog } from "electron"; 2 import { parseArgs } from "node:util"; 3 import { resolve } from "path"; 4 import "source-map-support/register"; ··· 94 } 95 const mainWindow = await makeWindow(); 96 97 let startPath; 98 if (nodeFrontend) { 99 startPath = `${loadAddr}${args.path}`; ··· 120 try { 121 const mainWindow = await makeWindow(); 122 123 const testId = uuidv7(); 124 const definitions = [ 125 { ··· 143 })); 144 const enc = encodeURIComponent(JSON.stringify(tests)); 145 146 if (nodeFrontend) { 147 - mainWindow.loadURL(`${addr}/multi/${enc}`); 148 } else { 149 - mainWindow.loadURL(`http://localhost:38081/multi/${enc}`); 150 } 151 152 let foundThumbnail = false; 153 const interval = setInterval(async () => {
··· 1 + import { app, BrowserWindow, dialog, globalShortcut } from "electron"; 2 import { parseArgs } from "node:util"; 3 import { resolve } from "path"; 4 import "source-map-support/register"; ··· 94 } 95 const mainWindow = await makeWindow(); 96 97 + globalShortcut.register("CommandOrControl+Shift+I", () => { 98 + mainWindow.webContents.toggleDevTools(); 99 + }); 100 + 101 let startPath; 102 if (nodeFrontend) { 103 startPath = `${loadAddr}${args.path}`; ··· 124 try { 125 const mainWindow = await makeWindow(); 126 127 + globalShortcut.register("CommandOrControl+Shift+I", () => { 128 + mainWindow.webContents.toggleDevTools(); 129 + }); 130 + 131 const testId = uuidv7(); 132 const definitions = [ 133 { ··· 151 })); 152 const enc = encodeURIComponent(JSON.stringify(tests)); 153 154 + console.log(`http://localhost:38081/multi/${enc}`); 155 + 156 + let load; 157 if (nodeFrontend) { 158 + load = `${addr}/multi/${enc}`; 159 } else { 160 + load = `http://localhost:38081/multi/${enc}`; 161 } 162 + console.log(`opening ${load}`); 163 + mainWindow.loadURL(load); 164 165 let foundThumbnail = false; 166 const interval = setInterval(async () => {
+3 -15
pkg/api/app-updates.go
··· 224 return nil, err 225 } 226 227 - file2, err := fs.Open("expoConfig.json") 228 - if err != nil { 229 - return nil, err 230 - } 231 - bs2, err := io.ReadAll(file2) 232 - if err != nil { 233 - return nil, err 234 - } 235 - extra := map[string]any{} 236 - err = json.Unmarshal(bs2, &extra) 237 - if err != nil { 238 - return nil, err 239 - } 240 241 rt, ok := extra["runtimeVersion"] 242 if !ok { 243 - return nil, fmt.Errorf("expoConfig.json missing runtimeVersion") 244 } 245 runtimeVersion, ok := rt.(string) 246 if !ok { 247 - return nil, fmt.Errorf("expoConfig.json has runtimeVersion that's not a string") 248 } 249 250 var privateKey *rsa.PrivateKey
··· 224 return nil, err 225 } 226 227 + extra, err := app.PackageJSON() 228 229 rt, ok := extra["runtimeVersion"] 230 if !ok { 231 + return nil, fmt.Errorf("package.json missing runtimeVersion") 232 } 233 runtimeVersion, ok := rt.(string) 234 if !ok { 235 + return nil, fmt.Errorf("package.json has runtimeVersion that's not a string") 236 } 237 238 var privateKey *rsa.PrivateKey
+46 -3
pkg/api/playback.go
··· 6 "fmt" 7 "io" 8 "net/http" 9 "path/filepath" 10 "strconv" 11 "strings" ··· 104 } 105 } 106 107 - func (a *AquareumAPI) HandleHLSPlayback(ctx context.Context) httprouter.Handle { 108 return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 109 user := p.ByName("user") 110 if user == "" { 111 errors.WriteHTTPBadRequest(w, "user required", nil) ··· 124 } 125 dir := getDir() 126 fullpath := filepath.Join(dir, file) 127 - http.ServeFile(w, r, fullpath) 128 - } 129 } 130 131 func (a *AquareumAPI) HandleThumbnailPlayback(ctx context.Context) httprouter.Handle {
··· 6 "fmt" 7 "io" 8 "net/http" 9 + "os" 10 "path/filepath" 11 "strconv" 12 "strings" ··· 105 } 106 } 107 108 + var epoch = time.Unix(0, 0).Format(time.RFC1123) 109 + 110 + var noCacheHeaders = map[string]string{ 111 + "Expires": epoch, 112 + "Cache-Control": "no-cache, private, max-age=0", 113 + "Pragma": "no-cache", 114 + "X-Accel-Expires": "0", 115 + } 116 + 117 + var etagHeaders = []string{ 118 + "ETag", 119 + "If-Modified-Since", 120 + "If-Match", 121 + "If-None-Match", 122 + "If-Range", 123 + "If-Unmodified-Since", 124 + } 125 + 126 + func NoCache(h httprouter.Handle) httprouter.Handle { 127 return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 128 + // Delete any ETag headers that may have been set 129 + for _, v := range etagHeaders { 130 + if r.Header.Get(v) != "" { 131 + r.Header.Del(v) 132 + } 133 + } 134 + 135 + // Set our NoCache headers 136 + for k, v := range noCacheHeaders { 137 + w.Header().Set(k, v) 138 + } 139 + 140 + h(w, r, p) 141 + } 142 + } 143 + 144 + func (a *AquareumAPI) HandleHLSPlayback(ctx context.Context) httprouter.Handle { 145 + return NoCache(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 146 user := p.ByName("user") 147 if user == "" { 148 errors.WriteHTTPBadRequest(w, "user required", nil) ··· 161 } 162 dir := getDir() 163 fullpath := filepath.Join(dir, file) 164 + f, err := os.Open(fullpath) 165 + if err != nil { 166 + errors.WriteHTTPInternalServerError(w, "could not open file", err) 167 + return 168 + } 169 + defer f.Close() 170 + http.ServeContent(w, r, file, time.Now(), f) 171 + }) 172 } 173 174 func (a *AquareumAPI) HandleThumbnailPlayback(ctx context.Context) httprouter.Handle {
+28 -1
pkg/cmd/aquareum.go
··· 40 41 // parse the CLI and fire up an aquareum node! 42 func start(build *config.BuildFlags, platformJobs []jobFunc) error { 43 if len(os.Args) > 1 && os.Args[1] == "stream" { 44 if len(os.Args) != 3 { 45 fmt.Println("usage: aquareum stream [user]") ··· 101 fs.IntVar(&cli.MistHTTPPort, "mist-http-port", 18080, "MistServer HTTP port (internal use only)") 102 } 103 104 - err := cli.Parse( 105 fs, os.Args[1:], 106 ) 107 if err != nil {
··· 40 41 // parse the CLI and fire up an aquareum node! 42 func start(build *config.BuildFlags, platformJobs []jobFunc) error { 43 + selfTest := len(os.Args) > 1 && os.Args[1] == "self-test" 44 + err := media.RunSelfTest(context.Background()) 45 + if err != nil { 46 + if selfTest { 47 + fmt.Println(err.Error()) 48 + os.Exit(1) 49 + } else { 50 + retryCount, _ := strconv.Atoi(os.Getenv("AQUAREUM_SELFTEST_RETRY")) 51 + if retryCount >= 3 { 52 + log.Error(context.Background(), "gstreamer self-test failed 3 times, giving up", "error", err) 53 + return err 54 + } 55 + log.Log(context.Background(), "error in gstreamer self-test, attempting recovery", "error", err, "retry", retryCount+1) 56 + os.Setenv("AQUAREUM_SELFTEST_RETRY", strconv.Itoa(retryCount+1)) 57 + err := syscall.Exec(os.Args[0], os.Args[1:], os.Environ()) 58 + if err != nil { 59 + log.Error(context.Background(), "error in gstreamer self-test, could not restart", "error", err) 60 + return err 61 + } 62 + panic("invalid code path: exec succeeded but we're still here???") 63 + } 64 + } 65 + if selfTest { 66 + fmt.Println("self-test successful!") 67 + os.Exit(0) 68 + } 69 + 70 if len(os.Args) > 1 && os.Args[1] == "stream" { 71 if len(os.Args) != 3 { 72 fmt.Println("usage: aquareum stream [user]") ··· 128 fs.IntVar(&cli.MistHTTPPort, "mist-http-port", 18080, "MistServer HTTP port (internal use only)") 129 } 130 131 + err = cli.Parse( 132 fs, os.Args[1:], 133 ) 134 if err != nil {
+109 -331
yarn.lock
··· 1923 languageName: node 1924 linkType: hard 1925 1926 - "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.23.2": 1927 version: 7.24.7 1928 resolution: "@babel/runtime@npm:7.24.7" 1929 dependencies: ··· 2030 dependencies: 2031 "@jridgewell/trace-mapping": "npm:0.3.9" 2032 checksum: 10/b6e38a1712fab242c86a241c229cf562195aad985d0564bd352ac404be583029e89e93028ffd2c251d2c407ecac5fb0cbdca94a2d5c10f29ac806ede0508b3ff 2033 languageName: node 2034 linkType: hard 2035 ··· 3148 languageName: node 3149 linkType: hard 3150 3151 - "@expo/metro-runtime@npm:3.2.1, @expo/metro-runtime@npm:~3.2.1": 3152 version: 3.2.1 3153 resolution: "@expo/metro-runtime@npm:3.2.1" 3154 peerDependencies: ··· 3259 version: 1.0.0 3260 resolution: "@expo/sdk-runtime-versions@npm:1.0.0" 3261 checksum: 10/0942d5a356f590e8dc795761456cc48b3e2d6a38ad2a02d6774efcdc5a70424e05623b4e3e5d2fec0cdc30f40dde05c14391c781607eed3971bf8676518bfd9d 3262 - languageName: node 3263 - linkType: hard 3264 - 3265 - "@expo/server@npm:^0.4.0": 3266 - version: 0.4.3 3267 - resolution: "@expo/server@npm:0.4.3" 3268 - dependencies: 3269 - "@remix-run/node": "npm:^2.7.2" 3270 - abort-controller: "npm:^3.0.0" 3271 - debug: "npm:^4.3.4" 3272 - source-map-support: "npm:~0.5.21" 3273 - checksum: 10/3965a05cd05efcc241a5e3ab8dfc33853ba64f4c4995de68c5c71f80638fccc01659fad8a750580c222a8572a09cbfa80470d2308f9a3e39bf32cecb29f5fdb0 3274 languageName: node 3275 linkType: hard 3276 ··· 5498 languageName: node 5499 linkType: hard 5500 5501 - "@radix-ui/react-compose-refs@npm:1.0.0": 5502 - version: 1.0.0 5503 - resolution: "@radix-ui/react-compose-refs@npm:1.0.0" 5504 - dependencies: 5505 - "@babel/runtime": "npm:^7.13.10" 5506 - peerDependencies: 5507 - react: ^16.8 || ^17.0 || ^18.0 5508 - checksum: 10/fb98be2e275a1a758ccac647780ff5b04be8dcf25dcea1592db3b691fecf719c4c0700126da605b2f512dd89caa111352b9fad59528d736b4e0e9a0e134a74a1 5509 - languageName: node 5510 - linkType: hard 5511 - 5512 - "@radix-ui/react-slot@npm:1.0.1": 5513 - version: 1.0.1 5514 - resolution: "@radix-ui/react-slot@npm:1.0.1" 5515 - dependencies: 5516 - "@babel/runtime": "npm:^7.13.10" 5517 - "@radix-ui/react-compose-refs": "npm:1.0.0" 5518 - peerDependencies: 5519 - react: ^16.8 || ^17.0 || ^18.0 5520 - checksum: 10/b00fc6ec54a20785263540d9e4a0e3a13d9bc54d7af49b64f6a268eba4a6560c291bd95bbaa7cf7609fdf6fd0ebae54605bb01313de3fa180b06f2a321e9a3b4 5521 - languageName: node 5522 - linkType: hard 5523 - 5524 "@rainbow-me/rainbowkit@npm:2": 5525 version: 2.1.3 5526 resolution: "@rainbow-me/rainbowkit@npm:2.1.3" ··· 5953 languageName: node 5954 linkType: hard 5955 5956 - "@react-navigation/bottom-tabs@npm:~6.5.7": 5957 - version: 6.5.20 5958 - resolution: "@react-navigation/bottom-tabs@npm:6.5.20" 5959 dependencies: 5960 - "@react-navigation/elements": "npm:^1.3.30" 5961 color: "npm:^4.2.3" 5962 warn-once: "npm:^0.1.0" 5963 peerDependencies: ··· 5966 react-native: "*" 5967 react-native-safe-area-context: ">= 3.0.0" 5968 react-native-screens: ">= 3.0.0" 5969 - checksum: 10/327c5a6706a5e990295aa56e3ebda96b196ab67f9edbaa6eb6b80adbe131dddbb1eb564f95c6c0003bfd1e28e4040315a0a7cfd22bd7a79230b41ed6f9ba276d 5970 languageName: node 5971 linkType: hard 5972 5973 - "@react-navigation/core@npm:^6.4.16": 5974 - version: 6.4.16 5975 - resolution: "@react-navigation/core@npm:6.4.16" 5976 dependencies: 5977 "@react-navigation/routers": "npm:^6.1.9" 5978 escape-string-regexp: "npm:^4.0.0" 5979 nanoid: "npm:^3.1.23" 5980 query-string: "npm:^7.1.3" 5981 react-is: "npm:^16.13.0" 5982 - use-latest-callback: "npm:^0.1.9" 5983 peerDependencies: 5984 react: "*" 5985 - checksum: 10/1b58f1566c55412247f06c7a2c769ac588b595a75dc81945dc5b90d7b371fbcf982c16327adb956733467d0bbdc7a93fe3aedce0a8246dda653d07b239e727b0 5986 languageName: node 5987 linkType: hard 5988 5989 - "@react-navigation/elements@npm:^1.3.30": 5990 - version: 1.3.30 5991 - resolution: "@react-navigation/elements@npm:1.3.30" 5992 peerDependencies: 5993 "@react-navigation/native": ^6.0.0 5994 react: "*" 5995 react-native: "*" 5996 react-native-safe-area-context: ">= 3.0.0" 5997 - checksum: 10/caf0321ed2a632aa63473e18d05020228bba81bb39a6e4076fdd17fec2597bcad8cb8d6d7483653ecb465d007e4733c683995ca59144ee908db9a21cb47b13da 5998 languageName: node 5999 linkType: hard 6000 6001 - "@react-navigation/native-stack@npm:~6.9.12": 6002 - version: 6.9.26 6003 - resolution: "@react-navigation/native-stack@npm:6.9.26" 6004 dependencies: 6005 - "@react-navigation/elements": "npm:^1.3.30" 6006 warn-once: "npm:^0.1.0" 6007 peerDependencies: 6008 "@react-navigation/native": ^6.0.0 ··· 6010 react-native: "*" 6011 react-native-safe-area-context: ">= 3.0.0" 6012 react-native-screens: ">= 3.0.0" 6013 - checksum: 10/75c7bb5647d065f45ab234b9d44f8562641225c02b2c354be9fba6a1b8b53f216e8dd0c3749492fdf598d08bd9569749a1d6ad93fa40f15e9f141b35b0809ef9 6014 languageName: node 6015 linkType: hard 6016 6017 - "@react-navigation/native@npm:^6.1.17, @react-navigation/native@npm:~6.1.6": 6018 - version: 6.1.17 6019 - resolution: "@react-navigation/native@npm:6.1.17" 6020 dependencies: 6021 - "@react-navigation/core": "npm:^6.4.16" 6022 escape-string-regexp: "npm:^4.0.0" 6023 fast-deep-equal: "npm:^3.1.3" 6024 nanoid: "npm:^3.1.23" 6025 peerDependencies: 6026 react: "*" 6027 react-native: "*" 6028 - checksum: 10/f0b0ef565ddfd5a9bfa2448e0f0d2f18616144877acc6b2330b6b0966ed2c33702c445c553443651acc8488f8af840ffa5ee9353ddfd582e4b3ff26ecb85fbb5 6029 languageName: node 6030 linkType: hard 6031 ··· 6046 "@spacingbat3/lss": "npm:^1.0.0" 6047 semver: "npm:^7.3.8" 6048 checksum: 10/8bb04678ddbe9d1d4c0f54e1c668b1ec74f6d159af9255c7b6e4a7d29ec3b098461591ac7de7bb214d1e39c27094e0e4831fac928f0aa234c8e39ed17b400174 6049 - languageName: node 6050 - linkType: hard 6051 - 6052 - "@remix-run/node@npm:^2.7.2": 6053 - version: 2.10.2 6054 - resolution: "@remix-run/node@npm:2.10.2" 6055 - dependencies: 6056 - "@remix-run/server-runtime": "npm:2.10.2" 6057 - "@remix-run/web-fetch": "npm:^4.4.2" 6058 - "@web3-storage/multipart-parser": "npm:^1.0.0" 6059 - cookie-signature: "npm:^1.1.0" 6060 - source-map-support: "npm:^0.5.21" 6061 - stream-slice: "npm:^0.1.2" 6062 - undici: "npm:^6.11.1" 6063 - peerDependencies: 6064 - typescript: ^5.1.0 6065 - peerDependenciesMeta: 6066 - typescript: 6067 - optional: true 6068 - checksum: 10/8a092a6d7e0bfd9a2245882cf916a2097fbb3fa20848b30f038d435d0334f238bf39afc0684be4b48afe4f18d11ac6601d8e7bc54afa8057122444b896473425 6069 - languageName: node 6070 - linkType: hard 6071 - 6072 - "@remix-run/router@npm:1.17.1": 6073 - version: 1.17.1 6074 - resolution: "@remix-run/router@npm:1.17.1" 6075 - checksum: 10/5efc598626cd81688ac26e0abd08204b980831ead8cd2c4b8a27e0c169ee4777fc609fa289c093b93efc3a1e335304698c6961276c2309348444ec7209836c83 6076 - languageName: node 6077 - linkType: hard 6078 - 6079 - "@remix-run/server-runtime@npm:2.10.2": 6080 - version: 2.10.2 6081 - resolution: "@remix-run/server-runtime@npm:2.10.2" 6082 - dependencies: 6083 - "@remix-run/router": "npm:1.17.1" 6084 - "@types/cookie": "npm:^0.6.0" 6085 - "@web3-storage/multipart-parser": "npm:^1.0.0" 6086 - cookie: "npm:^0.6.0" 6087 - set-cookie-parser: "npm:^2.4.8" 6088 - source-map: "npm:^0.7.3" 6089 - turbo-stream: "npm:2.2.0" 6090 - peerDependencies: 6091 - typescript: ^5.1.0 6092 - peerDependenciesMeta: 6093 - typescript: 6094 - optional: true 6095 - checksum: 10/a9906dd479f173676c7b72b7b019c8b13a67c38e80790ea4280b4748587e26d7ffaf1423b61c98888e88c087a139e8023e191ac0e21ae5e3165d990015ddc9ed 6096 - languageName: node 6097 - linkType: hard 6098 - 6099 - "@remix-run/web-blob@npm:^3.1.0": 6100 - version: 3.1.0 6101 - resolution: "@remix-run/web-blob@npm:3.1.0" 6102 - dependencies: 6103 - "@remix-run/web-stream": "npm:^1.1.0" 6104 - web-encoding: "npm:1.1.5" 6105 - checksum: 10/24b95b90e2a5bfb17478c9e88a03c8de8db2cc6cbe744ee845cd8bfc278744f3d0a6f782f63b3361eef59488f544b60172fb98c628f113c936f597b303bc58f3 6106 - languageName: node 6107 - linkType: hard 6108 - 6109 - "@remix-run/web-fetch@npm:^4.4.2": 6110 - version: 4.4.2 6111 - resolution: "@remix-run/web-fetch@npm:4.4.2" 6112 - dependencies: 6113 - "@remix-run/web-blob": "npm:^3.1.0" 6114 - "@remix-run/web-file": "npm:^3.1.0" 6115 - "@remix-run/web-form-data": "npm:^3.1.0" 6116 - "@remix-run/web-stream": "npm:^1.1.0" 6117 - "@web3-storage/multipart-parser": "npm:^1.0.0" 6118 - abort-controller: "npm:^3.0.0" 6119 - data-uri-to-buffer: "npm:^3.0.1" 6120 - mrmime: "npm:^1.0.0" 6121 - checksum: 10/46961dae587d1d9eb1c678113cf8fcbe0e5779b3fdedd8b136c7065b47e967056d1508924992a58887b03ab406c44c838bca5d0f7d08a4bbaab9609178c3fe4b 6122 - languageName: node 6123 - linkType: hard 6124 - 6125 - "@remix-run/web-file@npm:^3.1.0": 6126 - version: 3.1.0 6127 - resolution: "@remix-run/web-file@npm:3.1.0" 6128 - dependencies: 6129 - "@remix-run/web-blob": "npm:^3.1.0" 6130 - checksum: 10/c5ce184fc8e3a8d5736798c9fa784a3416890382be707da927926d173e67227dc60ae2494be680bf0074a00fac5a9a737387ce820349fb2fecdc31be034854a0 6131 - languageName: node 6132 - linkType: hard 6133 - 6134 - "@remix-run/web-form-data@npm:^3.1.0": 6135 - version: 3.1.0 6136 - resolution: "@remix-run/web-form-data@npm:3.1.0" 6137 - dependencies: 6138 - web-encoding: "npm:1.1.5" 6139 - checksum: 10/4eaa98da8f8827d2fd6e676920c6352679b35e0fd11f83d99f8a1ee009e8e51a0c710e8d228cd481ad00a81b3afe53a1db97321f3c6c16e14a3d67858e238a91 6140 - languageName: node 6141 - linkType: hard 6142 - 6143 - "@remix-run/web-stream@npm:^1.1.0": 6144 - version: 1.1.0 6145 - resolution: "@remix-run/web-stream@npm:1.1.0" 6146 - dependencies: 6147 - web-streams-polyfill: "npm:^3.1.1" 6148 - checksum: 10/9904b1539feee3a86d667e9803783dfc78e21b665a4e67edfd795bd1acee753fda88f50abbebf7cffa010539ed5287b4a0d09f55101b80f2c891c15db1066eea 6149 languageName: node 6150 linkType: hard 6151 ··· 8877 languageName: node 8878 linkType: hard 8879 8880 - "@types/cookie@npm:^0.6.0": 8881 - version: 0.6.0 8882 - resolution: "@types/cookie@npm:0.6.0" 8883 - checksum: 10/b883348d5bf88695fbc2c2276b1c49859267a55cae3cf11ea1dccc1b3be15b466e637ce3242109ba27d616c77c6aa4efe521e3d557110b4fdd9bc332a12445c2 8884 - languageName: node 8885 - linkType: hard 8886 - 8887 "@types/debug@npm:^4.1.7": 8888 version: 4.1.12 8889 resolution: "@types/debug@npm:4.1.12" ··· 8947 "@types/minimatch": "npm:*" 8948 "@types/node": "npm:*" 8949 checksum: 10/6ae717fedfdfdad25f3d5a568323926c64f52ef35897bcac8aca8e19bc50c0bd84630bbd063e5d52078b2137d8e7d3c26eabebd1a2f03ff350fff8a91e79fc19 8950 languageName: node 8951 linkType: hard 8952 ··· 9921 languageName: node 9922 linkType: hard 9923 9924 - "@web3-storage/multipart-parser@npm:^1.0.0": 9925 - version: 1.0.0 9926 - resolution: "@web3-storage/multipart-parser@npm:1.0.0" 9927 - checksum: 10/20d7a4330392d83f727586477fc6e709f8fca3b3664365b0d9d3041abe2aeb13f53030a3997a28a39cc74930863ebb625878b8c90789e5c990e4b64d9f22a93d 9928 - languageName: node 9929 - linkType: hard 9930 - 9931 "@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": 9932 version: 1.12.1 9933 resolution: "@webassemblyjs/ast@npm:1.12.1" ··· 10132 bin: 10133 js-yaml: bin/js-yaml.js 10134 checksum: 10/83642debff31400764e8721ba8f386e0f5444b118c7a6c17dbdcb316b56fefa061ea0587af47de75e04d60059215a703a1ca8bbc479149581cd57d752cb3d4e0 10135 - languageName: node 10136 - linkType: hard 10137 - 10138 - "@zxing/text-encoding@npm:0.9.0": 10139 - version: 0.9.0 10140 - resolution: "@zxing/text-encoding@npm:0.9.0" 10141 - checksum: 10/268e4ef64b8eaa32b990240bdfd1f7b3e2b501a6ed866a565f7c9747f04ac884fbe0537fe12bb05d9241b98fb111270c0fd0023ef0a02d23a6619b4589e98f6b 10142 languageName: node 10143 linkType: hard 10144 ··· 10581 "@rainbow-me/rainbowkit": "npm:2" 10582 "@react-native-firebase/app": "npm:^20.3.0" 10583 "@react-native-firebase/messaging": "npm:^20.3.0" 10584 - "@react-navigation/native": "npm:^6.1.17" 10585 "@tamagui/babel-plugin": "npm:^1.102.3" 10586 "@tamagui/config": "npm:^1.102.3" 10587 "@tamagui/lucide-icons": "npm:^1.102.3" ··· 10600 expo-font: "npm:~12.0.9" 10601 expo-linking: "npm:~6.3.1" 10602 expo-notifications: "npm:~0.28.10" 10603 - expo-router: "npm:~3.5.18" 10604 expo-splash-screen: "npm:~0.27.5" 10605 expo-status-bar: "npm:^1.12.1" 10606 expo-system-ui: "npm:~3.0.7" ··· 10611 react: "npm:18.3.1" 10612 react-dom: "npm:18.3.1" 10613 react-native: "npm:0.74.3" 10614 react-native-markdown-display: "npm:^7.0.2" 10615 - react-native-reanimated: "npm:~3.14.0" 10616 - react-native-safe-area-context: "npm:4.10.8" 10617 - react-native-screens: "npm:~3.32.0" 10618 react-native-svg: "npm:15.4.0" 10619 react-native-web: "npm:^0.19.12" 10620 react-native-webview: "npm:13.10.5" ··· 12493 languageName: node 12494 linkType: hard 12495 12496 - "cookie-signature@npm:^1.1.0": 12497 - version: 1.2.1 12498 - resolution: "cookie-signature@npm:1.2.1" 12499 - checksum: 10/b871138a81382173d51dde5c1c56e8b313bc4b9e5f2f67d0d63be50fd43b92a25cb9bd72c2fc2935c0c6cb6cce834e7e2fd12830d7ec289ccac5bdec19dd14eb 12500 - languageName: node 12501 - linkType: hard 12502 - 12503 - "cookie@npm:0.6.0, cookie@npm:^0.6.0": 12504 version: 0.6.0 12505 resolution: "cookie@npm:0.6.0" 12506 checksum: 10/c1f8f2ea7d443b9331680598b0ae4e6af18a618c37606d1bbdc75bec8361cce09fe93e727059a673f2ba24467131a9fb5a4eec76bb1b149c1b3e1ccb268dc583 ··· 12798 version: 7.0.0 12799 resolution: "dargs@npm:7.0.0" 12800 checksum: 10/b8f1e3cba59c42e1f13a114ad4848c3fc1cf7470f633ee9e9f1043762429bc97d91ae31b826fb135eefde203a3fdb20deb0c0a0222ac29d937b8046085d668d1 12801 - languageName: node 12802 - linkType: hard 12803 - 12804 - "data-uri-to-buffer@npm:^3.0.1": 12805 - version: 3.0.1 12806 - resolution: "data-uri-to-buffer@npm:3.0.1" 12807 - checksum: 10/c59c3009686a78c071806b72f4810856ec28222f0f4e252aa495ec027ed9732298ceea99c50328cf59b151dd34cbc3ad6150bbb43e41fc56fa19f48c99e9fc30 12808 languageName: node 12809 linkType: hard 12810 ··· 14622 languageName: node 14623 linkType: hard 14624 14625 - "expo-router@npm:~3.5.18": 14626 - version: 3.5.18 14627 - resolution: "expo-router@npm:3.5.18" 14628 - dependencies: 14629 - "@expo/metro-runtime": "npm:3.2.1" 14630 - "@expo/server": "npm:^0.4.0" 14631 - "@radix-ui/react-slot": "npm:1.0.1" 14632 - "@react-navigation/bottom-tabs": "npm:~6.5.7" 14633 - "@react-navigation/native": "npm:~6.1.6" 14634 - "@react-navigation/native-stack": "npm:~6.9.12" 14635 - expo-splash-screen: "npm:0.27.5" 14636 - react-native-helmet-async: "npm:2.0.4" 14637 - schema-utils: "npm:^4.0.1" 14638 - peerDependencies: 14639 - "@react-navigation/drawer": ^6.5.8 14640 - expo: "*" 14641 - expo-constants: "*" 14642 - expo-linking: "*" 14643 - expo-status-bar: "*" 14644 - react-native-reanimated: "*" 14645 - react-native-safe-area-context: "*" 14646 - react-native-screens: "*" 14647 - peerDependenciesMeta: 14648 - "@react-navigation/drawer": 14649 - optional: true 14650 - "@testing-library/jest-native": 14651 - optional: true 14652 - react-native-reanimated: 14653 - optional: true 14654 - checksum: 10/94aa615155ffb2efd706ae54c0c6398836b15e8a583762d132e0ab2286dccaacfd87331df00c458be1f0812af4e95559e5bae2787ead43048f856d93f44432c7 14655 - languageName: node 14656 - linkType: hard 14657 - 14658 - "expo-splash-screen@npm:0.27.5, expo-splash-screen@npm:~0.27.5": 14659 version: 0.27.5 14660 resolution: "expo-splash-screen@npm:0.27.5" 14661 dependencies: ··· 16484 minimalistic-assert: "npm:^1.0.0" 16485 minimalistic-crypto-utils: "npm:^1.0.1" 16486 checksum: 10/0298a1445b8029a69b713d918ecaa84a1d9f614f5857e0c6e1ca517abfa1357216987b2ee08cc6cc73ba82a6c6ddf2ff11b9717a653530ef03be599d4699b836 16487 languageName: node 16488 linkType: hard 16489 ··· 20079 languageName: node 20080 linkType: hard 20081 20082 - "mrmime@npm:^1.0.0": 20083 - version: 1.0.1 20084 - resolution: "mrmime@npm:1.0.1" 20085 - checksum: 10/a157e833ffe76648ab2107319deeff024b80b136ec66c60fae9d339009a1bb72c57ec1feecfd6a905dfd3df29e2299e850bff84b69cad790cc9bd9ab075834d1 20086 - languageName: node 20087 - linkType: hard 20088 - 20089 "ms@npm:2.0.0": 20090 version: 2.0.0 20091 resolution: "ms@npm:2.0.0" ··· 22339 peerDependencies: 22340 react: ^18.3.1 22341 checksum: 10/3f4b73a3aa083091173b29812b10394dd06f4ac06aff410b74702cfb3aa29d7b0ced208aab92d5272919b612e5cda21aeb1d54191848cf6e46e9e354f3541f81 22342 - languageName: node 22343 - linkType: hard 22344 - 22345 - "react-fast-compare@npm:^3.2.2": 22346 - version: 3.2.2 22347 - resolution: "react-fast-compare@npm:3.2.2" 22348 - checksum: 10/a6826180ba75cefba1c8d3ac539735f9b627ca05d3d307fe155487f5d0228d376dac6c9708d04a283a7b9f9aee599b637446635b79c8c8753d0b4eece56c125c 22349 languageName: node 22350 linkType: hard 22351 ··· 22365 languageName: node 22366 linkType: hard 22367 22368 - "react-is@npm:^16.13.0, react-is@npm:^16.13.1": 22369 version: 16.13.1 22370 resolution: "react-is@npm:16.13.1" 22371 checksum: 10/5aa564a1cde7d391ac980bedee21202fc90bdea3b399952117f54fb71a932af1e5902020144fb354b4690b2414a0c7aafe798eb617b76a3d441d956db7726fdf ··· 22411 languageName: node 22412 linkType: hard 22413 22414 - "react-native-helmet-async@npm:2.0.4": 22415 - version: 2.0.4 22416 - resolution: "react-native-helmet-async@npm:2.0.4" 22417 dependencies: 22418 invariant: "npm:^2.2.4" 22419 - react-fast-compare: "npm:^3.2.2" 22420 - shallowequal: "npm:^1.1.0" 22421 peerDependencies: 22422 - react: ^16.6.0 || ^17.0.0 || ^18.0.0 22423 - checksum: 10/217bd0eaa61d426a512634369c70c44ce8b92127ec626dc40c65b72b1be1534ed3ed00ba2dd1a9ad77d1716ce8fd1e6db3f7209534303a652d9932a962d2c830 22424 languageName: node 22425 linkType: hard 22426 ··· 22439 languageName: node 22440 linkType: hard 22441 22442 - "react-native-reanimated@npm:~3.14.0": 22443 - version: 3.14.0 22444 - resolution: "react-native-reanimated@npm:3.14.0" 22445 dependencies: 22446 "@babel/plugin-transform-arrow-functions": "npm:^7.0.0-0" 22447 "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.0.0-0" ··· 22455 "@babel/core": ^7.0.0-0 22456 react: "*" 22457 react-native: "*" 22458 - checksum: 10/b780949f52bb0d66521c973173d01776febbedffa4ee86309f31b2cf6b1cae2d0a0b7e6a177c2dbc5f6d92ba3912b6b8bde9b3b42dd39fc0ef792acbd909096b 22459 languageName: node 22460 linkType: hard 22461 22462 - "react-native-safe-area-context@npm:4.10.8": 22463 - version: 4.10.8 22464 - resolution: "react-native-safe-area-context@npm:4.10.8" 22465 peerDependencies: 22466 react: "*" 22467 react-native: "*" 22468 - checksum: 10/3d8151b09ea6a6558e9ece7c3ef62904f6e19931e281ac4dbe900e3f63469bd834d6424c36d4e3a550de290cca5ac591e5f376de73f04a5a209edcb95abff0f9 22469 languageName: node 22470 linkType: hard 22471 22472 - "react-native-screens@npm:~3.32.0": 22473 - version: 3.32.0 22474 - resolution: "react-native-screens@npm:3.32.0" 22475 dependencies: 22476 react-freeze: "npm:^1.0.0" 22477 warn-once: "npm:^0.1.0" 22478 peerDependencies: 22479 react: "*" 22480 react-native: "*" 22481 - checksum: 10/22f27b2082e0c8d70165c8a26dcf55b4da2a18166d4b9a0b434f558edea57d71458800d5086940fe7cabe8b7e28c8614cf227f35152171d08583f4ef6f6f3a35 22482 languageName: node 22483 linkType: hard 22484 ··· 23471 languageName: node 23472 linkType: hard 23473 23474 - "schema-utils@npm:^4.0.0, schema-utils@npm:^4.0.1": 23475 version: 4.2.0 23476 resolution: "schema-utils@npm:4.2.0" 23477 dependencies: ··· 23668 languageName: node 23669 linkType: hard 23670 23671 - "set-cookie-parser@npm:^2.4.8": 23672 - version: 2.6.0 23673 - resolution: "set-cookie-parser@npm:2.6.0" 23674 - checksum: 10/8d451ebadb760989f93b634942c79de3c925ca7a986d133d08a80c40b5ae713ce12e354f0d5245c49f288c52daa7bd6554d5dc52f8a4eecaaf5e192881cf2b1f 23675 - languageName: node 23676 - linkType: hard 23677 - 23678 "set-function-length@npm:^1.2.1": 23679 version: 1.2.2 23680 resolution: "set-function-length@npm:1.2.2" ··· 23771 languageName: node 23772 linkType: hard 23773 23774 - "shallowequal@npm:^1.1.0": 23775 - version: 1.1.0 23776 - resolution: "shallowequal@npm:1.1.0" 23777 - checksum: 10/f4c1de0837f106d2dbbfd5d0720a5d059d1c66b42b580965c8f06bb1db684be8783538b684092648c981294bf817869f743a066538771dbecb293df78f765e00 23778 - languageName: node 23779 - linkType: hard 23780 - 23781 "shebang-command@npm:^1.2.0": 23782 version: 1.2.0 23783 resolution: "shebang-command@npm:1.2.0" ··· 24293 version: 1.0.3 24294 resolution: "stream-shift@npm:1.0.3" 24295 checksum: 10/a24c0a3f66a8f9024bd1d579a533a53be283b4475d4e6b4b3211b964031447bdf6532dd1f3c2b0ad66752554391b7c62bd7ca4559193381f766534e723d50242 24296 - languageName: node 24297 - linkType: hard 24298 - 24299 - "stream-slice@npm:^0.1.2": 24300 - version: 0.1.2 24301 - resolution: "stream-slice@npm:0.1.2" 24302 - checksum: 10/6fa948ea58523f11f72e796f99579ff2bbecdff080d63b25762b0c0d282ac9a2d98af0f6e84dcc8d24c6284b2f7ce92ce0f9a1c8f77c91ac273954754e53c781 24303 languageName: node 24304 linkType: hard 24305 ··· 25226 languageName: node 25227 linkType: hard 25228 25229 - "turbo-stream@npm:2.2.0": 25230 - version: 2.2.0 25231 - resolution: "turbo-stream@npm:2.2.0" 25232 - checksum: 10/1a61be95585d6c901c7f626497765e28c38332f6562f4206e8a295dca87297d044f2413f369764365a6cbb13aae3c21fee2aace4525ff32c1e787e418916914e 25233 - languageName: node 25234 - linkType: hard 25235 - 25236 "type-check@npm:^0.4.0, type-check@npm:~0.4.0": 25237 version: 0.4.0 25238 resolution: "type-check@npm:0.4.0" ··· 25535 languageName: node 25536 linkType: hard 25537 25538 - "undici@npm:^6.11.1": 25539 - version: 6.19.2 25540 - resolution: "undici@npm:6.19.2" 25541 - checksum: 10/f4895c0c1e2fcde18076ac98965c231875811b8e14ceab1a7b19e940ed1a2fce69e0864e1186bfdc9347dffe0f39a00daf6dd5e7c0169b2b33de59a859dc2f1d 25542 - languageName: node 25543 - linkType: hard 25544 - 25545 "unenv@npm:^1.9.0": 25546 version: 1.10.0 25547 resolution: "unenv@npm:1.10.0" ··· 25831 languageName: node 25832 linkType: hard 25833 25834 - "use-latest-callback@npm:^0.1.9": 25835 - version: 0.1.11 25836 - resolution: "use-latest-callback@npm:0.1.11" 25837 peerDependencies: 25838 react: ">=16.8" 25839 - checksum: 10/cc6df404a4ed3a39d0eb014a815c2e568a6abbe8c5ff09f9205a08bf68e86201826ed633865a6056ca0fd9581e0e7e70f18ca26c592a9eaccea5d244cf283b1f 25840 languageName: node 25841 linkType: hard 25842 ··· 25892 languageName: node 25893 linkType: hard 25894 25895 - "util@npm:^0.12.3, util@npm:^0.12.4, util@npm:^0.12.5": 25896 version: 0.12.5 25897 resolution: "util@npm:0.12.5" 25898 dependencies: ··· 26123 dependencies: 26124 defaults: "npm:^1.0.3" 26125 checksum: 10/182ebac8ca0b96845fae6ef44afd4619df6987fe5cf552fdee8396d3daa1fb9b8ec5c6c69855acb7b3c1231571393bd1f0a4cdc4028d421575348f64bb0a8817 26126 - languageName: node 26127 - linkType: hard 26128 - 26129 - "web-encoding@npm:1.1.5": 26130 - version: 1.1.5 26131 - resolution: "web-encoding@npm:1.1.5" 26132 - dependencies: 26133 - "@zxing/text-encoding": "npm:0.9.0" 26134 - util: "npm:^0.12.3" 26135 - dependenciesMeta: 26136 - "@zxing/text-encoding": 26137 - optional: true 26138 - checksum: 10/243518cfa8388ac05eeb4041bd330d38c599476ff9a93239b386d1ba2af130089a2fcefb0cf65b385f989105ff460ae69dca7e42236f4d98dc776b04e558cdb5 26139 - languageName: node 26140 - linkType: hard 26141 - 26142 - "web-streams-polyfill@npm:^3.1.1": 26143 - version: 3.3.3 26144 - resolution: "web-streams-polyfill@npm:3.3.3" 26145 - checksum: 10/8e7e13501b3834094a50abe7c0b6456155a55d7571312b89570012ef47ec2a46d766934768c50aabad10a9c30dd764a407623e8bfcc74fcb58495c29130edea9 26146 languageName: node 26147 linkType: hard 26148
··· 1923 languageName: node 1924 linkType: hard 1925 1926 + "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.23.2": 1927 version: 7.24.7 1928 resolution: "@babel/runtime@npm:7.24.7" 1929 dependencies: ··· 2030 dependencies: 2031 "@jridgewell/trace-mapping": "npm:0.3.9" 2032 checksum: 10/b6e38a1712fab242c86a241c229cf562195aad985d0564bd352ac404be583029e89e93028ffd2c251d2c407ecac5fb0cbdca94a2d5c10f29ac806ede0508b3ff 2033 + languageName: node 2034 + linkType: hard 2035 + 2036 + "@egjs/hammerjs@npm:^2.0.17": 2037 + version: 2.0.17 2038 + resolution: "@egjs/hammerjs@npm:2.0.17" 2039 + dependencies: 2040 + "@types/hammerjs": "npm:^2.0.36" 2041 + checksum: 10/f695129d45edfcfd6c5f2d1d36186da36ffade013991972ce23721a6b7ad7f214ce282abc4023e3f6b63062620852a63e897b523f247804afc7acd188fee9d9d 2042 languageName: node 2043 linkType: hard 2044 ··· 3157 languageName: node 3158 linkType: hard 3159 3160 + "@expo/metro-runtime@npm:~3.2.1": 3161 version: 3.2.1 3162 resolution: "@expo/metro-runtime@npm:3.2.1" 3163 peerDependencies: ··· 3268 version: 1.0.0 3269 resolution: "@expo/sdk-runtime-versions@npm:1.0.0" 3270 checksum: 10/0942d5a356f590e8dc795761456cc48b3e2d6a38ad2a02d6774efcdc5a70424e05623b4e3e5d2fec0cdc30f40dde05c14391c781607eed3971bf8676518bfd9d 3271 languageName: node 3272 linkType: hard 3273 ··· 5495 languageName: node 5496 linkType: hard 5497 5498 "@rainbow-me/rainbowkit@npm:2": 5499 version: 2.1.3 5500 resolution: "@rainbow-me/rainbowkit@npm:2.1.3" ··· 5927 languageName: node 5928 linkType: hard 5929 5930 + "@react-navigation/bottom-tabs@npm:^6.6.1": 5931 + version: 6.6.1 5932 + resolution: "@react-navigation/bottom-tabs@npm:6.6.1" 5933 dependencies: 5934 + "@react-navigation/elements": "npm:^1.3.31" 5935 color: "npm:^4.2.3" 5936 warn-once: "npm:^0.1.0" 5937 peerDependencies: ··· 5940 react-native: "*" 5941 react-native-safe-area-context: ">= 3.0.0" 5942 react-native-screens: ">= 3.0.0" 5943 + checksum: 10/572f67c1ea26ac52a0c599064957ec6ac5cd0eee810d2e3cd5b4f4beb1016fbd5c5e8eb54c4d62836e1afe90c5ef922b31a288f41e0c442123dcfc417aa8fd7f 5944 languageName: node 5945 linkType: hard 5946 5947 + "@react-navigation/core@npm:^6.4.17": 5948 + version: 6.4.17 5949 + resolution: "@react-navigation/core@npm:6.4.17" 5950 dependencies: 5951 "@react-navigation/routers": "npm:^6.1.9" 5952 escape-string-regexp: "npm:^4.0.0" 5953 nanoid: "npm:^3.1.23" 5954 query-string: "npm:^7.1.3" 5955 react-is: "npm:^16.13.0" 5956 + use-latest-callback: "npm:^0.2.1" 5957 peerDependencies: 5958 react: "*" 5959 + checksum: 10/481470361c7dd638d8af513ca559265829e8de5a2ff18c207d8d1c9e2d65606318061ffe369afbccfea3c6d027d38ad539ae5bae8863d9cedd8eaeafeb18426c 5960 languageName: node 5961 linkType: hard 5962 5963 + "@react-navigation/drawer@npm:^6.7.2": 5964 + version: 6.7.2 5965 + resolution: "@react-navigation/drawer@npm:6.7.2" 5966 + dependencies: 5967 + "@react-navigation/elements": "npm:^1.3.31" 5968 + color: "npm:^4.2.3" 5969 + warn-once: "npm:^0.1.0" 5970 peerDependencies: 5971 "@react-navigation/native": ^6.0.0 5972 react: "*" 5973 react-native: "*" 5974 + react-native-gesture-handler: ">= 1.0.0" 5975 + react-native-reanimated: ">= 1.0.0" 5976 react-native-safe-area-context: ">= 3.0.0" 5977 + react-native-screens: ">= 3.0.0" 5978 + checksum: 10/970da3f2f78458bbbf30aa2c1a2e071c22a63a4404e2f1f4f6c916b0e33c4faec0693da15a9b52d8f3e0c857008a20f57f310eddb8dd0c1958cfa1aa7fa710ae 5979 + languageName: node 5980 + linkType: hard 5981 + 5982 + "@react-navigation/elements@npm:^1.3.31": 5983 + version: 1.3.31 5984 + resolution: "@react-navigation/elements@npm:1.3.31" 5985 + peerDependencies: 5986 + "@react-navigation/native": ^6.0.0 5987 + react: "*" 5988 + react-native: "*" 5989 + react-native-safe-area-context: ">= 3.0.0" 5990 + checksum: 10/379b3657300f9ab8043979f1ecaea95dce96253903db8d6954468e39dc7cf0710cc08345fa6625071a1505b6442a395e0e20bde39c0b997fd90fea370275fc08 5991 languageName: node 5992 linkType: hard 5993 5994 + "@react-navigation/native-stack@npm:^6.11.0": 5995 + version: 6.11.0 5996 + resolution: "@react-navigation/native-stack@npm:6.11.0" 5997 dependencies: 5998 + "@react-navigation/elements": "npm:^1.3.31" 5999 warn-once: "npm:^0.1.0" 6000 peerDependencies: 6001 "@react-navigation/native": ^6.0.0 ··· 6003 react-native: "*" 6004 react-native-safe-area-context: ">= 3.0.0" 6005 react-native-screens: ">= 3.0.0" 6006 + checksum: 10/d27212088dde4ca16c78d8f85187d452d56cc9243506c049d2595b61c10eda44368ab01c53e729777d9900fe5c3a4dfeae2598b1a534508d6a1df8ab35c1a4dc 6007 languageName: node 6008 linkType: hard 6009 6010 + "@react-navigation/native@npm:^6.1.18": 6011 + version: 6.1.18 6012 + resolution: "@react-navigation/native@npm:6.1.18" 6013 dependencies: 6014 + "@react-navigation/core": "npm:^6.4.17" 6015 escape-string-regexp: "npm:^4.0.0" 6016 fast-deep-equal: "npm:^3.1.3" 6017 nanoid: "npm:^3.1.23" 6018 peerDependencies: 6019 react: "*" 6020 react-native: "*" 6021 + checksum: 10/1c16813e7d1d796519d0c3a9163de8be6d4af0afa74d9d88ec6729f8c0f533540250f09e39063f4a1eafb9ff71c3f3a9cc9d420ba75aa3eb7f42834f4ba0ee20 6022 languageName: node 6023 linkType: hard 6024 ··· 6039 "@spacingbat3/lss": "npm:^1.0.0" 6040 semver: "npm:^7.3.8" 6041 checksum: 10/8bb04678ddbe9d1d4c0f54e1c668b1ec74f6d159af9255c7b6e4a7d29ec3b098461591ac7de7bb214d1e39c27094e0e4831fac928f0aa234c8e39ed17b400174 6042 languageName: node 6043 linkType: hard 6044 ··· 8770 languageName: node 8771 linkType: hard 8772 8773 "@types/debug@npm:^4.1.7": 8774 version: 4.1.12 8775 resolution: "@types/debug@npm:4.1.12" ··· 8833 "@types/minimatch": "npm:*" 8834 "@types/node": "npm:*" 8835 checksum: 10/6ae717fedfdfdad25f3d5a568323926c64f52ef35897bcac8aca8e19bc50c0bd84630bbd063e5d52078b2137d8e7d3c26eabebd1a2f03ff350fff8a91e79fc19 8836 + languageName: node 8837 + linkType: hard 8838 + 8839 + "@types/hammerjs@npm:^2.0.36": 8840 + version: 2.0.46 8841 + resolution: "@types/hammerjs@npm:2.0.46" 8842 + checksum: 10/1b6502d668f45ca49fb488c01f7938d3aa75e989d70c64801c8feded7d659ca1a118f745c1b604d220efe344c93231767d5cc68c05e00e069c14539b6143cfd9 8843 languageName: node 8844 linkType: hard 8845 ··· 9814 languageName: node 9815 linkType: hard 9816 9817 "@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": 9818 version: 1.12.1 9819 resolution: "@webassemblyjs/ast@npm:1.12.1" ··· 10018 bin: 10019 js-yaml: bin/js-yaml.js 10020 checksum: 10/83642debff31400764e8721ba8f386e0f5444b118c7a6c17dbdcb316b56fefa061ea0587af47de75e04d60059215a703a1ca8bbc479149581cd57d752cb3d4e0 10021 languageName: node 10022 linkType: hard 10023 ··· 10460 "@rainbow-me/rainbowkit": "npm:2" 10461 "@react-native-firebase/app": "npm:^20.3.0" 10462 "@react-native-firebase/messaging": "npm:^20.3.0" 10463 + "@react-navigation/bottom-tabs": "npm:^6.6.1" 10464 + "@react-navigation/drawer": "npm:^6.7.2" 10465 + "@react-navigation/native": "npm:^6.1.18" 10466 + "@react-navigation/native-stack": "npm:^6.11.0" 10467 "@tamagui/babel-plugin": "npm:^1.102.3" 10468 "@tamagui/config": "npm:^1.102.3" 10469 "@tamagui/lucide-icons": "npm:^1.102.3" ··· 10482 expo-font: "npm:~12.0.9" 10483 expo-linking: "npm:~6.3.1" 10484 expo-notifications: "npm:~0.28.10" 10485 expo-splash-screen: "npm:~0.27.5" 10486 expo-status-bar: "npm:^1.12.1" 10487 expo-system-ui: "npm:~3.0.7" ··· 10492 react: "npm:18.3.1" 10493 react-dom: "npm:18.3.1" 10494 react-native: "npm:0.74.3" 10495 + react-native-gesture-handler: "npm:~2.16.1" 10496 react-native-markdown-display: "npm:^7.0.2" 10497 + react-native-reanimated: "npm:~3.10.1" 10498 + react-native-safe-area-context: "npm:4.10.5" 10499 + react-native-screens: "npm:3.31.1" 10500 react-native-svg: "npm:15.4.0" 10501 react-native-web: "npm:^0.19.12" 10502 react-native-webview: "npm:13.10.5" ··· 12375 languageName: node 12376 linkType: hard 12377 12378 + "cookie@npm:0.6.0": 12379 version: 0.6.0 12380 resolution: "cookie@npm:0.6.0" 12381 checksum: 10/c1f8f2ea7d443b9331680598b0ae4e6af18a618c37606d1bbdc75bec8361cce09fe93e727059a673f2ba24467131a9fb5a4eec76bb1b149c1b3e1ccb268dc583 ··· 12673 version: 7.0.0 12674 resolution: "dargs@npm:7.0.0" 12675 checksum: 10/b8f1e3cba59c42e1f13a114ad4848c3fc1cf7470f633ee9e9f1043762429bc97d91ae31b826fb135eefde203a3fdb20deb0c0a0222ac29d937b8046085d668d1 12676 languageName: node 12677 linkType: hard 12678 ··· 14490 languageName: node 14491 linkType: hard 14492 14493 + "expo-splash-screen@npm:~0.27.5": 14494 version: 0.27.5 14495 resolution: "expo-splash-screen@npm:0.27.5" 14496 dependencies: ··· 16319 minimalistic-assert: "npm:^1.0.0" 16320 minimalistic-crypto-utils: "npm:^1.0.1" 16321 checksum: 10/0298a1445b8029a69b713d918ecaa84a1d9f614f5857e0c6e1ca517abfa1357216987b2ee08cc6cc73ba82a6c6ddf2ff11b9717a653530ef03be599d4699b836 16322 + languageName: node 16323 + linkType: hard 16324 + 16325 + "hoist-non-react-statics@npm:^3.3.0": 16326 + version: 3.3.2 16327 + resolution: "hoist-non-react-statics@npm:3.3.2" 16328 + dependencies: 16329 + react-is: "npm:^16.7.0" 16330 + checksum: 10/1acbe85f33e5a39f90c822ad4d28b24daeb60f71c545279431dc98c312cd28a54f8d64788e477fe21dc502b0e3cf58589ebe5c1ad22af27245370391c2d24ea6 16331 languageName: node 16332 linkType: hard 16333 ··· 19923 languageName: node 19924 linkType: hard 19925 19926 "ms@npm:2.0.0": 19927 version: 2.0.0 19928 resolution: "ms@npm:2.0.0" ··· 22176 peerDependencies: 22177 react: ^18.3.1 22178 checksum: 10/3f4b73a3aa083091173b29812b10394dd06f4ac06aff410b74702cfb3aa29d7b0ced208aab92d5272919b612e5cda21aeb1d54191848cf6e46e9e354f3541f81 22179 languageName: node 22180 linkType: hard 22181 ··· 22195 languageName: node 22196 linkType: hard 22197 22198 + "react-is@npm:^16.13.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0": 22199 version: 16.13.1 22200 resolution: "react-is@npm:16.13.1" 22201 checksum: 10/5aa564a1cde7d391ac980bedee21202fc90bdea3b399952117f54fb71a932af1e5902020144fb354b4690b2414a0c7aafe798eb617b76a3d441d956db7726fdf ··· 22241 languageName: node 22242 linkType: hard 22243 22244 + "react-native-gesture-handler@npm:~2.16.1": 22245 + version: 2.16.2 22246 + resolution: "react-native-gesture-handler@npm:2.16.2" 22247 dependencies: 22248 + "@egjs/hammerjs": "npm:^2.0.17" 22249 + hoist-non-react-statics: "npm:^3.3.0" 22250 invariant: "npm:^2.2.4" 22251 + lodash: "npm:^4.17.21" 22252 + prop-types: "npm:^15.7.2" 22253 peerDependencies: 22254 + react: "*" 22255 + react-native: "*" 22256 + checksum: 10/227799f5e16f9725d81db524fc06c1fbef226835a5ee989642d132748832f7543ebc750d4309e49bcaa0fb6d1e7dd6b74028785e4b755978ab7f66f05b414c18 22257 languageName: node 22258 linkType: hard 22259 ··· 22272 languageName: node 22273 linkType: hard 22274 22275 + "react-native-reanimated@npm:~3.10.1": 22276 + version: 3.10.1 22277 + resolution: "react-native-reanimated@npm:3.10.1" 22278 dependencies: 22279 "@babel/plugin-transform-arrow-functions": "npm:^7.0.0-0" 22280 "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.0.0-0" ··· 22288 "@babel/core": ^7.0.0-0 22289 react: "*" 22290 react-native: "*" 22291 + checksum: 10/bb2a0802fd619999dd5d1dd9734875fc14699de29d3537003997e85040dd3a0b430716192388c71544d600fc7abf243af1cfc24ce2513c282b046e8bb3430125 22292 languageName: node 22293 linkType: hard 22294 22295 + "react-native-safe-area-context@npm:4.10.5": 22296 + version: 4.10.5 22297 + resolution: "react-native-safe-area-context@npm:4.10.5" 22298 peerDependencies: 22299 react: "*" 22300 react-native: "*" 22301 + checksum: 10/0bc7b4cd442e7a5deb0470f3b915ec2cf6b54337ea527fc7f5bde4f4dd062d420cb2b40fb886df96853f01951cfcb69a35fb4f6f47561cef5e195d9d68c54c3c 22302 languageName: node 22303 linkType: hard 22304 22305 + "react-native-screens@npm:3.31.1": 22306 + version: 3.31.1 22307 + resolution: "react-native-screens@npm:3.31.1" 22308 dependencies: 22309 react-freeze: "npm:^1.0.0" 22310 warn-once: "npm:^0.1.0" 22311 peerDependencies: 22312 react: "*" 22313 react-native: "*" 22314 + checksum: 10/54a44fc5a34848195a8e004b9ee2269e8a3560c5fa7922d80eaa1fc6283990d6ef77b3d3ec6004d7df2ad95279d486faf25bb68a82a0e958493891adb7dbac9c 22315 languageName: node 22316 linkType: hard 22317 ··· 23304 languageName: node 23305 linkType: hard 23306 23307 + "schema-utils@npm:^4.0.0": 23308 version: 4.2.0 23309 resolution: "schema-utils@npm:4.2.0" 23310 dependencies: ··· 23501 languageName: node 23502 linkType: hard 23503 23504 "set-function-length@npm:^1.2.1": 23505 version: 1.2.2 23506 resolution: "set-function-length@npm:1.2.2" ··· 23597 languageName: node 23598 linkType: hard 23599 23600 "shebang-command@npm:^1.2.0": 23601 version: 1.2.0 23602 resolution: "shebang-command@npm:1.2.0" ··· 24112 version: 1.0.3 24113 resolution: "stream-shift@npm:1.0.3" 24114 checksum: 10/a24c0a3f66a8f9024bd1d579a533a53be283b4475d4e6b4b3211b964031447bdf6532dd1f3c2b0ad66752554391b7c62bd7ca4559193381f766534e723d50242 24115 languageName: node 24116 linkType: hard 24117 ··· 25038 languageName: node 25039 linkType: hard 25040 25041 "type-check@npm:^0.4.0, type-check@npm:~0.4.0": 25042 version: 0.4.0 25043 resolution: "type-check@npm:0.4.0" ··· 25340 languageName: node 25341 linkType: hard 25342 25343 "unenv@npm:^1.9.0": 25344 version: 1.10.0 25345 resolution: "unenv@npm:1.10.0" ··· 25629 languageName: node 25630 linkType: hard 25631 25632 + "use-latest-callback@npm:^0.2.1": 25633 + version: 0.2.1 25634 + resolution: "use-latest-callback@npm:0.2.1" 25635 peerDependencies: 25636 react: ">=16.8" 25637 + checksum: 10/da5718eda625738cc7dac8fb502d0f8f2039435eb71203565a72c32e0f5769e7b8ddac074e650066636e7f4b29b45524f751cb18a2b430856d98879bbb10d274 25638 languageName: node 25639 linkType: hard 25640 ··· 25690 languageName: node 25691 linkType: hard 25692 25693 + "util@npm:^0.12.4, util@npm:^0.12.5": 25694 version: 0.12.5 25695 resolution: "util@npm:0.12.5" 25696 dependencies: ··· 25921 dependencies: 25922 defaults: "npm:^1.0.3" 25923 checksum: 10/182ebac8ca0b96845fae6ef44afd4619df6987fe5cf552fdee8396d3daa1fb9b8ec5c6c69855acb7b3c1231571393bd1f0a4cdc4028d421575348f64bb0a8817 25924 languageName: node 25925 linkType: hard 25926