Skip to content

Commit

Permalink
Enhancement for web and package updates (#66)
Browse files Browse the repository at this point in the history
1. Makes web fully workable
2. Updates b2c-sample to make it work in web too
3. Updates packages to latest
  • Loading branch information
GSingh01 authored Jul 30, 2022
1 parent 481f57e commit 8f9a584
Show file tree
Hide file tree
Showing 12 changed files with 398 additions and 234 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules
.expo/
.pnp.*
.yarn/*
!.yarn/patches
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
],
"scripts": {
"start": "yarn workspace b2c-sample start",
"android": "yarn workspace b2c-sample start --android",
"ios": "yarn workspace b2c-sample start --ios",
"web": "yarn workspace b2c-sample start --web",
"android": "yarn workspace b2c-sample android",
"ios": "yarn workspace b2c-sample ios",
"web": "yarn workspace b2c-sample web",
"test": "jest",
"watch": "jest --watch",
"compile": "yarn workspace ad-b2c-react-native compile",
Expand All @@ -28,9 +28,13 @@
"@babel/preset-typescript": "^7.16.5",
"@testing-library/jest-native": "^4.0.4",
"@testing-library/react-native": "^9.0.0",
"@types/babel__core": "^7.1.19",
"@types/jest": "^27.0.3",
"babel-jest": "^27.4.5",
"jest": "^27.4.7",
"metro-react-native-babel-preset": "^0.66.2"
},
"resolutions": {
"@types/react": "17.0.21"
}
}
4 changes: 3 additions & 1 deletion packages/b2c-sample/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React from "react";
import { Text } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
Expand Down Expand Up @@ -29,6 +29,8 @@ export default function App() {
passwordResetPolicy="B2C_1_PwdReset"
profileEditPolicy="B2C_1_ProfleEdit"
redirectURI={Linking.createURL("redirect")}
createNewTask={Constants.appOwnership === "expo"}
showInRecents={Constants.appOwnership === "expo"}
>
<Stack.Navigator>
<Stack.Screen name={RouteNames.home} component={Home} />
Expand Down
3 changes: 2 additions & 1 deletion packages/b2c-sample/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"package": "ad.b2c.react.native.sample"
},
"web": {
"favicon": "./assets/favicon.png"
"favicon": "./assets/favicon.png",
"build": { "babel": { "include": [ "ad-b2c-react-native" ] }}
},
"sdkVersion": "45.0.0"
},
Expand Down
22 changes: 13 additions & 9 deletions packages/b2c-sample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"name": "b2c-sample",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"start": "expo start --https --localhost",
"android": "expo start -a --localhost",
"ios": "expo start -i --localhost",
"web": "expo start -w --https --localhost",
"eject": "expo eject",
"cpToBin": "shx cp ../../node_modules/.bin/react-native* ./node_modules/.bin/",
"postinstall": "expo-yarn-workspaces postinstall & yarn cpToBin"
Expand All @@ -19,16 +19,16 @@
]
},
"dependencies": {
"@react-navigation/native": "^6.0.6",
"@react-navigation/native-stack": "^6.2.5",
"@react-navigation/native": "^6.0.11",
"@react-navigation/native-stack": "^6.7.0",
"ad-b2c-react-native": "2.0.0",
"expo": "^45.0.0",
"expo-constants": "~13.1.1",
"expo-dev-client": "~0.9.6",
"expo-dev-client": "~1.0.1",
"expo-linking": "~3.1.0",
"expo-modules-core": "~0.9.2",
"expo-status-bar": "~1.3.0",
"expo-web-browser": "~10.2.0",
"expo-web-browser": "~10.2.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-native": "0.68.2",
Expand All @@ -38,11 +38,15 @@
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@types/babel__core": "^7.1.19",
"@types/react": "~17.0.21",
"@types/react-native": "~0.67.6",
"expo-yarn-workspaces": "^1.6.0",
"expo-yarn-workspaces": "^2.0.0",
"shx": "^0.3.4",
"typescript": "~4.3.5"
},
"resolutions": {
"@types/react": "17.0.21"
},
"private": true
}
102 changes: 87 additions & 15 deletions packages/b2c-sample/src/Protected.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useNavigation } from "@react-navigation/core";
import { useFocusEffect, useNavigationState } from "@react-navigation/native";
import { useAuth, useToken } from "ad-b2c-react-native";
import React, { useEffect, useState } from "react";
import { Button, Text, StyleSheet, View } from "react-native";
import { WebBrowserAuthSessionResult } from "expo-web-browser";
import React, { useCallback, useEffect, useState } from "react";
import { Button, Text, StyleSheet, View, Platform } from "react-native";
import { RootStackNavigationProp, RouteNames } from "./navTypes";

const styles = StyleSheet.create({
Expand All @@ -17,25 +19,85 @@ export default function () {
const { getTokensAsync, isLoading, error, isAuthentic } = useToken();
const { logOutAsync, editProfileAsync, resetPasswordAsync } = useAuth();
const nav = useNavigation<RootStackNavigationProp>();
const [tokenRes, setTokenRes] = useState({
const [newUrl, setNewUrl] = useState("");
const [tokenRes, setTokenRes] = useState<
Awaited<ReturnType<typeof getTokensAsync>>
>({
access: "",
id: "",
expiresOn: 0,
url: "",
error: "",
isAuthentic: false,
});
const routesLength = useNavigationState((state) => {
return state.routes.length;
});

useEffect(() => {
getTokensAsync().then((x) => {
setTokenRes(x);
});
}, [isAuthentic]);
useFocusEffect(
useCallback(() => {
setResetPasswordError("");
setNewUrl("");
getTokensAsync().then((x) => {
if (x.error) {
nav.replace(RouteNames.home);
}
setTokenRes(x);
if (x.url) {
setNewUrl(x.url);
}
});
}, [isAuthentic, routesLength])
);

function browserResultHandler(x: WebBrowserAuthSessionResult) {
if (x.type === "success") {
setNewUrl(x.url);
}
}

const [resetPasswordError, setResetPasswordError] = useState("");
useEffect(() => {
if (error.includes("AADB2C90118")) {
setTimeout(() => {
resetPasswordAsync();
}, 1);
if (newUrl) {
if (newUrl.includes("AADB2C90118")) {
setTimeout(() => {
resetPasswordAsync().catch((ex) => {
setResetPasswordError(ex.toString());
});
}, 1);
}

if (Platform.OS === "web") {
const url = new URL(newUrl);
const searchParams = url.searchParams;
const code = searchParams.get("code");
if (code) {
nav.navigate(RouteNames.redirect, {
code: code,
state: searchParams.get("state") || "",
error: "",
error_description: "",
});
}
}
}
}, [error]);
}, [newUrl]);

if (resetPasswordError) {
return (
<View style={styles.container}>
<Text>Please manually press reset password</Text>
<Button
title="Reset password"
onPress={() => resetPasswordAsync().then(browserResultHandler)}
/>
<Text>
Reason:
{resetPasswordError}
</Text>
</View>
);
}

if (error) {
return <Text>Error: {error}</Text>;
Expand All @@ -45,15 +107,25 @@ export default function () {
return <Text>Loading...</Text>;
}

if (!isAuthentic) {
return <Text>Could not authenticate</Text>;
}

const { access, id, expiresOn } = tokenRes;
return (
<View style={styles.container}>
<Button
title="Logout"
onPress={() => logOutAsync().then(() => nav.navigate(RouteNames.home))}
/>
<Button title="Edit profile" onPress={editProfileAsync} />
<Button title="Reset password" onPress={resetPasswordAsync} />
<Button
title="Reset password"
onPress={() => resetPasswordAsync().then(browserResultHandler)}
/>
<Button
title="Edit profile"
onPress={() => editProfileAsync().then(browserResultHandler)}
/>
<Text>Protected component example </Text>
<Text>accessToken: {access}</Text>
<Text>idToken: {id}</Text>
Expand Down
10 changes: 5 additions & 5 deletions packages/b2c-sample/src/Redirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ export default function () {
const nav = useNavigation<RootStackNavigationProp>();
const route = useRoute<AuthScreenRouteProp>();
useEffect(() => {
const { code, state } = route.params;
handleRedirectAsync(code, state)
const params = route.params;
handleRedirectAsync(params?.code, params?.state)
.then(() => {
if (nav.canGoBack()) {
nav.goBack();
} else {
nav.navigate(RouteNames.home);
nav.replace(RouteNames.home);
}
})
.catch((ex) => {
.catch((ex: any) => {
console.log(ex);
nav.navigate(RouteNames.home);
nav.replace(RouteNames.home);
});
}, [route.params]);
return <Text>Authenticating</Text>;
Expand Down
78 changes: 67 additions & 11 deletions packages/lib/src/hooks/useToken.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,51 @@ describe("useToken", () => {
type: "success",
});

await waitFor(result.current.getTokensAsync);
const expectedRes = {
expiresOn: 0,
access: "",
id: "",
error: null,
isAuthentic: false,
url: undefined,
};

expect(result.current.error).toBe("");
await waitFor(() =>
expect(result.current.getTokensAsync()).resolves.toStrictEqual(
expectedRes
)
);
expect(result.current.error).toBe(null);
expect(result.current.isLoading).toBe(false);
});

it("sets isLoading false and error corrects when signInAsync receives password reset redirect", async () => {
it("sets isLoading false and url correctly when signInAsync receives password reset redirect", async () => {
const { result } = renderHook(() => useToken());
const { result: contextRes } = renderHook(() =>
useContext(AuthContext)
);
contextRes.current.signInAsync.mockResolvedValueOnce({
const signInResult = {
type: "success",
url: "AADB2C90118",
});
url: "https://AADB2C90118",
};
contextRes.current.signInAsync.mockResolvedValueOnce(signInResult);

await waitFor(result.current.getTokensAsync);
const expectedRes = {
expiresOn: 0,
access: "",
id: "",
error: null,
isAuthentic: false,
url: signInResult.url,
};

await waitFor(() =>
expect(result.current.getTokensAsync()).resolves.toStrictEqual(
expectedRes
)
);

expect(result.current.error).toBe("Resetting password AADB2C90118");
expect(result.current.error).toBe(null);
expect(result.current.isLoading).toBe(false);
});

Expand Down Expand Up @@ -128,16 +154,44 @@ describe("useToken", () => {
const { result: contextRes } = renderHook(() =>
useContext(AuthContext)
);
contextRes.current.signInAsync.mockResolvedValueOnce({
const signInResult = {
type: "success",
});
url: "testUrl",
};
contextRes.current.signInAsync.mockResolvedValueOnce(signInResult);

const expectedRes = {
expiresOn: 0,
access: "",
id: "",
error: null,
isAuthentic: false,
url: signInResult.url,
};
await waitFor(() =>
expect(result.current.getTokensAsync()).resolves.toStrictEqual(
expectedRes
)
);
});

it("returns handleSignInAsync result when signInResponse.url is not defined", async () => {
const { result } = renderHook(() => useToken());
const { result: contextRes } = renderHook(() =>
useContext(AuthContext)
);
const signInResult = {
type: "success",
};
contextRes.current.signInAsync.mockResolvedValueOnce(signInResult);

const expectedRes = {
expiresOn: 0,
access: "",
id: "",
error: "",
error: null,
isAuthentic: false,
url: undefined,
};
await waitFor(() =>
expect(result.current.getTokensAsync()).resolves.toStrictEqual(
Expand Down Expand Up @@ -247,6 +301,7 @@ describe("useToken", () => {
...data,
error: null,
isAuthentic: true,
url: "",
};
await waitFor(() =>
expect(result.current.getTokensAsync()).resolves.toStrictEqual(
Expand All @@ -270,6 +325,7 @@ describe("useToken", () => {
id: "",
error,
isAuthentic: true, //because exception doesnot mean current token is invalid
url: "",
};
await waitFor(() =>
expect(result.current.getTokensAsync()).resolves.toStrictEqual(data)
Expand Down
Loading

0 comments on commit 8f9a584

Please sign in to comment.