Skip to content

Commit 83e17d1

Browse files
committed
init commit
0 parents  commit 83e17d1

24 files changed

+7124
-0
lines changed

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
6+
# generated directory
7+
**/generated
8+
9+
# output directory
10+
/out
11+
12+
# msbuild output directories
13+
/bin
14+
/obj

UserPresencesControl/App.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as React from "react";
2+
import { IInputs } from "./generated/ManifestTypes";
3+
import { AppProvider } from "./AppContext";
4+
import { AuthProvider } from "./AuthProvider";
5+
import { Login } from "./components/Login";
6+
import { Users } from "./components/Users";
7+
import { Message } from "./components/Message";
8+
9+
export interface IAppProps {
10+
componentContext: ComponentFramework.Context<IInputs>
11+
authProvider: AuthProvider
12+
}
13+
14+
export const App: React.FC<IAppProps> = (props: IAppProps) => {
15+
return (
16+
<AppProvider {...props}>
17+
<Message></Message>
18+
<Login></Login>
19+
<Users></Users>
20+
</AppProvider>
21+
);
22+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import * as React from "react";
2+
import { createContext, useReducer, useContext, Dispatch } from "react";
3+
import { IInputs } from "./generated/ManifestTypes";
4+
import { rootReducer, IRootState } from "./store";
5+
import { UserlistActions } from "./store/userlist/actions";
6+
import { AuthActions, AuthActionTypes } from "./store/auth/actions";
7+
import { AuthProvider, IIdentity } from "./AuthProvider";
8+
import { IAppProps } from "./App";
9+
10+
const initialState: IRootState = {
11+
auth: {
12+
authenticated: false
13+
},
14+
userlist: {
15+
users: []
16+
}
17+
};
18+
19+
export interface IAppContext {
20+
authProvider: AuthProvider,
21+
componentContext: ComponentFramework.Context<IInputs>,
22+
state: IRootState,
23+
dispatch: Dispatch<UserlistActions | AuthActions>
24+
}
25+
26+
export const AppContext = createContext<IAppContext>({} as IAppContext);
27+
28+
export const AppProvider: React.FC<IAppProps> = (props) => {
29+
const [state, dispatch] = useReducer(rootReducer, initialState);
30+
const { authProvider, componentContext } = props;
31+
return (
32+
<AppContext.Provider value={{ authProvider, componentContext, state, dispatch }} >
33+
{props.children}
34+
</AppContext.Provider>
35+
);
36+
};
37+
38+
export const useAppContext = () => useContext(AppContext);
39+
40+
export const useAuthContext = () => {
41+
const { authProvider, state, dispatch } = useAppContext();
42+
const { auth } = state;
43+
44+
const login = () => {
45+
authProvider.login().then(() => {
46+
dispatch({
47+
type: AuthActionTypes.LOGIN_SUCCESS
48+
});
49+
}, (err) => {
50+
dispatch({
51+
type: AuthActionTypes.LOGIN_FAILURE,
52+
payload: err
53+
});
54+
});
55+
};
56+
57+
const logout = () => {
58+
authProvider.logout();
59+
dispatch({
60+
type: AuthActionTypes.LOGOUT
61+
});
62+
};
63+
64+
const getAccessToken = async (): Promise<string> => {
65+
return authProvider.getAccessToken();
66+
};
67+
68+
const handleAuthentication = async () => {
69+
const account = authProvider.getAccount();
70+
if (authProvider.getAccount()) {
71+
await authProvider.getIdentity().then((identity?: IIdentity) => {
72+
if (identity) {
73+
dispatch({
74+
type: AuthActionTypes.SET_IDENTITY,
75+
payload: identity
76+
});
77+
}
78+
}, (err) => {
79+
dispatch({
80+
type: AuthActionTypes.AUTH_FAILURE,
81+
payload: err
82+
});
83+
});
84+
}
85+
return account;
86+
};
87+
88+
return {
89+
authenticated: auth.authenticated,
90+
identity: auth.identity,
91+
login,
92+
logout,
93+
getAccessToken,
94+
handleAuthentication
95+
};
96+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { UserAgentApplication } from 'msal';
2+
import { IConfig } from "./interfaces/IConfig";
3+
import { getUserDetails } from './GraphService';
4+
5+
export interface IIdentity {
6+
displayName: string,
7+
email: string
8+
}
9+
10+
export class AuthProvider {
11+
private _userAgentApplication: UserAgentApplication;
12+
13+
constructor(private _config: IConfig) {
14+
this._userAgentApplication = new UserAgentApplication({
15+
auth: {
16+
clientId: _config.appId,
17+
redirectUri: _config.appRedirectUrl,
18+
authority: _config.appAuthority,
19+
postLogoutRedirectUri: window.location.href
20+
},
21+
cache: {
22+
cacheLocation: "sessionStorage",
23+
storeAuthStateInCookie: true
24+
}
25+
})
26+
}
27+
28+
async login() {
29+
await this._userAgentApplication.loginPopup({
30+
scopes: this._config.appScopes,
31+
prompt: "select_account"
32+
});
33+
}
34+
35+
logout() {
36+
this._userAgentApplication.logout();
37+
}
38+
39+
getAccount() {
40+
return this._userAgentApplication.getAccount();
41+
}
42+
43+
async getIdentity(): Promise<IIdentity | undefined> {
44+
const accessToken = await this.getAccessToken();
45+
if (accessToken) {
46+
const user = await getUserDetails(accessToken);
47+
return {
48+
displayName: user.displayName,
49+
email: user.mail || user.userPrincipalName
50+
};
51+
}
52+
}
53+
54+
async getAccessToken(): Promise<string> {
55+
try {
56+
const silentResponse = await this._userAgentApplication.acquireTokenSilent({
57+
scopes: this._config.appScopes
58+
});
59+
return silentResponse.accessToken;
60+
} catch (err) {
61+
if (this.isInteractionRequired(err)) {
62+
const interactiveResponse = await this._userAgentApplication.acquireTokenPopup({
63+
scopes: this._config.appScopes
64+
});
65+
return interactiveResponse.accessToken;
66+
} else {
67+
throw err;
68+
}
69+
}
70+
}
71+
72+
private isInteractionRequired(error: Error): boolean {
73+
if (error.message?.length <= 0) {
74+
return false;
75+
}
76+
77+
return (
78+
error.message.indexOf('consent_required') > -1 ||
79+
error.message.indexOf('interaction_required') > -1 ||
80+
error.message.indexOf('login_required') > -1
81+
);
82+
}
83+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<manifest>
3+
<control namespace="TRH" constructor="UserPresencesControl" version="0.0.1" display-name-key="Control_Display_Key" description-key="Control_Desc_Key" control-type="standard">
4+
<data-set name="dataSet" display-name-key="Dataset_Display_Key" cds-data-set-options="displayCommandBar:true;displayViewSelector:true;displayQuickFindSearch:true">
5+
<property-set name="fullname" display-name-key="fullname_Display_Key" description-key="fullname_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />
6+
<property-set name="jobTitle" display-name-key="jobTitle_Display_Key" description-key="jobTitle_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />
7+
</data-set>
8+
<property name="azureADAppId" display-name-key="AzureADAppId_Display_Key" description-key="AzureADAppId_Desc_Key" of-type="SingleLine.Text" usage="input" required="true" />
9+
<property name="azureADAppAuthority" display-name-key="AzureADAppAuthority_Display_Key" description-key="AzureADAppAuthority_Desc_Key" of-type="SingleLine.Text" usage="input" required="true" />
10+
<property name="azureADAppRedirectUrl" display-name-key="AzureADAppRedirectUrl_Display_Key" description-key="AzureADAppRedirectUrl_Desc_Key" of-type="SingleLine.Text" usage="input" required="true" />
11+
<resources>
12+
<code path="index.ts" order="1"/>
13+
<resx path="strings/UserPresencesControl.1033.resx" version="1.0.0" />
14+
</resources>
15+
<feature-usage>
16+
<uses-feature name="Utility" required="true" />
17+
</feature-usage>
18+
</control>
19+
</manifest>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import graph = require("@microsoft/microsoft-graph-client");
2+
3+
export const getAuthenticatedClient = (accessToken: string) => {
4+
return graph.Client.init({
5+
authProvider: (done: any) => {
6+
done(null, accessToken);
7+
}
8+
});
9+
}
10+
11+
// Doc reference: https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http
12+
export const getUserDetails = async(accessToken: string) => {
13+
const client = getAuthenticatedClient(accessToken);
14+
const user = await client.api("/me")
15+
.select(["department","displayName","id","mail","userPrincipalName"])
16+
.get();
17+
return user;
18+
}
19+
20+
// Doc reference: https://docs.microsoft.com/en-us/graph/api/cloudcommunications-getpresencesbyuserid?view=graph-rest-beta
21+
export const getPresencesByUserId = async(accessToken: string, ids: string[]) => {
22+
const client = getAuthenticatedClient(accessToken);
23+
const res = await client.api("/communications/getPresencesByUserId")
24+
.version("beta")
25+
.post({ids});
26+
return res;
27+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as React from "react";
2+
import { useEffect } from "react";
3+
import { ActionButton, Stack } from "office-ui-fabric-react";
4+
import { useAuthContext } from "../AppContext";
5+
6+
export const Login: React.FC = () => {
7+
const { authenticated, identity, login, logout, handleAuthentication } = useAuthContext();
8+
9+
useEffect(() => {
10+
handleAuthentication();
11+
},[authenticated])
12+
13+
return (
14+
<Stack verticalAlign={"center"} horizontalAlign={"end"} horizontal tokens={{ childrenGap: 4 }}>
15+
{authenticated && (
16+
<ActionButton iconProps={{ iconName: "Signout" }} onClick={logout}>{identity?.displayName || ""}</ActionButton>
17+
)}
18+
{!authenticated && (
19+
<ActionButton iconProps={{ iconName: "Signin" }} onClick={login}>Sign in</ActionButton>
20+
)}
21+
</Stack>
22+
);
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as React from "react";
2+
import { MessageBar, MessageBarType } from "office-ui-fabric-react";
3+
import { useAppContext } from "../AppContext";
4+
5+
export const Message: React.FC = () => {
6+
const { state } = useAppContext();
7+
const { auth, userlist } = state;
8+
9+
const message = auth.error ?? userlist.error;
10+
if (message) console.log(message.detail);
11+
12+
return (
13+
<div>
14+
{message &&
15+
<MessageBar messageBarType={MessageBarType.error}>{message.message}</MessageBar>
16+
}
17+
</div>
18+
);
19+
}

0 commit comments

Comments
 (0)