Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions PRIVACY_POLICY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Privacy Policy for Open MobileUI

Last Updated: 17 Dec 2025
Last Updated: 11 Sep 2026

## Introduction

Expand Down Expand Up @@ -28,6 +28,14 @@ The App may request the following optional permissions, only when you choose to

You can deny these permissions and still use the App’s core functionality.

## Crash and Error Reporting (Sentry)

The App uses Sentry to track crashes and application errors so we can diagnose and fix problems.

When an error occurs, Sentry receives the error/crash message and stack trace, along with diagnostic details about failed requests to your Open WebUI server: the HTTP status and method, the request path (the server’s domain/host is stripped and never sent), and the request/response body. Body content is length-truncated, and any fields that may contain conversation text (such as content, message, text, or prompt) are redacted before being sent.

We do not send your chat content, AI prompts or responses, or your Open WebUI server’s address/domain to Sentry.

## Google Sign-In (Optional)

If your Open WebUI instance is configured to support Google Sign-In, the App may initiate a Google sign-in flow. We do not receive or store your Google password. Google may process authentication-related data under Google’s policies.
Expand All @@ -38,7 +46,6 @@ We do not collect, store, or have access to (on our servers):

- Your conversations, chat messages, attachments, or AI prompts/responses
- Usage analytics or telemetry
- Crash reports
- Location data
- Contacts or address book data
- Advertising identifiers
Expand Down
2 changes: 0 additions & 2 deletions apps/mobile/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,5 @@ EXPO_PUBLIC_APP_NAME=
EXPO_PUBLIC_APP_SLUG=
EXPO_PUBLIC_IS_INTERNAL_RELEASE=
SENTRY_DSN=
AMPLITUDE_API_KEY_DEV=
AMPLITUDE_API_KEY_PROD=
SUPABASE_URL=
SUPABASE_PUBLISHABLE_KEY=
10 changes: 3 additions & 7 deletions apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
sentry: {
dsn: process.env.SENTRY_DSN,
},
amplitude: {
apiKeyDev: process.env.AMPLITUDE_API_KEY_DEV,
apiKeyProd: process.env.AMPLITUDE_API_KEY_PROD,
},
supabase: {
url: process.env.SUPABASE_URL,
publishableKey: process.env.SUPABASE_PUBLISHABLE_KEY,
Expand All @@ -40,7 +36,7 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
slug: process.env.EXPO_PUBLIC_APP_SLUG as string,
scheme: process.env.EXPO_PUBLIC_APP_SCHEME as string,
owner: process.env.EXPO_PUBLIC_APP_OWNER as string,
version: '1.12.0',
version: '1.12.1',
userInterfaceStyle: 'automatic',
orientation: 'portrait',
icon: './assets/icon.png',
Expand All @@ -59,7 +55,7 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
supportsTablet: false,
buildNumber: appEnv.select({
default: '18',
production: '43',
production: '44',
}),
config: {
usesNonExemptEncryption: false,
Expand All @@ -78,7 +74,7 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
playStoreUrl: `https://play.google.com/store/apps/details?id=${playStoreAppId}`,
versionCode: appEnv.select({
default: 15,
production: 43,
production: 44,
}),
adaptiveIcon: {
foregroundImage: './assets/adaptive-icon.png',
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useLogout } from '@open-webui-react-native/mobile/shared/features/use-l
import { fonts } from '@open-webui-react-native/mobile/shared/ui/styles';
import { StatusBar, View } from '@open-webui-react-native/mobile/shared/ui/ui-kit';
import { navigationConfig } from '@open-webui-react-native/mobile/shared/utils/navigation';
import { beforeBreadcrumb } from '@open-webui-react-native/shared/data-access/api-client';
import { appState$ } from '@open-webui-react-native/shared/data-access/app-state';
import { authState$ } from '@open-webui-react-native/shared/data-access/auth';
import {
Expand All @@ -14,7 +15,6 @@ import {
import { queryClient } from '@open-webui-react-native/shared/data-access/query-client';
import { useSocket } from '@open-webui-react-native/shared/data-access/websocket';
import { useNetworkConnection } from '@open-webui-react-native/shared/features/network';
import { analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';
import { constants, LanguageCode } from '@open-webui-react-native/shared/utils/config';
import { setupReactotron } from '@open-webui-react-native/shared/utils/reactotron';
import { supabaseService } from '@open-webui-react-native/shared/utils/supabase-service';
Expand Down Expand Up @@ -45,6 +45,7 @@ Sentry.init({
debug: false,
integrations: [reactNavigationIntegration],
enabled: !__DEV__,
beforeBreadcrumb,
});

const translations = {
Expand Down Expand Up @@ -121,7 +122,6 @@ Object.entries({ en, ru, es, pt, fr, de, zh, ja }).forEach(([locale, pluralizer]
const useLanguage = setLanguage(translations, constants.defaultLocale);

setupReactotron('open-web-ui');
analyticsService.init();
supabaseService.init();

// Prevent the splash screen from auto-hiding before asset loading is complete.
Expand Down
1 change: 0 additions & 1 deletion apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"ios": "expo run:ios"
},
"dependencies": {
"@amplitude/analytics-react-native": "^1.6.8",
"@expo/metro-config": "~57.0.0",
"@expo/metro-runtime": "~57.0.7",
"@expo/ui": "~57.0.10",
Expand Down
6 changes: 0 additions & 6 deletions libs/mobile/chat/features/chat/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {
import { FileData, ImageData, Role } from '@open-webui-react-native/shared/data-access/common';
import { useSubscribeToQueryCache } from '@open-webui-react-native/shared/data-access/query-client';
import { webSocketConfig, webSocketState$ } from '@open-webui-react-native/shared/data-access/websocket';
import { AnalyticsEvent, analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';
import { ToastService } from '@open-webui-react-native/shared/utils/toast-service';
import { useAppStateChange } from '@open-webui-react-native/shared/utils/use-app-state-change';
import { ActiveInputMode } from './enums';
Expand Down Expand Up @@ -213,10 +212,6 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
return ToastService.showError(translate('TEXT_MODEL_NOT_SELECTED'));
}

if (options.includes(ChatGenerationOption.IMAGE_GENERATION)) {
analyticsService.trackEvent(AnalyticsEvent.GENERATE_IMAGE_USED);
}

if (isResponseGenerating && isMessageQueueEnabled) {
// NOTE: Snapshot attachments now — they're cleared by resetAttachments() below and must not
// be re-read live when this queued message is flushed later.
Expand All @@ -231,7 +226,6 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
sendMessage(inputValue, selectedModelId, options, attachedFiles.get(), attachedImages.get());
}

analyticsService.trackEvent(AnalyticsEvent.MESSAGE_SENT, { modelId: selectedModelId });
reset();
resetAttachments();
// NOTE: Forces input rerender to reset it to its initial height after submit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
} from '@open-webui-react-native/shared/data-access/api';
import { Role } from '@open-webui-react-native/shared/data-access/common';
import { socketService } from '@open-webui-react-native/shared/data-access/websocket';
import { AnalyticsEvent, analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';
import { ChatAiMessage } from '../ai-message';
import { ChatBottomButton } from '../chat-bottom-button';
import { ChatUserMessage } from '../user-message';
Expand Down Expand Up @@ -186,7 +185,6 @@ export default function ChatMessagesList({
};

const handleFollowUpPress = (text: string): void => {
analyticsService.trackEvent(AnalyticsEvent.FOLLOW_UP_USED);
onFollowUpPress(text);
};

Expand Down
7 changes: 0 additions & 7 deletions libs/mobile/chat/features/create-chat/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { AppText, Icon, View } from '@open-webui-react-native/mobile/shared/ui/u
import { FormValues } from '@open-webui-react-native/mobile/shared/utils/form';
import { ChatGenerationOption, usersApi } from '@open-webui-react-native/shared/data-access/api';
import { webSocketState$ } from '@open-webui-react-native/shared/data-access/websocket';
import { AnalyticsEvent, analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';
import { ToastService } from '@open-webui-react-native/shared/utils/toast-service';
import { SearchFolderView, SearchModelView } from './components';

Expand Down Expand Up @@ -48,7 +47,6 @@ export function CreateChat({
reset();
resetAttachments();
onChatCreated(id);
analyticsService.trackEvent(AnalyticsEvent.NEW_CHAT_CREATED);
};

const { startChatCreation, isLoading: isCreating } = useCreateNewChat({ onSuccess: handleChatCreated });
Expand All @@ -68,12 +66,7 @@ export function CreateChat({
return ToastService.showError(translate('TEXT_MODEL_NOT_SELECTED'));
}

if (options.includes(ChatGenerationOption.IMAGE_GENERATION)) {
analyticsService.trackEvent(AnalyticsEvent.GENERATE_IMAGE_USED);
}

startChatCreation(inputValue, modelId, options, attachedFiles.get(), attachedImages.get(), folderId);
analyticsService.trackEvent(AnalyticsEvent.MESSAGE_SENT, { modelId });
})();

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
} from '@open-webui-react-native/shared/data-access/api';
import { AttachedImage, FileData, ImageData } from '@open-webui-react-native/shared/data-access/common';
import { withOfflineGuard } from '@open-webui-react-native/shared/features/network';
import { AnalyticsEvent, analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';
import { FeatureID, isFeatureEnabled } from '@open-webui-react-native/shared/utils/feature-flag';
import { toDataUrl } from '@open-webui-react-native/shared/utils/files';
import { ToastService } from '@open-webui-react-native/shared/utils/toast-service';
Expand Down Expand Up @@ -164,7 +163,6 @@ export function FormChatInput<T extends FieldValues>({
const onCompleteRecording = (text: string): void => {
setIsDictateMode(false);
field.onChange(text);
analyticsService.trackEvent(AnalyticsEvent.DICTATION_MODE_USED);
};

const onStopGenerationPress = (): void => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
prepareCreateFolderPayload,
} from '@open-webui-react-native/shared/data-access/api';
import { AttachedFile, FileData, FileType } from '@open-webui-react-native/shared/data-access/common';
import { AnalyticsEvent, analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';
import { FeatureID, isFeatureEnabled } from '@open-webui-react-native/shared/utils/feature-flag';
import { getDocumentFormData } from '@open-webui-react-native/shared/utils/files';
import { ToastService } from '@open-webui-react-native/shared/utils/toast-service';
Expand Down Expand Up @@ -136,7 +135,6 @@ export function UpsertFolderSheet({ ref, ...props }: UpsertFolderSheetProps): Re
});
} else {
await createFolder(payload);
analyticsService.trackEvent(AnalyticsEvent.NEW_FOLDER_CREATED);
}
closeModal();
resetKnowledge();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export function ContactSupportSheet({ ref, ...props }: ContactSupportSheetProps)
platform: Platform.OS,
appVersion: Constants.expoConfig?.version,
apiVersion: configuration?.version,
userId: profile?.id,
attachments: images,
});

Expand Down
2 changes: 0 additions & 2 deletions libs/mobile/shared/features/use-logout/src/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { authApi, resetChatSessionState } from '@open-webui-react-native/shared/
import { authState$ } from '@open-webui-react-native/shared/data-access/auth';
import { cookieService } from '@open-webui-react-native/shared/data-access/cookie';
import { queryPersister } from '@open-webui-react-native/shared/data-access/persist-query-storage';
import { analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';

export const useLogout = (): { logout: () => Promise<void>; isLoading: boolean } => {
const queryClient = useQueryClient();
Expand All @@ -19,7 +18,6 @@ export const useLogout = (): { logout: () => Promise<void>; isLoading: boolean }
queryClient.removeQueries();
resetChatSessionState();
cookieService.clearAll();
analyticsService.resetUser();
await queryPersister.removeClient();
};

Expand Down
1 change: 1 addition & 0 deletions libs/shared/data-access/api-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './service';
export * from './config';
export * from './types';
export * from './utils/before-breadcrumb';
export * from './utils/capture-api-error';
20 changes: 20 additions & 0 deletions libs/shared/data-access/api-client/src/utils/before-breadcrumb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { stripOrigin } from '@open-webui-react-native/shared/utils/strings';
import type { Breadcrumb } from '@sentry/react-native';

const networkBreadcrumbCategories = new Set(['xhr', 'http', 'fetch']);

// Sentry records the absolute request URL in network breadcrumbs, which exposes the address
// of the user's self-hosted server. Keep only the path and query so the host does not leak.
export const beforeBreadcrumb = (breadcrumb: Breadcrumb): Breadcrumb => {
if (!breadcrumb.category || !networkBreadcrumbCategories.has(breadcrumb.category)) {
return breadcrumb;
}

const url = breadcrumb.data?.url;

if (typeof url !== 'string') {
return breadcrumb;
}

return { ...breadcrumb, data: { ...breadcrumb.data, url: stripOrigin(url) } };
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Sentry from '@sentry/react-native';
import { AxiosError, isAxiosError } from 'axios';
import { queryClient } from '@open-webui-react-native/shared/data-access/query-client';
import { getApiUrl } from '@open-webui-react-native/shared/utils/config';
import { stripOrigin } from '@open-webui-react-native/shared/utils/strings';
import { ApiErrorData } from '../types';

const MAX_STRING_LENGTH = 500;
Expand Down Expand Up @@ -84,21 +84,20 @@ const getRequestUrl = (error: AxiosError): string | undefined => {
const { baseURL, url } = error.config ?? {};

if (!url) {
return baseURL;
return baseURL ? stripOrigin(baseURL) : undefined;
}

if (url.startsWith('http')) {
return url;
return stripOrigin(url);
}

return `${baseURL ?? ''}${url}`;
return url;
};

const getApiInstanceMeta = (): { apiUrl: string; apiVersion?: string } => {
const getApiInstanceMeta = (): { apiVersion?: string } => {
const configuration = queryClient.getQueryData<{ version?: string }>(['config']);

return {
apiUrl: getApiUrl(),
apiVersion: configuration?.version,
};
};
Expand All @@ -109,7 +108,7 @@ export type CaptureApiErrorParams = {
};

export const captureApiError = (error: unknown, { operation, context = {} }: CaptureApiErrorParams): void => {
const { apiUrl, apiVersion } = getApiInstanceMeta();
const { apiVersion } = getApiInstanceMeta();

if (isAxiosError(error)) {
const axiosError = error as AxiosError<ApiErrorData>;
Expand All @@ -124,7 +123,6 @@ export const captureApiError = (error: unknown, { operation, context = {} }: Cap
scope.setLevel('error');

scope.setContext('api', {
apiUrl,
apiVersion,
url: getRequestUrl(axiosError),
method: config?.method,
Expand All @@ -148,7 +146,7 @@ export const captureApiError = (error: unknown, { operation, context = {} }: Cap
Sentry.withScope((scope) => {
scope.setTag('api.operation', operation);
scope.setTag('api.version', apiVersion ?? 'unknown');
scope.setContext('api', { apiUrl, apiVersion });
scope.setContext('api', { apiVersion });
scope.setContext('api_request', sanitizeContextForSentry(context));
Sentry.captureException(error);
});
Expand Down
18 changes: 2 additions & 16 deletions libs/shared/features/use-init-requests/src/hook.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
import { useEffect } from 'react';
import { appConfigurationApi, authApi, modelsApi, usersApi } from '@open-webui-react-native/shared/data-access/api';
import { analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';

interface UseInitRequestsResult {
isLoading: boolean;
}

export const useInitRequests = (): UseInitRequestsResult => {
const { isLoading: isModelsLoading } = modelsApi.useGetModels();
const { data: profile, isLoading: isProfileLoading } = authApi.useGetProfile();
const { isLoading: isProfileLoading } = authApi.useGetProfile();
const { isLoading: isUserSettingsLoading } = usersApi.useGetUserSettings();
const { data: configuration, isLoading: isConfigurationLoading } = appConfigurationApi.useGetAppConfiguration();
const { isLoading: isConfigurationLoading } = appConfigurationApi.useGetAppConfiguration();

const isLoading = isModelsLoading || isProfileLoading || isUserSettingsLoading || isConfigurationLoading;

useEffect(() => {
if (profile?.id) {
analyticsService.setUser(profile.id);
}
}, [profile]);

useEffect(() => {
if (configuration?.version) {
analyticsService.setUserProperties({ apiVersion: configuration.version });
}
}, [configuration]);

return { isLoading };
};
12 changes: 0 additions & 12 deletions libs/shared/utils/analytics-service/.babelrc

This file was deleted.

7 changes: 0 additions & 7 deletions libs/shared/utils/analytics-service/README.md

This file was deleted.

12 changes: 0 additions & 12 deletions libs/shared/utils/analytics-service/eslint.config.cjs

This file was deleted.

Loading
Loading