Skip to content

Commit

Permalink
implement inproxy context using new module; simplify notifications pe…
Browse files Browse the repository at this point in the history
…rmission process

use a single conduit event handler, but store inproxy stats state in a separate context

format

use useQuery cache to track data emitted by ConduitModule

move common useQuery definitions into their own file
  • Loading branch information
tmgrask committed Sep 25, 2024
1 parent 729eea6 commit 4570dc6
Show file tree
Hide file tree
Showing 17 changed files with 721 additions and 636 deletions.
101 changes: 4 additions & 97 deletions src/account/context.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,21 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import React from "react";

import {
Ed25519KeyPair,
deriveEd25519KeyPair,
keyPairToBase64nopad,
} from "@/src/common/cryptography";
import { handleError, wrapError } from "@/src/common/errors";
import {
DEFAULT_INPROXY_LIMIT_BYTES_PER_SECOND,
DEFAULT_INPROXY_MAX_CLIENTS,
} from "@/src/constants";
// TODO: pending new psiphon module
//import { useInProxyContext } from "@/src/psiphon/context";
import { useInProxyContext } from "@/src/inproxy/mockContext";
import { InProxyParametersSchema } from "@/src/inproxy/types";
import { formatConduitBip32Path } from "@/src/inproxy/utils";

export interface AccountContextValue {
rootKeyPair: Ed25519KeyPair;
conduitKeyPair: Ed25519KeyPair;
}

const AccountContext = React.createContext<AccountContextValue | null>(null);

