msteams-mcp 0.2.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.
Files changed (80) hide show
  1. package/README.md +229 -0
  2. package/dist/__fixtures__/api-responses.d.ts +228 -0
  3. package/dist/__fixtures__/api-responses.js +217 -0
  4. package/dist/api/chatsvc-api.d.ts +171 -0
  5. package/dist/api/chatsvc-api.js +459 -0
  6. package/dist/api/csa-api.d.ts +44 -0
  7. package/dist/api/csa-api.js +148 -0
  8. package/dist/api/index.d.ts +6 -0
  9. package/dist/api/index.js +6 -0
  10. package/dist/api/substrate-api.d.ts +50 -0
  11. package/dist/api/substrate-api.js +305 -0
  12. package/dist/auth/crypto.d.ts +32 -0
  13. package/dist/auth/crypto.js +66 -0
  14. package/dist/auth/index.d.ts +6 -0
  15. package/dist/auth/index.js +6 -0
  16. package/dist/auth/session-store.d.ts +82 -0
  17. package/dist/auth/session-store.js +136 -0
  18. package/dist/auth/token-extractor.d.ts +69 -0
  19. package/dist/auth/token-extractor.js +330 -0
  20. package/dist/browser/auth.d.ts +43 -0
  21. package/dist/browser/auth.js +232 -0
  22. package/dist/browser/context.d.ts +40 -0
  23. package/dist/browser/context.js +121 -0
  24. package/dist/browser/session.d.ts +34 -0
  25. package/dist/browser/session.js +92 -0
  26. package/dist/constants.d.ts +54 -0
  27. package/dist/constants.js +72 -0
  28. package/dist/index.d.ts +8 -0
  29. package/dist/index.js +12 -0
  30. package/dist/research/explore.d.ts +11 -0
  31. package/dist/research/explore.js +267 -0
  32. package/dist/research/search-research.d.ts +17 -0
  33. package/dist/research/search-research.js +317 -0
  34. package/dist/server.d.ts +64 -0
  35. package/dist/server.js +291 -0
  36. package/dist/teams/api-interceptor.d.ts +54 -0
  37. package/dist/teams/api-interceptor.js +391 -0
  38. package/dist/teams/direct-api.d.ts +321 -0
  39. package/dist/teams/direct-api.js +1305 -0
  40. package/dist/teams/messages.d.ts +14 -0
  41. package/dist/teams/messages.js +142 -0
  42. package/dist/teams/search.d.ts +40 -0
  43. package/dist/teams/search.js +458 -0
  44. package/dist/test/cli.d.ts +12 -0
  45. package/dist/test/cli.js +328 -0
  46. package/dist/test/debug-search.d.ts +10 -0
  47. package/dist/test/debug-search.js +147 -0
  48. package/dist/test/manual-test.d.ts +11 -0
  49. package/dist/test/manual-test.js +160 -0
  50. package/dist/test/mcp-harness.d.ts +17 -0
  51. package/dist/test/mcp-harness.js +427 -0
  52. package/dist/tools/auth-tools.d.ts +26 -0
  53. package/dist/tools/auth-tools.js +127 -0
  54. package/dist/tools/index.d.ts +45 -0
  55. package/dist/tools/index.js +12 -0
  56. package/dist/tools/message-tools.d.ts +139 -0
  57. package/dist/tools/message-tools.js +433 -0
  58. package/dist/tools/people-tools.d.ts +46 -0
  59. package/dist/tools/people-tools.js +123 -0
  60. package/dist/tools/registry.d.ts +23 -0
  61. package/dist/tools/registry.js +61 -0
  62. package/dist/tools/search-tools.d.ts +79 -0
  63. package/dist/tools/search-tools.js +168 -0
  64. package/dist/types/errors.d.ts +58 -0
  65. package/dist/types/errors.js +132 -0
  66. package/dist/types/result.d.ts +43 -0
  67. package/dist/types/result.js +51 -0
  68. package/dist/types/teams.d.ts +79 -0
  69. package/dist/types/teams.js +5 -0
  70. package/dist/utils/api-config.d.ts +66 -0
  71. package/dist/utils/api-config.js +113 -0
  72. package/dist/utils/auth-guards.d.ts +29 -0
  73. package/dist/utils/auth-guards.js +54 -0
  74. package/dist/utils/http.d.ts +29 -0
  75. package/dist/utils/http.js +111 -0
  76. package/dist/utils/parsers.d.ts +187 -0
  77. package/dist/utils/parsers.js +574 -0
  78. package/dist/utils/parsers.test.d.ts +7 -0
  79. package/dist/utils/parsers.test.js +360 -0
  80. package/package.json +58 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Secure session state storage.
