agent-messenger 2.9.0 → 2.10.1
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/.claude-plugin/plugin.json +1 -1
- package/dist/package.json +1 -1
- package/dist/src/platforms/teams/client.d.ts +9 -1
- package/dist/src/platforms/teams/client.d.ts.map +1 -1
- package/dist/src/platforms/teams/client.js +69 -18
- package/dist/src/platforms/teams/client.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +7 -2
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/commands/channel.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/channel.js +18 -3
- package/dist/src/platforms/teams/commands/channel.js.map +1 -1
- package/dist/src/platforms/teams/commands/file.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/file.js +18 -3
- package/dist/src/platforms/teams/commands/file.js.map +1 -1
- package/dist/src/platforms/teams/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/message.js +24 -4
- package/dist/src/platforms/teams/commands/message.js.map +1 -1
- package/dist/src/platforms/teams/commands/reaction.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/reaction.js +12 -2
- package/dist/src/platforms/teams/commands/reaction.js.map +1 -1
- package/dist/src/platforms/teams/commands/snapshot.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/snapshot.js +6 -1
- package/dist/src/platforms/teams/commands/snapshot.js.map +1 -1
- package/dist/src/platforms/teams/commands/team.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/team.js +6 -1
- package/dist/src/platforms/teams/commands/team.js.map +1 -1
- package/dist/src/platforms/teams/commands/user.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/user.js +18 -3
- package/dist/src/platforms/teams/commands/user.js.map +1 -1
- package/dist/src/platforms/teams/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/whoami.js +6 -1
- package/dist/src/platforms/teams/commands/whoami.js.map +1 -1
- package/dist/src/platforms/teams/credential-manager.d.ts +3 -1
- package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/teams/credential-manager.js +6 -1
- package/dist/src/platforms/teams/credential-manager.js.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +7 -2
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/token-extractor.d.ts +3 -1
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +73 -10
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/types.d.ts +17 -0
- package/dist/src/platforms/teams/types.d.ts.map +1 -1
- package/dist/src/platforms/teams/types.js +2 -0
- package/dist/src/platforms/teams/types.js.map +1 -1
- package/dist/src/platforms/webex/client.d.ts +3 -0
- package/dist/src/platforms/webex/client.d.ts.map +1 -1
- package/dist/src/platforms/webex/client.js +58 -13
- package/dist/src/platforms/webex/client.js.map +1 -1
- package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/auth.js +61 -10
- package/dist/src/platforms/webex/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/webex/credential-manager.js +18 -6
- package/dist/src/platforms/webex/credential-manager.js.map +1 -1
- package/dist/src/platforms/webex/encryption.d.ts.map +1 -1
- package/dist/src/platforms/webex/encryption.js +3 -1
- package/dist/src/platforms/webex/encryption.js.map +1 -1
- package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/ensure-auth.js +10 -2
- package/dist/src/platforms/webex/ensure-auth.js.map +1 -1
- package/dist/src/platforms/webex/token-extractor.d.ts +1 -0
- package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/webex/token-extractor.js +21 -4
- package/dist/src/platforms/webex/token-extractor.js.map +1 -1
- package/e2e/webex.e2e.test.ts +57 -0
- package/package.json +1 -1
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +1 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/teams/client.test.ts +34 -30
- package/src/platforms/teams/client.ts +92 -20
- package/src/platforms/teams/commands/auth.test.ts +6 -2
- package/src/platforms/teams/commands/auth.ts +7 -2
- package/src/platforms/teams/commands/channel.test.ts +6 -6
- package/src/platforms/teams/commands/channel.ts +18 -3
- package/src/platforms/teams/commands/file.ts +18 -3
- package/src/platforms/teams/commands/message.ts +24 -4
- package/src/platforms/teams/commands/reaction.ts +12 -2
- package/src/platforms/teams/commands/snapshot.ts +6 -1
- package/src/platforms/teams/commands/team.test.ts +2 -2
- package/src/platforms/teams/commands/team.ts +6 -1
- package/src/platforms/teams/commands/user.ts +18 -3
- package/src/platforms/teams/commands/whoami.ts +6 -1
- package/src/platforms/teams/credential-manager.test.ts +25 -0
- package/src/platforms/teams/credential-manager.ts +13 -3
- package/src/platforms/teams/ensure-auth.test.ts +6 -1
- package/src/platforms/teams/ensure-auth.ts +7 -2
- package/src/platforms/teams/token-extractor.test.ts +112 -98
- package/src/platforms/teams/token-extractor.ts +83 -12
- package/src/platforms/teams/types.test.ts +17 -0
- package/src/platforms/teams/types.ts +6 -0
- package/src/platforms/webex/client.test.ts +157 -13
- package/src/platforms/webex/client.ts +64 -15
- package/src/platforms/webex/commands/auth.test.ts +122 -1
- package/src/platforms/webex/commands/auth.ts +72 -17
- package/src/platforms/webex/credential-manager.test.ts +63 -0
- package/src/platforms/webex/credential-manager.ts +22 -8
- package/src/platforms/webex/encryption.test.ts +54 -0
- package/src/platforms/webex/encryption.ts +3 -1
- package/src/platforms/webex/ensure-auth.ts +10 -2
- package/src/platforms/webex/token-extractor.test.ts +32 -3
- package/src/platforms/webex/token-extractor.ts +26 -5
|
@@ -55,18 +55,18 @@ describe('TeamsClient', () => {
|
|
|
55
55
|
|
|
56
56
|
describe('login', () => {
|
|
57
57
|
test('requires token', async () => {
|
|
58
|
-
await expect(new TeamsClient().login({ token: '' })).rejects.toThrow(TeamsError)
|
|
59
|
-
await expect(new TeamsClient().login({ token: '' })).rejects.toThrow('Token is required')
|
|
58
|
+
await expect(new TeamsClient().login({ token: '', region: 'emea' })).rejects.toThrow(TeamsError)
|
|
59
|
+
await expect(new TeamsClient().login({ token: '', region: 'emea' })).rejects.toThrow('Token is required')
|
|
60
60
|
})
|
|
61
61
|
|
|
62
62
|
test('accepts valid token', async () => {
|
|
63
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
63
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
64
64
|
expect(client).toBeInstanceOf(TeamsClient)
|
|
65
65
|
})
|
|
66
66
|
|
|
67
67
|
test('accepts token with expiry time', async () => {
|
|
68
68
|
const expiresAt = new Date(Date.now() + 3600000).toISOString()
|
|
69
|
-
const client = await new TeamsClient().login({ token: 'test-token', tokenExpiresAt: expiresAt })
|
|
69
|
+
const client = await new TeamsClient().login({ token: 'test-token', tokenExpiresAt: expiresAt, region: 'emea' })
|
|
70
70
|
expect(client).toBeInstanceOf(TeamsClient)
|
|
71
71
|
})
|
|
72
72
|
})
|
|
@@ -74,7 +74,11 @@ describe('TeamsClient', () => {
|
|
|
74
74
|
describe('token expiry', () => {
|
|
75
75
|
test('throws when token is expired', async () => {
|
|
76
76
|
const expiredAt = new Date(Date.now() - 1000).toISOString()
|
|
77
|
-
const client = await new TeamsClient().login({
|
|
77
|
+
const client = await new TeamsClient().login({
|
|
78
|
+
token: 'expired-token',
|
|
79
|
+
tokenExpiresAt: expiredAt,
|
|
80
|
+
region: 'emea',
|
|
81
|
+
})
|
|
78
82
|
|
|
79
83
|
await expect(client.testAuth()).rejects.toThrow(TeamsError)
|
|
80
84
|
await expect(client.testAuth()).rejects.toThrow('Token has expired')
|
|
@@ -87,7 +91,7 @@ describe('TeamsClient', () => {
|
|
|
87
91
|
locale: 'en-us',
|
|
88
92
|
})
|
|
89
93
|
|
|
90
|
-
const client = await new TeamsClient().login({ token: 'valid-token', tokenExpiresAt: expiresAt })
|
|
94
|
+
const client = await new TeamsClient().login({ token: 'valid-token', tokenExpiresAt: expiresAt, region: 'emea' })
|
|
91
95
|
const user = await client.testAuth()
|
|
92
96
|
|
|
93
97
|
expect(user.id).toBe('ME')
|
|
@@ -102,7 +106,7 @@ describe('TeamsClient', () => {
|
|
|
102
106
|
locale: 'en-us',
|
|
103
107
|
})
|
|
104
108
|
|
|
105
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
109
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
106
110
|
const user = await client.testAuth()
|
|
107
111
|
|
|
108
112
|
expect(user.id).toBe('ME')
|
|
@@ -117,7 +121,7 @@ describe('TeamsClient', () => {
|
|
|
117
121
|
test('throws TeamsError on API error', async () => {
|
|
118
122
|
mockResponse({ message: 'Unauthorized', code: 'unauthorized' }, 401)
|
|
119
123
|
|
|
120
|
-
const client = await new TeamsClient().login({ token: 'bad-token' })
|
|
124
|
+
const client = await new TeamsClient().login({ token: 'bad-token', region: 'emea' })
|
|
121
125
|
await expect(client.testAuth()).rejects.toThrow(TeamsError)
|
|
122
126
|
})
|
|
123
127
|
})
|
|
@@ -153,7 +157,7 @@ describe('TeamsClient', () => {
|
|
|
153
157
|
],
|
|
154
158
|
})
|
|
155
159
|
|
|
156
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
160
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
157
161
|
const teams = await client.listTeams()
|
|
158
162
|
|
|
159
163
|
expect(teams).toHaveLength(2)
|
|
@@ -169,7 +173,7 @@ describe('TeamsClient', () => {
|
|
|
169
173
|
test('returns team info', async () => {
|
|
170
174
|
mockResponse({ id: '111', name: 'Test Team', description: 'A test team' })
|
|
171
175
|
|
|
172
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
176
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
173
177
|
const team = await client.getTeam('111')
|
|
174
178
|
|
|
175
179
|
expect(team.id).toBe('111')
|
|
@@ -185,7 +189,7 @@ describe('TeamsClient', () => {
|
|
|
185
189
|
{ id: 'ch2', team_id: '111', name: 'Random', type: 'standard' },
|
|
186
190
|
])
|
|
187
191
|
|
|
188
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
192
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
189
193
|
const channels = await client.listChannels('111')
|
|
190
194
|
|
|
191
195
|
expect(channels).toHaveLength(2)
|
|
@@ -198,7 +202,7 @@ describe('TeamsClient', () => {
|
|
|
198
202
|
test('returns channel info', async () => {
|
|
199
203
|
mockResponse({ id: 'ch1', team_id: '111', name: 'General', type: 'standard' })
|
|
200
204
|
|
|
201
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
205
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
202
206
|
const channel = await client.getChannel('111', 'ch1')
|
|
203
207
|
|
|
204
208
|
expect(channel.id).toBe('ch1')
|
|
@@ -217,7 +221,7 @@ describe('TeamsClient', () => {
|
|
|
217
221
|
timestamp: '2024-01-01T00:00:00.000Z',
|
|
218
222
|
})
|
|
219
223
|
|
|
220
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
224
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
221
225
|
const message = await client.sendMessage('111', 'ch1', 'Hello world')
|
|
222
226
|
|
|
223
227
|
expect(message.content).toBe('Hello world')
|
|
@@ -239,7 +243,7 @@ describe('TeamsClient', () => {
|
|
|
239
243
|
},
|
|
240
244
|
])
|
|
241
245
|
|
|
242
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
246
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
243
247
|
const messages = await client.getMessages('111', 'ch1', 50)
|
|
244
248
|
|
|
245
249
|
expect(messages).toHaveLength(1)
|
|
@@ -252,7 +256,7 @@ describe('TeamsClient', () => {
|
|
|
252
256
|
test('uses default limit of 50', async () => {
|
|
253
257
|
mockResponse([])
|
|
254
258
|
|
|
255
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
259
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
256
260
|
await client.getMessages('111', 'ch1')
|
|
257
261
|
|
|
258
262
|
expect(fetchCalls[0].url).toBe(
|
|
@@ -271,7 +275,7 @@ describe('TeamsClient', () => {
|
|
|
271
275
|
timestamp: '2024-01-01T00:00:00.000Z',
|
|
272
276
|
})
|
|
273
277
|
|
|
274
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
278
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
275
279
|
const message = await client.getMessage('111', 'ch1', 'msg1')
|
|
276
280
|
|
|
277
281
|
expect(message.id).toBe('msg1')
|
|
@@ -285,7 +289,7 @@ describe('TeamsClient', () => {
|
|
|
285
289
|
test('deletes message', async () => {
|
|
286
290
|
mockResponse(null, 204)
|
|
287
291
|
|
|
288
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
292
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
289
293
|
await client.deleteMessage('111', 'ch1', 'msg1')
|
|
290
294
|
|
|
291
295
|
expect(fetchCalls[0].url).toBe(
|
|
@@ -299,7 +303,7 @@ describe('TeamsClient', () => {
|
|
|
299
303
|
test('adds reaction to message', async () => {
|
|
300
304
|
mockResponse(null, 204)
|
|
301
305
|
|
|
302
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
306
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
303
307
|
await client.addReaction('111', 'ch1', 'msg1', 'like')
|
|
304
308
|
|
|
305
309
|
expect(fetchCalls[0].url).toBe(
|
|
@@ -314,7 +318,7 @@ describe('TeamsClient', () => {
|
|
|
314
318
|
test('removes reaction from message', async () => {
|
|
315
319
|
mockResponse(null, 204)
|
|
316
320
|
|
|
317
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
321
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
318
322
|
await client.removeReaction('111', 'ch1', 'msg1', 'like')
|
|
319
323
|
|
|
320
324
|
expect(fetchCalls[0].url).toBe(
|
|
@@ -331,7 +335,7 @@ describe('TeamsClient', () => {
|
|
|
331
335
|
{ id: 'u2', displayName: 'User 2', email: 'user2@example.com' },
|
|
332
336
|
])
|
|
333
337
|
|
|
334
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
338
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
335
339
|
const users = await client.listUsers('111')
|
|
336
340
|
|
|
337
341
|
expect(users).toHaveLength(2)
|
|
@@ -344,7 +348,7 @@ describe('TeamsClient', () => {
|
|
|
344
348
|
test('returns user info', async () => {
|
|
345
349
|
mockResponse({ id: 'u1', displayName: 'Test User', email: 'test@example.com' })
|
|
346
350
|
|
|
347
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
351
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
348
352
|
const user = await client.getUser('u1')
|
|
349
353
|
|
|
350
354
|
expect(user.id).toBe('u1')
|
|
@@ -365,7 +369,7 @@ describe('TeamsClient', () => {
|
|
|
365
369
|
url: 'https://teams.microsoft.com/files/file1',
|
|
366
370
|
})
|
|
367
371
|
|
|
368
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
372
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
369
373
|
const file = await client.uploadFile('111', 'ch1', tempFile)
|
|
370
374
|
|
|
371
375
|
expect(file.name).toBe('test-teams-upload.txt')
|
|
@@ -381,7 +385,7 @@ describe('TeamsClient', () => {
|
|
|
381
385
|
{ id: 'file2', name: 'image.png', size: 2048, url: 'https://example.com/image.png' },
|
|
382
386
|
])
|
|
383
387
|
|
|
384
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
388
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
385
389
|
const files = await client.listFiles('111', 'ch1')
|
|
386
390
|
|
|
387
391
|
expect(files).toHaveLength(2)
|
|
@@ -401,7 +405,7 @@ describe('TeamsClient', () => {
|
|
|
401
405
|
'X-RateLimit-Reset': String(Date.now() / 1000 + 60),
|
|
402
406
|
})
|
|
403
407
|
|
|
404
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
408
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
405
409
|
await client.testAuth()
|
|
406
410
|
|
|
407
411
|
const startTime = Date.now()
|
|
@@ -416,7 +420,7 @@ describe('TeamsClient', () => {
|
|
|
416
420
|
mockResponse({ message: 'Rate limited' }, 429, { 'Retry-After': '0.1' })
|
|
417
421
|
mockResponse({ userDetails: JSON.stringify({ name: 'User' }), locale: 'en-us' })
|
|
418
422
|
|
|
419
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
423
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
420
424
|
const user = await client.testAuth()
|
|
421
425
|
|
|
422
426
|
expect(user.id).toBe('ME')
|
|
@@ -428,7 +432,7 @@ describe('TeamsClient', () => {
|
|
|
428
432
|
mockResponse({ message: 'Rate limited' }, 429, { 'Retry-After': '0.01' })
|
|
429
433
|
}
|
|
430
434
|
|
|
431
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
435
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
432
436
|
await expect(client.testAuth()).rejects.toThrow(TeamsError)
|
|
433
437
|
expect(fetchCalls.length).toBeLessThanOrEqual(4)
|
|
434
438
|
})
|
|
@@ -439,7 +443,7 @@ describe('TeamsClient', () => {
|
|
|
439
443
|
mockResponse({ message: 'Internal Server Error' }, 500)
|
|
440
444
|
mockResponse({ userDetails: JSON.stringify({ name: 'User' }), locale: 'en-us' })
|
|
441
445
|
|
|
442
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
446
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
443
447
|
const user = await client.testAuth()
|
|
444
448
|
|
|
445
449
|
expect(user.id).toBe('ME')
|
|
@@ -449,7 +453,7 @@ describe('TeamsClient', () => {
|
|
|
449
453
|
test('does not retry on 4xx client errors (except 429)', async () => {
|
|
450
454
|
mockResponse({ message: 'Not Found' }, 404)
|
|
451
455
|
|
|
452
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
456
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
453
457
|
await expect(client.testAuth()).rejects.toThrow(TeamsError)
|
|
454
458
|
expect(fetchCalls.length).toBe(1)
|
|
455
459
|
})
|
|
@@ -459,7 +463,7 @@ describe('TeamsClient', () => {
|
|
|
459
463
|
mockResponse({ message: 'Error' }, 500)
|
|
460
464
|
mockResponse({ userDetails: JSON.stringify({ name: 'User' }), locale: 'en-us' })
|
|
461
465
|
|
|
462
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
466
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
463
467
|
const startTime = Date.now()
|
|
464
468
|
await client.testAuth()
|
|
465
469
|
const elapsed = Date.now() - startTime
|
|
@@ -474,7 +478,7 @@ describe('TeamsClient', () => {
|
|
|
474
478
|
mockResponse([])
|
|
475
479
|
mockResponse([])
|
|
476
480
|
|
|
477
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
481
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
478
482
|
await client.getMessages('team1', 'ch1')
|
|
479
483
|
await client.getMessages('team2', 'ch2')
|
|
480
484
|
|
|
@@ -2,7 +2,15 @@ import { readFile } from 'node:fs/promises'
|
|
|
2
2
|
import { basename } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import { TeamsCredentialManager } from './credential-manager'
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
TeamsAccountType,
|
|
7
|
+
TeamsChannel,
|
|
8
|
+
TeamsFile,
|
|
9
|
+
TeamsMessage,
|
|
10
|
+
TeamsRegion,
|
|
11
|
+
TeamsTeam,
|
|
12
|
+
TeamsUser,
|
|
13
|
+
} from './types'
|
|
6
14
|
import { TeamsError } from './types'
|
|
7
15
|
|
|
8
16
|
interface RateLimitBucket {
|
|
@@ -10,18 +18,28 @@ interface RateLimitBucket {
|
|
|
10
18
|
resetAt: number
|
|
11
19
|
}
|
|
12
20
|
|
|
13
|
-
const
|
|
21
|
+
const PERSONAL_MSG_API_BASE = 'https://msgapi.teams.live.com/v1'
|
|
14
22
|
const CSA_API_BASE = 'https://teams.microsoft.com/api'
|
|
15
23
|
const MAX_RETRIES = 3
|
|
16
24
|
const BASE_BACKOFF_MS = 100
|
|
25
|
+
const DEFAULT_REGION: TeamsRegion = 'amer'
|
|
26
|
+
const REGIONS: TeamsRegion[] = ['amer', 'emea', 'apac']
|
|
17
27
|
|
|
18
28
|
export class TeamsClient {
|
|
19
29
|
private token: string | null = null
|
|
20
30
|
private tokenExpiresAt?: Date
|
|
31
|
+
private isPersonalAccount: boolean = false
|
|
32
|
+
private region: TeamsRegion = DEFAULT_REGION
|
|
33
|
+
private regionDiscovered: boolean = false
|
|
21
34
|
private buckets: Map<string, RateLimitBucket> = new Map()
|
|
22
35
|
private globalRateLimitUntil: number = 0
|
|
23
36
|
|
|
24
|
-
async login(credentials?: {
|
|
37
|
+
async login(credentials?: {
|
|
38
|
+
token: string
|
|
39
|
+
tokenExpiresAt?: string
|
|
40
|
+
accountType?: TeamsAccountType
|
|
41
|
+
region?: TeamsRegion
|
|
42
|
+
}): Promise<this> {
|
|
25
43
|
if (credentials) {
|
|
26
44
|
if (!credentials.token) {
|
|
27
45
|
throw new TeamsError('Token is required', 'missing_token')
|
|
@@ -30,6 +48,11 @@ export class TeamsClient {
|
|
|
30
48
|
if (credentials.tokenExpiresAt) {
|
|
31
49
|
this.tokenExpiresAt = new Date(credentials.tokenExpiresAt)
|
|
32
50
|
}
|
|
51
|
+
this.isPersonalAccount = credentials.accountType === 'personal'
|
|
52
|
+
if (credentials.region) {
|
|
53
|
+
this.region = credentials.region
|
|
54
|
+
this.regionDiscovered = true
|
|
55
|
+
}
|
|
33
56
|
return this
|
|
34
57
|
}
|
|
35
58
|
|
|
@@ -43,7 +66,16 @@ export class TeamsClient {
|
|
|
43
66
|
'no_credentials',
|
|
44
67
|
)
|
|
45
68
|
}
|
|
46
|
-
return this.login({
|
|
69
|
+
return this.login({
|
|
70
|
+
token: creds.token,
|
|
71
|
+
tokenExpiresAt: creds.tokenExpiresAt,
|
|
72
|
+
accountType: creds.accountType,
|
|
73
|
+
region: creds.region,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getRegion(): TeamsRegion {
|
|
78
|
+
return this.region
|
|
47
79
|
}
|
|
48
80
|
|
|
49
81
|
private ensureAuth(): string {
|
|
@@ -108,12 +140,47 @@ export class TeamsClient {
|
|
|
108
140
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
109
141
|
}
|
|
110
142
|
|
|
111
|
-
private
|
|
143
|
+
private getMsgApiBase(): string {
|
|
144
|
+
if (this.isPersonalAccount) return PERSONAL_MSG_API_BASE
|
|
145
|
+
return `https://${this.region}.ng.msg.teams.microsoft.com/v1`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private async discoverRegion(): Promise<void> {
|
|
149
|
+
if (this.isPersonalAccount) {
|
|
150
|
+
this.regionDiscovered = true
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const token = this.ensureAuth()
|
|
155
|
+
|
|
156
|
+
for (const region of REGIONS) {
|
|
157
|
+
try {
|
|
158
|
+
const response = await fetch(`https://${region}.ng.msg.teams.microsoft.com/v1/users/ME/properties`, {
|
|
159
|
+
headers: {
|
|
160
|
+
'X-Skypetoken': token,
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
if (response.ok || response.status !== 403) {
|
|
165
|
+
this.region = region
|
|
166
|
+
break
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this.regionDiscovered = true
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private async request<T>(method: string, path: string, body?: unknown, baseUrl?: string): Promise<T> {
|
|
112
175
|
if (this.isTokenExpired()) {
|
|
113
176
|
throw new TeamsError('Token has expired. Run "auth extract" to refresh.', 'token_expired')
|
|
114
177
|
}
|
|
115
178
|
|
|
116
|
-
|
|
179
|
+
if (baseUrl === undefined && !this.regionDiscovered) {
|
|
180
|
+
await this.discoverRegion()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const url = `${baseUrl ?? this.getMsgApiBase()}${path}`
|
|
117
184
|
const bucketKey = this.getBucketKey(method, path)
|
|
118
185
|
|
|
119
186
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
@@ -144,7 +211,7 @@ export class TeamsClient {
|
|
|
144
211
|
const errorBody = (await response.json().catch(() => null)) as {
|
|
145
212
|
message?: string
|
|
146
213
|
} | null
|
|
147
|
-
throw new TeamsError(errorBody?.message
|
|
214
|
+
throw new TeamsError(errorBody?.message || 'Rate limited', 'rate_limited')
|
|
148
215
|
}
|
|
149
216
|
|
|
150
217
|
if (response.status >= 500 && attempt < MAX_RETRIES) {
|
|
@@ -158,7 +225,7 @@ export class TeamsClient {
|
|
|
158
225
|
code?: string | number
|
|
159
226
|
} | null
|
|
160
227
|
throw new TeamsError(
|
|
161
|
-
errorBody?.message
|
|
228
|
+
errorBody?.message || `HTTP ${response.status}`,
|
|
162
229
|
errorBody?.code?.toString() ?? `http_${response.status}`,
|
|
163
230
|
)
|
|
164
231
|
}
|
|
@@ -173,12 +240,16 @@ export class TeamsClient {
|
|
|
173
240
|
throw new TeamsError('Request failed after retries', 'max_retries')
|
|
174
241
|
}
|
|
175
242
|
|
|
176
|
-
private async requestFormData<T>(path: string, formData: FormData, baseUrl
|
|
243
|
+
private async requestFormData<T>(path: string, formData: FormData, baseUrl?: string): Promise<T> {
|
|
177
244
|
if (this.isTokenExpired()) {
|
|
178
245
|
throw new TeamsError('Token has expired. Run "auth extract" to refresh.', 'token_expired')
|
|
179
246
|
}
|
|
180
247
|
|
|
181
|
-
|
|
248
|
+
if (baseUrl === undefined && !this.regionDiscovered) {
|
|
249
|
+
await this.discoverRegion()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const url = `${baseUrl ?? this.getMsgApiBase()}${path}`
|
|
182
253
|
const bucketKey = this.getBucketKey('POST', path)
|
|
183
254
|
|
|
184
255
|
await this.waitForRateLimit(bucketKey)
|
|
@@ -199,7 +270,7 @@ export class TeamsClient {
|
|
|
199
270
|
code?: string | number
|
|
200
271
|
} | null
|
|
201
272
|
throw new TeamsError(
|
|
202
|
-
errorBody?.message
|
|
273
|
+
errorBody?.message || `HTTP ${response.status}`,
|
|
203
274
|
errorBody?.code?.toString() ?? `http_${response.status}`,
|
|
204
275
|
)
|
|
205
276
|
}
|
|
@@ -210,13 +281,14 @@ export class TeamsClient {
|
|
|
210
281
|
async testAuth(): Promise<TeamsUser> {
|
|
211
282
|
interface UserProperties {
|
|
212
283
|
userDetails?: string
|
|
284
|
+
primaryMemberName?: string
|
|
213
285
|
locale?: string
|
|
214
286
|
}
|
|
215
287
|
const props = await this.request<UserProperties>('GET', '/users/ME/properties')
|
|
216
288
|
const userDetails = props.userDetails ? JSON.parse(props.userDetails) : {}
|
|
217
289
|
return {
|
|
218
290
|
id: 'ME',
|
|
219
|
-
displayName: userDetails.name || 'Teams User',
|
|
291
|
+
displayName: userDetails.name || props.primaryMemberName || 'Teams User',
|
|
220
292
|
}
|
|
221
293
|
}
|
|
222
294
|
|
|
@@ -272,7 +344,7 @@ export class TeamsClient {
|
|
|
272
344
|
async sendMessage(teamId: string, channelId: string, content: string): Promise<TeamsMessage> {
|
|
273
345
|
return this.request<TeamsMessage>(
|
|
274
346
|
'POST',
|
|
275
|
-
`/csa/
|
|
347
|
+
`/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages`,
|
|
276
348
|
{ content },
|
|
277
349
|
CSA_API_BASE,
|
|
278
350
|
)
|
|
@@ -281,7 +353,7 @@ export class TeamsClient {
|
|
|
281
353
|
async getMessages(teamId: string, channelId: string, limit: number = 50): Promise<TeamsMessage[]> {
|
|
282
354
|
return this.request<TeamsMessage[]>(
|
|
283
355
|
'GET',
|
|
284
|
-
`/csa/
|
|
356
|
+
`/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages?limit=${limit}`,
|
|
285
357
|
undefined,
|
|
286
358
|
CSA_API_BASE,
|
|
287
359
|
)
|
|
@@ -290,7 +362,7 @@ export class TeamsClient {
|
|
|
290
362
|
async getMessage(teamId: string, channelId: string, messageId: string): Promise<TeamsMessage> {
|
|
291
363
|
return this.request<TeamsMessage>(
|
|
292
364
|
'GET',
|
|
293
|
-
`/csa/
|
|
365
|
+
`/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}`,
|
|
294
366
|
undefined,
|
|
295
367
|
CSA_API_BASE,
|
|
296
368
|
)
|
|
@@ -299,7 +371,7 @@ export class TeamsClient {
|
|
|
299
371
|
async deleteMessage(teamId: string, channelId: string, messageId: string): Promise<void> {
|
|
300
372
|
return this.request<void>(
|
|
301
373
|
'DELETE',
|
|
302
|
-
`/csa/
|
|
374
|
+
`/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}`,
|
|
303
375
|
undefined,
|
|
304
376
|
CSA_API_BASE,
|
|
305
377
|
)
|
|
@@ -308,7 +380,7 @@ export class TeamsClient {
|
|
|
308
380
|
async addReaction(teamId: string, channelId: string, messageId: string, emoji: string): Promise<void> {
|
|
309
381
|
return this.request<void>(
|
|
310
382
|
'POST',
|
|
311
|
-
`/csa/
|
|
383
|
+
`/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}/reactions`,
|
|
312
384
|
{ emoji },
|
|
313
385
|
CSA_API_BASE,
|
|
314
386
|
)
|
|
@@ -317,7 +389,7 @@ export class TeamsClient {
|
|
|
317
389
|
async removeReaction(teamId: string, channelId: string, messageId: string, emoji: string): Promise<void> {
|
|
318
390
|
return this.request<void>(
|
|
319
391
|
'DELETE',
|
|
320
|
-
`/csa/
|
|
392
|
+
`/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}/reactions/${emoji}`,
|
|
321
393
|
undefined,
|
|
322
394
|
CSA_API_BASE,
|
|
323
395
|
)
|
|
@@ -339,7 +411,7 @@ export class TeamsClient {
|
|
|
339
411
|
formData.append('file', new Blob([fileBuffer]), filename)
|
|
340
412
|
|
|
341
413
|
return this.requestFormData<TeamsFile>(
|
|
342
|
-
`/csa/
|
|
414
|
+
`/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/files`,
|
|
343
415
|
formData,
|
|
344
416
|
CSA_API_BASE,
|
|
345
417
|
)
|
|
@@ -348,7 +420,7 @@ export class TeamsClient {
|
|
|
348
420
|
async listFiles(teamId: string, channelId: string): Promise<TeamsFile[]> {
|
|
349
421
|
return this.request<TeamsFile[]>(
|
|
350
422
|
'GET',
|
|
351
|
-
`/csa/
|
|
423
|
+
`/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/files`,
|
|
352
424
|
undefined,
|
|
353
425
|
CSA_API_BASE,
|
|
354
426
|
)
|
|
@@ -12,6 +12,7 @@ let credManagerLoadConfigSpy: ReturnType<typeof spyOn>
|
|
|
12
12
|
let credManagerSaveConfigSpy: ReturnType<typeof spyOn>
|
|
13
13
|
let credManagerClearCredentialsSpy: ReturnType<typeof spyOn>
|
|
14
14
|
let credManagerIsTokenExpiredSpy: ReturnType<typeof spyOn>
|
|
15
|
+
let clientGetRegionSpy: ReturnType<typeof spyOn>
|
|
15
16
|
|
|
16
17
|
beforeEach(() => {
|
|
17
18
|
extractorExtractSpy = spyOn(TeamsTokenExtractor.prototype, 'extract').mockResolvedValue([
|
|
@@ -29,6 +30,8 @@ beforeEach(() => {
|
|
|
29
30
|
{ id: 'team-2', name: 'Team Two' },
|
|
30
31
|
])
|
|
31
32
|
|
|
33
|
+
clientGetRegionSpy = spyOn(TeamsClient.prototype, 'getRegion').mockReturnValue('emea')
|
|
34
|
+
|
|
32
35
|
credManagerLoadConfigSpy = spyOn(TeamsCredentialManager.prototype, 'loadConfig').mockResolvedValue(null)
|
|
33
36
|
|
|
34
37
|
credManagerSaveConfigSpy = spyOn(TeamsCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
|
|
@@ -48,6 +51,7 @@ afterEach(() => {
|
|
|
48
51
|
credManagerSaveConfigSpy?.mockRestore()
|
|
49
52
|
credManagerClearCredentialsSpy?.mockRestore()
|
|
50
53
|
credManagerIsTokenExpiredSpy?.mockRestore()
|
|
54
|
+
clientGetRegionSpy?.mockRestore()
|
|
51
55
|
})
|
|
52
56
|
|
|
53
57
|
test('extract: calls TeamsTokenExtractor', async () => {
|
|
@@ -59,7 +63,7 @@ test('extract: calls TeamsTokenExtractor', async () => {
|
|
|
59
63
|
})
|
|
60
64
|
|
|
61
65
|
test('extract: validates token with TeamsClient', async () => {
|
|
62
|
-
const client = await new TeamsClient().login({ token: 'test-skype-token-123' })
|
|
66
|
+
const client = await new TeamsClient().login({ token: 'test-skype-token-123', region: 'emea' })
|
|
63
67
|
const authInfo = await client.testAuth()
|
|
64
68
|
expect(authInfo).toBeDefined()
|
|
65
69
|
expect(authInfo.id).toBe('user-123')
|
|
@@ -67,7 +71,7 @@ test('extract: validates token with TeamsClient', async () => {
|
|
|
67
71
|
})
|
|
68
72
|
|
|
69
73
|
test('extract: discovers teams', async () => {
|
|
70
|
-
const client = await new TeamsClient().login({ token: 'test-skype-token-123' })
|
|
74
|
+
const client = await new TeamsClient().login({ token: 'test-skype-token-123', region: 'emea' })
|
|
71
75
|
const teams = await client.listTeams()
|
|
72
76
|
expect(teams).toHaveLength(2)
|
|
73
77
|
expect(teams[0].id).toBe('team-1')
|
|
@@ -16,7 +16,8 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
|
|
|
16
16
|
return
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const debugLog = options.debug ? (msg: string) => debug(`[debug] ${msg}`) : undefined
|
|
20
|
+
const extractor = new TeamsTokenExtractor(undefined, undefined, debugLog)
|
|
20
21
|
|
|
21
22
|
if (process.platform === 'darwin') {
|
|
22
23
|
console.log('')
|
|
@@ -72,7 +73,7 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
try {
|
|
75
|
-
const client = await new TeamsClient().login({ token })
|
|
76
|
+
const client = await new TeamsClient().login({ token, accountType })
|
|
76
77
|
const authInfo = await client.testAuth()
|
|
77
78
|
const teams = await client.listTeams()
|
|
78
79
|
|
|
@@ -88,6 +89,7 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
|
|
|
88
89
|
const account: TeamsAccount = {
|
|
89
90
|
token,
|
|
90
91
|
token_expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
|
92
|
+
region: client.getRegion(),
|
|
91
93
|
account_type: accountType,
|
|
92
94
|
user_name: authInfo.displayName,
|
|
93
95
|
current_team: teams[0]?.id ?? null,
|
|
@@ -191,6 +193,7 @@ async function extractManualToken(token: string, options: { pretty?: boolean; de
|
|
|
191
193
|
const account: TeamsAccount = {
|
|
192
194
|
token,
|
|
193
195
|
token_expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
|
196
|
+
region: client.getRegion(),
|
|
194
197
|
account_type: accountType,
|
|
195
198
|
user_name: authInfo.displayName,
|
|
196
199
|
current_team: teams[0].id,
|
|
@@ -295,6 +298,8 @@ export async function statusAction(options: { pretty?: boolean }): Promise<void>
|
|
|
295
298
|
const client = await new TeamsClient().login({
|
|
296
299
|
token: account.token,
|
|
297
300
|
tokenExpiresAt: account.token_expires_at ?? undefined,
|
|
301
|
+
accountType: account.account_type,
|
|
302
|
+
region: account.region,
|
|
298
303
|
})
|
|
299
304
|
const authInfo = await client.testAuth()
|
|
300
305
|
displayName = authInfo.displayName
|
|
@@ -67,7 +67,7 @@ afterEach(() => {
|
|
|
67
67
|
|
|
68
68
|
test('list: returns channels from team', async () => {
|
|
69
69
|
// given
|
|
70
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
70
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
71
71
|
|
|
72
72
|
// when
|
|
73
73
|
const channels = await client.listChannels('team-1')
|
|
@@ -80,7 +80,7 @@ test('list: returns channels from team', async () => {
|
|
|
80
80
|
|
|
81
81
|
test('list: includes channel metadata', async () => {
|
|
82
82
|
// given
|
|
83
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
83
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
84
84
|
const channels = await client.listChannels('team-1')
|
|
85
85
|
|
|
86
86
|
// when
|
|
@@ -95,7 +95,7 @@ test('list: includes channel metadata', async () => {
|
|
|
95
95
|
|
|
96
96
|
test('info: returns channel details', async () => {
|
|
97
97
|
// given
|
|
98
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
98
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
99
99
|
|
|
100
100
|
// when
|
|
101
101
|
const channel = await client.getChannel('team-1', 'ch-1')
|
|
@@ -108,7 +108,7 @@ test('info: returns channel details', async () => {
|
|
|
108
108
|
|
|
109
109
|
test('info: throws error for non-existent channel', async () => {
|
|
110
110
|
// given
|
|
111
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
111
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
112
112
|
|
|
113
113
|
// when/then
|
|
114
114
|
try {
|
|
@@ -121,7 +121,7 @@ test('info: throws error for non-existent channel', async () => {
|
|
|
121
121
|
|
|
122
122
|
test('history: returns messages', async () => {
|
|
123
123
|
// given
|
|
124
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
124
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
125
125
|
|
|
126
126
|
// when
|
|
127
127
|
const messages = await client.getMessages('team-1', 'ch-1', 50)
|
|
@@ -136,7 +136,7 @@ test('history: returns messages', async () => {
|
|
|
136
136
|
|
|
137
137
|
test('history: includes message metadata', async () => {
|
|
138
138
|
// given
|
|
139
|
-
const client = await new TeamsClient().login({ token: 'test-token' })
|
|
139
|
+
const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
|
|
140
140
|
const messages = await client.getMessages('team-1', 'ch-1', 50)
|
|
141
141
|
|
|
142
142
|
// when
|