Skip to content
Open
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
2 changes: 1 addition & 1 deletion packages/@magic-sdk/react-native-bare/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const config: Config.InitialOptions = {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
},
transformIgnorePatterns: [
'node_modules/(?!((react-native|@magic-sdk/provider|@magic-sdk/react-native.*|react-navigation|@react-native|react-native-gesture-handler|react-native-event-listeners)/).*)',
'node_modules/(?!((react-native|@magic-sdk/provider|@magic-sdk/react-native.*|react-navigation|@react-native|react-native-gesture-handler|react-native-event-listeners|react-native-inappbrowser-reborn)/).*)',
],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};
Expand Down
11 changes: 11 additions & 0 deletions packages/@magic-sdk/react-native-bare/src/lib/links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Linking } from 'react-native';

export const openInBrowser = async (url: string) => {
const supported = await Linking.canOpenURL(url);

if (supported) {
await Linking.openURL(url);
} else {
console.warn(`Cannot open URL: ${url}`);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Linking, StyleSheet, View } from 'react-native';
import { WebView } from 'react-native-webview';
import { SafeAreaView } from 'react-native-safe-area-context';
import { ViewController, createModalNotReadyError } from '@magic-sdk/provider';
import { MagicMessageEvent } from '@magic-sdk/types';
import { MagicIncomingWindowMessage, MagicMessageEvent } from '@magic-sdk/types';
import { isTypedArray } from 'lodash';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { EventRegister } from 'react-native-event-listeners';
Expand All @@ -13,6 +13,7 @@ import { useInternetConnection } from './hooks';
import { getRefreshTokenInKeychain, setRefreshTokenInKeychain } from './native-crypto/keychain';
import { getDpop } from './native-crypto/dpop';
import { checkNativeModules } from './native-crypto/check-native-modules';
import { openInBrowser } from './lib/links';

const MAGIC_PAYLOAD_FLAG_TYPED_ARRAY = 'MAGIC_PAYLOAD_FLAG_TYPED_ARRAY';
const OPEN_IN_DEVICE_BROWSER = 'open_in_device_browser';
Expand Down Expand Up @@ -117,6 +118,17 @@ export class ReactNativeWebViewController extends ViewController {
}
}, [mountOverlay]);

useEffect(() => {
const removeHandler = this.on(MagicIncomingWindowMessage.MAGIC_OPEN_MOBILE_URL, (event: MagicMessageEvent) => {
const url = event.data.response.result?.url;
if (!url) return;

openInBrowser(url);
});

return removeHandler;
}, []);

/**
* Saves a reference to the underlying `<WebView>` node so we can interact
* with incoming messages.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Linking } from 'react-native';
import { openInBrowser } from '../../../src/lib/links';

const mockCanOpenURL = Linking.canOpenURL as jest.Mock;
const mockOpenURL = Linking.openURL as jest.Mock;

beforeEach(() => {
jest.resetAllMocks();
});

describe('openInBrowser', () => {
it('calls Linking.openURL when the URL is supported', async () => {
const url = 'https://magic.link';
mockCanOpenURL.mockResolvedValue(true);
mockOpenURL.mockResolvedValue(undefined);

await openInBrowser(url);

expect(mockCanOpenURL).toHaveBeenCalledWith(url);
expect(mockOpenURL).toHaveBeenCalledWith(url);
});

it('does not call Linking.openURL when the URL is not supported', async () => {
const url = 'unsupported://scheme';
mockCanOpenURL.mockResolvedValue(false);

await openInBrowser(url);

expect(mockCanOpenURL).toHaveBeenCalledWith(url);
expect(mockOpenURL).not.toHaveBeenCalled();
});

it('logs a warning when the URL is not supported', async () => {
const url = 'unsupported://scheme';
mockCanOpenURL.mockResolvedValue(false);
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});

await openInBrowser(url);

expect(warnSpy).toHaveBeenCalledWith(`Cannot open URL: ${url}`);
});
});
10 changes: 10 additions & 0 deletions packages/@magic-sdk/react-native-expo/src/lib/links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Linking } from 'react-native';

export const openInBrowser = async (url: string) => {
const supported = await Linking.canOpenURL(url);
if (supported) {
await Linking.openURL(url);
} else {
console.warn(`Cannot open URL: ${url}`);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { Linking, StyleSheet, View } from 'react-native';
import { WebView } from 'react-native-webview';
import { SafeAreaView } from 'react-native-safe-area-context';
import { ViewController, createModalNotReadyError } from '@magic-sdk/provider';
import { MagicMessageEvent } from '@magic-sdk/types';
import { MagicIncomingWindowMessage, MagicMessageEvent } from '@magic-sdk/types';
import { isTypedArray } from 'lodash';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { EventRegister } from 'react-native-event-listeners';
/* global NodeJS */
import Global = NodeJS.Global;
import { useInternetConnection } from './hooks';
import { openInBrowser } from './lib/links';

