Skip to content

Commit

Permalink
feat(browser,react,vue): enable userinfo endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
charIeszhao committed Sep 13, 2022
1 parent 3ed3965 commit cae6eff
Show file tree
Hide file tree
Showing 10 changed files with 78 additions and 27 deletions.
10 changes: 5 additions & 5 deletions packages/browser-sample/src/pages/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const Home = async (app, logtoClient) => {
renderButton(container, { isAuthenticated, onClickSignIn, onClickSignOut });

if (isAuthenticated) {
const idTokenClaims = await logtoClient.getIdTokenClaims();
renderTable(container, { idTokenClaims });
const userInfo = await logtoClient.fetchUserInfo();
renderTable(container, { userInfo });
}
})();

Expand Down Expand Up @@ -51,7 +51,7 @@ const renderButton = (container, states) => {
};

const renderTable = (container, states) => {
const { idTokenClaims } = states;
const { userInfo } = states;

// Generate display table for ID token claims
const table = document.createElement('table');
Expand All @@ -67,12 +67,12 @@ const renderTable = (container, states) => {
table.append(thead);

const tbody = document.createElement('tbody');
for (const [key, value] of Object.entries(idTokenClaims)) {
for (const [key, value] of Object.entries(userInfo)) {
const tr = document.createElement('tr');
const tdName = document.createElement('td');
const tdValue = document.createElement('td');
tdName.innerHTML = key;
tdValue.innerHTML = value;
tdValue.innerHTML = typeof value === 'string' ? value : JSON.stringify(value);
tr.append(tdName, tdValue);
tbody.append(tr);
}
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type {
LogtoErrorCode,
LogtoConfig,
LogtoClientErrorCode,
UserInfoResponse,
} from '@logto/client';
export { LogtoError, OidcError, Prompt, LogtoRequestError, LogtoClientError } from '@logto/client';

Expand Down
18 changes: 9 additions & 9 deletions packages/react-sample/src/pages/Home/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { useLogto, IdTokenClaims } from '@logto/react';
import { useLogto, UserInfoResponse } from '@logto/react';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';

import { baseUrl, redirectUrl } from '../../consts';
import * as styles from './index.module.scss';

const Home = () => {
const { isAuthenticated, signIn, signOut, getIdTokenClaims } = useLogto();
const [idTokenClaims, setIdTokenClaims] = useState<IdTokenClaims>();
const { isAuthenticated, signIn, signOut, fetchUserInfo } = useLogto();
const [user, setUser] = useState<UserInfoResponse>();

useEffect(() => {
(async () => {
if (isAuthenticated) {
const claims = await getIdTokenClaims();
setIdTokenClaims(claims);
const userInfo = await fetchUserInfo();
setUser(userInfo);
}
})();
}, [getIdTokenClaims, isAuthenticated]);
}, [setUser, fetchUserInfo, isAuthenticated]);

return (
<div className={styles.container}>
Expand All @@ -41,7 +41,7 @@ const Home = () => {
Sign Out
</button>
)}
{isAuthenticated && idTokenClaims && (
{isAuthenticated && user && (
<>
<table className={styles.table}>
<thead>
Expand All @@ -51,10 +51,10 @@ const Home = () => {
</tr>
</thead>
<tbody>
{Object.entries(idTokenClaims).map(([key, value]) => (
{Object.entries(user).map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{value}</td>
<td>{typeof value === 'string' ? value : JSON.stringify(value)}</td>
</tr>
))}
</tbody>
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/hooks/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,12 @@ describe('useLogto', () => {
});

await waitFor(() => {
const { signIn, signOut, getAccessToken, getIdTokenClaims } = result.current;
const { signIn, signOut, fetchUserInfo, getAccessToken, getIdTokenClaims } = result.current;

expect(result.error).toBeUndefined();
expect(signIn).not.toBeUndefined();
expect(signOut).not.toBeUndefined();
expect(fetchUserInfo).not.toBeUndefined();
expect(getAccessToken).not.toBeUndefined();
expect(getIdTokenClaims).not.toBeUndefined();
});
Expand Down
20 changes: 19 additions & 1 deletion packages/react/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IdTokenClaims } from '@logto/browser';
import { IdTokenClaims, UserInfoResponse } from '@logto/browser';
import { useCallback, useContext, useEffect, useRef } from 'react';

import { LogtoContext, throwContextError } from '../context';
Expand All @@ -7,6 +7,7 @@ type Logto = {
isAuthenticated: boolean;
isLoading: boolean;
error?: Error;
fetchUserInfo: () => Promise<UserInfoResponse | undefined>;
getAccessToken: (resource?: string) => Promise<string | undefined>;
getIdTokenClaims: () => Promise<IdTokenClaims | undefined>;
signIn: (redirectUri: string) => Promise<void>;
Expand Down Expand Up @@ -142,6 +143,22 @@ const useLogto = (): Logto => {
[logtoClient, setLoadingState, handleError]
);

const fetchUserInfo = useCallback(async () => {
if (!logtoClient) {
return throwContextError();
}

try {
setLoadingState(true);

return await logtoClient.fetchUserInfo();
} catch (error: unknown) {
handleError(error, 'Unexpected error occurred while fetching user info.');
} finally {
setLoadingState(false);
}
}, [logtoClient, setLoadingState, handleError]);

const getAccessToken = useCallback(
async (resource?: string) => {
if (!logtoClient) {
Expand Down Expand Up @@ -183,6 +200,7 @@ const useLogto = (): Logto => {
error,
signIn,
signOut,
fetchUserInfo,
getAccessToken,
getIdTokenClaims,
};
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export type { LogtoContextProps } from './context';
export type {
LogtoConfig,
IdTokenClaims,
UserInfoResponse,
LogtoErrorCode,
LogtoClientErrorCode,
} from '@logto/browser';
Expand Down
18 changes: 10 additions & 8 deletions packages/vue-sample/src/views/HomeView.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<script setup lang="ts">
import { useLogto, type IdTokenClaims } from "@logto/vue";
import { useLogto, type UserInfoResponse } from "@logto/vue";
import { RouterLink } from "vue-router";
import { ref, watchEffect } from "vue";
import { baseUrl, redirectUrl } from "../consts";
const { isAuthenticated, getIdTokenClaims, signIn, signOut } = useLogto();
const idTokenClaims = ref<IdTokenClaims>();
const { isAuthenticated, fetchUserInfo, signIn, signOut } = useLogto();
const user = ref<UserInfoResponse>();
const onClickSignIn = () => {
void signIn(redirectUrl);
Expand All @@ -17,8 +17,8 @@ const onClickSignOut = () => {
watchEffect(async () => {
if (isAuthenticated.value) {
const claims = await getIdTokenClaims();
idTokenClaims.value = claims;
const userInfo = await fetchUserInfo();
user.value = userInfo;
}
});
</script>
Expand All @@ -28,7 +28,7 @@ watchEffect(async () => {
<h3>Logto Vue Sample</h3>
<button v-if="!isAuthenticated" @click="onClickSignIn">Sign In</button>
<button v-else @click="onClickSignOut">Sign Out</button>
<div v-if="isAuthenticated && idTokenClaims">
<div v-if="isAuthenticated && user">
<table class="table">
<thead>
<tr>
Expand All @@ -37,9 +37,11 @@ watchEffect(async () => {
</tr>
</thead>
<tbody>
<tr v-for="(value, key) in idTokenClaims" v-bind:key="key">
<tr v-for="(value, key) in user" v-bind:key="key">
<td>{{ key }}</td>
<td>{{ value }}</td>
<td>
{{ typeof value === "string" ? value : JSON.stringify(value) }}
</td>
</tr>
</tbody>
</table>
Expand Down
13 changes: 11 additions & 2 deletions packages/vue/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,16 @@ describe('useLogto', () => {
});

test('should inject Logto context data', () => {
const { isAuthenticated, isLoading, error, signIn, signOut, getAccessToken, getIdTokenClaims } =
useLogto();
const {
isAuthenticated,
isLoading,
error,
signIn,
signOut,
getAccessToken,
getIdTokenClaims,
fetchUserInfo,
} = useLogto();

expect(isAuthenticated.value).toBe(false);
expect(isLoading.value).toBe(false);
Expand All @@ -96,6 +104,7 @@ describe('useLogto', () => {
expect(signOut).toBeInstanceOf(Function);
expect(getAccessToken).toBeInstanceOf(Function);
expect(getIdTokenClaims).toBeInstanceOf(Function);
expect(fetchUserInfo).toBeInstanceOf(Function);
});

test('should return error when getAccessToken fails', async () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/vue/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import LogtoClient, { IdTokenClaims, LogtoConfig } from '@logto/browser';
import LogtoClient, { IdTokenClaims, LogtoConfig, UserInfoResponse } from '@logto/browser';
import { App, inject, readonly, Ref, watchEffect } from 'vue';

import { logtoInjectionKey, contextInjectionKey } from './consts';
Expand All @@ -8,6 +8,7 @@ import { createPluginMethods } from './plugin';
export type {
LogtoConfig,
IdTokenClaims,
UserInfoResponse,
LogtoErrorCode,
LogtoClientErrorCode,
} from '@logto/browser';
Expand All @@ -22,6 +23,7 @@ type Logto = {
isAuthenticated: Readonly<Ref<boolean>>;
isLoading: Readonly<Ref<boolean>>;
error: Readonly<Ref<Error | undefined>>;
fetchUserInfo: () => Promise<UserInfoResponse | undefined>;
getAccessToken: (resource?: string) => Promise<string | undefined>;
getIdTokenClaims: () => Promise<IdTokenClaims | undefined>;
signIn: (redirectUri: string) => Promise<void>;
Expand Down
17 changes: 17 additions & 0 deletions packages/vue/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ export const createPluginMethods = (context: Context) => {
}
};

const fetchUserInfo = async () => {
if (!logtoClient.value) {
return throwContextError();
}

try {
setLoading(true);

return await logtoClient.value.fetchUserInfo();
} catch (error: unknown) {
setError(error, 'Unexpected error occurred while fetching user info.');
} finally {
setLoading(false);
}
};

const getAccessToken = async (resource?: string) => {
if (!logtoClient.value) {
return throwContextError();
Expand Down Expand Up @@ -85,6 +101,7 @@ export const createPluginMethods = (context: Context) => {
return {
signIn,
signOut,
fetchUserInfo,
getAccessToken,
getIdTokenClaims,
handleSignInCallback,
Expand Down

0 comments on commit cae6eff

Please sign in to comment.