Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"preset": "jest-expo"
},
"dependencies": {
"@magicbell/react-headless": "5.1.0",
"@magicbell/react-headless": "https://pkg.pr.new/@magicbell/react-headless@465",
"@magicbell/user-client": "https://pkg.pr.new/@magicbell/user-client@465",
"@react-native-async-storage/async-storage": "1.23.1",
"@react-navigation/native": "^7.0.14",
"@react-navigation/native-stack": "^7.2.0",
Expand Down
7 changes: 1 addition & 6 deletions src/components/MagicBellProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ export default function MagicBellProvider({ children }: PropsWithChildren<IProps

if (credentials) {
return (
<MagicBell.MagicBellProvider
apiKey={credentials.apiKey}
userEmail={credentials.userEmail}
userKey={credentials.userHmac}
serverURL={credentials.serverURL}
>
<MagicBell.MagicBellProvider serverURL={credentials.serverURL} token={credentials.userJWTToken}>
<>{children}</>
</MagicBell.MagicBellProvider>
);
Expand Down
16 changes: 3 additions & 13 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,12 @@ export const routes = {

export const config: { [key: string]: Credentials } = {
prod: {
apiKey: 'd6a3cf19179a45a5daa9ac7f3f37e9d49914d2ad',
userEmail: '[email protected]',
userHmac: '5n4ooUtzydnYq5GYh6PIWGeP2alepTf/Qgb/Sp/g3Co=',
userJWTToken: '',
serverURL: 'https://api.magicbell.com',
},
local: {
apiKey: '8cd17191a14339cb1d4e58c4ea471eeca51d2c70',
userEmail: '[email protected]',
userHmac: '',
serverURL: 'https://1b35-79-153-3-135.ngrok-free.app',
},
review: {
apiKey: '552efd58f59315d065e45b07f8d8f8a2751c2b5b',
userEmail: '[email protected]',
userHmac: '5n4ooUtzydnYq5GYh6PIWGeP2alepTf/Qgb/Sp/g3Co=',
serverURL: 'https://api-4374.magicbell.cloud/',
userJWTToken: '',
serverURL: 'http://localhost:3000',
},
};

Expand Down
31 changes: 13 additions & 18 deletions src/hooks/useAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';

import { UserClient } from 'magicbell/user-client';
import { Client as UserClient } from '@magicbell/user-client';
import useDeviceToken from './useDeviceToken';

const storageKey = 'mb';
const storageKey = 'mbv2';

export type Credentials = {
apiKey: string;
userEmail: string;
userHmac: string;
userJWTToken: string;
serverURL: string;
};

Expand Down Expand Up @@ -40,8 +38,8 @@ export default function CredentialsProvider({ children }: { children: React.Reac
if (validCredentials) {
setCredentials(validCredentials);
} else {
await deleteCredentials();
setCredentials(null);
alert('Invalid sign in credentials');
}
}, []);
const signOut = useCallback(async () => {
Expand All @@ -64,26 +62,23 @@ const getCredentials = async () => {
return null;
}
try {
const { apiKey, userEmail, userHmac, serverURL } = JSON.parse(value);
const { serverURL, userJWTToken } = JSON.parse(value);
const client = new UserClient({
apiKey: apiKey,
userEmail: userEmail,
userHmac: userHmac,
host: serverURL,
token: userJWTToken,
baseUrl: `${serverURL}/v2`,
});
const config = await client.request({
method: 'GET',
path: '/config',
});
if (config) {
return { apiKey, userEmail, userHmac, serverURL };

// Doing a basic request to see if the token is valid.
// TODO: replace this with a more generic endpoint like `/v2/config` once that's available in the API spec
const testResponse = await client.channels.getMobilePushApnsTokens();
if (testResponse) {
return { serverURL, userJWTToken };
}
} catch (e) {
console.error('Error parsing credentials', e);
await deleteCredentials();
return null;
}
return null;
};

const storeCredentials = async (value: Credentials) => {
Expand Down
79 changes: 33 additions & 46 deletions src/hooks/useDeviceToken.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,72 +5,59 @@ import {
getIosPushNotificationServiceEnvironmentAsync,
} from 'expo-application';
import { getDevicePushTokenAsync, requestPermissionsAsync } from 'expo-notifications';
import { UserClient } from 'magicbell/user-client';
import { ApnsToken, FcmToken, Client as UserClient } from '@magicbell/user-client';
import React, { useEffect } from 'react';
import { Platform } from 'react-native';
import { Credentials } from './useAuth';

const clientWithCredentials = (credentials: Credentials) =>
new UserClient({
apiKey: credentials.apiKey,
userEmail: credentials.userEmail,
userHmac: credentials.userHmac,
host: credentials.serverURL,
});

const tokenPath = Platform.select({
ios: '/channels/mobile_push/apns/tokens',
android: '/channels/mobile_push/fcm/tokens',
})!;

const apnsTokenPayload = async (token: string): Promise<any> => {
const apnsTokenPayload = async (token: string): Promise<ApnsToken> => {
const isSimulator = (await getIosApplicationReleaseTypeAsync()) === ApplicationReleaseType.SIMULATOR;
const installationId =
(await getIosPushNotificationServiceEnvironmentAsync()) || isSimulator ? 'development' : 'production';
return {
apns: {
device_token: token,
installation_id: installationId,
app_id: applicationId,
},
deviceToken: token,
installationId: installationId,
appId: applicationId || undefined,
};
};

const fcmTokenPayload = (token: string): any => {
const fcmTokenPayload = (token: string): FcmToken => {
return {
fcm: {
device_token: token,
},
deviceToken: token,
};
};

const registerTokenWithCredentials = async (token: string, credentials: Credentials) => {
const data = Platform.OS === 'ios' ? await apnsTokenPayload(token) : fcmTokenPayload(token);

console.log('posting token', token);
const client = clientWithCredentials(credentials);
client
.request({
method: 'POST',
path: tokenPath,
data: data,
})
.catch((err) => {
console.log('post token error', err);
});
const client = new UserClient({ baseUrl: `${credentials.serverURL}/v2`, token: credentials.userJWTToken });
console.info('posting token', token);
if (Platform.OS === 'ios') {
const payload = await apnsTokenPayload(token);
try {
await client.channels.saveMobilePushApnsToken(payload);
} catch (error) {
console.error('Error registering APNS token: ', error);
}
} else if (Platform.OS === 'android') {
const payload = await fcmTokenPayload(token);
try {
await client.channels.saveMobilePushFcmToken(payload);
} catch (error) {
console.error('Error registering FCM token: ', error);
}
} else {
console.warn(`not posting token on platform ${Platform.OS}`);
}
};

const unregisterTokenWithCredentials = async (token: string, credentials: Credentials) => {
console.log('deleting token', token);
const client = clientWithCredentials(credentials);
client
.request({
method: 'DELETE',
path: tokenPath + '/' + token,
})
.catch((err) => {
console.log('delete token error', err);
});
const client = new UserClient({ baseUrl: `${credentials.serverURL}/v2`, token: credentials.userJWTToken });

if (Platform.OS === 'ios') {
await client.channels.discardMobilePushApnsToken(token);
} else if (Platform.OS === 'android') {
await client.channels.discardMobilePushFcmToken(token);
}
};

export default function useDeviceToken(credentials: Credentials | null | undefined) {
Expand Down
15 changes: 4 additions & 11 deletions src/hooks/useReviewCredentials.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,14 @@ import { Platform } from 'react-native';
* ATTENTION: This is only for MagicBell internal use. You should not follow this example in your production app.
*
* Example URL:
* x-magicbell-review://connect?apiHost=[...]&apiKey=[...]&userEmail=[...]&userHmac=[...]
* x-magicbell-review://connect?apiHost=[...]&userJWTToken=[...]
*
*/
const parseLaunchURLCredentials = (url: URL): Credentials | null => {
var serverURL = url.searchParams.get('apiHost');
const apiKey = url.searchParams.get('apiKey');
const userJWTToken = url.searchParams.get('userJWTToken');

// TODO: support userExternalID as well
const userEmail = url.searchParams.get('userEmail');

const userHmac = url.searchParams.get('userHmac');

if (!serverURL || !apiKey || !userEmail || !userHmac) {
if (!serverURL || !userJWTToken) {
console.warn('Could not parse credentials from launch URL: ', url.toString());
return null;
}
Expand All @@ -37,9 +32,7 @@ const parseLaunchURLCredentials = (url: URL): Credentials | null => {

const credentials: Credentials = {
serverURL,
apiKey,
userHmac,
userEmail,
userJWTToken,
};
return credentials;
};
Expand Down
20 changes: 6 additions & 14 deletions src/screens/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,20 @@ export const SignInScreen = (): React.JSX.Element => {
const defaultCredentials = reviewCredentials || currentConfig;
const [loading, setLoading] = useState(false);
const [serverURL, setServerURL] = useState(defaultCredentials.serverURL);
const [apiKey, setApiKey] = useState(defaultCredentials.apiKey);
const [userEmail, setUserEmail] = useState(defaultCredentials.userEmail);
const [userHmac, setUserHmac] = useState(defaultCredentials.userHmac);
const [userJWTToken, setUserJWTToken] = useState(defaultCredentials.userJWTToken);

useEffect(() => {
if (reviewCredentials) {
setServerURL(reviewCredentials.serverURL);
setApiKey(reviewCredentials.apiKey);
setUserEmail(reviewCredentials.userEmail);
setUserHmac(reviewCredentials.userHmac);
setUserJWTToken(reviewCredentials.userJWTToken);
}
}, [reviewCredentials]);

const handleSubmit = useCallback(async () => {
setLoading(true);
await signIn({ apiKey, userEmail, userHmac, serverURL });
await signIn({ serverURL, userJWTToken });
setLoading(false);
}, [signIn, apiKey, userEmail, userHmac, serverURL]);
}, [signIn, serverURL, userJWTToken]);

if (credentials) {
throw new Error('User is already signed in');
Expand Down Expand Up @@ -75,9 +71,7 @@ export const SignInScreen = (): React.JSX.Element => {
onValueChange={
((itemValue: keyof typeof config) => {
const c = config[itemValue];
setApiKey(c.apiKey);
setUserEmail(c.userEmail);
setUserHmac(c.userHmac);
setUserJWTToken(c.userJWTToken);
setServerURL(c.serverURL);
}) as (itemValue: string) => void
}
Expand All @@ -87,9 +81,7 @@ export const SignInScreen = (): React.JSX.Element => {
})}
</Select>
</Box>
<TextInput placeholder="Project API Key" value={apiKey} onChangeText={setApiKey} />
<TextInput placeholder="User email" value={userEmail} onChangeText={setUserEmail} />
<TextInput placeholder="User HMAC" value={userHmac} onChangeText={setUserHmac} />
<TextInput placeholder="User JWT Token" value={userJWTToken} onChangeText={setUserJWTToken} />
<TextInput placeholder="Server URL" value={serverURL} onChangeText={setServerURL} />
<CustomButton title="Sign in" loading={loading} onPress={handleSubmit} />
</View>
Expand Down
Loading