teams-api-mcp 0.1.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/LICENSE +21 -0
- package/README.md +406 -0
- package/SKILL.md +130 -0
- package/dist/actions.d.ts +49 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +842 -0
- package/dist/actions.js.map +1 -0
- package/dist/api.d.ts +111 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +630 -0
- package/dist/api.js.map +1 -0
- package/dist/auth.d.ts +53 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +479 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +178 -0
- package/dist/cli.js.map +1 -0
- package/dist/mcp-server.d.ts +35 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +144 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/teams-client.d.ts +197 -0
- package/dist/teams-client.d.ts.map +1 -0
- package/dist/teams-client.js +545 -0
- package/dist/teams-client.js.map +1 -0
- package/dist/token-store.d.ts +14 -0
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +79 -0
- package/dist/token-store.js.map +1 -0
- package/dist/types.d.ts +245 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TeamsClient — the public API for interacting with Microsoft Teams.
|
|
4
|
+
*
|
|
5
|
+
* This is the primary entry point for the teams-api package.
|
|
6
|
+
* It wraps authentication, conversation listing, message reading/sending,
|
|
7
|
+
* and member management behind a clean, strongly-typed interface.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // Auto-login (macOS with FIDO2 passkey)
|
|
11
|
+
* const client = await TeamsClient.fromAutoLogin({
|
|
12
|
+
* email: "user@company.com",
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* // List conversations
|
|
16
|
+
* const conversations = await client.listConversations();
|
|
17
|
+
*
|
|
18
|
+
* // Read messages
|
|
19
|
+
* const messages = await client.getMessages(conversations[0].id);
|
|
20
|
+
*
|
|
21
|
+
* // Send a message
|
|
22
|
+
* await client.sendMessage(conversations[0].id, "Hello from the API!");
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // From an existing token
|
|
26
|
+
* const client = TeamsClient.fromToken(
|
|
27
|
+
* "skype-token-here",
|
|
28
|
+
* "apac",
|
|
29
|
+
* "optional-bearer-token",
|
|
30
|
+
* "optional-substrate-token",
|
|
31
|
+
* );
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.TeamsClient = exports.formatOutput = exports.actions = exports.clearToken = exports.loadToken = exports.saveToken = exports.isSuccessfulRecording = exports.extractMeetingTitle = exports.extractTranscriptUrl = exports.parseVtt = exports.fetchTranscriptVtt = exports.fetchTranscript = exports.searchChats = exports.searchPeople = exports.fetchProfiles = exports.ApiRateLimitError = exports.ApiAuthError = exports.parseRawMessage = exports.SYSTEM_STREAM_TYPES = exports.acquireTokenViaDebugSession = exports.acquireTokenViaInteractiveLogin = exports.acquireTokenViaAutoLogin = void 0;
|
|
35
|
+
const api_js_1 = require("./api.js");
|
|
36
|
+
const auth_js_1 = require("./auth.js");
|
|
37
|
+
const token_store_js_1 = require("./token-store.js");
|
|
38
|
+
var auth_js_2 = require("./auth.js");
|
|
39
|
+
Object.defineProperty(exports, "acquireTokenViaAutoLogin", { enumerable: true, get: function () { return auth_js_2.acquireTokenViaAutoLogin; } });
|
|
40
|
+
Object.defineProperty(exports, "acquireTokenViaInteractiveLogin", { enumerable: true, get: function () { return auth_js_2.acquireTokenViaInteractiveLogin; } });
|
|
41
|
+
Object.defineProperty(exports, "acquireTokenViaDebugSession", { enumerable: true, get: function () { return auth_js_2.acquireTokenViaDebugSession; } });
|
|
42
|
+
var types_js_1 = require("./types.js");
|
|
43
|
+
Object.defineProperty(exports, "SYSTEM_STREAM_TYPES", { enumerable: true, get: function () { return types_js_1.SYSTEM_STREAM_TYPES; } });
|
|
44
|
+
var api_js_2 = require("./api.js");
|
|
45
|
+
Object.defineProperty(exports, "parseRawMessage", { enumerable: true, get: function () { return api_js_2.parseRawMessage; } });
|
|
46
|
+
Object.defineProperty(exports, "ApiAuthError", { enumerable: true, get: function () { return api_js_2.ApiAuthError; } });
|
|
47
|
+
Object.defineProperty(exports, "ApiRateLimitError", { enumerable: true, get: function () { return api_js_2.ApiRateLimitError; } });
|
|
48
|
+
Object.defineProperty(exports, "fetchProfiles", { enumerable: true, get: function () { return api_js_2.fetchProfiles; } });
|
|
49
|
+
Object.defineProperty(exports, "searchPeople", { enumerable: true, get: function () { return api_js_2.searchPeople; } });
|
|
50
|
+
Object.defineProperty(exports, "searchChats", { enumerable: true, get: function () { return api_js_2.searchChats; } });
|
|
51
|
+
Object.defineProperty(exports, "fetchTranscript", { enumerable: true, get: function () { return api_js_2.fetchTranscript; } });
|
|
52
|
+
Object.defineProperty(exports, "fetchTranscriptVtt", { enumerable: true, get: function () { return api_js_2.fetchTranscriptVtt; } });
|
|
53
|
+
Object.defineProperty(exports, "parseVtt", { enumerable: true, get: function () { return api_js_2.parseVtt; } });
|
|
54
|
+
Object.defineProperty(exports, "extractTranscriptUrl", { enumerable: true, get: function () { return api_js_2.extractTranscriptUrl; } });
|
|
55
|
+
Object.defineProperty(exports, "extractMeetingTitle", { enumerable: true, get: function () { return api_js_2.extractMeetingTitle; } });
|
|
56
|
+
Object.defineProperty(exports, "isSuccessfulRecording", { enumerable: true, get: function () { return api_js_2.isSuccessfulRecording; } });
|
|
57
|
+
var token_store_js_2 = require("./token-store.js");
|
|
58
|
+
Object.defineProperty(exports, "saveToken", { enumerable: true, get: function () { return token_store_js_2.saveToken; } });
|
|
59
|
+
Object.defineProperty(exports, "loadToken", { enumerable: true, get: function () { return token_store_js_2.loadToken; } });
|
|
60
|
+
Object.defineProperty(exports, "clearToken", { enumerable: true, get: function () { return token_store_js_2.clearToken; } });
|
|
61
|
+
var actions_js_1 = require("./actions.js");
|
|
62
|
+
Object.defineProperty(exports, "actions", { enumerable: true, get: function () { return actions_js_1.actions; } });
|
|
63
|
+
Object.defineProperty(exports, "formatOutput", { enumerable: true, get: function () { return actions_js_1.formatOutput; } });
|
|
64
|
+
const SYSTEM_STREAMS = [
|
|
65
|
+
"streamofannotations",
|
|
66
|
+
"streamofthreads",
|
|
67
|
+
"streamofnotifications",
|
|
68
|
+
"streamofmentions",
|
|
69
|
+
"streamofnotes",
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* Extract the MRI suffix from a sender URL or return the input unchanged.
|
|
73
|
+
* API messages use full URLs (e.g. ".../contacts/8:orgid:uuid"),
|
|
74
|
+
* while member IDs are just the MRI suffix ("8:orgid:uuid").
|
|
75
|
+
*/
|
|
76
|
+
function extractMriSuffix(senderMri) {
|
|
77
|
+
const contactsPrefix = "/contacts/";
|
|
78
|
+
const contactsIndex = senderMri.lastIndexOf(contactsPrefix);
|
|
79
|
+
if (contactsIndex >= 0) {
|
|
80
|
+
return senderMri.slice(contactsIndex + contactsPrefix.length);
|
|
81
|
+
}
|
|
82
|
+
return senderMri;
|
|
83
|
+
}
|
|
84
|
+
class TeamsClient {
|
|
85
|
+
token;
|
|
86
|
+
autoLoginOptions = null;
|
|
87
|
+
cachedDisplayName = null;
|
|
88
|
+
constructor(token) {
|
|
89
|
+
this.token = token;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create a client with secure token caching and automatic refresh.
|
|
93
|
+
*
|
|
94
|
+
* This is the recommended entry point. It:
|
|
95
|
+
* 1. Checks the macOS Keychain for a cached, non-expired token
|
|
96
|
+
* 2. If none found, launches auto-login to acquire a fresh token
|
|
97
|
+
* 3. Saves the token to the Keychain for future use
|
|
98
|
+
* 4. Automatically re-acquires the token if a 401 occurs mid-session
|
|
99
|
+
*
|
|
100
|
+
* Token lifetime is ~24 hours. Cached tokens are reused within 23 hours.
|
|
101
|
+
*/
|
|
102
|
+
static async create(options) {
|
|
103
|
+
const log = options.verbose ? console.error.bind(console) : () => { };
|
|
104
|
+
const cachedToken = (0, token_store_js_1.loadToken)(options.email);
|
|
105
|
+
if (cachedToken) {
|
|
106
|
+
log("Using cached token from Keychain");
|
|
107
|
+
const client = new TeamsClient(cachedToken);
|
|
108
|
+
client.autoLoginOptions = options;
|
|
109
|
+
return client;
|
|
110
|
+
}
|
|
111
|
+
log("No cached token found, acquiring via auto-login...");
|
|
112
|
+
const token = await (0, auth_js_1.acquireTokenViaAutoLogin)(options);
|
|
113
|
+
(0, token_store_js_1.saveToken)(options.email, token);
|
|
114
|
+
log("Token saved to Keychain");
|
|
115
|
+
const client = new TeamsClient(token);
|
|
116
|
+
client.autoLoginOptions = options;
|
|
117
|
+
return client;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Create a client by automatically logging in via FIDO2 passkey.
|
|
121
|
+
*
|
|
122
|
+
* Launches system Chrome with a fresh profile, completes the Microsoft
|
|
123
|
+
* Entra ID FIDO2 login flow using a platform authenticator, and
|
|
124
|
+
* captures the skype token. Zero user interaction required.
|
|
125
|
+
*/
|
|
126
|
+
static async fromAutoLogin(options) {
|
|
127
|
+
const token = await (0, auth_js_1.acquireTokenViaAutoLogin)(options);
|
|
128
|
+
return new TeamsClient(token);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Create a client by connecting to a running Chrome debug session.
|
|
132
|
+
*
|
|
133
|
+
* Requires Chrome started with --remote-debugging-port and Teams
|
|
134
|
+
* already open and authenticated.
|
|
135
|
+
*/
|
|
136
|
+
static async fromDebugSession(options) {
|
|
137
|
+
const token = await (0, auth_js_1.acquireTokenViaDebugSession)(options);
|
|
138
|
+
return new TeamsClient(token);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create a client via interactive browser login.
|
|
142
|
+
*
|
|
143
|
+
* Opens a visible Chromium window where the user manually logs into
|
|
144
|
+
* Teams. Works on all platforms (macOS, Windows, Linux) without
|
|
145
|
+
* requiring FIDO2 passkeys or system Chrome.
|
|
146
|
+
*/
|
|
147
|
+
static async fromInteractiveLogin(options) {
|
|
148
|
+
const token = await (0, auth_js_1.acquireTokenViaInteractiveLogin)(options);
|
|
149
|
+
return new TeamsClient(token);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Create a client from an existing token bundle.
|
|
153
|
+
*
|
|
154
|
+
* `bearerToken` enables profile/member resolution and `substrateToken`
|
|
155
|
+
* enables reliable people/chat search. Both are optional; basic chat
|
|
156
|
+
* operations only require `skypeToken`.
|
|
157
|
+
*/
|
|
158
|
+
static fromToken(skypeToken, region = "apac", bearerToken, substrateToken) {
|
|
159
|
+
return new TeamsClient({ skypeToken, region, bearerToken, substrateToken });
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Clear a cached token from the macOS Keychain.
|
|
163
|
+
*
|
|
164
|
+
* Use this to force a fresh login on the next `create()` call.
|
|
165
|
+
*/
|
|
166
|
+
static clearCachedToken(email) {
|
|
167
|
+
(0, token_store_js_1.clearToken)(email);
|
|
168
|
+
}
|
|
169
|
+
/** Get the underlying token (for persistence or debugging). */
|
|
170
|
+
getToken() {
|
|
171
|
+
return { ...this.token };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* List conversations (chats, group chats, meetings, channels).
|
|
175
|
+
*
|
|
176
|
+
* By default, excludes system streams (annotations, notifications,
|
|
177
|
+
* mentions). Set `excludeSystemStreams: false` to include everything.
|
|
178
|
+
*/
|
|
179
|
+
async listConversations(options) {
|
|
180
|
+
return this.withTokenRefresh(async () => {
|
|
181
|
+
const pageSize = options?.pageSize ?? 50;
|
|
182
|
+
const excludeSystem = options?.excludeSystemStreams ?? true;
|
|
183
|
+
const conversations = await (0, api_js_1.fetchConversations)(this.token, pageSize);
|
|
184
|
+
if (!excludeSystem) {
|
|
185
|
+
return conversations;
|
|
186
|
+
}
|
|
187
|
+
return conversations.filter((conversation) => !SYSTEM_STREAMS.includes(conversation.threadType));
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Find a conversation by topic name (case-insensitive partial match).
|
|
192
|
+
*
|
|
193
|
+
* When a Substrate token is available, also searches via the Substrate
|
|
194
|
+
* chat search API for matches by name or member. Falls back to local
|
|
195
|
+
* topic matching when Substrate is unavailable.
|
|
196
|
+
*/
|
|
197
|
+
async findConversation(query) {
|
|
198
|
+
return this.withTokenRefresh(async () => {
|
|
199
|
+
const conversations = await this.listConversations({ pageSize: 100 });
|
|
200
|
+
const queryLower = query.toLowerCase();
|
|
201
|
+
// Try local topic matching first (fast, no extra API call)
|
|
202
|
+
const topicMatch = conversations.find((conversation) => conversation.topic &&
|
|
203
|
+
conversation.topic.toLowerCase().includes(queryLower));
|
|
204
|
+
if (topicMatch)
|
|
205
|
+
return topicMatch;
|
|
206
|
+
// Use Substrate chat search for broader matching (by member names, etc.)
|
|
207
|
+
if (this.token.substrateToken) {
|
|
208
|
+
const chatResults = await (0, api_js_1.searchChats)(this.token, query, 5);
|
|
209
|
+
for (const chatResult of chatResults) {
|
|
210
|
+
const matchingConversation = conversations.find((conversation) => conversation.id === chatResult.threadId);
|
|
211
|
+
if (matchingConversation)
|
|
212
|
+
return matchingConversation;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Search for people in the organization directory by name.
|
|
220
|
+
*
|
|
221
|
+
* Uses the Substrate search API (requires a Substrate token captured
|
|
222
|
+
* during authentication). Returns matching people with MRIs, emails,
|
|
223
|
+
* job titles, and departments.
|
|
224
|
+
*/
|
|
225
|
+
async findPeople(query, maxResults = 10) {
|
|
226
|
+
return this.withTokenRefresh(async () => {
|
|
227
|
+
return (0, api_js_1.searchPeople)(this.token, query, maxResults);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Search for chats by name or member name.
|
|
232
|
+
*
|
|
233
|
+
* Uses the Substrate search API (requires a Substrate token captured
|
|
234
|
+
* during authentication). Returns matching chats with thread IDs,
|
|
235
|
+
* member lists, and matching member details.
|
|
236
|
+
*/
|
|
237
|
+
async findChats(query, maxResults = 10) {
|
|
238
|
+
return this.withTokenRefresh(async () => {
|
|
239
|
+
return (0, api_js_1.searchChats)(this.token, query, maxResults);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Find a 1:1 conversation with a specific person.
|
|
244
|
+
*
|
|
245
|
+
* Uses the Substrate people/chat search API to find the person by name
|
|
246
|
+
* and locate the corresponding 1:1 chat thread. Falls back to scanning
|
|
247
|
+
* message history when the Substrate token is unavailable.
|
|
248
|
+
*
|
|
249
|
+
* Also checks the self-chat (48:notes) if the query matches the
|
|
250
|
+
* current user's name.
|
|
251
|
+
*/
|
|
252
|
+
async findOneOnOneConversation(personName) {
|
|
253
|
+
return this.withTokenRefresh(async () => {
|
|
254
|
+
const targetLower = personName.toLowerCase();
|
|
255
|
+
// Check self-chat first
|
|
256
|
+
const conversations = await (0, api_js_1.fetchConversations)(this.token, 100);
|
|
257
|
+
const selfChat = conversations.find((conversation) => conversation.id.startsWith("48:notes"));
|
|
258
|
+
if (selfChat) {
|
|
259
|
+
const currentUserName = await this.getCurrentUserDisplayName();
|
|
260
|
+
if (currentUserName.toLowerCase().includes(targetLower)) {
|
|
261
|
+
return {
|
|
262
|
+
conversationId: selfChat.id,
|
|
263
|
+
memberDisplayName: `${currentUserName} (self)`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Primary: use Substrate search API for people + chat lookup
|
|
268
|
+
if (this.token.substrateToken) {
|
|
269
|
+
// Search for the person to confirm they exist and get their MRI
|
|
270
|
+
const people = await (0, api_js_1.searchPeople)(this.token, personName, 5);
|
|
271
|
+
const matchedPerson = people.find((person) => person.displayName.toLowerCase().includes(targetLower));
|
|
272
|
+
if (matchedPerson) {
|
|
273
|
+
// Search for chats that include this person
|
|
274
|
+
const chats = await (0, api_js_1.searchChats)(this.token, personName, 10);
|
|
275
|
+
// Look for a 1:1 chat (Chat type, exactly 2 members)
|
|
276
|
+
for (const chat of chats) {
|
|
277
|
+
if (chat.threadType === "Chat" &&
|
|
278
|
+
chat.totalMemberCount === 2 &&
|
|
279
|
+
chat.matchingMembers.some((member) => member.mri === matchedPerson.mri)) {
|
|
280
|
+
return {
|
|
281
|
+
conversationId: chat.threadId,
|
|
282
|
+
memberDisplayName: matchedPerson.displayName,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// If no 1:1 chat was found in search results, check if any
|
|
287
|
+
// conversation in our list matches the person's MRI
|
|
288
|
+
const personUuid = matchedPerson.mri.replace("8:orgid:", "");
|
|
289
|
+
const matchingConversation = conversations.find((conversation) => conversation.id.includes(personUuid) &&
|
|
290
|
+
conversation.threadType === "chat");
|
|
291
|
+
if (matchingConversation) {
|
|
292
|
+
return {
|
|
293
|
+
conversationId: matchingConversation.id,
|
|
294
|
+
memberDisplayName: matchedPerson.displayName,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
// Fallback: scan message senders when substrate token is unavailable
|
|
301
|
+
const untitledChats = conversations.filter((conversation) => conversation.threadType === "chat" &&
|
|
302
|
+
!conversation.topic &&
|
|
303
|
+
!conversation.id.startsWith("48:"));
|
|
304
|
+
for (const chat of untitledChats) {
|
|
305
|
+
try {
|
|
306
|
+
const page = await (0, api_js_1.fetchMessagesPage)(this.token, chat.id, 10);
|
|
307
|
+
const textMessages = page.messages.filter((message) => message.messageType === "RichText/Html" ||
|
|
308
|
+
message.messageType === "Text");
|
|
309
|
+
const senderNames = [
|
|
310
|
+
...new Set(textMessages
|
|
311
|
+
.map((message) => message.senderDisplayName)
|
|
312
|
+
.filter((name) => name.length > 0)),
|
|
313
|
+
];
|
|
314
|
+
for (const senderName of senderNames) {
|
|
315
|
+
if (senderName.toLowerCase().includes(targetLower)) {
|
|
316
|
+
return {
|
|
317
|
+
conversationId: chat.id,
|
|
318
|
+
memberDisplayName: senderName,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get all messages from a conversation.
|
|
332
|
+
*
|
|
333
|
+
* Follows pagination links to fetch the complete message history.
|
|
334
|
+
* Use `maxPages` and `pageSize` to control how much is fetched.
|
|
335
|
+
*/
|
|
336
|
+
async getMessages(conversationId, options) {
|
|
337
|
+
return this.withTokenRefresh(async () => {
|
|
338
|
+
const maxPages = options?.maxPages ?? 100;
|
|
339
|
+
const pageSize = options?.pageSize ?? 200;
|
|
340
|
+
const allMessages = [];
|
|
341
|
+
let backwardLink;
|
|
342
|
+
for (let pageIndex = 0; pageIndex < maxPages; pageIndex++) {
|
|
343
|
+
const result = await (0, api_js_1.fetchMessagesPage)(this.token, conversationId, pageSize, backwardLink);
|
|
344
|
+
allMessages.push(...result.messages);
|
|
345
|
+
options?.onProgress?.(allMessages.length);
|
|
346
|
+
if (!result.backwardLink)
|
|
347
|
+
break;
|
|
348
|
+
backwardLink = result.backwardLink;
|
|
349
|
+
}
|
|
350
|
+
return allMessages;
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Get one page of messages from a conversation.
|
|
355
|
+
*
|
|
356
|
+
* Returns the page along with a backwardLink for manual pagination.
|
|
357
|
+
*/
|
|
358
|
+
async getMessagesPage(conversationId, pageSize = 50, backwardLink) {
|
|
359
|
+
return this.withTokenRefresh(async () => {
|
|
360
|
+
return (0, api_js_1.fetchMessagesPage)(this.token, conversationId, pageSize, backwardLink);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Send a message to a conversation.
|
|
365
|
+
*
|
|
366
|
+
* The `format` parameter controls how `content` is interpreted:
|
|
367
|
+
* - `"text"` — plain text, sent as-is
|
|
368
|
+
* - `"markdown"` (default) — converted from Markdown to HTML
|
|
369
|
+
* - `"html"` — raw HTML, sent as-is
|
|
370
|
+
*/
|
|
371
|
+
async sendMessage(conversationId, content, format = "markdown") {
|
|
372
|
+
return this.withTokenRefresh(async () => {
|
|
373
|
+
const displayName = await this.getCurrentUserDisplayName();
|
|
374
|
+
return (0, api_js_1.postMessage)(this.token, conversationId, content, displayName, format);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Get members of a conversation.
|
|
379
|
+
*
|
|
380
|
+
* Display names are resolved via the Teams middle-tier profile API when a
|
|
381
|
+
* Bearer token is available. Falls back to scanning message history when it
|
|
382
|
+
* is not.
|
|
383
|
+
*/
|
|
384
|
+
async getMembers(conversationId) {
|
|
385
|
+
return this.withTokenRefresh(async () => {
|
|
386
|
+
const members = await (0, api_js_1.fetchMembers)(this.token, conversationId);
|
|
387
|
+
const unresolvedPeople = members.filter((member) => member.memberType === "person" && !member.displayName);
|
|
388
|
+
if (unresolvedPeople.length === 0) {
|
|
389
|
+
return members;
|
|
390
|
+
}
|
|
391
|
+
// Primary: resolve via middle-tier profile API (requires bearerToken)
|
|
392
|
+
if (this.token.bearerToken) {
|
|
393
|
+
const mris = unresolvedPeople.map((member) => member.id);
|
|
394
|
+
try {
|
|
395
|
+
const profiles = await (0, api_js_1.fetchProfiles)(this.token, mris);
|
|
396
|
+
const profileLookup = new Map(profiles.map((profile) => [profile.mri, profile.displayName]));
|
|
397
|
+
for (const member of members) {
|
|
398
|
+
if (!member.displayName) {
|
|
399
|
+
member.displayName = profileLookup.get(member.id) ?? "";
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return members;
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
// Fall through to message-history fallback
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Fallback: resolve from message sender names in conversation history
|
|
409
|
+
const unresolvedMris = new Set(unresolvedPeople.map((member) => member.id));
|
|
410
|
+
const nameLookup = new Map();
|
|
411
|
+
const maxPages = 10;
|
|
412
|
+
let backwardLink;
|
|
413
|
+
try {
|
|
414
|
+
for (let page = 0; page < maxPages; page++) {
|
|
415
|
+
const result = await (0, api_js_1.fetchMessagesPage)(this.token, conversationId, 200, backwardLink);
|
|
416
|
+
for (const message of result.messages) {
|
|
417
|
+
if (message.senderDisplayName) {
|
|
418
|
+
const mriSuffix = extractMriSuffix(message.senderMri);
|
|
419
|
+
if (unresolvedMris.has(mriSuffix)) {
|
|
420
|
+
nameLookup.set(mriSuffix, message.senderDisplayName);
|
|
421
|
+
unresolvedMris.delete(mriSuffix);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (unresolvedMris.size === 0 || !result.backwardLink) {
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
backwardLink = result.backwardLink;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
// If message fetch fails, return members with whatever names we have
|
|
433
|
+
}
|
|
434
|
+
for (const member of members) {
|
|
435
|
+
if (!member.displayName) {
|
|
436
|
+
member.displayName = nameLookup.get(member.id) ?? "";
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return members;
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Get the meeting transcript for a conversation.
|
|
444
|
+
*
|
|
445
|
+
* Searches messages for a successful recording, extracts the AMS
|
|
446
|
+
* transcript URL, fetches the VTT, and parses it into structured entries.
|
|
447
|
+
*/
|
|
448
|
+
async getTranscript(conversationId) {
|
|
449
|
+
return this.withTokenRefresh(async () => {
|
|
450
|
+
return (0, api_js_1.fetchTranscript)(this.token, conversationId);
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Get the display name of the currently authenticated user.
|
|
455
|
+
*
|
|
456
|
+
* Resolved by reading messages from the self-chat (48:notes) or
|
|
457
|
+
* falling back to the user properties endpoint.
|
|
458
|
+
*/
|
|
459
|
+
async getCurrentUserDisplayName() {
|
|
460
|
+
return this.withTokenRefresh(async () => {
|
|
461
|
+
if (this.cachedDisplayName) {
|
|
462
|
+
return this.cachedDisplayName;
|
|
463
|
+
}
|
|
464
|
+
const conversations = await (0, api_js_1.fetchConversations)(this.token, 10);
|
|
465
|
+
// First try: self-chat messages (most reliable)
|
|
466
|
+
for (const conversation of conversations) {
|
|
467
|
+
try {
|
|
468
|
+
if (!conversation.id.startsWith("48:notes"))
|
|
469
|
+
continue;
|
|
470
|
+
const page = await (0, api_js_1.fetchMessagesPage)(this.token, conversation.id, 10);
|
|
471
|
+
const textMessage = page.messages.find((message) => (message.messageType === "RichText/Html" ||
|
|
472
|
+
message.messageType === "Text") &&
|
|
473
|
+
message.senderDisplayName.length > 0);
|
|
474
|
+
if (textMessage) {
|
|
475
|
+
this.cachedDisplayName = textMessage.senderDisplayName;
|
|
476
|
+
return this.cachedDisplayName;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// Fallback: user properties endpoint (userDetails JSON field)
|
|
484
|
+
try {
|
|
485
|
+
const properties = await (0, api_js_1.fetchUserProperties)(this.token);
|
|
486
|
+
if (typeof properties.displayname === "string") {
|
|
487
|
+
this.cachedDisplayName = properties.displayname;
|
|
488
|
+
return this.cachedDisplayName;
|
|
489
|
+
}
|
|
490
|
+
if (typeof properties.userDetails === "string") {
|
|
491
|
+
try {
|
|
492
|
+
const userDetails = JSON.parse(properties.userDetails);
|
|
493
|
+
if (userDetails.name) {
|
|
494
|
+
this.cachedDisplayName = userDetails.name;
|
|
495
|
+
return this.cachedDisplayName;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
// Malformed JSON — fall through
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
// Fall through
|
|
505
|
+
}
|
|
506
|
+
return "Unknown User";
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Re-acquire the token via auto-login and update the cached version.
|
|
511
|
+
*
|
|
512
|
+
* Called automatically by `withTokenRefresh` on 401 errors.
|
|
513
|
+
*/
|
|
514
|
+
async refreshToken() {
|
|
515
|
+
if (!this.autoLoginOptions) {
|
|
516
|
+
throw new Error("Cannot refresh token: no auto-login options configured");
|
|
517
|
+
}
|
|
518
|
+
(0, token_store_js_1.clearToken)(this.autoLoginOptions.email);
|
|
519
|
+
const freshToken = await (0, auth_js_1.acquireTokenViaAutoLogin)(this.autoLoginOptions);
|
|
520
|
+
(0, token_store_js_1.saveToken)(this.autoLoginOptions.email, freshToken);
|
|
521
|
+
this.token = freshToken;
|
|
522
|
+
this.cachedDisplayName = null;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Wrap an operation with automatic token refresh on 401.
|
|
526
|
+
*
|
|
527
|
+
* If the operation throws an `ApiAuthError` and auto-login options
|
|
528
|
+
* are configured, the token is re-acquired and the operation retried
|
|
529
|
+
* exactly once.
|
|
530
|
+
*/
|
|
531
|
+
async withTokenRefresh(operation) {
|
|
532
|
+
try {
|
|
533
|
+
return await operation();
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
if (error instanceof api_js_1.ApiAuthError && this.autoLoginOptions) {
|
|
537
|
+
await this.refreshToken();
|
|
538
|
+
return await operation();
|
|
539
|
+
}
|
|
540
|
+
throw error;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
exports.TeamsClient = TeamsClient;
|
|
545
|
+
//# sourceMappingURL=teams-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teams-client.js","sourceRoot":"","sources":["../src/teams-client.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;;AAqBH,qCAYkB;AAClB,uCAImB;AACnB,qDAAoE;AAEpE,qCAImB;AAHjB,mHAAA,wBAAwB,OAAA;AACxB,0HAAA,+BAA+B,OAAA;AAC/B,sHAAA,2BAA2B,OAAA;AAwB7B,uCAAiD;AAAxC,+GAAA,mBAAmB,OAAA;AAC5B,mCAakB;AAZhB,yGAAA,eAAe,OAAA;AACf,sGAAA,YAAY,OAAA;AACZ,2GAAA,iBAAiB,OAAA;AACjB,uGAAA,aAAa,OAAA;AACb,sGAAA,YAAY,OAAA;AACZ,qGAAA,WAAW,OAAA;AACX,yGAAA,eAAe,OAAA;AACf,4GAAA,kBAAkB,OAAA;AAClB,kGAAA,QAAQ,OAAA;AACR,8GAAA,oBAAoB,OAAA;AACpB,6GAAA,mBAAmB,OAAA;AACnB,+GAAA,qBAAqB,OAAA;AAEvB,mDAAoE;AAA3D,2GAAA,SAAS,OAAA;AAAE,2GAAA,SAAS,OAAA;AAAE,4GAAA,UAAU,OAAA;AACzC,2CAAqD;AAA5C,qGAAA,OAAO,OAAA;AAAE,0GAAA,YAAY,OAAA;AAO9B,MAAM,cAAc,GAAsB;IACxC,qBAAqB;IACrB,iBAAiB;IACjB,uBAAuB;IACvB,kBAAkB;IAClB,eAAe;CAChB,CAAC;AAEF;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,SAAiB;IACzC,MAAM,cAAc,GAAG,YAAY,CAAC;IACpC,MAAM,aAAa,GAAG,SAAS,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC;IAC5D,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO,SAAS,CAAC,KAAK,CAAC,aAAa,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAa,WAAW;IACd,KAAK,CAAa;IAClB,gBAAgB,GAA4B,IAAI,CAAC;IACjD,iBAAiB,GAAkB,IAAI,CAAC;IAEhD,YAAoB,KAAiB;QACnC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED;;;;;;;;;;OAUG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAyB;QAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;QAErE,MAAM,WAAW,GAAG,IAAA,0BAAS,EAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,WAAW,EAAE,CAAC;YAChB,GAAG,CAAC,kCAAkC,CAAC,CAAC;YACxC,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,WAAW,CAAC,CAAC;YAC5C,MAAM,CAAC,gBAAgB,GAAG,OAAO,CAAC;YAClC,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,GAAG,CAAC,oDAAoD,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,MAAM,IAAA,kCAAwB,EAAC,OAAO,CAAC,CAAC;QACtD,IAAA,0BAAS,EAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAChC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QAE/B,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,CAAC,gBAAgB,GAAG,OAAO,CAAC;QAClC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,OAAyB;QAClD,MAAM,KAAK,GAAG,MAAM,IAAA,kCAAwB,EAAC,OAAO,CAAC,CAAC;QACtD,OAAO,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAC3B,OAA4B;QAE5B,MAAM,KAAK,GAAG,MAAM,IAAA,qCAA2B,EAAC,OAAO,CAAC,CAAC;QACzD,OAAO,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAC/B,OAAiC;QAEjC,MAAM,KAAK,GAAG,MAAM,IAAA,yCAA+B,EAAC,OAAO,CAAC,CAAC;QAC7D,OAAO,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,SAAS,CACd,UAAkB,EAClB,MAAM,GAAG,MAAM,EACf,WAAoB,EACpB,cAAuB;QAEvB,OAAO,IAAI,WAAW,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,gBAAgB,CAAC,KAAa;QACnC,IAAA,2BAAU,EAAC,KAAK,CAAC,CAAC;IACpB,CAAC;IAED,+DAA+D;IAC/D,QAAQ;QACN,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,iBAAiB,CACrB,OAAkC;QAElC,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACtC,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,EAAE,CAAC;YACzC,MAAM,aAAa,GAAG,OAAO,EAAE,oBAAoB,IAAI,IAAI,CAAC;YAE5D,MAAM,aAAa,GAAG,MAAM,IAAA,2BAAkB,EAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YAErE,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,OAAO,aAAa,CAAC;YACvB,CAAC;YAED,OAAO,aAAa,CAAC,MAAM,CACzB,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,YAAY,CAAC,UAAU,CAAC,CACpE,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,gBAAgB,CAAC,KAAa;QAClC,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACtC,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;YACtE,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;YAEvC,2DAA2D;YAC3D,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CACnC,CAAC,YAAY,EAAE,EAAE,CACf,YAAY,CAAC,KAAK;gBAClB,YAAY,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CACxD,CAAC;YACF,IAAI,UAAU;gBAAE,OAAO,UAAU,CAAC;YAElC,yEAAyE;YACzE,IAAI,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;gBAC9B,MAAM,WAAW,GAAG,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;gBAC5D,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;oBACrC,MAAM,oBAAoB,GAAG,aAAa,CAAC,IAAI,CAC7C,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,KAAK,UAAU,CAAC,QAAQ,CAC1D,CAAC;oBACF,IAAI,oBAAoB;wBAAE,OAAO,oBAAoB,CAAC;gBACxD,CAAC;YACH,CAAC;YAED,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,UAAU,CACd,KAAa,EACb,UAAU,GAAG,EAAE;QAEf,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACtC,OAAO,IAAA,qBAAY,EAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,SAAS,CAAC,KAAa,EAAE,UAAU,GAAG,EAAE;QAC5C,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACtC,OAAO,IAAA,oBAAW,EAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,wBAAwB,CAC5B,UAAkB;QAElB,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACtC,MAAM,WAAW,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;YAE7C,wBAAwB;YACxB,MAAM,aAAa,GAAG,MAAM,IAAA,2BAAkB,EAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAChE,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CACnD,YAAY,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CACvC,CAAC;YACF,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,yBAAyB,EAAE,CAAC;gBAC/D,IAAI,eAAe,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;oBACxD,OAAO;wBACL,cAAc,EAAE,QAAQ,CAAC,EAAE;wBAC3B,iBAAiB,EAAE,GAAG,eAAe,SAAS;qBAC/C,CAAC;gBACJ,CAAC;YACH,CAAC;YAED,6DAA6D;YAC7D,IAAI,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;gBAC9B,gEAAgE;gBAChE,MAAM,MAAM,GAAG,MAAM,IAAA,qBAAY,EAAC,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;gBAC7D,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAC3C,MAAM,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CACvD,CAAC;gBAEF,IAAI,aAAa,EAAE,CAAC;oBAClB,4CAA4C;oBAC5C,MAAM,KAAK,GAAG,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;oBAE5D,qDAAqD;oBACrD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;wBACzB,IACE,IAAI,CAAC,UAAU,KAAK,MAAM;4BAC1B,IAAI,CAAC,gBAAgB,KAAK,CAAC;4BAC3B,IAAI,CAAC,eAAe,CAAC,IAAI,CACvB,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,KAAK,aAAa,CAAC,GAAG,CAC7C,EACD,CAAC;4BACD,OAAO;gCACL,cAAc,EAAE,IAAI,CAAC,QAAQ;gCAC7B,iBAAiB,EAAE,aAAa,CAAC,WAAW;6BAC7C,CAAC;wBACJ,CAAC;oBACH,CAAC;oBAED,2DAA2D;oBAC3D,oDAAoD;oBACpD,MAAM,UAAU,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;oBAC7D,MAAM,oBAAoB,GAAG,aAAa,CAAC,IAAI,CAC7C,CAAC,YAAY,EAAE,EAAE,CACf,YAAY,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC;wBACpC,YAAY,CAAC,UAAU,KAAK,MAAM,CACrC,CAAC;oBACF,IAAI,oBAAoB,EAAE,CAAC;wBACzB,OAAO;4BACL,cAAc,EAAE,oBAAoB,CAAC,EAAE;4BACvC,iBAAiB,EAAE,aAAa,CAAC,WAAW;yBAC7C,CAAC;oBACJ,CAAC;gBACH,CAAC;gBAED,OAAO,IAAI,CAAC;YACd,CAAC;YAED,qEAAqE;YACrE,MAAM,aAAa,GAAG,aAAa,CAAC,MAAM,CACxC,CAAC,YAAY,EAAE,EAAE,CACf,YAAY,CAAC,UAAU,KAAK,MAAM;gBAClC,CAAC,YAAY,CAAC,KAAK;gBACnB,CAAC,YAAY,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,CACrC,CAAC;YAEF,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;gBACjC,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,MAAM,IAAA,0BAAiB,EAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;oBAC9D,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CACvC,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,WAAW,KAAK,eAAe;wBACvC,OAAO,CAAC,WAAW,KAAK,MAAM,CACjC,CAAC;oBAEF,MAAM,WAAW,GAAG;wBAClB,GAAG,IAAI,GAAG,CACR,YAAY;6BACT,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAC;6BAC3C,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CACrC;qBACF,CAAC;oBAEF,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;wBACrC,IAAI,UAAU,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;4BACnD,OAAO;gCACL,cAAc,EAAE,IAAI,CAAC,EAAE;gCACvB,iBAAiB,EAAE,UAAU;6BAC9B,CAAC;wBACJ,CAAC;oBACH,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,SAAS;gBACX,CAAC;YACH,CAAC;YAED,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CACf,cAAsB,EACtB,OAA4B;QAE5B,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACtC,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,GAAG,CAAC;YAC1C,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,GAAG,CAAC;YAC1C,MAAM,WAAW,GAAc,EAAE,CAAC;YAClC,IAAI,YAAgC,CAAC;YAErC,KAAK,IAAI,SAAS,GAAG,CAAC,EAAE,SAAS,GAAG,QAAQ,EAAE,SAAS,EAAE,EAAE,CAAC;gBAC1D,MAAM,MAAM,GAAG,MAAM,IAAA,0BAAiB,EACpC,IAAI,CAAC,KAAK,EACV,cAAc,EACd,QAAQ,EACR,YAAY,CACb,CAAC;gBACF,WAAW,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAErC,OAAO,EAAE,UAAU,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;gBAE1C,IAAI,CAAC,MAAM,CAAC,YAAY;oBAAE,MAAM;gBAChC,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;YACrC,CAAC;YAED,OAAO,WAAW,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,eAAe,CACnB,cAAsB,EACtB,QAAQ,GAAG,EAAE,EACb,YAAqB;QAErB,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACtC,OAAO,IAAA,0BAAiB,EACtB,IAAI,CAAC,KAAK,EACV,cAAc,EACd,QAAQ,EACR,YAAY,CACb,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,WAAW,CACf,cAAsB,EACtB,OAAe,EACf,SAAwB,UAAU;QAElC,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACtC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,yBAAyB,EAAE,CAAC;YAC3D,OAAO,IAAA,oBAAW,EAChB,IAAI,CAAC,KAAK,EACV,cAAc,EACd,OAAO,EACP,WAAW,EACX,MAAM,CACP,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,UAAU,CAAC,cAAsB;QACrC,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACtC,MAAM,OAAO,GAAG,MAAM,IAAA,qBAAY,EAAC,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;YAE/D,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,CACrC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,WAAW,CAClE,CAAC;YAEF,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClC,OAAO,OAAO,CAAC;YACjB,CAAC;YAED,sEAAsE;YACtE,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;gBAC3B,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACzD,IAAI,CAAC;oBACH,MAAM,QAAQ,GAAG,MAAM,IAAA,sBAAa,EAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;oBACvD,MAAM,aAAa,GAAG,IAAI,GAAG,CAC3B,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAC9D,CAAC;oBACF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;wBAC7B,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;4BACxB,MAAM,CAAC,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;wBAC1D,CAAC;oBACH,CAAC;oBACD,OAAO,OAAO,CAAC;gBACjB,CAAC;gBAAC,MAAM,CAAC;oBACP,2CAA2C;gBAC7C,CAAC;YACH,CAAC;YAED,sEAAsE;YACtE,MAAM,cAAc,GAAG,IAAI,GAAG,CAC5B,gBAAgB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAC5C,CAAC;YACF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;YAC7C,MAAM,QAAQ,GAAG,EAAE,CAAC;YACpB,IAAI,YAAgC,CAAC;YAErC,IAAI,CAAC;gBACH,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC;oBAC3C,MAAM,MAAM,GAAG,MAAM,IAAA,0BAAiB,EACpC,IAAI,CAAC,KAAK,EACV,cAAc,EACd,GAAG,EACH,YAAY,CACb,CAAC;oBAEF,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;wBACtC,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;4BAC9B,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;4BACtD,IAAI,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gCAClC,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;gCACrD,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;4BACnC,CAAC;wBACH,CAAC;oBACH,CAAC;oBAED,IAAI,cAAc,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;wBACtD,MAAM;oBACR,CAAC;oBACD,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;gBACrC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,qEAAqE;YACvE,CAAC;YAED,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;oBACxB,MAAM,CAAC,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;gBACvD,CAAC;YACH,CAAC;YAED,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,aAAa,CAAC,cAAsB;QACxC,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACtC,OAAO,IAAA,wBAAe,EAAC,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,yBAAyB;QAC7B,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACtC,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBAC3B,OAAO,IAAI,CAAC,iBAAiB,CAAC;YAChC,CAAC;YAED,MAAM,aAAa,GAAG,MAAM,IAAA,2BAAkB,EAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAE/D,gDAAgD;YAChD,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;gBACzC,IAAI,CAAC;oBACH,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;wBAAE,SAAS;oBAEtD,MAAM,IAAI,GAAG,MAAM,IAAA,0BAAiB,EAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;oBACtE,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CACpC,CAAC,OAAO,EAAE,EAAE,CACV,CAAC,OAAO,CAAC,WAAW,KAAK,eAAe;wBACtC,OAAO,CAAC,WAAW,KAAK,MAAM,CAAC;wBACjC,OAAO,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,CACvC,CAAC;oBAEF,IAAI,WAAW,EAAE,CAAC;wBAChB,IAAI,CAAC,iBAAiB,GAAG,WAAW,CAAC,iBAAiB,CAAC;wBACvD,OAAO,IAAI,CAAC,iBAAiB,CAAC;oBAChC,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,SAAS;gBACX,CAAC;YACH,CAAC;YAED,8DAA8D;YAC9D,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,MAAM,IAAA,4BAAmB,EAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACzD,IAAI,OAAO,UAAU,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;oBAC/C,IAAI,CAAC,iBAAiB,GAAG,UAAU,CAAC,WAAW,CAAC;oBAChD,OAAO,IAAI,CAAC,iBAAiB,CAAC;gBAChC,CAAC;gBACD,IAAI,OAAO,UAAU,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;oBAC/C,IAAI,CAAC;wBACH,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAEpD,CAAC;wBACF,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC;4BACrB,IAAI,CAAC,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC;4BAC1C,OAAO,IAAI,CAAC,iBAAiB,CAAC;wBAChC,CAAC;oBACH,CAAC;oBAAC,MAAM,CAAC;wBACP,gCAAgC;oBAClC,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,eAAe;YACjB,CAAC;YAED,OAAO,cAAc,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,YAAY;QACxB,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QAED,IAAA,2BAAU,EAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,UAAU,GAAG,MAAM,IAAA,kCAAwB,EAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACzE,IAAA,0BAAS,EAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QACnD,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC;QACxB,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAChC,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,gBAAgB,CAAI,SAA2B;QAC3D,IAAI,CAAC;YACH,OAAO,MAAM,SAAS,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,qBAAY,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAC3D,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;gBAC1B,OAAO,MAAM,SAAS,EAAE,CAAC;YAC3B,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF;AAllBD,kCAklBC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure token persistence using the macOS Keychain.
|
|
3
|
+
*
|
|
4
|
+
* Stores Teams tokens in the system Keychain via the `security` CLI tool.
|
|
5
|
+
* Tokens are base64-encoded JSON with an acquisition timestamp.
|
|
6
|
+
* Expired tokens (older than TOKEN_LIFETIME) are automatically cleared.
|
|
7
|
+
*
|
|
8
|
+
* Uses `execFileSync` (not `execSync`) to avoid shell injection risks.
|
|
9
|
+
*/
|
|
10
|
+
import type { TeamsToken } from "./types.js";
|
|
11
|
+
export declare function saveToken(email: string, token: TeamsToken): void;
|
|
12
|
+
export declare function loadToken(email: string): TeamsToken | null;
|
|
13
|
+
export declare function clearToken(email: string): void;
|
|
14
|
+
//# sourceMappingURL=token-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../src/token-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAa7C,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAoBhE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAiC1D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAY9C"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Secure token persistence using the macOS Keychain.
|
|
4
|
+
*
|
|
5
|
+
* Stores Teams tokens in the system Keychain via the `security` CLI tool.
|
|
6
|
+
* Tokens are base64-encoded JSON with an acquisition timestamp.
|
|
7
|
+
* Expired tokens (older than TOKEN_LIFETIME) are automatically cleared.
|
|
8
|
+
*
|
|
9
|
+
* Uses `execFileSync` (not `execSync`) to avoid shell injection risks.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.saveToken = saveToken;
|
|
13
|
+
exports.loadToken = loadToken;
|
|
14
|
+
exports.clearToken = clearToken;
|
|
15
|
+
const node_child_process_1 = require("node:child_process");
|
|
16
|
+
const KEYCHAIN_SERVICE = "teams-api";
|
|
17
|
+
const TOKEN_LIFETIME = 23 * 60 * 60 * 1_000; // 23 hours (tokens last ~24h, 1h safety margin)
|
|
18
|
+
function saveToken(email, token) {
|
|
19
|
+
const storedToken = {
|
|
20
|
+
skypeToken: token.skypeToken,
|
|
21
|
+
region: token.region,
|
|
22
|
+
bearerToken: token.bearerToken,
|
|
23
|
+
substrateToken: token.substrateToken,
|
|
24
|
+
acquiredAt: Date.now(),
|
|
25
|
+
};
|
|
26
|
+
const encoded = Buffer.from(JSON.stringify(storedToken)).toString("base64");
|
|
27
|
+
(0, node_child_process_1.execFileSync)("security", [
|
|
28
|
+
"add-generic-password",
|
|
29
|
+
"-a",
|
|
30
|
+
email,
|
|
31
|
+
"-s",
|
|
32
|
+
KEYCHAIN_SERVICE,
|
|
33
|
+
"-w",
|
|
34
|
+
encoded,
|
|
35
|
+
"-U",
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
38
|
+
function loadToken(email) {
|
|
39
|
+
let encoded;
|
|
40
|
+
try {
|
|
41
|
+
encoded = (0, node_child_process_1.execFileSync)("security", ["find-generic-password", "-a", email, "-s", KEYCHAIN_SERVICE, "-w"], { encoding: "utf-8" }).trim();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
let storedToken;
|
|
47
|
+
try {
|
|
48
|
+
storedToken = JSON.parse(Buffer.from(encoded, "base64").toString("utf-8"));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
clearToken(email);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
if (Date.now() - storedToken.acquiredAt > TOKEN_LIFETIME) {
|
|
55
|
+
clearToken(email);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
skypeToken: storedToken.skypeToken,
|
|
60
|
+
region: storedToken.region,
|
|
61
|
+
bearerToken: storedToken.bearerToken,
|
|
62
|
+
substrateToken: storedToken.substrateToken,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function clearToken(email) {
|
|
66
|
+
try {
|
|
67
|
+
(0, node_child_process_1.execFileSync)("security", [
|
|
68
|
+
"delete-generic-password",
|
|
69
|
+
"-a",
|
|
70
|
+
email,
|
|
71
|
+
"-s",
|
|
72
|
+
KEYCHAIN_SERVICE,
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Token may not exist in keychain, that's fine
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=token-store.js.map
|