/**
* A Ryve account is defined by a BIP-39 Mnemonic, the associated blockchain
* account, and an Ed25519 key pair. The mnemonic is used to derive the rest of
* the account information.
* A Conduit account is defined by a BIP-39 Mnemonic and a derived Ed25519 key.
* The key derivation function uses a device nonce to support multiple conduits
* per account, though this multi-device functionality is not yet implemented.
* This context provides these values to authenticated routes within the app.
* Establishing authentication state is handled by `useAuthContext`.
*/
Expand All @@ -51,15 +39,8 @@ export function AccountProvider({
deviceNonce: number;
children: React.ReactNode;
}) {
const rootKeyPair = React.useMemo(() => {
const derived = deriveEd25519KeyPair(mnemonic);
if (derived instanceof Error) {
throw derived;
}
return derived;
}, [mnemonic]);

const conduitKeyPair = React.useMemo(() => {
// TODO: store in asyncstorage to save startup time
const derived = deriveEd25519KeyPair(
mnemonic,
formatConduitBip32Path(deviceNonce),
Expand All @@ -70,81 +51,7 @@ export function AccountProvider({
return derived;
}, [mnemonic]);

// TODO: pending new psiphon module
const { selectInProxyParameters } = useInProxyContext();

// We store the user-controllable InProxy settings in AsyncStorage, so that
// they can be persisted at the application layer instead of only at the VPN
// module layer. This also allows us to have defaults that are different
// than what the module uses. The values stored in AsyncStorage will be
// taken as the source of truth.
async function loadInProxyParameters() {
try {
// Retrieve stored inproxy parameters from the application layer
const storedInProxyMaxClients =
await AsyncStorage.getItem("InProxyMaxClients");

const storedInProxyLimitBytesPerSecond = await AsyncStorage.getItem(
"InProxyLimitBytesPerSecond",
);

// Prepare the stored/default parameters from the application layer
const storedInProxyParameters = InProxyParametersSchema.parse({
privateKey: keyPairToBase64nopad(conduitKeyPair),
maxClients: storedInProxyMaxClients
? parseInt(storedInProxyMaxClients)
: DEFAULT_INPROXY_MAX_CLIENTS,
limitUpstreamBytesPerSecond: storedInProxyLimitBytesPerSecond
? parseInt(storedInProxyLimitBytesPerSecond)
: DEFAULT_INPROXY_LIMIT_BYTES_PER_SECOND,
limitDownstreamBytesPerSecond: storedInProxyLimitBytesPerSecond
? parseInt(storedInProxyLimitBytesPerSecond)
: DEFAULT_INPROXY_LIMIT_BYTES_PER_SECOND,
});

// sets the inproxy parameters in the psiphon context. This call
// also updates the context's state value for the inproxy
// parameters, so an explicit call to sync them is not needed.
// TODO: pending new psiphon module
await selectInProxyParameters(storedInProxyParameters);

// Write the defaults to AsyncStorage if they aren't there
if (!storedInProxyMaxClients) {
await AsyncStorage.setItem(
"InProxyMaxClients",
storedInProxyParameters.maxClients.toString(),
);
}
if (!storedInProxyLimitBytesPerSecond) {
await AsyncStorage.setItem(
"InProxyLimitBytesPerSecond",
storedInProxyParameters.limitUpstreamBytesPerSecond.toString(),
);
}
} catch (error) {
handleError(wrapError(error, "Failed to load inproxy parameters"));
}
}

// Loads InProxy parameters on first mount. This is done in the account
// context because it is the first place where the conduitKeyPair is ready.
// It could be done in the psiphon context, but this would require some
// refactoring of the order of the context providers in the app.
React.useEffect(() => {
// Note that right now, this means that we ALWAYS set the InProxy params
// in the module on app start, even if they have not changed. This could
// be made more precise by having this effect depend on the InProxy
// params stored in the psiphon vpn context, but since these values are
// currently stored as an object the context would need to expose state
// values that we can use in this dependency array that don't have the
// pitfals of objects as dependencies (hence why this doesn't just use
// inProxyParameters as a dependency).
// https://react.dev/learn/removing-effect-dependencies#does-some-reactive-value-change-unintentionally
loadInProxyParameters();
}, []);

const value = {
rootKeyPair,
conduitKeyPair,
} as AccountContextValue;

Expand Down
29 changes: 12 additions & 17 deletions src/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import React from "react";

import { AccountProvider } from "@/src/account/context";
import { useAuthContext } from "@/src/auth/context";
import {
InProxyActivityProvider,
InProxyProvider,
} from "@/src/inproxy/mockContext";
import { InProxyProvider } from "@/src/inproxy/context";

export default function AppLayout() {
const { mnemonic, deviceNonce } = useAuthContext();
Expand All @@ -16,18 +13,16 @@ export default function AppLayout() {
return <Redirect href="/" />;
}
return (
<InProxyProvider>
<InProxyActivityProvider>
<AccountProvider mnemonic={mnemonic} deviceNonce={deviceNonce}>
<Stack
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="index" />
</Stack>
</AccountProvider>
</InProxyActivityProvider>
</InProxyProvider>
<AccountProvider mnemonic={mnemonic} deviceNonce={deviceNonce}>
<InProxyProvider>
<Stack
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="index" />
</Stack>
</InProxyProvider>
</AccountProvider>
);
}
21 changes: 9 additions & 12 deletions src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import i18nService from "@/src/i18n/i18n";
i18nService.initI18n();

import { AuthProvider } from "@/src/auth/context";
import { NotificationsProvider } from "@/src/notifications/context";
import { palette } from "@/src/styles";

// Prevent the splash screen from auto-hiding before asset loading is complete.
Expand All @@ -38,17 +37,15 @@ export default function RootLayout() {
<ThemeProvider value={DarkTheme}>
{!loaded ? null : (
<QueryClientProvider client={queryClient}>
<NotificationsProvider>
<AuthProvider>
<Stack
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="(app)" />
</Stack>
</AuthProvider>
</NotificationsProvider>
<AuthProvider>
<Stack
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="(app)" />
</Stack>
</AuthProvider>
</QueryClientProvider>
)}
</ThemeProvider>
Expand Down
32 changes: 18 additions & 14 deletions src/components/ConduitOrbToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,20 @@ import {
withTiming,
} from "react-native-reanimated";

//import { useInProxyContext } from "@/src/psiphon/context";
import { useInProxyContext } from "@/src/inproxy/context";
import {
useInProxyActivityContext,
useInProxyContext,
} from "@/src/inproxy/mockContext";
useInProxyCurrentConnectedClients,
useInProxyStatus,
} from "@/src/inproxy/hooks";
import { palette, sharedStyles as ss } from "@/src/styles";

export function ConduitOrbToggle({ size }: { size: number }) {
const { t } = useTranslation();
const { toggleInProxy, getInProxyStatus } = useInProxyContext();
const { inProxyCurrentConnectedClients } = useInProxyActivityContext();
const { toggleInProxy } = useInProxyContext();

const { data: inProxyStatus } = useInProxyStatus();
const { data: inProxyCurrentConnectedClients } =
useInProxyCurrentConnectedClients();

const radius = size / 4;
const centeringTransform = [
Expand Down Expand Up @@ -126,18 +129,20 @@ export function ConduitOrbToggle({ size }: { size: number }) {
restSpeedThreshold: 2,
}),
);
buttonTextColourIndex.value = withDelay(
delay,
withTiming(0, { duration: 1000 }),
);
if (delay > 0) {
// if delay is zero, InProxy is running, so don't show button text
buttonTextColourIndex.value = withDelay(
delay,
withTiming(0, { duration: 1000 }),
);
}
}

const [animationState, setAnimationState] = React.useState("loading");

// play in initial animation and video
const [showVideo, setShowVideo] = React.useState(false);
React.useEffect(() => {
const inProxyStatus = getInProxyStatus();
if (inProxyStatus === "RUNNING") {
// Already Running: play intro animation without delay
setShowVideo(false);
Expand All @@ -148,11 +153,10 @@ export function ConduitOrbToggle({ size }: { size: number }) {
animateIntro(2800);
}
// implicit do nothing if status is unknown
}, [getInProxyStatus]);
}, [inProxyStatus]);

// set animation state based on InProxy state
React.useEffect(() => {
const inProxyStatus = getInProxyStatus();
if (inProxyStatus === "RUNNING") {
if (inProxyCurrentConnectedClients === 0) {
if (animationState !== "announcing") {
Expand All @@ -174,7 +178,7 @@ export function ConduitOrbToggle({ size }: { size: number }) {
}
}
// implicit do nothing if status is unknown
}, [getInProxyStatus, inProxyCurrentConnectedClients]);
}, [inProxyStatus, inProxyCurrentConnectedClients]);

const buttonGradientColours = useDerivedValue(() => {
return [
Expand Down
Loading

0 comments on commit 4570dc6

Please sign in to comment.