diff --git a/android/app/build.gradle b/android/app/build.gradle index 1899ac0..52c616a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -182,6 +182,9 @@ dependencies { // Psiphon lib implementation (name: 'ca.psiphon', ext: 'aar') + // Guava (need for ListenableFuture in the Worker related code) + implementation 'com.google.guava:guava:33.0.0-android' + // Autovalue compileOnly "com.google.auto.value:auto-value-annotations:1.10.4" annotationProcessor "com.google.auto.value:auto-value:1.10.4" diff --git a/src/account/context.tsx b/src/account/context.tsx index 3562443..a0a74b2 100644 --- a/src/account/context.tsx +++ b/src/account/context.tsx @@ -13,11 +13,9 @@ import { } from "@/src/constants"; // TODO: pending new psiphon module //import { useInProxyContext } from "@/src/psiphon/context"; -import { - InProxyParametersSchema, - formatConduitBip32Path, -} from "@/src/psiphon/inproxy"; -import { useInProxyContext } from "@/src/psiphon/mockContext"; +import { useInProxyContext } from "@/src/inproxy/mockContext"; +import { InProxyParametersSchema } from "@/src/inproxy/types"; +import { formatConduitBip32Path } from "@/src/inproxy/utils"; export interface AccountContextValue { rootKeyPair: Ed25519KeyPair; diff --git a/src/app/(app)/_layout.tsx b/src/app/(app)/_layout.tsx index 4a49382..bfab6f3 100644 --- a/src/app/(app)/_layout.tsx +++ b/src/app/(app)/_layout.tsx @@ -6,7 +6,7 @@ import { useAuthContext } from "@/src/auth/context"; import { InProxyActivityProvider, InProxyProvider, -} from "@/src/psiphon/mockContext"; +} from "@/src/inproxy/mockContext"; export default function AppLayout() { const { mnemonic, deviceNonce } = useAuthContext(); diff --git a/src/components/ConduitOrbToggle.tsx b/src/components/ConduitOrbToggle.tsx index 1a59424..f3dd1fb 100644 --- a/src/components/ConduitOrbToggle.tsx +++ b/src/components/ConduitOrbToggle.tsx @@ -33,7 +33,7 @@ import { import { useInProxyActivityContext, useInProxyContext, -} from "@/src/psiphon/mockContext"; +} from "@/src/inproxy/mockContext"; import { palette, sharedStyles as ss } from "@/src/styles"; export function ConduitOrbToggle({ size }: { size: number }) { @@ -137,12 +137,12 @@ export function ConduitOrbToggle({ size }: { size: number }) { // play in initial animation and video const [showVideo, setShowVideo] = React.useState(false); React.useEffect(() => { - const inProxyStatus = getInProxyStatus().status; - if (inProxyStatus === "running") { + const inProxyStatus = getInProxyStatus(); + if (inProxyStatus === "RUNNING") { // Already Running: play intro animation without delay setShowVideo(false); animateIntro(0); - } else if (inProxyStatus === "stopped") { + } else if (inProxyStatus === "STOPPED") { // Stopped: play intro video and delay animation setShowVideo(true); animateIntro(2800); @@ -152,8 +152,8 @@ export function ConduitOrbToggle({ size }: { size: number }) { // set animation state based on InProxy state React.useEffect(() => { - const inProxyStatus = getInProxyStatus().status; - if (inProxyStatus === "running") { + const inProxyStatus = getInProxyStatus(); + if (inProxyStatus === "RUNNING") { if (inProxyCurrentConnectedClients === 0) { if (animationState !== "announcing") { animateAnnouncing(); @@ -166,7 +166,7 @@ export function ConduitOrbToggle({ size }: { size: number }) { } randomizeVelocity.setActive(true); } - } else if (inProxyStatus === "stopped") { + } else if (inProxyStatus === "STOPPED") { randomizeVelocity.setActive(false); if (!["idle", "loading"].includes(animationState)) { animateTurnOff(); diff --git a/src/components/ConduitSettings.tsx b/src/components/ConduitSettings.tsx index 7e6983e..d8f76f7 100644 --- a/src/components/ConduitSettings.tsx +++ b/src/components/ConduitSettings.tsx @@ -25,8 +25,9 @@ import { handleError, wrapError } from "@/src/common/errors"; import { MBToBytes, bytesToMB } from "@/src/common/utils"; import { EditableNumberSlider } from "@/src/components/EditableNumberSlider"; import { ProxyID } from "@/src/components/ProxyID"; -import { InProxyParametersSchema, getProxyId } from "@/src/psiphon/inproxy"; -import { useInProxyContext } from "@/src/psiphon/mockContext"; +import { useInProxyContext } from "@/src/inproxy/mockContext"; +import { InProxyParametersSchema } from "@/src/inproxy/types"; +import { getProxyId } from "@/src/inproxy/utils"; import { lineItemStyle, palette, sharedStyles as ss } from "@/src/styles"; import { useDerivedValue, @@ -121,7 +122,7 @@ export function ConduitSettings() { settingsChanged = true; } if (settingsChanged) { - if (getInProxyStatus().status === "running") { + if (getInProxyStatus() === "RUNNING") { setDisplayRestartConfirmation(true); } else { await commitChanges(); @@ -328,10 +329,10 @@ export function ConduitSettings() { const fadeIn = useSharedValue(0); React.useEffect(() => { const inProxyStatus = getInProxyStatus(); - if (inProxyStatus.status === "running") { + if (inProxyStatus === "RUNNING") { // fade in right away fadeIn.value = withTiming(1, { duration: 2000 }); - } else if (inProxyStatus.status === "stopped") { + } else if (inProxyStatus === "STOPPED") { // fade in after a delay for particle animation fadeIn.value = withDelay(2800, withTiming(1, { duration: 2000 })); } diff --git a/src/components/ConduitStatus.tsx b/src/components/ConduitStatus.tsx index d8fbef7..457fcd6 100644 --- a/src/components/ConduitStatus.tsx +++ b/src/components/ConduitStatus.tsx @@ -19,7 +19,7 @@ import { niceBytes } from "@/src/common/utils"; import { useInProxyActivityContext, useInProxyContext, -} from "@/src/psiphon/mockContext"; +} from "@/src/inproxy/mockContext"; import { palette, sharedStyles as ss } from "@/src/styles"; export function ConduitStatus({ @@ -48,9 +48,9 @@ export function ConduitStatus({ const fadeInGradient = useSharedValue(0); React.useEffect(() => { const inProxyStatus = getInProxyStatus(); - if (inProxyStatus.status === "running") { + if (inProxyStatus === "RUNNING") { fadeInGradient.value = withTiming(1, { duration: 2000 }); - } else if (inProxyStatus.status === "stopped") { + } else if (inProxyStatus === "STOPPED") { fadeInGradient.value = withDelay( 2800, withTiming(1, { duration: 2000 }), @@ -64,14 +64,14 @@ export function ConduitStatus({ const [shouldAnimateIn, setShouldAnimateIn] = React.useState(true); const [shouldAnimateOut, setShouldAnimateOut] = React.useState(true); React.useEffect(() => { - const inProxyStatus = getInProxyStatus().status; - if (inProxyStatus === "running") { + const inProxyStatus = getInProxyStatus(); + if (inProxyStatus === "RUNNING") { if (shouldAnimateIn) { fader.value = withTiming(1, { duration: 1000 }); setShouldAnimateIn(false); setShouldAnimateOut(true); } - } else if (inProxyStatus === "stopped") { + } else if (inProxyStatus === "STOPPED") { if (shouldAnimateOut) { fader.value = withTiming(0, { duration: 1000 }); setShouldAnimateIn(true); diff --git a/src/components/LogoWordmark.tsx b/src/components/LogoWordmark.tsx index 0baf765..b7f883f 100644 --- a/src/components/LogoWordmark.tsx +++ b/src/components/LogoWordmark.tsx @@ -21,7 +21,7 @@ import { // @ts-ignore (this file is gitignored) import { GIT_HASH } from "@/src/git-hash"; -import { useInProxyContext } from "@/src/psiphon/mockContext"; +import { useInProxyContext } from "@/src/inproxy/mockContext"; import { palette, sharedStyles as ss } from "@/src/styles"; export function LogoWordmark({ @@ -43,10 +43,10 @@ export function LogoWordmark({ const fadeIn = useSharedValue(0); React.useEffect(() => { const inProxyStatus = getInProxyStatus(); - if (inProxyStatus.status === "running") { + if (inProxyStatus === "RUNNING") { // fade in right away fadeIn.value = withTiming(1, { duration: 2000 }); - } else if (inProxyStatus.status === "stopped") { + } else if (inProxyStatus === "STOPPED") { // fade in after a delay for particle animation fadeIn.value = withDelay(2800, withTiming(1, { duration: 2000 })); } diff --git a/src/nativemodule/README.md b/src/inproxy/README.md similarity index 100% rename from src/nativemodule/README.md rename to src/inproxy/README.md diff --git a/src/psiphon/mockContext.tsx b/src/inproxy/mockContext.tsx similarity index 89% rename from src/psiphon/mockContext.tsx rename to src/inproxy/mockContext.tsx index 6224b1c..308dd03 100644 --- a/src/psiphon/mockContext.tsx +++ b/src/inproxy/mockContext.tsx @@ -1,16 +1,18 @@ import AsyncStorage from "@react-native-async-storage/async-storage"; import React from "react"; +import { z } from "zod"; import { InProxyActivityByPeriod, InProxyActivityStats, InProxyParameters, - InProxyStatus, - InProxyStatusSchema, + InProxyStatusEnum, + InProxyStatusEnumSchema, +} from "@/src/inproxy/types"; +import { getDefaultInProxyParameters, getZeroedInProxyActivityStats, -} from "@/src/psiphon/inproxy"; -import { z } from "zod"; +} from "@/src/inproxy/utils"; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); async function* generateMockData( @@ -136,7 +138,7 @@ export function InProxyActivityProvider({ } } - if (getInProxyStatus().status === "running") { + if (getInProxyStatus() === "RUNNING") { runMock(); } else { stopMock(); @@ -160,7 +162,7 @@ export interface InProxyContextValue { inProxyMustUpgrade: boolean; toggleInProxy: () => Promise; selectInProxyParameters: (params: InProxyParameters) => Promise; - getInProxyStatus: () => InProxyStatus; + getInProxyStatus: () => InProxyStatusEnum; sendFeedback: () => Promise; } @@ -179,17 +181,18 @@ export function useInProxyContext(): InProxyContextValue { return value; } -const InProxyStateSchema = z.object({ +const InProxyStateSyncSchema = z.object({ running: z.boolean(), synced: z.boolean(), }); -type InProxyState = z.infer; +type InProxyStateSync = z.infer; export function InProxyProvider({ children }: { children: React.ReactNode }) { - const [inProxyState, setInProxyState] = React.useState({ - running: false, - synced: false, - }); + const [inProxyStateSync, setInProxyStateSync] = + React.useState({ + running: false, + synced: false, + }); const [inProxyParameters, setInProxyParameters] = React.useState(getDefaultInProxyParameters()); @@ -213,12 +216,12 @@ export function InProxyProvider({ children }: { children: React.ReactNode }) { const running = await AsyncStorage.getItem("MockInProxyRunning"); console.log("check if inproxy running, stored value: ", running); if (running === "1") { - setInProxyState({ + setInProxyStateSync({ running: true, synced: true, }); } else { - setInProxyState({ + setInProxyStateSync({ running: false, synced: true, }); @@ -239,32 +242,35 @@ export function InProxyProvider({ children }: { children: React.ReactNode }) { params.limitUpstreamBytesPerSecond.toString(), ); setInProxyParameters(params); - console.log("MOCK: InProxy parameters selected successfully", params); + console.log("MOCK: InProxy parameters selected successfully"); } async function toggleInProxy(): Promise { //await requestNotificationsPermissions(); await AsyncStorage.setItem( "MockInProxyRunning", - inProxyState.running ? "0" : "1", + inProxyStateSync.running ? "0" : "1", ); - setInProxyState({ running: !inProxyState.running, synced: true }); + setInProxyStateSync({ + running: !inProxyStateSync.running, + synced: true, + }); console.log("MOCK: InProxyModule.toggleInProxy() invoked"); } const getInProxyStatus = React.useCallback(() => { let state; - if (!inProxyState.synced) { - state = { status: "unknown" }; + if (!inProxyStateSync.synced) { + state = "UNKNOWN"; } else { - if (inProxyState.running) { - state = { status: "running" }; + if (inProxyStateSync.running) { + state = "RUNNING"; } else { - state = { status: "stopped" }; + state = "STOPPED"; } } - return InProxyStatusSchema.parse(state); - }, [inProxyState]); + return InProxyStatusEnumSchema.parse(state); + }, [inProxyStateSync]); async function sendFeedback(): Promise { console.log("MOCK: InProxyModule.sendFeedback() invoked"); diff --git a/src/inproxy/types.ts b/src/inproxy/types.ts new file mode 100644 index 0000000..bbe1db3 --- /dev/null +++ b/src/inproxy/types.ts @@ -0,0 +1,67 @@ +import { z } from "zod"; + +import { Base64Unpadded64Bytes } from "@/src/common/validators"; + +export const InProxyStatusEnumSchema = z.enum([ + "RUNNING", + "STOPPED", + "UNKNOWN", +]); + +export const InProxyStateSchema = z.object({ + status: InProxyStatusEnumSchema, + networkState: z.enum(["HAS_INTERNET", "NO_INTERNET"]), +}); + +export const InProxyErrorSchema = z.object({ + data: z.enum([ + "proxyStartFailed", + "proxyRestartFailed", + "inProxyMustUpgrade", + ]), +}); + +export const InProxyActivityDataByPeriodSchema = z.object({ + bytesUp: z.array(z.number()).length(288), + bytesDown: z.array(z.number()).length(288), + connectingClients: z.array(z.number()).length(288), + connectedClients: z.array(z.number()).length(288), +}); + +export const InProxyActivityStatsSchema = z.object({ + elapsedTime: z.number(), + totalBytesUp: z.number(), + totalBytesDown: z.number(), + currentConnectingClients: z.number(), + currentConnectedClients: z.number(), + dataByPeriod: z.object({ + "1000ms": InProxyActivityDataByPeriodSchema, + }), +}); + +export const InProxyEventSchema = z.object({ + type: z.enum(["proxyState", "proxyError", "inProxyActivityStats"]), + data: z.union([ + InProxyStateSchema, + InProxyErrorSchema, + InProxyActivityStatsSchema, + ]), +}); + +// These are the user-configurable parameters for the inproxy. +export const InProxyParametersSchema = z.object({ + privateKey: Base64Unpadded64Bytes, + maxClients: z.number().int().positive(), + limitUpstreamBytesPerSecond: z.number().int().positive(), + limitDownstreamBytesPerSecond: z.number().int().positive(), +}); + +export type InProxyParameters = z.infer; +export type InProxyStatusEnum = z.infer; +export type InProxyState = z.infer; +export type InProxyError = z.infer; +export type InProxyActivityStats = z.infer; +export type InProxyActivityByPeriod = z.infer< + typeof InProxyActivityDataByPeriodSchema +>; +export type InProxyEvent = z.infer; diff --git a/src/psiphon/inproxy.ts b/src/inproxy/utils.ts similarity index 63% rename from src/psiphon/inproxy.ts rename to src/inproxy/utils.ts index 52a37b7..1bc847e 100644 --- a/src/psiphon/inproxy.ts +++ b/src/inproxy/utils.ts @@ -8,54 +8,16 @@ import { keyPairToBase64nopad, } from "@/src/common/cryptography"; import { wrapError } from "@/src/common/errors"; -import { Base64Unpadded64Bytes } from "@/src/common/validators"; import { DEFAULT_INPROXY_LIMIT_BYTES_PER_SECOND, DEFAULT_INPROXY_MAX_CLIENTS, } from "@/src/constants"; - -export const InProxyActivityDataByPeriodSchema = z.object({ - bytesUp: z.array(z.number()).length(288), - bytesDown: z.array(z.number()).length(288), - connectingClients: z.array(z.number()).length(288), - connectedClients: z.array(z.number()).length(288), -}); - -export const InProxyActivityStatsSchema = z.object({ - elapsedTime: z.number(), - totalBytesUp: z.number(), - totalBytesDown: z.number(), - currentConnectingClients: z.number(), - currentConnectedClients: z.number(), - dataByPeriod: z.object({ - "1000ms": InProxyActivityDataByPeriodSchema, - }), -}); - -// These are the user-configurable parameters for the inproxy. -export const InProxyParametersSchema = z.object({ - privateKey: Base64Unpadded64Bytes, - maxClients: z.number().int().positive(), - limitUpstreamBytesPerSecond: z.number().int().positive(), - limitDownstreamBytesPerSecond: z.number().int().positive(), - // personalCompartmentIds: z.array(z.string()), // eventually... -}); - -export const InProxyErrorSchema = z.object({ - action: z.enum(["inProxyMustUpgrade"]), -}); - -export const InProxyStatusSchema = z.object({ - status: z.enum(["running", "stopped", "unknown"]), -}); - -export type InProxyParameters = z.infer; -export type InProxyActivityStats = z.infer; -export type InProxyActivityByPeriod = z.infer< - typeof InProxyActivityDataByPeriodSchema ->; -export type InProxyError = z.infer; -export type InProxyStatus = z.infer; +import { + InProxyActivityStats, + InProxyActivityStatsSchema, + InProxyParameters, + InProxyParametersSchema, +} from "@/src/inproxy/types"; export function getDefaultInProxyParameters(): InProxyParameters { const ephemeralKey = generateEd25519KeyPair();