3
+ *
4
+ * Handles reading and writing session state with:
5
+ * - Encryption at rest
6
+ * - Restricted file permissions
7
+ * - Automatic migration from plaintext
8
+ */
9
+ export declare const PROJECT_ROOT: string;
10
+ export declare const USER_DATA_DIR: string;
11
+ export declare const SESSION_STATE_PATH: string;
12
+ export declare const TOKEN_CACHE_PATH: string;
13
+ /** Session state as stored by Playwright. */
14
+ export interface SessionState {
15
+ cookies: Array<{
16
+ name: string;
17
+ value: string;
18
+ domain?: string;
19
+ path?: string;
20
+ expires?: number;
21
+ httpOnly?: boolean;
22
+ secure?: boolean;
23
+ sameSite?: 'Strict' | 'Lax' | 'None';
24
+ }>;
25
+ origins: Array<{
26
+ origin: string;
27
+ localStorage: Array<{
28
+ name: string;
29
+ value: string;
30
+ }>;
31
+ }>;
32
+ }
33
+ /** Token cache structure. */
34
+ export interface TokenCache {
35
+ substrateToken: string;
36
+ substrateTokenExpiry: number;
37
+ extractedAt: number;
38
+ }
39
+ /**
40
+ * Ensures the user data directory exists.
41
+ */
42
+ export declare function ensureUserDataDir(): void;
43
+ /**
44
+ * Checks if session state file exists.
45
+ */
46
+ export declare function hasSessionState(): boolean;
47
+ /**
48
+ * Reads the session state.
49
+ */
50
+ export declare function readSessionState(): SessionState | null;
51
+ /**
52
+ * Writes the session state securely.
53
+ */
54
+ export declare function writeSessionState(state: SessionState): void;
55
+ /**
56
+ * Deletes the session state file.
57
+ */
58
+ export declare function clearSessionState(): void;
59
+ /**
60
+ * Gets the age of the session state in hours.
61
+ */
62
+ export declare function getSessionAge(): number | null;
63
+ /**
64
+ * Checks if session is likely expired (>12 hours old).
65
+ */
66
+ export declare function isSessionLikelyExpired(): boolean;
67
+ /**
68
+ * Reads the token cache.
69
+ */
70
+ export declare function readTokenCache(): TokenCache | null;
71
+ /**
72
+ * Writes the token cache securely.
73
+ */
74
+ export declare function writeTokenCache(cache: TokenCache): void;
75
+ /**
76
+ * Clears the token cache.
77
+ */
78
+ export declare function clearTokenCache(): void;
79
+ /**
80
+ * Gets the Teams origin from session state.
81
+ */
82
+ export declare function getTeamsOrigin(state: SessionState): SessionState['origins'][number] | null;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Secure session state storage.
3
+ *
4
+ * Handles reading and writing session state with:
5
+ * - Encryption at rest
6
+ * - Restricted file permissions
7
+ * - Automatic migration from plaintext
8
+ */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+ import { encrypt, decrypt, isEncrypted } from './crypto.js';
13
+ import { SESSION_EXPIRY_HOURS } from '../constants.js';
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ export const PROJECT_ROOT = path.resolve(__dirname, '../..');
16
+ export const USER_DATA_DIR = path.join(PROJECT_ROOT, '.user-data');
17
+ export const SESSION_STATE_PATH = path.join(PROJECT_ROOT, 'session-state.json');
18
+ export const TOKEN_CACHE_PATH = path.join(PROJECT_ROOT, 'token-cache.json');
19
+ /** File permission mode: owner read/write only. */
20
+ const SECURE_FILE_MODE = 0o600;
21
+ /**
22
+ * Ensures the user data directory exists.
23
+ */
24
+ export function ensureUserDataDir() {
25
+ if (!fs.existsSync(USER_DATA_DIR)) {
26
+ fs.mkdirSync(USER_DATA_DIR, { recursive: true, mode: 0o700 });
27
+ }
28
+ }
29
+ /**
30
+ * Writes data securely with encryption and file permissions.
31
+ */
32
+ function writeSecure(filePath, data) {
33
+ const json = JSON.stringify(data, null, 2);
34
+ const encrypted = encrypt(json);
35
+ fs.writeFileSync(filePath, JSON.stringify(encrypted, null, 2), {
36
+ mode: SECURE_FILE_MODE,
37
+ encoding: 'utf8',
38
+ });
39
+ }
40
+ /**
41
+ * Reads data securely, handling both encrypted and legacy plaintext.
42
+ */
43
+ function readSecure(filePath) {
44
+ if (!fs.existsSync(filePath)) {
45
+ return null;
46
+ }
47
+ try {
48
+ const content = fs.readFileSync(filePath, 'utf8');
49
+ const parsed = JSON.parse(content);
50
+ // Check if this is encrypted data
51
+ if (isEncrypted(parsed)) {
52
+ const decrypted = decrypt(parsed);
53
+ return JSON.parse(decrypted);
54
+ }
55
+ // Legacy plaintext - migrate to encrypted
56
+ writeSecure(filePath, parsed);
57
+ return parsed;
58
+ }
59
+ catch (error) {
60
+ // If decryption fails (different machine, corrupted), return null
61
+ console.error(`Failed to read ${filePath}:`, error instanceof Error ? error.message : error);
62
+ return null;
63
+ }
64
+ }
65
+ /**
66
+ * Checks if session state file exists.
67
+ */
68
+ export function hasSessionState() {
69
+ return fs.existsSync(SESSION_STATE_PATH);
70
+ }
71
+ /**
72
+ * Reads the session state.
73
+ */
74
+ export function readSessionState() {
75
+ return readSecure(SESSION_STATE_PATH);
76
+ }
77
+ /**
78
+ * Writes the session state securely.
79
+ */
80
+ export function writeSessionState(state) {
81
+ writeSecure(SESSION_STATE_PATH, state);
82
+ }
83
+ /**
84
+ * Deletes the session state file.
85
+ */
86
+ export function clearSessionState() {
87
+ if (fs.existsSync(SESSION_STATE_PATH)) {
88
+ fs.unlinkSync(SESSION_STATE_PATH);
89
+ }
90
+ }
91
+ /**
92
+ * Gets the age of the session state in hours.
93
+ */
94
+ export function getSessionAge() {
95
+ if (!hasSessionState()) {
96
+ return null;
97
+ }
98
+ const stats = fs.statSync(SESSION_STATE_PATH);
99
+ const ageMs = Date.now() - stats.mtimeMs;
100
+ return ageMs / (1000 * 60 * 60);
101
+ }
102
+ /**
103
+ * Checks if session is likely expired (>12 hours old).
104
+ */
105
+ export function isSessionLikelyExpired() {
106
+ const age = getSessionAge();
107
+ if (age === null)
108
+ return true;
109
+ return age > SESSION_EXPIRY_HOURS;
110
+ }
111
+ /**
112
+ * Reads the token cache.
113
+ */
114
+ export function readTokenCache() {
115
+ return readSecure(TOKEN_CACHE_PATH);
116
+ }
117
+ /**
118
+ * Writes the token cache securely.
119
+ */
120
+ export function writeTokenCache(cache) {
121
+ writeSecure(TOKEN_CACHE_PATH, cache);
122
+ }
123
+ /**
124
+ * Clears the token cache.
125
+ */
126
+ export function clearTokenCache() {
127
+ if (fs.existsSync(TOKEN_CACHE_PATH)) {
128
+ fs.unlinkSync(TOKEN_CACHE_PATH);
129
+ }
130
+ }
131
+ /**
132
+ * Gets the Teams origin from session state.
133
+ */
134
+ export function getTeamsOrigin(state) {
135
+ return state.origins?.find(o => o.origin === 'https://teams.microsoft.com') ?? null;
136
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Token extraction from session state.
3
+ *
4
+ * Extracts various authentication tokens from Playwright's saved session state.
5
+ */
6
+ import { clearTokenCache, type SessionState } from './session-store.js';
7
+ import { type UserProfile } from '../utils/parsers.js';
8
+ /** Substrate search token information. */
9
+ export interface SubstrateTokenInfo {
10
+ token: string;
11
+ expiry: Date;
12
+ }
13
+ /** Teams API token information. */
14
+ export interface TeamsTokenInfo {
15
+ token: string;
16
+ expiry: Date;
17
+ userMri: string;
18
+ }
19
+ /** Message authentication information (cookies). */
20
+ export interface MessageAuthInfo {
21
+ skypeToken: string;
22
+ authToken: string;
23
+ userMri: string;
24
+ }
25
+ /**
26
+ * Extracts the Substrate search token from session state.
27
+ */
28
+ export declare function extractSubstrateToken(state?: SessionState): SubstrateTokenInfo | null;
29
+ /**
30
+ * Gets a valid Substrate token, either from cache or by extracting from session.
31
+ */
32
+ export declare function getValidSubstrateToken(): string | null;
33
+ /**
34
+ * Checks if we have a valid Substrate token.
35
+ */
36
+ export declare function hasValidSubstrateToken(): boolean;
37
+ /**
38
+ * Gets Substrate token status for diagnostics.
39
+ */
40
+ export declare function getSubstrateTokenStatus(): {
41
+ hasToken: boolean;
42
+ expiresAt?: string;
43
+ minutesRemaining?: number;
44
+ };
45
+ /**
46
+ * Extracts the Teams chat API token from session state.
47
+ */
48
+ export declare function extractTeamsToken(state?: SessionState): TeamsTokenInfo | null;
49
+ /**
50
+ * Extracts authentication info needed for messaging API (uses cookies).
51
+ */
52
+ export declare function extractMessageAuth(state?: SessionState): MessageAuthInfo | null;
53
+ /**
54
+ * Extracts the CSA token for the conversationFolders API.
55
+ */
56
+ export declare function extractCsaToken(state?: SessionState): string | null;
57
+ /**
58
+ * Gets the current user's profile from cached JWT tokens.
59
+ */
60
+ export declare function getUserProfile(state?: SessionState): UserProfile | null;
61
+ /**
62
+ * Gets user's display name from session state.
63
+ */
64
+ export declare function getUserDisplayName(state?: SessionState): string | null;
65
+ /**
66
+ * Checks if tokens in session state are expired.
67
+ */
68
+ export declare function areTokensExpired(state?: SessionState): boolean;
69
+ export { clearTokenCache };
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Token extraction from session state.
3
+ *
4
+ * Extracts various authentication tokens from Playwright's saved session state.
5
+ */
6
+ import { readSessionState, readTokenCache, writeTokenCache, clearTokenCache, getTeamsOrigin, } from './session-store.js';
7
+ import { parseJwtProfile } from '../utils/parsers.js';
8
+ /**
9
+ * Extracts the Substrate search token from session state.
10
+ */
11
+ export function extractSubstrateToken(state) {
12
+ const sessionState = state ?? readSessionState();
13
+ if (!sessionState)
14
+ return null;
15
+ const teamsOrigin = getTeamsOrigin(sessionState);
16
+ if (!teamsOrigin)
17
+ return null;
18
+ for (const item of teamsOrigin.localStorage) {
19
+ try {
20
+ const val = JSON.parse(item.value);
21
+ if (val.target?.includes('substrate.office.com/search/SubstrateSearch')) {
22
+ const token = val.secret;
23
+ if (!token || typeof token !== 'string')
24
+ continue;
25
+ const parts = token.split('.');
26
+ if (parts.length === 3) {
27
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
28
+ const expiry = new Date(payload.exp * 1000);
29
+ return { token, expiry };
30
+ }
31
+ }
32
+ }
33
+ catch {
34
+ continue;
35
+ }
36
+ }
37
+ return null;
38
+ }
39
+ /**
40
+ * Gets a valid Substrate token, either from cache or by extracting from session.
41
+ */
42
+ export function getValidSubstrateToken() {
43
+ // Try cache first
44
+ const cache = readTokenCache();
45
+ if (cache && cache.substrateTokenExpiry > Date.now()) {
46
+ return cache.substrateToken;
47
+ }
48
+ // Extract from session
49
+ const extracted = extractSubstrateToken();
50
+ if (!extracted)
51
+ return null;
52
+ // Check if not expired
53
+ if (extracted.expiry.getTime() <= Date.now()) {
54
+ return null;
55
+ }
56
+ // Cache the token
57
+ const newCache = {
58
+ substrateToken: extracted.token,
59
+ substrateTokenExpiry: extracted.expiry.getTime(),
60
+ extractedAt: Date.now(),
61
+ };
62
+ writeTokenCache(newCache);
63
+ return extracted.token;
64
+ }
65
+ /**
66
+ * Checks if we have a valid Substrate token.
67
+ */
68
+ export function hasValidSubstrateToken() {
69
+ return getValidSubstrateToken() !== null;
70
+ }
71
+ /**
72
+ * Gets Substrate token status for diagnostics.
73
+ */
74
+ export function getSubstrateTokenStatus() {
75
+ const extracted = extractSubstrateToken();
76
+ if (!extracted) {
77
+ return { hasToken: false };
78
+ }
79
+ const now = Date.now();
80
+ const expiryMs = extracted.expiry.getTime();
81
+ return {
82
+ hasToken: expiryMs > now,
83
+ expiresAt: extracted.expiry.toISOString(),
84
+ minutesRemaining: Math.max(0, Math.round((expiryMs - now) / 1000 / 60)),
85
+ };
86
+ }
87
+ /**
88
+ * Extracts the Teams chat API token from session state.
89
+ */
90
+ export function extractTeamsToken(state) {
91
+ const sessionState = state ?? readSessionState();
92
+ if (!sessionState)
93
+ return null;
94
+ const teamsOrigin = getTeamsOrigin(sessionState);
95
+ if (!teamsOrigin)
96
+ return null;
97
+ let chatToken = null;
98
+ let chatTokenExpiry = null;
99
+ let skypeToken = null;
100
+ let skypeTokenExpiry = null;
101
+ let userMri = null;
102
+ for (const item of teamsOrigin.localStorage) {
103
+ try {
104
+ const val = JSON.parse(item.value);
105
+ if (!val.target || !val.secret)
106
+ continue;
107
+ const secret = val.secret;
108
+ if (typeof secret !== 'string' || !secret.startsWith('ey'))
109
+ continue;
110
+ const parts = secret.split('.');
111
+ if (parts.length !== 3)
112
+ continue;
113
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
114
+ const tokenExpiry = new Date(payload.exp * 1000);
115
+ // Extract user MRI from any token
116
+ if (payload.oid && !userMri) {
117
+ userMri = `8:orgid:${payload.oid}`;
118
+ }
119
+ // Prefer chatsvcagg.teams.microsoft.com token
120
+ if (val.target.includes('chatsvcagg.teams.microsoft.com')) {
121
+ if (!chatTokenExpiry || tokenExpiry > chatTokenExpiry) {
122
+ chatToken = secret;
123
+ chatTokenExpiry = tokenExpiry;
124
+ }
125
+ }
126
+ // Fallback to api.spaces.skype.com token
127
+ if (val.target.includes('api.spaces.skype.com')) {
128
+ if (!skypeTokenExpiry || tokenExpiry > skypeTokenExpiry) {
129
+ skypeToken = secret;
130
+ skypeTokenExpiry = tokenExpiry;
131
+ }
132
+ }
133
+ }
134
+ catch {
135
+ continue;
136
+ }
137
+ }
138
+ // If we still don't have userMri, try to get it from the Substrate token
139
+ if (!userMri) {
140
+ const substrateInfo = extractSubstrateToken(sessionState);
141
+ if (substrateInfo) {
142
+ try {
143
+ const parts = substrateInfo.token.split('.');
144
+ if (parts.length === 3) {
145
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
146
+ if (payload.oid) {
147
+ userMri = `8:orgid:${payload.oid}`;
148
+ }
149
+ }
150
+ }
151
+ catch {
152
+ // Ignore
153
+ }
154
+ }
155
+ }
156
+ // Prefer chatsvc token, fallback to skype token
157
+ const token = chatToken || skypeToken;
158
+ const expiry = chatToken ? chatTokenExpiry : skypeTokenExpiry;
159
+ if (token && expiry && userMri && expiry.getTime() > Date.now()) {
160
+ return { token, expiry, userMri };
161
+ }
162
+ return null;
163
+ }
164
+ /**
165
+ * Extracts authentication info needed for messaging API (uses cookies).
166
+ */
167
+ export function extractMessageAuth(state) {
168
+ const sessionState = state ?? readSessionState();
169
+ if (!sessionState)
170
+ return null;
171
+ let skypeToken = null;
172
+ let authToken = null;
173
+ let userMri = null;
174
+ // Extract tokens from cookies
175
+ for (const cookie of sessionState.cookies || []) {
176
+ if (cookie.name === 'skypetoken_asm' && cookie.domain?.includes('teams.microsoft.com')) {
177
+ skypeToken = cookie.value;
178
+ }
179
+ if (cookie.name === 'authtoken' && cookie.domain?.includes('teams.microsoft.com')) {
180
+ authToken = decodeURIComponent(cookie.value);
181
+ if (authToken.startsWith('Bearer=')) {
182
+ authToken = authToken.substring(7);
183
+ }
184
+ }
185
+ }
186
+ // Get userMri from skypeToken payload
187
+ if (skypeToken) {
188
+ try {
189
+ const parts = skypeToken.split('.');
190
+ if (parts.length >= 2) {
191
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
192
+ if (payload.skypeid) {
193
+ userMri = payload.skypeid;
194
+ }
195
+ }
196
+ }
197
+ catch {
198
+ // Not a JWT format, that's fine
199
+ }
200
+ }
201
+ // Fallback to extracting userMri from authToken
202
+ if (!userMri && authToken) {
203
+ try {
204
+ const parts = authToken.split('.');
205
+ if (parts.length === 3) {
206
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
207
+ if (payload.oid) {
208
+ userMri = `8:orgid:${payload.oid}`;
209
+ }
210
+ }
211
+ }
212
+ catch {
213
+ // Ignore
214
+ }
215
+ }
216
+ if (skypeToken && authToken && userMri) {
217
+ return { skypeToken, authToken, userMri };
218
+ }
219
+ return null;
220
+ }
221
+ /**
222
+ * Extracts the CSA token for the conversationFolders API.
223
+ */
224
+ export function extractCsaToken(state) {
225
+ const sessionState = state ?? readSessionState();
226
+ if (!sessionState)
227
+ return null;
228
+ for (const origin of sessionState.origins || []) {
229
+ for (const item of origin.localStorage || []) {
230
+ if (item.name.includes('chatsvcagg.teams.microsoft.com') && !item.name.startsWith('tmp.')) {
231
+ try {
232
+ const data = JSON.parse(item.value);
233
+ if (data.secret) {
234
+ return data.secret;
235
+ }
236
+ }
237
+ catch {
238
+ // Ignore parse errors
239
+ }
240
+ }
241
+ }
242
+ }
243
+ return null;
244
+ }
245
+ /**
246
+ * Gets the current user's profile from cached JWT tokens.
247
+ */
248
+ export function getUserProfile(state) {
249
+ const sessionState = state ?? readSessionState();
250
+ if (!sessionState)
251
+ return null;
252
+ const teamsOrigin = getTeamsOrigin(sessionState);
253
+ if (!teamsOrigin)
254
+ return null;
255
+ // Look through localStorage for any JWT with user info
256
+ for (const item of teamsOrigin.localStorage) {
257
+ try {
258
+ const val = JSON.parse(item.value);
259
+ if (!val.secret || typeof val.secret !== 'string')
260
+ continue;
261
+ if (!val.secret.startsWith('ey'))
262
+ continue;
263
+ const parts = val.secret.split('.');
264
+ if (parts.length !== 3)
265
+ continue;
266
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
267
+ const profile = parseJwtProfile(payload);
268
+ if (profile) {
269
+ return profile;
270
+ }
271
+ }
272
+ catch {
273
+ continue;
274
+ }
275
+ }
276
+ return null;
277
+ }
278
+ /**
279
+ * Gets user's display name from session state.
280
+ */
281
+ export function getUserDisplayName(state) {
282
+ const sessionState = state ?? readSessionState();
283
+ if (!sessionState)
284
+ return null;
285
+ const teamsOrigin = getTeamsOrigin(sessionState);
286
+ if (!teamsOrigin)
287
+ return null;
288
+ for (const item of teamsOrigin.localStorage) {
289
+ try {
290
+ if (item.value?.includes('displayName') || item.value?.includes('givenName')) {
291
+ const val = JSON.parse(item.value);
292
+ if (val.displayName)
293
+ return val.displayName;
294
+ if (val.name?.displayName)
295
+ return val.name.displayName;
296
+ }
297
+ }
298
+ catch {
299
+ continue;
300
+ }
301
+ }
302
+ // Try to get from token
303
+ const teamsToken = extractTeamsToken(sessionState);
304
+ if (teamsToken) {
305
+ try {
306
+ const parts = teamsToken.token.split('.');
307
+ if (parts.length === 3) {
308
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
309
+ if (payload.name)
310
+ return payload.name;
311
+ }
312
+ }
313
+ catch {
314
+ // Ignore
315
+ }
316
+ }
317
+ return null;
318
+ }
319
+ /**
320
+ * Checks if tokens in session state are expired.
321
+ */
322
+ export function areTokensExpired(state) {
323
+ const sessionState = state ?? readSessionState();
324
+ if (!sessionState)
325
+ return true;
326
+ const substrate = extractSubstrateToken(sessionState);
327
+ return !substrate || substrate.expiry.getTime() <= Date.now();
328
+ }
329
+ // Re-export clearTokenCache for convenience
330
+ export { clearTokenCache };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Authentication handling for Microsoft Teams.
3
+ * Manages login detection and manual authentication flows.
4
+ */
5
+ import type { Page, BrowserContext } from 'playwright';
6
+ export interface AuthStatus {
7
+ isAuthenticated: boolean;
8
+ isOnLoginPage: boolean;
9
+ currentUrl: string;
10
+ }
11
+ /**
12
+ * Gets the current authentication status.
13
+ */
14
+ export declare function getAuthStatus(page: Page): Promise<AuthStatus>;
15
+ /**
16
+ * Navigates to Teams and checks authentication status.
17
+ */
18
+ export declare function navigateToTeams(page: Page): Promise<AuthStatus>;
19
+ /**
20
+ * Waits for the user to complete manual authentication.
21
+ * Returns when authenticated or throws after timeout.
22
+ *
23
+ * @param page - The page to monitor
24
+ * @param context - Browser context for saving session
25
+ * @param timeoutMs - Maximum time to wait (default: 5 minutes)
26
+ * @param onProgress - Callback for progress updates
27
+ */
28
+ export declare function waitForManualLogin(page: Page, context: BrowserContext, timeoutMs?: number, onProgress?: (message: string) => void): Promise<void>;
29
+ /**
30
+ * Performs a full authentication flow:
31
+ * 1. Navigate to Teams
32
+ * 2. Check if already authenticated
33
+ * 3. If not, wait for manual login
34
+ *
35
+ * @param page - The page to use
36
+ * @param context - Browser context for session management
37
+ * @param onProgress - Callback for progress updates
38
+ */
39
+ export declare function ensureAuthenticated(page: Page, context: BrowserContext, onProgress?: (message: string) => void): Promise<void>;
40
+ /**
41
+ * Forces a new login by clearing session and navigating to Teams.
42
+ */
43
+ export declare function forceNewLogin(page: Page, context: BrowserContext, onProgress?: (message: string) => void): Promise<void>;