msteams-mcp 0.28.0 → 0.29.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/README.md +1 -1
- package/dist/api/csa-api.js +8 -8
- package/dist/auth/crypto.d.ts +4 -0
- package/dist/auth/crypto.js +12 -0
- package/dist/auth/crypto.test.js +18 -1
- package/dist/auth/session-store.d.ts +6 -9
- package/dist/auth/session-store.js +88 -12
- package/dist/auth/token-extractor.d.ts +74 -31
- package/dist/auth/token-extractor.js +432 -171
- package/dist/auth/token-refresh-http.d.ts +10 -3
- package/dist/auth/token-refresh-http.js +219 -155
- package/dist/auth/token-refresh-http.test.js +19 -125
- package/dist/auth/token-refresh.d.ts +21 -6
- package/dist/auth/token-refresh.js +103 -62
- package/dist/browser/auth.d.ts +35 -0
- package/dist/browser/auth.js +57 -18
- package/dist/browser/context.d.ts +14 -4
- package/dist/browser/context.js +18 -7
- package/dist/cli.js +1 -1
- package/dist/constants.d.ts +12 -11
- package/dist/constants.js +15 -11
- package/dist/index.js +0 -0
- package/dist/server.d.ts +53 -1
- package/dist/server.js +167 -10
- package/dist/tools/auth-tools.js +77 -36
- package/dist/tools/file-tools.d.ts +3 -24
- package/dist/tools/file-tools.js +4 -31
- package/dist/tools/index.d.ts +8 -2
- package/dist/tools/index.js +0 -1
- package/dist/tools/meeting-tools.js +2 -2
- package/dist/tools/message-tools.d.ts +6 -6
- package/dist/tools/message-tools.js +21 -21
- package/dist/tools/people-tools.js +4 -4
- package/dist/tools/registry.d.ts +4 -4
- package/dist/tools/registry.js +3 -5
- package/dist/tools/registry.test.js +14 -4
- package/dist/tools/search-tools.js +4 -4
- package/dist/tools/tag-tools.js +1 -1
- package/dist/types/errors.d.ts +0 -4
- package/dist/types/errors.js +0 -11
- package/dist/utils/api-config.d.ts +0 -40
- package/dist/utils/api-config.js +0 -50
- package/dist/utils/auth-guards.d.ts +16 -23
- package/dist/utils/auth-guards.js +65 -81
- package/dist/utils/auth-guards.test.js +2 -37
- package/package.json +1 -1
- package/dist/api/assignments-api.d.ts +0 -124
- package/dist/api/assignments-api.js +0 -327
- package/dist/api/assignments-api.test.d.ts +0 -7
- package/dist/api/assignments-api.test.js +0 -302
- package/dist/api/graph-files-api.d.ts +0 -34
- package/dist/api/graph-files-api.js +0 -169
- package/dist/api/graph-files-api.test.d.ts +0 -1
- package/dist/api/graph-files-api.test.js +0 -152
- package/dist/api/index.d.ts +0 -10
- package/dist/api/index.js +0 -10
- package/dist/auth/assignments-refresh.test.d.ts +0 -1
- package/dist/auth/assignments-refresh.test.js +0 -43
- package/dist/auth/assignments-token.test.d.ts +0 -1
- package/dist/auth/assignments-token.test.js +0 -36
- package/dist/auth/index.d.ts +0 -7
- package/dist/auth/index.js +0 -7
- package/dist/auth/token-refresh.test.d.ts +0 -1
- package/dist/auth/token-refresh.test.js +0 -31
- package/dist/research/auth-research.d.ts +0 -10
- package/dist/research/auth-research.js +0 -175
- package/dist/research/explore.d.ts +0 -11
- package/dist/research/explore.js +0 -287
- package/dist/research/search-research.d.ts +0 -17
- package/dist/research/search-research.js +0 -317
- package/dist/test/debug-search.d.ts +0 -10
- package/dist/test/debug-search.js +0 -147
- package/dist/test/mcp-harness.d.ts +0 -17
- package/dist/test/mcp-harness.js +0 -476
- package/dist/tools/assignment-tools.d.ts +0 -90
- package/dist/tools/assignment-tools.js +0 -151
- package/dist/tools/assignment-tools.test.d.ts +0 -1
- package/dist/tools/assignment-tools.test.js +0 -13
- package/dist/utils/assignments-auth.test.d.ts +0 -1
- package/dist/utils/assignments-auth.test.js +0 -57
- package/dist/utils/auth-guards-assignments.test.d.ts +0 -5
- package/dist/utils/auth-guards-assignments.test.js +0 -89
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ See [CLI Usage](#cli-usage) for commands.
|
|
|
117
117
|
| `teams_search_email` | Search emails in your mailbox (same auth as Teams - no extra login) |
|
|
118
118
|
| `teams_list_chats` | List recent conversations (1:1, group, meeting, channel) with a last-message preview |
|
|
119
119
|
| `teams_get_message` | Get a single message by ID with full content (any age); includes reactions |
|
|
120
|
-
| `teams_get_thread` | Get messages from a conversation/thread; includes reactions; `threadRootId` scopes to one channel thread; `fromUrl` accepts a Teams message deep link |
|
|
120
|
+
| `teams_get_thread` | Get messages from a conversation/thread; includes reactions; `threadRootId` scopes to one channel thread; `fromUrl` accepts a Teams message deep link; `cursor` pages back through older history |
|
|
121
121
|
| `teams_find_channel` | Find channels by name (your teams + org-wide discovery) |
|
|
122
122
|
| `teams_get_tags` | List a team's channel tags for tag @mentions |
|
|
123
123
|
| `teams_get_activity` | Get activity feed (mentions, reactions, replies, notifications) |
|
package/dist/api/csa-api.js
CHANGED
|
@@ -20,8 +20,8 @@ export async function getFavorites() {
|
|
|
20
20
|
return authResult;
|
|
21
21
|
}
|
|
22
22
|
const { auth, csaToken } = authResult.value;
|
|
23
|
-
const {
|
|
24
|
-
const url = CSA_API.conversationFolders(
|
|
23
|
+
const { region, baseUrl } = getApiConfig();
|
|
24
|
+
const url = CSA_API.conversationFolders(region, baseUrl);
|
|
25
25
|
const response = await httpRequest(url, {
|
|
26
26
|
method: 'GET',
|
|
27
27
|
headers: getCsaHeaders(auth.skypeToken, csaToken, baseUrl),
|
|
@@ -94,7 +94,7 @@ async function modifyFavorite(conversationId, action) {
|
|
|
94
94
|
return authResult;
|
|
95
95
|
}
|
|
96
96
|
const { auth, csaToken } = authResult.value;
|
|
97
|
-
const {
|
|
97
|
+
const { region, baseUrl } = getApiConfig();
|
|
98
98
|
// Get current folder state
|
|
99
99
|
const currentState = await getFavorites();
|
|
100
100
|
if (!currentState.ok) {
|
|
@@ -103,7 +103,7 @@ async function modifyFavorite(conversationId, action) {
|
|
|
103
103
|
if (!currentState.value.folderId) {
|
|
104
104
|
return err(createError(ErrorCode.NOT_FOUND, 'Could not find Favorites folder'));
|
|
105
105
|
}
|
|
106
|
-
const url = CSA_API.conversationFolders(
|
|
106
|
+
const url = CSA_API.conversationFolders(region, baseUrl);
|
|
107
107
|
const response = await httpRequest(url, {
|
|
108
108
|
method: 'POST',
|
|
109
109
|
headers: getCsaHeaders(auth.skypeToken, csaToken, baseUrl),
|
|
@@ -135,8 +135,8 @@ export async function getMyTeamsAndChannels() {
|
|
|
135
135
|
return authResult;
|
|
136
136
|
}
|
|
137
137
|
const { auth, csaToken } = authResult.value;
|
|
138
|
-
const {
|
|
139
|
-
const url = CSA_API.teamsList(
|
|
138
|
+
const { region, baseUrl } = getApiConfig();
|
|
139
|
+
const url = CSA_API.teamsList(region, baseUrl);
|
|
140
140
|
const response = await httpRequest(url, {
|
|
141
141
|
method: 'GET',
|
|
142
142
|
headers: getCsaHeaders(auth.skypeToken, csaToken, baseUrl),
|
|
@@ -156,8 +156,8 @@ export async function getCustomEmojis() {
|
|
|
156
156
|
return authResult;
|
|
157
157
|
}
|
|
158
158
|
const { auth, csaToken } = authResult.value;
|
|
159
|
-
const {
|
|
160
|
-
const url = CSA_API.customEmojis(
|
|
159
|
+
const { region, baseUrl } = getApiConfig();
|
|
160
|
+
const url = CSA_API.customEmojis(region, baseUrl);
|
|
161
161
|
const response = await httpRequest(url, {
|
|
162
162
|
method: 'GET',
|
|
163
163
|
headers: getCsaHeaders(auth.skypeToken, csaToken, baseUrl),
|
package/dist/auth/crypto.d.ts
CHANGED
|
@@ -26,3 +26,7 @@ export declare function encrypt(plaintext: string): EncryptedData;
|
|
|
26
26
|
* @throws Error if decryption fails (wrong machine, corrupted data, etc.)
|
|
27
27
|
*/
|
|
28
28
|
export declare function decrypt(data: EncryptedData): string;
|
|
29
|
+
/**
|
|
30
|
+
* Checks if data looks like encrypted format.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isEncrypted(data: unknown): data is EncryptedData;
|
package/dist/auth/crypto.js
CHANGED
|
@@ -52,3 +52,15 @@ export function decrypt(data) {
|
|
|
52
52
|
decrypted += decipher.final('utf8');
|
|
53
53
|
return decrypted;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Checks if data looks like encrypted format.
|
|
57
|
+
*/
|
|
58
|
+
export function isEncrypted(data) {
|
|
59
|
+
if (!data || typeof data !== 'object')
|
|
60
|
+
return false;
|
|
61
|
+
const obj = data;
|
|
62
|
+
return (typeof obj.iv === 'string' &&
|
|
63
|
+
typeof obj.content === 'string' &&
|
|
64
|
+
typeof obj.tag === 'string' &&
|
|
65
|
+
typeof obj.version === 'number');
|
|
66
|
+
}
|
package/dist/auth/crypto.test.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Unit tests for encryption utilities.
|
|
3
3
|
*/
|
|
4
4
|
import { describe, it, expect } from 'vitest';
|
|
5
|
-
import { encrypt, decrypt } from './crypto.js';
|
|
5
|
+
import { encrypt, decrypt, isEncrypted } from './crypto.js';
|
|
6
6
|
describe('encrypt/decrypt round-trip', () => {
|
|
7
7
|
it('encrypts and decrypts a simple string', () => {
|
|
8
8
|
const plaintext = 'hello world';
|
|
@@ -36,6 +36,23 @@ describe('encrypt/decrypt round-trip', () => {
|
|
|
36
36
|
expect(encrypted1.content).not.toBe(encrypted2.content);
|
|
37
37
|
});
|
|
38
38
|
});
|
|
39
|
+
describe('isEncrypted', () => {
|
|
40
|
+
it('returns true for encrypted data structure', () => {
|
|
41
|
+
const encrypted = encrypt('test');
|
|
42
|
+
expect(isEncrypted(encrypted)).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
it('returns false for plain objects', () => {
|
|
45
|
+
expect(isEncrypted({})).toBe(false);
|
|
46
|
+
expect(isEncrypted({ content: 'abc' })).toBe(false);
|
|
47
|
+
expect(isEncrypted({ iv: 'abc' })).toBe(false);
|
|
48
|
+
expect(isEncrypted(null)).toBe(false);
|
|
49
|
+
expect(isEncrypted(undefined)).toBe(false);
|
|
50
|
+
expect(isEncrypted('string')).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
it('returns true for object with all required fields', () => {
|
|
53
|
+
expect(isEncrypted({ content: 'x', iv: 'y', tag: 'z', version: 1 })).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
39
56
|
describe('decrypt error handling', () => {
|
|
40
57
|
it('throws on tampered ciphertext', () => {
|
|
41
58
|
const encrypted = encrypt('secret');
|
|
@@ -9,18 +9,11 @@
|
|
|
9
9
|
* Session files are stored in a user-specific config directory (~/.teams-mcp-server/)
|
|
10
10
|
* to ensure consistency regardless of how the server is invoked (npx, global install, etc.).
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
|
-
* User-specific config directory for teams-mcp-server.
|
|
14
|
-
* - Windows: %APPDATA%\teams-mcp-server\
|
|
15
|
-
* - macOS/Linux: ~/.teams-mcp-server/
|
|
16
|
-
*/
|
|
12
|
+
export declare const PROJECT_ROOT: string;
|
|
17
13
|
export declare const CONFIG_DIR: string;
|
|
14
|
+
export declare const USER_DATA_DIR: string;
|
|
18
15
|
export declare const SESSION_STATE_PATH: string;
|
|
19
16
|
export declare const TOKEN_CACHE_PATH: string;
|
|
20
|
-
/**
|
|
21
|
-
* Ensures the config directory exists with secure permissions.
|
|
22
|
-
*/
|
|
23
|
-
export declare function ensureConfigDir(): void;
|
|
24
17
|
/** Session state as stored by Playwright. */
|
|
25
18
|
export interface SessionState {
|
|
26
19
|
cookies: Array<{
|
|
@@ -47,6 +40,10 @@ export interface TokenCache {
|
|
|
47
40
|
substrateTokenExpiry: number;
|
|
48
41
|
extractedAt: number;
|
|
49
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Ensures the user data directory exists.
|
|
45
|
+
*/
|
|
46
|
+
export declare function ensureUserDataDir(): void;
|
|
50
47
|
/**
|
|
51
48
|
* Checks if session state file exists.
|
|
52
49
|
*/
|
|
@@ -12,26 +12,89 @@
|
|
|
12
12
|
import * as fs from 'fs';
|
|
13
13
|
import * as os from 'os';
|
|
14
14
|
import * as path from 'path';
|
|
15
|
-
import {
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { encrypt, decrypt, isEncrypted } from './crypto.js';
|
|
16
17
|
import { SESSION_EXPIRY_HOURS } from '../constants.js';
|
|
17
18
|
import * as log from '../utils/logger.js';
|
|
19
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
20
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
+
* Gets the user's home directory with fallback.
|
|
22
|
+
* os.homedir() can throw in rare edge cases (missing env vars, broken passwd).
|
|
23
|
+
*/
|
|
24
|
+
function getHomeDirSafe() {
|
|
25
|
+
try {
|
|
26
|
+
return os.homedir();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Gets the user-specific config directory for teams-mcp-server.
|
|
34
|
+
* - Windows: %APPDATA%\teams-mcp-server\ (e.g., C:\Users\name\AppData\Roaming\teams-mcp-server\)
|
|
21
35
|
* - macOS/Linux: ~/.teams-mcp-server/
|
|
36
|
+
* - Fallback: ./teams-mcp-server-data/ relative to package (legacy behaviour)
|
|
22
37
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
function getConfigDir() {
|
|
39
|
+
const homeDir = getHomeDirSafe();
|
|
40
|
+
if (process.platform === 'win32') {
|
|
41
|
+
const appData = process.env.APPDATA || (homeDir ? path.join(homeDir, 'AppData', 'Roaming') : null);
|
|
42
|
+
if (appData) {
|
|
43
|
+
return path.join(appData, 'teams-mcp-server');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (homeDir) {
|
|
47
|
+
return path.join(homeDir, '.teams-mcp-server');
|
|
48
|
+
}
|
|
49
|
+
// Fallback to package-relative directory if home directory unavailable
|
|
50
|
+
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
51
|
+
return path.join(projectRoot, 'teams-mcp-server-data');
|
|
52
|
+
}
|
|
53
|
+
export const PROJECT_ROOT = path.resolve(__dirname, '../..');
|
|
54
|
+
export const CONFIG_DIR = getConfigDir();
|
|
55
|
+
export const USER_DATA_DIR = path.join(CONFIG_DIR, '.user-data');
|
|
26
56
|
export const SESSION_STATE_PATH = path.join(CONFIG_DIR, 'session-state.json');
|
|
27
57
|
export const TOKEN_CACHE_PATH = path.join(CONFIG_DIR, 'token-cache.json');
|
|
58
|
+
// Legacy paths for migration
|
|
59
|
+
const LEGACY_SESSION_PATH = path.join(PROJECT_ROOT, 'session-state.json');
|
|
60
|
+
const LEGACY_TOKEN_CACHE_PATH = path.join(PROJECT_ROOT, 'token-cache.json');
|
|
28
61
|
/** File permission mode: owner read/write only. */
|
|
29
62
|
const SECURE_FILE_MODE = 0o600;
|
|
30
63
|
/**
|
|
31
64
|
* Ensures the config directory exists with secure permissions.
|
|
32
65
|
*/
|
|
33
|
-
|
|
34
|
-
fs.
|
|
66
|
+
function ensureConfigDir() {
|
|
67
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
68
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Migrates a file from legacy location to new config directory.
|
|
73
|
+
* Only migrates if legacy exists and new location doesn't.
|
|
74
|
+
*/
|
|
75
|
+
function migrateIfNeeded(legacyPath, newPath) {
|
|
76
|
+
if (fs.existsSync(legacyPath) && !fs.existsSync(newPath)) {
|
|
77
|
+
ensureConfigDir();
|
|
78
|
+
try {
|
|
79
|
+
fs.copyFileSync(legacyPath, newPath);
|
|
80
|
+
fs.chmodSync(newPath, SECURE_FILE_MODE);
|
|
81
|
+
// Remove legacy file after successful copy
|
|
82
|
+
fs.unlinkSync(legacyPath);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// Log migration errors for debugging, but continue - will just create new session
|
|
86
|
+
log.warn('session-store', `Failed to migrate ${path.basename(legacyPath)}: ${error instanceof Error ? error.message : error}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Ensures the user data directory exists.
|
|
92
|
+
*/
|
|
93
|
+
export function ensureUserDataDir() {
|
|
94
|
+
ensureConfigDir();
|
|
95
|
+
if (!fs.existsSync(USER_DATA_DIR)) {
|
|
96
|
+
fs.mkdirSync(USER_DATA_DIR, { recursive: true, mode: 0o700 });
|
|
97
|
+
}
|
|
35
98
|
}
|
|
36
99
|
/**
|
|
37
100
|
* Writes data securely with encryption and file permissions.
|
|
@@ -45,16 +108,26 @@ function writeSecure(filePath, data) {
|
|
|
45
108
|
});
|
|
46
109
|
}
|
|
47
110
|
/**
|
|
48
|
-
* Reads
|
|
49
|
-
* (different machine, corrupted).
|
|
111
|
+
* Reads data securely, handling both encrypted and legacy plaintext.
|
|
50
112
|
*/
|
|
51
113
|
function readSecure(filePath) {
|
|
52
|
-
if (!fs.existsSync(filePath))
|
|
114
|
+
if (!fs.existsSync(filePath)) {
|
|
53
115
|
return null;
|
|
116
|
+
}
|
|
54
117
|
try {
|
|
55
|
-
|
|
118
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
119
|
+
const parsed = JSON.parse(content);
|
|
120
|
+
// Check if this is encrypted data
|
|
121
|
+
if (isEncrypted(parsed)) {
|
|
122
|
+
const decrypted = decrypt(parsed);
|
|
123
|
+
return JSON.parse(decrypted);
|
|
124
|
+
}
|
|
125
|
+
// Legacy plaintext - migrate to encrypted
|
|
126
|
+
writeSecure(filePath, parsed);
|
|
127
|
+
return parsed;
|
|
56
128
|
}
|
|
57
129
|
catch (error) {
|
|
130
|
+
// If decryption fails (different machine, corrupted), return null
|
|
58
131
|
log.error('session-store', `Failed to read ${filePath}: ${error instanceof Error ? error.message : error}`);
|
|
59
132
|
return null;
|
|
60
133
|
}
|
|
@@ -63,12 +136,14 @@ function readSecure(filePath) {
|
|
|
63
136
|
* Checks if session state file exists.
|
|
64
137
|
*/
|
|
65
138
|
export function hasSessionState() {
|
|
139
|
+
migrateIfNeeded(LEGACY_SESSION_PATH, SESSION_STATE_PATH);
|
|
66
140
|
return fs.existsSync(SESSION_STATE_PATH);
|
|
67
141
|
}
|
|
68
142
|
/**
|
|
69
143
|
* Reads the session state.
|
|
70
144
|
*/
|
|
71
145
|
export function readSessionState() {
|
|
146
|
+
migrateIfNeeded(LEGACY_SESSION_PATH, SESSION_STATE_PATH);
|
|
72
147
|
return readSecure(SESSION_STATE_PATH);
|
|
73
148
|
}
|
|
74
149
|
/**
|
|
@@ -110,6 +185,7 @@ export function isSessionLikelyExpired() {
|
|
|
110
185
|
* Reads the token cache.
|
|
111
186
|
*/
|
|
112
187
|
export function readTokenCache() {
|
|
188
|
+
migrateIfNeeded(LEGACY_TOKEN_CACHE_PATH, TOKEN_CACHE_PATH);
|
|
113
189
|
return readSecure(TOKEN_CACHE_PATH);
|
|
114
190
|
}
|
|
115
191
|
/**
|
|
@@ -5,17 +5,19 @@
|
|
|
5
5
|
* Teams stores MSAL tokens in localStorage; we parse these to get bearer tokens
|
|
6
6
|
* for various APIs (Substrate search, chatsvc messaging, etc.).
|
|
7
7
|
*/
|
|
8
|
-
import { type SessionState } from './session-store.js';
|
|
8
|
+
import { clearTokenCache, type SessionState } from './session-store.js';
|
|
9
9
|
import { type UserProfile } from '../utils/parsers.js';
|
|
10
|
-
/**
|
|
11
|
-
* Decodes a JWT token's payload without verifying the signature.
|
|
12
|
-
*/
|
|
13
|
-
export declare function decodeJwtPayload(token: string): Record<string, unknown> | null;
|
|
14
|
-
/** A JWT access token and its expiry. */
|
|
10
|
+
/** Substrate search token (for search/people APIs). */
|
|
15
11
|
export interface SubstrateTokenInfo {
|
|
16
12
|
token: string;
|
|
17
13
|
expiry: Date;
|
|
18
14
|
}
|
|
15
|
+
/** Teams chat API token (for chatsvc). */
|
|
16
|
+
export interface TeamsTokenInfo {
|
|
17
|
+
token: string;
|
|
18
|
+
expiry: Date;
|
|
19
|
+
userMri: string;
|
|
20
|
+
}
|
|
19
21
|
/** Cookie-based auth for messaging APIs. */
|
|
20
22
|
export interface MessageAuthInfo {
|
|
21
23
|
skypeToken: string;
|
|
@@ -25,17 +27,16 @@ export interface MessageAuthInfo {
|
|
|
25
27
|
/**
|
|
26
28
|
* Extracts the Substrate search token from session state.
|
|
27
29
|
* This token is used for search and people APIs.
|
|
28
|
-
*
|
|
29
|
-
* The target scope containing 'SubstrateSearch' matches all known formats:
|
|
30
|
-
* - substrate.office.com/search/SubstrateSearch (old)
|
|
31
|
-
* - substrate.office.com/SubstrateSearch-Internal.ReadWrite (new)
|
|
32
|
-
* - outlook.office.com/search/SubstrateSearch-Internal.ReadWrite (some Enterprise tenants)
|
|
33
30
|
*/
|
|
34
31
|
export declare function extractSubstrateToken(state?: SessionState): SubstrateTokenInfo | null;
|
|
35
32
|
/**
|
|
36
33
|
* Gets a valid Substrate token, either from cache or by extracting from session.
|
|
37
34
|
*/
|
|
38
35
|
export declare function getValidSubstrateToken(): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Checks if we have a valid Substrate token.
|
|
38
|
+
*/
|
|
39
|
+
export declare function hasValidSubstrateToken(): boolean;
|
|
39
40
|
/**
|
|
40
41
|
* Gets Substrate token status for diagnostics.
|
|
41
42
|
*/
|
|
@@ -45,28 +46,13 @@ export declare function getSubstrateTokenStatus(): {
|
|
|
45
46
|
minutesRemaining?: number;
|
|
46
47
|
};
|
|
47
48
|
/**
|
|
48
|
-
* Extracts the
|
|
49
|
+
* Extracts the Teams chat API token from session state.
|
|
49
50
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* the same EduAssignments permission names for a different resource.
|
|
54
|
-
*/
|
|
55
|
-
export declare function extractAssignmentsToken(state?: SessionState): SubstrateTokenInfo | null;
|
|
56
|
-
/**
|
|
57
|
-
* Extracts a Microsoft Graph token from session state. Teams web caches one for
|
|
58
|
-
* its own client; the on-demand refresh (`refreshTokensViaHttp('graph')`) renews it.
|
|
59
|
-
*/
|
|
60
|
-
export declare function extractGraphToken(state?: SessionState): SubstrateTokenInfo | null;
|
|
61
|
-
/** Remove only the rejected credential; leave other API tokens untouched. */
|
|
62
|
-
export declare function invalidateAccessToken(token: string): void;
|
|
63
|
-
/**
|
|
64
|
-
* Gets a valid EDU Assignments token from the session, or null. Refreshed on
|
|
65
|
-
* demand by `refreshTokensViaHttp('assignments')`.
|
|
51
|
+
* Teams stores multiple tokens for different services. We prefer:
|
|
52
|
+
* 1. chatsvcagg.teams.microsoft.com (primary chat API)
|
|
53
|
+
* 2. api.spaces.skype.com (fallback)
|
|
66
54
|
*/
|
|
67
|
-
export declare function
|
|
68
|
-
/** Gets a valid Microsoft Graph token from the session, or null. */
|
|
69
|
-
export declare function getValidGraphToken(): string | null;
|
|
55
|
+
export declare function extractTeamsToken(state?: SessionState): TeamsTokenInfo | null;
|
|
70
56
|
/**
|
|
71
57
|
* Extracts the Skype Spaces API token from session state.
|
|
72
58
|
*
|
|
@@ -112,6 +98,35 @@ export interface RegionConfig {
|
|
|
112
98
|
* base URLs (e.g., teams.microsoft.us).
|
|
113
99
|
*/
|
|
114
100
|
export declare function extractRegionConfig(state?: SessionState): RegionConfig | null;
|
|
101
|
+
/** User details from DISCOVER-USER-DETAILS. */
|
|
102
|
+
export interface UserDetails {
|
|
103
|
+
/** User's MRI (e.g., "8:orgid:abc..."). */
|
|
104
|
+
mri: string;
|
|
105
|
+
/** User's region (e.g., "amer", "emea"). */
|
|
106
|
+
region: string;
|
|
107
|
+
/** User's partition (e.g., "amer01"). */
|
|
108
|
+
userPartition: string;
|
|
109
|
+
/** Tenant's partition (e.g., "amer02"). */
|
|
110
|
+
tenantPartition: string;
|
|
111
|
+
/** License details. */
|
|
112
|
+
licenses: {
|
|
113
|
+
isFreemium: boolean;
|
|
114
|
+
isTrial: boolean;
|
|
115
|
+
isTeamsEnabled: boolean;
|
|
116
|
+
isCopilot: boolean;
|
|
117
|
+
isTranscriptEnabled: boolean;
|
|
118
|
+
isFrontline: boolean;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Extracts user details from DISCOVER-USER-DETAILS in localStorage.
|
|
123
|
+
*
|
|
124
|
+
* This provides user-specific info including:
|
|
125
|
+
* - User's MRI
|
|
126
|
+
* - Region and partition info
|
|
127
|
+
* - License details (Copilot, transcription, etc.)
|
|
128
|
+
*/
|
|
129
|
+
export declare function extractUserDetails(state?: SessionState): UserDetails | null;
|
|
115
130
|
/**
|
|
116
131
|
* Extracts authentication info needed for messaging API.
|
|
117
132
|
* Unlike other APIs, messaging uses cookies rather than localStorage tokens.
|
|
@@ -140,3 +155,31 @@ export declare function getUserProfile(state?: SessionState): UserProfile | null
|
|
|
140
155
|
* Searches localStorage entries first, then falls back to JWT claims.
|
|
141
156
|
*/
|
|
142
157
|
export declare function getUserDisplayName(state?: SessionState): string | null;
|
|
158
|
+
/**
|
|
159
|
+
* Checks if tokens in session state are expired.
|
|
160
|
+
*/
|
|
161
|
+
export declare function areTokensExpired(state?: SessionState): boolean;
|
|
162
|
+
export { clearTokenCache };
|
|
163
|
+
/**
|
|
164
|
+
* Discovered configuration from localStorage.
|
|
165
|
+
* Used for debugging and understanding what config is available.
|
|
166
|
+
*/
|
|
167
|
+
export interface DiscoveredConfig {
|
|
168
|
+
/** All DISCOVER-* keys with their parsed values. */
|
|
169
|
+
discoveryConfigs: Record<string, unknown>;
|
|
170
|
+
/** All keys that look like configuration (not tokens). */
|
|
171
|
+
configKeys: string[];
|
|
172
|
+
/** Content of interesting config keys (settings, flags). */
|
|
173
|
+
configContents: Record<string, unknown>;
|
|
174
|
+
/** Teams base URL extracted from discovery config. */
|
|
175
|
+
teamsBaseUrl: string | null;
|
|
176
|
+
/** Substrate URL if found in any config. */
|
|
177
|
+
substrateUrl: string | null;
|
|
178
|
+
/** All unique hosts found in config URLs. */
|
|
179
|
+
uniqueHosts: string[];
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Extracts all configuration data from localStorage for debugging.
|
|
183
|
+
* This helps discover what config is available from different tenants.
|
|
184
|
+
*/
|
|
185
|
+
export declare function discoverConfig(state?: SessionState): DiscoveredConfig | null;
|