diff --git a/apps/tlon-mobile/android/app/src/main/AndroidManifest.xml b/apps/tlon-mobile/android/app/src/main/AndroidManifest.xml
index 9f185e80eb..980f571091 100644
--- a/apps/tlon-mobile/android/app/src/main/AndroidManifest.xml
+++ b/apps/tlon-mobile/android/app/src/main/AndroidManifest.xml
@@ -4,7 +4,6 @@
-
diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj
index 67812cbc7b..a5e0dbc119 100644
--- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj
+++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj
@@ -1174,27 +1174,30 @@
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
);
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1265,27 +1268,30 @@
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
+ " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
);
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
diff --git a/apps/tlon-mobile/package.json b/apps/tlon-mobile/package.json
index 0083e4e235..3112f1ae71 100644
--- a/apps/tlon-mobile/package.json
+++ b/apps/tlon-mobile/package.json
@@ -72,6 +72,7 @@
"react-native-screens": "~3.29.0",
"react-native-storage": "^1.0.1",
"react-native-svg": "^14.1.0",
+ "react-native-url-polyfill": "^2.0.0",
"react-native-webview": "13.6.4",
"realm": "^12.6.0",
"tailwind-rn": "^4.2.0"
diff --git a/apps/tlon-mobile/src/App.tsx b/apps/tlon-mobile/src/App.tsx
index 4ba3add014..49507f8823 100644
--- a/apps/tlon-mobile/src/App.tsx
+++ b/apps/tlon-mobile/src/App.tsx
@@ -42,6 +42,7 @@ import { TlonLoginScreen } from './screens/TlonLoginScreen';
import { WelcomeScreen } from './screens/WelcomeScreen';
import type { OnboardingStackParamList } from './types';
import { posthogAsync, trackError } from './utils/posthog';
+import { getPathFromWer } from './utils/string';
type Props = {
wer?: string;
@@ -58,7 +59,7 @@ const App = ({ wer: initialWer }: Props) => {
const { wer, lure, priorityToken, clearDeepLink } = useDeepLink();
const navigation = useNavigation();
const screenOptions = useScreenOptions();
- const gotoPath = wer ?? initialWer;
+ const gotoPath = initialWer ? getPathFromWer(initialWer) : wer;
useEffect(() => {
if (isAuthenticated) {
diff --git a/apps/tlon-mobile/src/components/SingletonWebview.tsx b/apps/tlon-mobile/src/components/SingletonWebview.tsx
index 35f4588a9d..a4949ea038 100644
--- a/apps/tlon-mobile/src/components/SingletonWebview.tsx
+++ b/apps/tlon-mobile/src/components/SingletonWebview.tsx
@@ -10,6 +10,7 @@ import { addNotificationResponseReceivedListener } from 'expo-notifications';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Alert, Linking, useColorScheme } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
+import { URL } from 'react-native-url-polyfill';
import { WebView } from 'react-native-webview';
import { useTailwind } from 'tailwind-rn';
@@ -40,7 +41,7 @@ const createUri = (shipUrl: string, path?: string) =>
export const SingletonWebview = () => {
const tailwind = useTailwind();
- const { shipUrl = '', ship, clearShip } = useShip();
+ const { shipUrl = '', ship, clearShip, setShip } = useShip();
const webViewProps = useWebView();
const colorScheme = useColorScheme();
const safeAreaInsets = useSafeAreaInsets();
@@ -221,6 +222,24 @@ export const SingletonWebview = () => {
setTimeout(() => setAppLoaded(true), 10_000);
}}
onShouldStartLoadWithRequest={({ url }) => {
+ const parsedUrl = new URL(url);
+ const parsedShipUrl = new URL(shipUrl);
+ const redirectedToHttps =
+ parsedUrl.protocol === 'https:' &&
+ parsedShipUrl.protocol === 'http:' &&
+ parsedUrl.host === parsedShipUrl.host &&
+ parsedUrl.pathname.startsWith('/apps/groups');
+
+ // Allow redirect to HTTPS
+ if (redirectedToHttps) {
+ setShip({
+ ship,
+ shipUrl: parsedUrl.origin,
+ });
+
+ return true;
+ }
+
// Clear ship info if webview is redirecting to login page
if (url.includes(`${shipUrl}/~/login`)) {
clearShip();
diff --git a/apps/tlon-mobile/src/contexts/ship.tsx b/apps/tlon-mobile/src/contexts/ship.tsx
index 136554749b..817c1e96fc 100644
--- a/apps/tlon-mobile/src/contexts/ship.tsx
+++ b/apps/tlon-mobile/src/contexts/ship.tsx
@@ -6,6 +6,7 @@ import { NativeModules } from 'react-native';
import { configureApi } from '../lib/api';
import storage from '../lib/storage';
+import { transformShipURL } from '../utils/string';
const { UrbitModule } = NativeModules;
@@ -57,36 +58,39 @@ export const ShipProvider = ({ children }: { children: ReactNode }) => {
return;
}
+ // The passed shipUrl should already be normalized, but defensively ensure it is
+ const normalizedShipUrl = transformShipURL(shipUrl);
+
// Save to React Native stoage
- storage.save({ key: 'store', data: { ship, shipUrl } });
+ storage.save({ key: 'store', data: { ship, shipUrl: normalizedShipUrl } });
// Save context state
- setShipInfo({ ship, shipUrl });
+ setShipInfo({ ship, shipUrl: normalizedShipUrl });
// Configure API
- configureApi(ship, shipUrl);
+ configureApi(ship, normalizedShipUrl);
// Configure analytics
crashlytics().setAttribute(
'isHosted',
- shipUrl.includes('.tlon.network') ? 'true' : 'false'
+ normalizedShipUrl.includes('.tlon.network') ? 'true' : 'false'
);
// If cookie was passed in, use it, otherwise fetch from ship
if (authCookie) {
// Save to native storage
- UrbitModule.setUrbit(ship, shipUrl, authCookie);
+ UrbitModule.setUrbit(ship, normalizedShipUrl, authCookie);
} else {
// Run this in the background
(async () => {
// Fetch the root ship URL and parse headers
- const response = await fetch(shipUrl, {
+ const response = await fetch(normalizedShipUrl, {
credentials: 'include',
});
const fetchedAuthCookie = response.headers.get('set-cookie');
if (fetchedAuthCookie) {
// Save to native storage
- UrbitModule.setUrbit(ship, shipUrl, fetchedAuthCookie);
+ UrbitModule.setUrbit(ship, normalizedShipUrl, fetchedAuthCookie);
}
})();
}
diff --git a/apps/tlon-mobile/src/screens/TlonLoginScreen.tsx b/apps/tlon-mobile/src/screens/TlonLoginScreen.tsx
index 1498d2da5c..22d6d467aa 100644
--- a/apps/tlon-mobile/src/screens/TlonLoginScreen.tsx
+++ b/apps/tlon-mobile/src/screens/TlonLoginScreen.tsx
@@ -99,8 +99,14 @@ export const TlonLoginScreen = ({ navigation }: Props) => {
user,
});
}
- } catch (err) {
- setRemoteError((err as Error).message);
+ } catch (err: any) {
+ if ('name' in err && err.name === 'AbortError') {
+ setRemoteError(
+ 'Sorry, we could not connect to the server. Please try again later.'
+ );
+ } else {
+ setRemoteError((err as Error).message);
+ }
}
setIsSubmitting(false);
diff --git a/apps/tlon-mobile/src/utils/string.test.ts b/apps/tlon-mobile/src/utils/string.test.ts
index 6df699050f..9c16862553 100644
--- a/apps/tlon-mobile/src/utils/string.test.ts
+++ b/apps/tlon-mobile/src/utils/string.test.ts
@@ -3,58 +3,59 @@ import { expect, test } from 'vitest';
import { formatPhoneNumber, transformShipURL } from './string';
test('formats phone number without country code', () => {
- expect(formatPhoneNumber('5555555555'), '(555) 555-5555');
- expect(formatPhoneNumber('555-555-5555'), '(555) 555-5555');
+ expect(formatPhoneNumber('5555555555')).toBe('(555) 555-5555');
+ expect(formatPhoneNumber('555-555-5555')).toBe('(555) 555-5555');
});
test('formats phone number with country code', () => {
- expect(formatPhoneNumber('15555555555'), '+1 (555) 555-5555');
- expect(formatPhoneNumber('+15555555555'), '+1 (555) 555-5555');
+ expect(formatPhoneNumber('15555555555')).toBe('+1 (555) 555-5555');
+ expect(formatPhoneNumber('+15555555555')).toBe('+1 (555) 555-5555');
});
test('skips when given unknown format', () => {
- expect(formatPhoneNumber('1234'), '1234');
+ expect(formatPhoneNumber('1234')).toBe('1234');
});
test('transforms ship url without a protocol', () => {
- expect(
- transformShipURL('sampel-palnet.tlon.network'),
+ expect(transformShipURL('sampel-palnet.tlon.network')).toBe(
'https://sampel-palnet.tlon.network'
);
});
test('transforms ship url with a path', () => {
- expect(
- transformShipURL('sampel-palnet.tlon.network/apps/groups'),
+ expect(transformShipURL('sampel-palnet.tlon.network/apps/groups')).toBe(
'https://sampel-palnet.tlon.network'
);
});
test('transforms ship url with a protocol and extra whitespace', () => {
- expect(
- transformShipURL('https://sampel-palnet.tlon.network '),
+ expect(transformShipURL('https://sampel-palnet.tlon.network ')).toBe(
+ 'https://sampel-palnet.tlon.network'
+ );
+});
+
+test('transforms ship url with trailing slash', () => {
+ expect(transformShipURL('https://sampel-palnet.tlon.network/')).toBe(
'https://sampel-palnet.tlon.network'
);
});
test('if a ship url is already valid, it is returned as is', () => {
- expect(
- transformShipURL('https://sampel-palnet.tlon.network'),
+ expect(transformShipURL('https://sampel-palnet.tlon.network')).toBe(
'https://sampel-palnet.tlon.network'
);
});
test('if a ship url is an ip address with a port, it is returned as is', () => {
- expect(
- transformShipURL('http://192.168.0.1:8443'),
+ expect(transformShipURL('http://192.168.0.1:8443')).toBe(
'http://192.168.0.1:8443'
);
});
test('if a ship url is an ip address without a port, it is returned as is', () => {
- expect(transformShipURL('http://192.168.0.1'), 'http://192.168.0.1');
+ expect(transformShipURL('http://192.168.0.1')).toBe('http://192.168.0.1');
});
test('if a ship url is http://localhost, it is returned as is', () => {
- expect(transformShipURL('http://localhost'), 'http://localhost');
+ expect(transformShipURL('http://localhost')).toBe('http://localhost');
});
diff --git a/apps/tlon-mobile/src/utils/string.ts b/apps/tlon-mobile/src/utils/string.ts
index 6a0ce8b513..8ac2292f4f 100644
--- a/apps/tlon-mobile/src/utils/string.ts
+++ b/apps/tlon-mobile/src/utils/string.ts
@@ -47,7 +47,7 @@ export const formatPhoneNumber = (phoneNumber: string) => {
export const transformShipURL = (shipUrl: string) => {
// this definition is duplicated here because importing it from src/constants will cause vitest to
// attempt to import react-native libraries, which fill fail
- const SHIP_URL_REGEX = /^https?:\/\/([\w-]+\.)+[\w-]+(:\d+)?(?=\/?$)/;
+ const SHIP_URL_REGEX = /^https?:\/\/([\w-]+\.)+[\w-]+(:\d+)?$/;
const shipUrlMatch = shipUrl.match(SHIP_URL_REGEX);
let shipUrlToUse = shipUrl;
@@ -70,8 +70,51 @@ export const transformShipURL = (shipUrl: string) => {
'$1'
);
+ // Remove trailing slashes
+ transformed = transformed.replace(/\/+$/, '');
+
shipUrlToUse = transformed;
}
return shipUrlToUse;
};
+
+/**
+ * This function is used to determine the path to navigate to
+ * when the app is opened from a notification.
+ * @param wer The path from the backend
+ * @returns path to navigate to
+ * This is necessary because the backend sends a path to the app
+ * that is not always the correct path to navigate to (e.g. when
+ * there's a post in an "all notifications" channel, the path
+ * will be to a thread starting with the post, but we want to
+ * navigate to the post itself in the main chat).
+ * This same logic exists in the web app in Notification.tsx
+ */
+export const getPathFromWer = (wer: string): string => {
+ const pathParts = wer.split('/');
+ // if not going to a specific post, return the path
+ if (pathParts.length < 10) {
+ return wer;
+ }
+
+ const path = wer;
+ const parts = path.split('/');
+ const isChatMsg = parts.includes('chat');
+ const index = 8;
+ const post = parts[index + 1];
+ const reply = parts[index + 2];
+
+ // all replies should go to the post and scroll to the reply
+ if (reply) {
+ return `${parts.slice(0, index + 2).join('/')}?reply=${reply}`;
+ }
+
+ // chat messages should go to the channel and scroll to the message
+ if (isChatMsg) {
+ return `${parts.slice(0, index).join('/')}?msg=${post}`;
+ }
+
+ // all other posts should go to the post
+ return path;
+};
diff --git a/package-lock.json b/package-lock.json
index f38610b1a3..6b3a2e10be 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -68,6 +68,7 @@
"react-native-screens": "~3.29.0",
"react-native-storage": "^1.0.1",
"react-native-svg": "^14.1.0",
+ "react-native-url-polyfill": "^2.0.0",
"react-native-webview": "13.6.4",
"realm": "^12.6.0",
"tailwind-rn": "^4.2.0"
@@ -769,7 +770,7 @@
}
},
"apps/tlon-web": {
- "version": "5.5.0",
+ "version": "5.7.0",
"dependencies": {
"@aws-sdk/client-s3": "^3.190.0",
"@aws-sdk/s3-request-presigner": "^3.190.0",
@@ -32665,6 +32666,17 @@
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="
},
+ "node_modules/react-native-url-polyfill": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz",
+ "integrity": "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA==",
+ "dependencies": {
+ "whatwg-url-without-unicode": "8.0.0-3"
+ },
+ "peerDependencies": {
+ "react-native": "*"
+ }
+ },
"node_modules/react-native-web": {
"version": "0.19.10",
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.10.tgz",