const MAGIC_PAYLOAD_FLAG_TYPED_ARRAY = 'MAGIC_PAYLOAD_FLAG_TYPED_ARRAY';
const OPEN_IN_DEVICE_BROWSER = 'open_in_device_browser';
Expand Down Expand Up @@ -91,6 +92,17 @@ export class ReactNativeWebViewController extends ViewController {
this.isConnectedToInternet = isConnected;
}, [isConnected]);

useEffect(() => {
const removeHandler = this.on(MagicIncomingWindowMessage.MAGIC_OPEN_MOBILE_URL, (event: MagicMessageEvent) => {
const url = event.data.response.result?.url;
if (!url) return;

openInBrowser(url);
});

return removeHandler;
}, []);

useEffect(() => {
// reset lastMessage when webview is first mounted
AsyncStorage.setItem(LAST_MESSAGE_TIME, '');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Linking } from 'react-native';
import { openInBrowser } from '../../../src/lib/links';

const mockCanOpenURL = Linking.canOpenURL as jest.Mock;
const mockOpenURL = Linking.openURL as jest.Mock;

beforeEach(() => {
jest.resetAllMocks();
});

describe('openInBrowser', () => {
it('calls Linking.openURL when the URL is supported', async () => {
const url = 'https://magic.link';
mockCanOpenURL.mockResolvedValue(true);
mockOpenURL.mockResolvedValue(undefined);

await openInBrowser(url);

expect(mockCanOpenURL).toHaveBeenCalledWith(url);
expect(mockOpenURL).toHaveBeenCalledWith(url);
});

it('does not call Linking.openURL when the URL is not supported', async () => {
const url = 'unsupported://scheme';
mockCanOpenURL.mockResolvedValue(false);

await openInBrowser(url);

expect(mockCanOpenURL).toHaveBeenCalledWith(url);
expect(mockOpenURL).not.toHaveBeenCalled();
});

it('logs a warning when the URL is not supported', async () => {
const url = 'unsupported://scheme';
mockCanOpenURL.mockResolvedValue(false);
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});

await openInBrowser(url);

expect(warnSpy).toHaveBeenCalledWith(`Cannot open URL: ${url}`);
});
});
1 change: 1 addition & 0 deletions packages/@magic-sdk/types/src/core/message-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum MagicIncomingWindowMessage {
MAGIC_POPUP_RESPONSE = 'MAGIC_POPUP_RESPONSE',
MAGIC_POPUP_OAUTH_VERIFY_RESPONSE = 'MAGIC_POPUP_OAUTH_VERIFY_RESPONSE',
MAGIC_THIRD_PARTY_WALLET_REQUEST = 'MAGIC_THIRD_PARTY_WALLET_REQUEST',
MAGIC_OPEN_MOBILE_URL = 'MAGIC_OPEN_MOBILE_URL',
}

export enum MagicOutgoingWindowMessage {
Expand Down
Loading