@khester/create-dynamics-app 2.6.6 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifacts/registry.d.ts.map +1 -1
- package/dist/artifacts/registry.js +43 -6
- package/dist/artifacts/registry.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/scaffold.d.ts +1 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +2 -0
- package/dist/scaffold.js.map +1 -1
- package/dist/utils/copyTemplate.d.ts.map +1 -1
- package/dist/utils/copyTemplate.js +26 -7
- package/dist/utils/copyTemplate.js.map +1 -1
- package/package.json +2 -2
- package/templates/teams-app/README.md +32 -9
- package/templates/teams-app/{appPackage/manifest.json → _variants/tab.basic/appPackage/manifest.template.json} +6 -5
- package/templates/teams-app/_variants/tab.dashboard/appPackage/manifest.template.json +38 -0
- package/templates/teams-app/_variants/tab.dashboard/auth.html +15 -0
- package/templates/teams-app/_variants/tab.dashboard/env.local.template +3 -0
- package/templates/teams-app/_variants/tab.dashboard/src/App.tsx +49 -0
- package/templates/teams-app/_variants/tab.dashboard/src/auth-bridge.ts +52 -0
- package/templates/teams-app/_variants/tab.dashboard/src/components/Dashboard.tsx +33 -0
- package/templates/teams-app/_variants/tab.dashboard/src/components/widgets/StatsWidget.tsx +25 -0
- package/templates/teams-app/_variants/tab.dashboard/src/components/widgets/TasksWidget.tsx +20 -0
- package/templates/teams-app/_variants/tab.dashboard/src/hooks/useHashRoute.ts +16 -0
- package/templates/teams-app/_variants/tab.dashboard/src/hooks/useTabConfig.ts +70 -0
- package/templates/teams-app/_variants/tab.dashboard/src/hooks/useTeamsAuth.ts +121 -0
- package/templates/teams-app/_variants/tab.dashboard/src/pages/Config.tsx +118 -0
- package/templates/teams-app/_variants/tab.dashboard/src/pages/Remove.tsx +12 -0
- package/templates/teams-app/_variants/tab.dashboard/src/vite-env.d.ts +10 -0
- package/templates/teams-app/_variants/tab.dashboard/vite.config.ts +19 -0
- package/templates/teams-app/_variants/tab.sso/appPackage/manifest.template.json +31 -0
- package/templates/teams-app/_variants/tab.sso/auth.html +15 -0
- package/templates/teams-app/_variants/tab.sso/env.local.template +3 -0
- package/templates/teams-app/_variants/tab.sso/src/App.tsx +57 -0
- package/templates/teams-app/_variants/tab.sso/src/auth-bridge.ts +52 -0
- package/templates/teams-app/_variants/tab.sso/src/hooks/useTeamsAuth.ts +121 -0
- package/templates/teams-app/_variants/tab.sso/src/vite-env.d.ts +10 -0
- package/templates/teams-app/_variants/tab.sso/vite.config.ts +19 -0
- package/templates/teams-app/appPackage/color.png +0 -0
- package/templates/teams-app/appPackage/outline.png +0 -0
- package/templates/teams-app/gitignore +2 -0
- package/templates/teams-app/package.json +1 -1
- /package/templates/teams-app/{src → _variants/tab.basic/src}/App.tsx +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { app, authentication } from '@microsoft/teams-js';
|
|
3
|
+
import { PublicClientApplication, type AccountInfo } from '@azure/msal-browser';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Teams-first auth: try Teams SSO (`authentication.getAuthToken()`), fall back to
|
|
7
|
+
* MSAL. Inside the Teams iframe, interactive sign-in must go through the auth
|
|
8
|
+
* bridge (`auth.html`) — `loginPopup` is blocked there; outside Teams (plain
|
|
9
|
+
* browser tab) MSAL redirect works directly.
|
|
10
|
+
*
|
|
11
|
+
* VITE_CLIENT_ID / VITE_TENANT_ID come from `.env.local` (see env.local.template);
|
|
12
|
+
* `teams-app-manager.sh identity` writes them after registering the Entra app.
|
|
13
|
+
*/
|
|
14
|
+
const clientId = import.meta.env.VITE_CLIENT_ID ?? '';
|
|
15
|
+
const tenantId = import.meta.env.VITE_TENANT_ID ?? 'common';
|
|
16
|
+
|
|
17
|
+
const msalInstance = new PublicClientApplication({
|
|
18
|
+
auth: {
|
|
19
|
+
clientId,
|
|
20
|
+
authority: `https://login.microsoftonline.com/${tenantId}`,
|
|
21
|
+
redirectUri: window.location.origin,
|
|
22
|
+
},
|
|
23
|
+
cache: { cacheLocation: 'localStorage' },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export interface AuthState {
|
|
27
|
+
token: string | null;
|
|
28
|
+
account: AccountInfo | null;
|
|
29
|
+
inTeams: boolean;
|
|
30
|
+
loading: boolean;
|
|
31
|
+
error: Error | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function useTeamsAuth(): AuthState & { login: () => Promise<void> } {
|
|
35
|
+
const [state, setState] = useState<AuthState>({
|
|
36
|
+
token: null,
|
|
37
|
+
account: null,
|
|
38
|
+
inTeams: false,
|
|
39
|
+
loading: true,
|
|
40
|
+
error: null,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const getToken = useCallback(async () => {
|
|
44
|
+
// 1. Teams SSO — works once the app registration exposes api://{host}/{clientId}
|
|
45
|
+
// with the Teams client ids pre-authorized.
|
|
46
|
+
try {
|
|
47
|
+
await app.initialize();
|
|
48
|
+
const ssoToken = await authentication.getAuthToken();
|
|
49
|
+
setState({ token: ssoToken, account: null, inTeams: true, loading: false, error: null });
|
|
50
|
+
return;
|
|
51
|
+
} catch {
|
|
52
|
+
// Not in Teams, or SSO not consented yet — fall through to MSAL.
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. MSAL silent — a previous interactive sign-in left an account in the cache.
|
|
56
|
+
try {
|
|
57
|
+
await msalInstance.initialize();
|
|
58
|
+
const redirect = await msalInstance.handleRedirectPromise().catch(() => null);
|
|
59
|
+
const account = redirect?.account ?? msalInstance.getAllAccounts()[0];
|
|
60
|
+
if (account) {
|
|
61
|
+
const result = await msalInstance.acquireTokenSilent({
|
|
62
|
+
scopes: ['User.Read'],
|
|
63
|
+
account,
|
|
64
|
+
});
|
|
65
|
+
setState({
|
|
66
|
+
token: result.accessToken,
|
|
67
|
+
account: result.account,
|
|
68
|
+
inTeams: false,
|
|
69
|
+
loading: false,
|
|
70
|
+
error: null,
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
setState((prev) => ({ ...prev, loading: false }));
|
|
75
|
+
} catch (error) {
|
|
76
|
+
setState((prev) => ({ ...prev, loading: false, error: error as Error }));
|
|
77
|
+
}
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
80
|
+
/** Interactive sign-in. In Teams → auth bridge popup; in a browser → MSAL redirect. */
|
|
81
|
+
const login = useCallback(async () => {
|
|
82
|
+
if (!clientId) {
|
|
83
|
+
setState((prev) => ({
|
|
84
|
+
...prev,
|
|
85
|
+
error: new Error('VITE_CLIENT_ID is not set — copy env.local.template to .env.local'),
|
|
86
|
+
}));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
let inTeams = false;
|
|
90
|
+
try {
|
|
91
|
+
await app.initialize();
|
|
92
|
+
inTeams = true;
|
|
93
|
+
} catch {
|
|
94
|
+
// Plain browser tab.
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
if (inTeams) {
|
|
99
|
+
// Teams opens auth.html in a real window where MSAL's redirect flow is
|
|
100
|
+
// allowed; the bridge calls notifySuccess/notifyFailure when done.
|
|
101
|
+
await authentication.authenticate({
|
|
102
|
+
url: `${window.location.origin}/auth.html`,
|
|
103
|
+
width: 600,
|
|
104
|
+
height: 535,
|
|
105
|
+
});
|
|
106
|
+
await getToken();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
await msalInstance.initialize();
|
|
110
|
+
await msalInstance.loginRedirect({ scopes: ['User.Read'] });
|
|
111
|
+
} catch (error) {
|
|
112
|
+
setState((prev) => ({ ...prev, error: error as Error }));
|
|
113
|
+
}
|
|
114
|
+
}, [getToken]);
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
void getToken();
|
|
118
|
+
}, [getToken]);
|
|
119
|
+
|
|
120
|
+
return { ...state, login };
|
|
121
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import { fileURLToPath, URL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
// Teams tabs must be served over HTTPS; `vite --https` or a tunnel in dev.
|
|
8
|
+
server: { port: 3000 },
|
|
9
|
+
build: {
|
|
10
|
+
outDir: 'dist',
|
|
11
|
+
rollupOptions: {
|
|
12
|
+
// MPA: the tab plus the auth bridge Teams opens for interactive sign-in.
|
|
13
|
+
input: {
|
|
14
|
+
main: fileURLToPath(new URL('./index.html', import.meta.url)),
|
|
15
|
+
auth: fileURLToPath(new URL('./auth.html', import.meta.url)),
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
Binary file
|
|
Binary file
|
|
File without changes
|