mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-07-14 18:15:56 -04:00
feat(analytics): proxy PostHog ingestion through public-api on Vercel
- Add Vercel rewrites to proxy `/ingest/*` and `/ingest/static/*` to PostHog's US endpoints - Update native `.env.example` to point `EXPO_PUBLIC_POSTHOG_HOST` at the self-hosted proxy URL
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
EXPO_PUBLIC_POSTHOG_KEY=
|
||||
EXPO_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
EXPO_PUBLIC_POSTHOG_HOST=https://public-api.sofa.watch/ingest
|
||||
|
||||
@@ -10,6 +10,7 @@ npm-debug.*
|
||||
*.orig.*
|
||||
*.tar.gz
|
||||
*.ipa
|
||||
*.apk
|
||||
web-build/
|
||||
|
||||
# macOS
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
"production": {
|
||||
"autoIncrement": true
|
||||
},
|
||||
"preview": {
|
||||
"android": {
|
||||
"buildType": "apk"
|
||||
}
|
||||
},
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"ios": {
|
||||
|
||||
@@ -53,7 +53,7 @@ import { setPersistedLocale } from "@/lib/i18n";
|
||||
import { orpc } from "@/lib/orpc";
|
||||
import { isAnalyticsEnabled, setAnalyticsEnabled } from "@/lib/posthog";
|
||||
import { queryClient } from "@/lib/query-client";
|
||||
import { authClient, getServerUrl } from "@/lib/server";
|
||||
import { authClient, getServerUrl, requestServerChange } from "@/lib/server";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { activateLocale, isLocaleRTL, type SupportedLocale } from "@sofa/i18n";
|
||||
import { LOCALE_INFO } from "@sofa/i18n/locales";
|
||||
@@ -372,9 +372,9 @@ export default function SettingsScreen() {
|
||||
text: t`Continue`,
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
requestServerChange();
|
||||
await authClient.signOut();
|
||||
queryClient.clear();
|
||||
push("/(auth)/server-url");
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -21,7 +21,7 @@ export function OfflineBanner() {
|
||||
|
||||
const handleState = (state: Network.NetworkState) => {
|
||||
if (!mounted) return;
|
||||
const offline = !state.isConnected || !state.isInternetReachable;
|
||||
const offline = !state.isConnected || state.isInternetReachable === false;
|
||||
setIsOffline(offline);
|
||||
if (offline && wasOnline.current) {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
||||
|
||||
@@ -20,7 +20,7 @@ export function OfflineBanner() {
|
||||
|
||||
const handleState = (state: Network.NetworkState) => {
|
||||
if (!mounted) return;
|
||||
const offline = !state.isConnected || !state.isInternetReachable;
|
||||
const offline = !state.isConnected || state.isInternetReachable === false;
|
||||
setIsOffline(offline);
|
||||
if (offline && wasOnline.current) {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
||||
|
||||
@@ -10,10 +10,11 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { ScaledIcon } from "@/components/ui/scaled-icon";
|
||||
import { Text } from "@/components/ui/text";
|
||||
import { queryClient } from "@/lib/query-client";
|
||||
import { authClient, useServerReachability } from "@/lib/server";
|
||||
import { authClient, hasStoredServerUrl, useServerReachability } from "@/lib/server";
|
||||
import * as Haptics from "@/utils/haptics";
|
||||
|
||||
export function ServerUnreachableBanner() {
|
||||
const hasServerUrl = !!process.env.EXPO_PUBLIC_SERVER_URL || hasStoredServerUrl();
|
||||
const { isReachable } = useServerReachability();
|
||||
const insets = useSafeAreaInsets();
|
||||
const wasReachable = useRef(true);
|
||||
@@ -26,7 +27,7 @@ export function ServerUnreachableBanner() {
|
||||
|
||||
const handleState = (state: Network.NetworkState) => {
|
||||
if (!mounted) return;
|
||||
setIsDeviceOnline(!!state.isConnected && !!state.isInternetReachable);
|
||||
setIsDeviceOnline(!!state.isConnected && state.isInternetReachable !== false);
|
||||
};
|
||||
|
||||
Network.getNetworkStateAsync().then(handleState);
|
||||
@@ -39,11 +40,11 @@ export function ServerUnreachableBanner() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isReachable && wasReachable.current) {
|
||||
if (hasServerUrl && !isReachable && wasReachable.current) {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
||||
}
|
||||
wasReachable.current = isReachable;
|
||||
}, [isReachable]);
|
||||
}, [hasServerUrl, isReachable]);
|
||||
|
||||
const handleRetry = () => {
|
||||
const refetchSession = authClient.$store.atoms.session.get().refetch;
|
||||
@@ -55,8 +56,9 @@ export function ServerUnreachableBanner() {
|
||||
]);
|
||||
};
|
||||
|
||||
// Only show when device has internet but server is unreachable
|
||||
if (isReachable || !isDeviceOnline) return null;
|
||||
// Don't show on fresh install (no server configured yet) or when the device
|
||||
// itself is offline (OfflineBanner handles that) or when everything is fine.
|
||||
if (!hasServerUrl || isReachable || !isDeviceOnline) return null;
|
||||
|
||||
const useGlass = isLiquidGlassAvailable();
|
||||
|
||||
|
||||
@@ -9,10 +9,11 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { ScaledIcon } from "@/components/ui/scaled-icon";
|
||||
import { Text } from "@/components/ui/text";
|
||||
import { queryClient } from "@/lib/query-client";
|
||||
import { authClient, useServerReachability } from "@/lib/server";
|
||||
import { authClient, hasStoredServerUrl, useServerReachability } from "@/lib/server";
|
||||
import * as Haptics from "@/utils/haptics";
|
||||
|
||||
export function ServerUnreachableBanner() {
|
||||
const hasServerUrl = !!process.env.EXPO_PUBLIC_SERVER_URL || hasStoredServerUrl();
|
||||
const { isReachable } = useServerReachability();
|
||||
const insets = useSafeAreaInsets();
|
||||
const wasReachable = useRef(true);
|
||||
@@ -25,7 +26,7 @@ export function ServerUnreachableBanner() {
|
||||
|
||||
const handleState = (state: Network.NetworkState) => {
|
||||
if (!mounted) return;
|
||||
setIsDeviceOnline(!!state.isConnected && !!state.isInternetReachable);
|
||||
setIsDeviceOnline(!!state.isConnected && state.isInternetReachable !== false);
|
||||
};
|
||||
|
||||
Network.getNetworkStateAsync().then(handleState);
|
||||
@@ -38,11 +39,11 @@ export function ServerUnreachableBanner() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isReachable && wasReachable.current) {
|
||||
if (hasServerUrl && !isReachable && wasReachable.current) {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
||||
}
|
||||
wasReachable.current = isReachable;
|
||||
}, [isReachable]);
|
||||
}, [hasServerUrl, isReachable]);
|
||||
|
||||
const handleRetry = () => {
|
||||
const refetchSession = authClient.$store.atoms.session.get().refetch;
|
||||
@@ -54,8 +55,9 @@ export function ServerUnreachableBanner() {
|
||||
]);
|
||||
};
|
||||
|
||||
// Only show when device has internet but server is unreachable
|
||||
if (isReachable || !isDeviceOnline) return null;
|
||||
// Don't show on fresh install (no server configured yet) or when the device
|
||||
// itself is offline (OfflineBanner handles that) or when everything is fine.
|
||||
if (!hasServerUrl || isReachable || !isDeviceOnline) return null;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { msg } from "@lingui/core/macro";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
@@ -6,6 +7,7 @@ import { queryClient } from "@/lib/query-client";
|
||||
import {
|
||||
authClient,
|
||||
clearCachedSessionSeeded,
|
||||
consumeServerChangeRequest,
|
||||
ensureInstanceId,
|
||||
getCurrentInstanceId,
|
||||
hasStoredServerUrl,
|
||||
@@ -16,6 +18,7 @@ import {
|
||||
wasCachedSessionSeeded,
|
||||
} from "@/lib/server";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { i18n } from "@sofa/i18n";
|
||||
|
||||
/**
|
||||
* Manages the full server connection lifecycle:
|
||||
@@ -100,16 +103,17 @@ export function useServerConnection() {
|
||||
|
||||
const { replace } = useRouter();
|
||||
|
||||
// Navigate to login when session is lost. Stack.Protected handles screen
|
||||
// Navigate to auth when session is lost. Stack.Protected handles screen
|
||||
// availability, but enableFreeze can prevent the navigator from
|
||||
// transitioning on its own.
|
||||
useEffect(() => {
|
||||
if (prevSession.current && !session) {
|
||||
replace("/(auth)/login");
|
||||
const changingServer = consumeServerChangeRequest();
|
||||
replace(changingServer ? "/(auth)/server-url" : "/(auth)/login");
|
||||
|
||||
if (hadOptimisticSession.current) {
|
||||
toast.info("Session expired", {
|
||||
description: "Please sign in again.",
|
||||
toast.info(i18n._(msg`Session expired`), {
|
||||
description: i18n._(msg`Please sign in again.`),
|
||||
});
|
||||
clearCachedSessionSeeded();
|
||||
}
|
||||
|
||||
@@ -412,6 +412,28 @@ export function clearCachedSessionSeeded(): void {
|
||||
// serverManager — compound operations for server-url screen
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Server-change flag
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Signals that the next session-loss redirect should go to the server-url
|
||||
// screen instead of login (set by the "Change Server" flow in settings).
|
||||
let _serverChangeRequested = false;
|
||||
|
||||
export function requestServerChange(): void {
|
||||
_serverChangeRequested = true;
|
||||
}
|
||||
|
||||
export function consumeServerChangeRequest(): boolean {
|
||||
const was = _serverChangeRequested;
|
||||
_serverChangeRequested = false;
|
||||
return was;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// serverManager — compound operations for server-url screen
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const serverManager = {
|
||||
validateServerUrl,
|
||||
normalizeUrl,
|
||||
@@ -424,6 +446,10 @@ export const serverManager = {
|
||||
connectToServer(url: string, instanceId: string): void {
|
||||
registerServer(url, instanceId);
|
||||
setServerUrlInternal(url);
|
||||
// Validation just succeeded, so mark the server as reachable before
|
||||
// rebuilding. This prevents a banner flash between the monitor starting
|
||||
// (once hasServerUrl becomes true) and the first successful fetch.
|
||||
setReachable(true);
|
||||
authClient = buildAuthClient();
|
||||
for (const listener of serverUrlListeners) listener();
|
||||
},
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
{
|
||||
"bunVersion": "1.x"
|
||||
"bunVersion": "1.x",
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/ingest/static/:path(.*)",
|
||||
"destination": "https://us-assets.i.posthog.com/static/:path"
|
||||
},
|
||||
{
|
||||
"source": "/ingest/:path(.*)",
|
||||
"destination": "https://us.i.posthog.com/:path"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1650,6 +1650,10 @@ msgstr ""
|
||||
msgid "Play trailer"
|
||||
msgstr ""
|
||||
|
||||
#: apps/native/src/hooks/use-server-connection.ts
|
||||
msgid "Please sign in again."
|
||||
msgstr "Please sign in again."
|
||||
|
||||
#: apps/native/src/app/(tabs)/(explore)/index.tsx
|
||||
#: apps/web/src/routes/_app/explore.tsx
|
||||
msgid "Popular Movies"
|
||||
@@ -2091,6 +2095,10 @@ msgstr ""
|
||||
msgid "Server URL"
|
||||
msgstr ""
|
||||
|
||||
#: apps/native/src/hooks/use-server-connection.ts
|
||||
msgid "Session expired"
|
||||
msgstr "Session expired"
|
||||
|
||||
#: apps/native/src/app/(tabs)/(settings)/index.tsx
|
||||
msgid "Set password"
|
||||
msgstr ""
|
||||
|
||||
Reference in New Issue
Block a user