agent-messenger 2.23.1 → 2.23.3
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/webex/client.d.ts +18 -0
- package/dist/src/platforms/webex/client.d.ts.map +1 -1
- package/dist/src/platforms/webex/client.js +202 -49
- 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 +9 -6
- package/dist/src/platforms/webex/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/commands/member.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/member.js +2 -0
- package/dist/src/platforms/webex/commands/member.js.map +1 -1
- package/dist/src/platforms/webex/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/message.js +2 -0
- package/dist/src/platforms/webex/commands/message.js.map +1 -1
- package/dist/src/platforms/webex/commands/snapshot.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/snapshot.js +3 -1
- package/dist/src/platforms/webex/commands/snapshot.js.map +1 -1
- package/dist/src/platforms/webex/commands/space.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/space.js +5 -0
- package/dist/src/platforms/webex/commands/space.js.map +1 -1
- package/dist/src/platforms/webex/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/whoami.js +2 -0
- package/dist/src/platforms/webex/commands/whoami.js.map +1 -1
- package/dist/src/platforms/webex/id-normalizer.d.ts +11 -0
- package/dist/src/platforms/webex/id-normalizer.d.ts.map +1 -1
- package/dist/src/platforms/webex/id-normalizer.js +102 -20
- package/dist/src/platforms/webex/id-normalizer.js.map +1 -1
- package/dist/src/platforms/webex/index.d.ts +2 -2
- package/dist/src/platforms/webex/index.d.ts.map +1 -1
- package/dist/src/platforms/webex/index.js +1 -1
- package/dist/src/platforms/webex/index.js.map +1 -1
- package/dist/src/platforms/webex/types.d.ts +20 -0
- package/dist/src/platforms/webex/types.d.ts.map +1 -1
- package/dist/src/platforms/webex/types.js +10 -0
- package/dist/src/platforms/webex/types.js.map +1 -1
- package/dist/src/platforms/webexbot/client.d.ts +0 -4
- package/dist/src/platforms/webexbot/client.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/client.js +8 -65
- package/dist/src/platforms/webexbot/client.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/file.d.ts +2 -0
- package/dist/src/platforms/webexbot/commands/file.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/file.js +2 -0
- package/dist/src/platforms/webexbot/commands/file.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/member.d.ts +2 -0
- package/dist/src/platforms/webexbot/commands/member.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/member.js +2 -0
- package/dist/src/platforms/webexbot/commands/member.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/message.d.ts +4 -0
- package/dist/src/platforms/webexbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/message.js +6 -0
- package/dist/src/platforms/webexbot/commands/message.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/snapshot.d.ts +2 -0
- package/dist/src/platforms/webexbot/commands/snapshot.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/snapshot.js +10 -2
- package/dist/src/platforms/webexbot/commands/snapshot.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/space.d.ts +4 -0
- package/dist/src/platforms/webexbot/commands/space.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/space.js +5 -0
- package/dist/src/platforms/webexbot/commands/space.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/user.d.ts +3 -0
- package/dist/src/platforms/webexbot/commands/user.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/user.js +3 -0
- package/dist/src/platforms/webexbot/commands/user.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/whoami.d.ts +2 -0
- package/dist/src/platforms/webexbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/whoami.js +2 -0
- package/dist/src/platforms/webexbot/commands/whoami.js.map +1 -1
- package/dist/src/platforms/webexbot/index.d.ts +2 -2
- package/dist/src/platforms/webexbot/index.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/index.js +1 -1
- package/dist/src/platforms/webexbot/index.js.map +1 -1
- package/dist/src/tui/adapters/types.d.ts +3 -0
- package/dist/src/tui/adapters/types.d.ts.map +1 -1
- package/dist/src/tui/adapters/webex-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/webex-adapter.js +4 -0
- package/dist/src/tui/adapters/webex-adapter.js.map +1 -1
- package/docs/content/docs/cli/webex.mdx +2 -2
- 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-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +3 -3
- package/skills/agent-webexbot/SKILL.md +2 -2
- package/skills/agent-webexbot/references/common-patterns.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/webex/client.test.ts +94 -6
- package/src/platforms/webex/client.ts +226 -44
- package/src/platforms/webex/commands/auth.test.ts +3 -1
- package/src/platforms/webex/commands/auth.ts +12 -7
- package/src/platforms/webex/commands/member.test.ts +24 -8
- package/src/platforms/webex/commands/member.ts +2 -0
- package/src/platforms/webex/commands/message.test.ts +37 -23
- package/src/platforms/webex/commands/message.ts +2 -0
- package/src/platforms/webex/commands/snapshot.test.ts +18 -10
- package/src/platforms/webex/commands/snapshot.ts +3 -1
- package/src/platforms/webex/commands/space.test.ts +36 -17
- package/src/platforms/webex/commands/space.ts +5 -0
- package/src/platforms/webex/commands/whoami.test.ts +16 -6
- package/src/platforms/webex/commands/whoami.ts +2 -0
- package/src/platforms/webex/id-normalizer.test.ts +282 -2
- package/src/platforms/webex/id-normalizer.ts +112 -20
- package/src/platforms/webex/index.ts +2 -2
- package/src/platforms/webex/listener.test.ts +3 -0
- package/src/platforms/webex/types.test.ts +20 -0
- package/src/platforms/webex/types.ts +20 -0
- package/src/platforms/webex/typings/webex-message-handler.d.ts +40 -2
- package/src/platforms/webexbot/client.ts +8 -74
- package/src/platforms/webexbot/commands/file.ts +4 -0
- package/src/platforms/webexbot/commands/member.ts +4 -0
- package/src/platforms/webexbot/commands/message.ts +10 -0
- package/src/platforms/webexbot/commands/snapshot.ts +12 -2
- package/src/platforms/webexbot/commands/space.ts +9 -0
- package/src/platforms/webexbot/commands/user.test.ts +15 -5
- package/src/platforms/webexbot/commands/user.ts +6 -0
- package/src/platforms/webexbot/commands/whoami.ts +4 -0
- package/src/platforms/webexbot/index.ts +2 -2
- package/src/tui/adapters/types.ts +3 -0
- package/src/tui/adapters/webex-adapter.ts +4 -0
|
@@ -16,8 +16,13 @@ import {
|
|
|
16
16
|
normalizeMembership,
|
|
17
17
|
normalizeMessage,
|
|
18
18
|
normalizeRoomActivity,
|
|
19
|
+
normalizeSdkMembership,
|
|
20
|
+
normalizeSdkMessage,
|
|
21
|
+
normalizeSdkPerson,
|
|
19
22
|
toRestId,
|
|
23
|
+
toRef,
|
|
20
24
|
} from './id-normalizer'
|
|
25
|
+
import type { WebexMembership, WebexMessage, WebexPerson } from './types'
|
|
21
26
|
|
|
22
27
|
const RAW: MercuryActivity = {
|
|
23
28
|
id: 'activity-uuid',
|
|
@@ -28,6 +33,8 @@ const RAW: MercuryActivity = {
|
|
|
28
33
|
published: '2024-01-01T00:00:00Z',
|
|
29
34
|
}
|
|
30
35
|
|
|
36
|
+
const restId = (type: string, ref: string) => Buffer.from(`ciscospark://us/${type}/${ref}`).toString('base64url')
|
|
37
|
+
|
|
31
38
|
describe('toRestId / fromRestId', () => {
|
|
32
39
|
it('encodes a uuid into a ciscospark REST id round-trippable to the uuid', () => {
|
|
33
40
|
const restId = toRestId('abc-123', 'PEOPLE')
|
|
@@ -58,6 +65,40 @@ describe('toRestId / fromRestId', () => {
|
|
|
58
65
|
})
|
|
59
66
|
})
|
|
60
67
|
|
|
68
|
+
describe('toRef', () => {
|
|
69
|
+
it('returns a room uuid ref', () => {
|
|
70
|
+
const uuid = '12345678-1234-1234-1234-1234567890ab'
|
|
71
|
+
|
|
72
|
+
expect(toRef(toRestId(uuid, 'ROOM'))).toBe(uuid)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('returns a person uuid ref', () => {
|
|
76
|
+
const uuid = '22222222-2222-2222-2222-222222222222'
|
|
77
|
+
|
|
78
|
+
expect(toRef(toRestId(uuid, 'PEOPLE'))).toBe(uuid)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('returns a legacy person email ref', () => {
|
|
82
|
+
expect(toRef(toRestId('legacy@example.com', 'PEOPLE'))).toBe('legacy@example.com')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('returns an organization uuid ref', () => {
|
|
86
|
+
const uuid = '33333333-3333-3333-3333-333333333333'
|
|
87
|
+
|
|
88
|
+
expect(toRef(restId('ORGANIZATION', uuid))).toBe(uuid)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('returns a membership person-room pair ref', () => {
|
|
92
|
+
const membershipRef = '44444444-4444-4444-4444-444444444444:55555555-5555-5555-5555-555555555555'
|
|
93
|
+
|
|
94
|
+
expect(toRef(restId('MEMBERSHIP', membershipRef))).toBe(membershipRef)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('returns empty input unchanged', () => {
|
|
98
|
+
expect(toRef('')).toBe('')
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
61
102
|
describe('normalizeMessage', () => {
|
|
62
103
|
const message: DecryptedMessage = {
|
|
63
104
|
id: 'msg-uuid',
|
|
@@ -85,6 +126,23 @@ describe('normalizeMessage', () => {
|
|
|
85
126
|
expect(result.mentionedPeople).toEqual([toRestId('mention-uuid-1', 'PEOPLE'), toRestId('mention-uuid-2', 'PEOPLE')])
|
|
86
127
|
})
|
|
87
128
|
|
|
129
|
+
it('adds a raw uuid ref alongside every id', () => {
|
|
130
|
+
const result = normalizeMessage(message)
|
|
131
|
+
|
|
132
|
+
expect(result.ref).toBe('msg-uuid')
|
|
133
|
+
expect(result.parentRef).toBe('parent-uuid')
|
|
134
|
+
expect(result.roomRef).toBe('room-uuid')
|
|
135
|
+
expect(result.personRef).toBe('person-uuid')
|
|
136
|
+
expect(result.mentionedPeopleRefs).toEqual(['mention-uuid-1', 'mention-uuid-2'])
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('omits parentRef when parentId is absent', () => {
|
|
140
|
+
const { parentId: _omit, ...withoutParent } = message
|
|
141
|
+
const result = normalizeMessage(withoutParent)
|
|
142
|
+
|
|
143
|
+
expect(result.parentRef).toBeUndefined()
|
|
144
|
+
})
|
|
145
|
+
|
|
88
146
|
it('leaves non-id fields and raw untouched', () => {
|
|
89
147
|
const result = normalizeMessage(message)
|
|
90
148
|
|
|
@@ -120,8 +178,11 @@ describe('normalizeDeletedMessage', () => {
|
|
|
120
178
|
|
|
121
179
|
expect(normalizeDeletedMessage(deleted)).toEqual({
|
|
122
180
|
messageId: toRestId('msg-uuid', 'MESSAGE'),
|
|
181
|
+
messageRef: 'msg-uuid',
|
|
123
182
|
roomId: toRestId('room-uuid', 'ROOM'),
|
|
183
|
+
roomRef: 'room-uuid',
|
|
124
184
|
personId: toRestId('person-uuid', 'PEOPLE'),
|
|
185
|
+
personRef: 'person-uuid',
|
|
125
186
|
})
|
|
126
187
|
})
|
|
127
188
|
})
|
|
@@ -146,6 +207,25 @@ describe('normalizeMembership', () => {
|
|
|
146
207
|
expect(result.roomId).toBe(toRestId('room-uuid', 'ROOM'))
|
|
147
208
|
expect(result.raw).toBe(RAW)
|
|
148
209
|
})
|
|
210
|
+
|
|
211
|
+
it('sets ref to the raw activity id and adds refs for the rest', () => {
|
|
212
|
+
const membership: MembershipActivity = {
|
|
213
|
+
id: 'activity-uuid',
|
|
214
|
+
actorId: 'actor-uuid',
|
|
215
|
+
personId: 'member-uuid',
|
|
216
|
+
roomId: 'room-uuid',
|
|
217
|
+
action: 'add',
|
|
218
|
+
created: '2024-01-01T00:00:00Z',
|
|
219
|
+
raw: RAW,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const result = normalizeMembership(membership)
|
|
223
|
+
|
|
224
|
+
expect(result.ref).toBe('activity-uuid')
|
|
225
|
+
expect(result.actorRef).toBe('actor-uuid')
|
|
226
|
+
expect(result.personRef).toBe('member-uuid')
|
|
227
|
+
expect(result.roomRef).toBe('room-uuid')
|
|
228
|
+
})
|
|
149
229
|
})
|
|
150
230
|
|
|
151
231
|
describe('normalizeAttachmentAction', () => {
|
|
@@ -170,7 +250,27 @@ describe('normalizeAttachmentAction', () => {
|
|
|
170
250
|
expect(result.inputs).toEqual({ choice: 'yes' })
|
|
171
251
|
})
|
|
172
252
|
|
|
173
|
-
it('
|
|
253
|
+
it('adds a raw uuid ref alongside every id', () => {
|
|
254
|
+
const action: AttachmentAction = {
|
|
255
|
+
id: 'action-uuid',
|
|
256
|
+
messageId: 'msg-uuid',
|
|
257
|
+
personId: 'person-uuid',
|
|
258
|
+
personEmail: 'user@example.com',
|
|
259
|
+
roomId: 'room-uuid',
|
|
260
|
+
inputs: { choice: 'yes' },
|
|
261
|
+
created: '2024-01-01T00:00:00Z',
|
|
262
|
+
raw: RAW,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const result = normalizeAttachmentAction(action)
|
|
266
|
+
|
|
267
|
+
expect(result.ref).toBe('action-uuid')
|
|
268
|
+
expect(result.messageRef).toBe('msg-uuid')
|
|
269
|
+
expect(result.personRef).toBe('person-uuid')
|
|
270
|
+
expect(result.roomRef).toBe('room-uuid')
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('preserves an empty messageId and its ref', () => {
|
|
174
274
|
const action: AttachmentAction = {
|
|
175
275
|
id: 'action-uuid',
|
|
176
276
|
messageId: '',
|
|
@@ -182,7 +282,9 @@ describe('normalizeAttachmentAction', () => {
|
|
|
182
282
|
raw: RAW,
|
|
183
283
|
}
|
|
184
284
|
|
|
185
|
-
|
|
285
|
+
const result = normalizeAttachmentAction(action)
|
|
286
|
+
expect(result.messageId).toBe('')
|
|
287
|
+
expect(result.messageRef).toBe('')
|
|
186
288
|
})
|
|
187
289
|
})
|
|
188
290
|
|
|
@@ -204,4 +306,182 @@ describe('normalizeRoomActivity', () => {
|
|
|
204
306
|
expect(result.actorId).toBe(toRestId('actor-uuid', 'PEOPLE'))
|
|
205
307
|
expect(result.raw).toBe(RAW)
|
|
206
308
|
})
|
|
309
|
+
|
|
310
|
+
it('sets ref to the raw activity id and adds refs for the rest', () => {
|
|
311
|
+
const room: RoomActivity = {
|
|
312
|
+
id: 'activity-uuid',
|
|
313
|
+
roomId: 'room-uuid',
|
|
314
|
+
actorId: 'actor-uuid',
|
|
315
|
+
action: 'created',
|
|
316
|
+
created: '2024-01-01T00:00:00Z',
|
|
317
|
+
raw: RAW,
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const result = normalizeRoomActivity(room)
|
|
321
|
+
|
|
322
|
+
expect(result.ref).toBe('activity-uuid')
|
|
323
|
+
expect(result.roomRef).toBe('room-uuid')
|
|
324
|
+
expect(result.actorRef).toBe('actor-uuid')
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
describe('normalizeSdkPerson', () => {
|
|
329
|
+
const person: WebexPerson = {
|
|
330
|
+
id: restId('PEOPLE', 'person-uuid'),
|
|
331
|
+
ref: '',
|
|
332
|
+
emails: ['user@example.com'],
|
|
333
|
+
displayName: 'User',
|
|
334
|
+
orgId: restId('ORGANIZATION', 'org-uuid'),
|
|
335
|
+
orgRef: '',
|
|
336
|
+
type: 'person',
|
|
337
|
+
created: '2024-01-01T00:00:00Z',
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
it('decodes the bot identity id and orgId into raw uuid refs', () => {
|
|
341
|
+
const result = normalizeSdkPerson(person)
|
|
342
|
+
|
|
343
|
+
expect(result.ref).toBe('person-uuid')
|
|
344
|
+
expect(result.orgRef).toBe('org-uuid')
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('does not re-encode the already-REST ids', () => {
|
|
348
|
+
const result = normalizeSdkPerson(person)
|
|
349
|
+
|
|
350
|
+
expect(result.id).toBe(restId('PEOPLE', 'person-uuid'))
|
|
351
|
+
expect(result.orgId).toBe(restId('ORGANIZATION', 'org-uuid'))
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('leaves empty ids and refs empty', () => {
|
|
355
|
+
const result = normalizeSdkPerson({ ...person, id: '', orgId: '' })
|
|
356
|
+
|
|
357
|
+
expect(result.ref).toBe('')
|
|
358
|
+
expect(result.orgRef).toBe('')
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('returns a new object without mutating the input', () => {
|
|
362
|
+
const result = normalizeSdkPerson(person)
|
|
363
|
+
|
|
364
|
+
expect(result).not.toBe(person)
|
|
365
|
+
expect(person.ref).toBe('')
|
|
366
|
+
})
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
describe('normalizeSdkMessage', () => {
|
|
370
|
+
const message: WebexMessage = {
|
|
371
|
+
id: restId('MESSAGE', 'msg-uuid'),
|
|
372
|
+
ref: '',
|
|
373
|
+
roomId: restId('ROOM', 'room-uuid'),
|
|
374
|
+
roomRef: '',
|
|
375
|
+
roomType: 'group',
|
|
376
|
+
text: 'hello',
|
|
377
|
+
personId: restId('PEOPLE', 'person-uuid'),
|
|
378
|
+
personRef: '',
|
|
379
|
+
personEmail: 'user@example.com',
|
|
380
|
+
created: '2024-01-01T00:00:00Z',
|
|
381
|
+
parentId: restId('MESSAGE', 'parent-uuid'),
|
|
382
|
+
mentionedPeople: [restId('PEOPLE', 'mention-1'), restId('PEOPLE', 'mention-2')],
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
it('decodes id, roomId, personId and parentId into raw uuid refs', () => {
|
|
386
|
+
const result = normalizeSdkMessage(message)
|
|
387
|
+
|
|
388
|
+
expect(result.ref).toBe('msg-uuid')
|
|
389
|
+
expect(result.roomRef).toBe('room-uuid')
|
|
390
|
+
expect(result.personRef).toBe('person-uuid')
|
|
391
|
+
expect(result.parentRef).toBe('parent-uuid')
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('adds a mentionedPeopleRefs array of raw uuids', () => {
|
|
395
|
+
const result = normalizeSdkMessage(message)
|
|
396
|
+
|
|
397
|
+
expect(result.mentionedPeopleRefs).toEqual(['mention-1', 'mention-2'])
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
it('omits parentRef when parentId is absent', () => {
|
|
401
|
+
const { parentId: _omit, ...withoutParent } = message
|
|
402
|
+
const result = normalizeSdkMessage(withoutParent)
|
|
403
|
+
|
|
404
|
+
expect(result.parentRef).toBeUndefined()
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
it('omits mentionedPeopleRefs when mentionedPeople is absent', () => {
|
|
408
|
+
const { mentionedPeople: _omit, ...withoutMentions } = message
|
|
409
|
+
const result = normalizeSdkMessage(withoutMentions)
|
|
410
|
+
|
|
411
|
+
expect(result.mentionedPeopleRefs).toBeUndefined()
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
it('does not re-encode the already-REST ids', () => {
|
|
415
|
+
const result = normalizeSdkMessage(message)
|
|
416
|
+
|
|
417
|
+
expect(result.id).toBe(restId('MESSAGE', 'msg-uuid'))
|
|
418
|
+
expect(result.roomId).toBe(restId('ROOM', 'room-uuid'))
|
|
419
|
+
})
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
describe('normalizeSdkMembership', () => {
|
|
423
|
+
const membership: WebexMembership = {
|
|
424
|
+
id: restId('MEMBERSHIP', 'membership-uuid'),
|
|
425
|
+
ref: '',
|
|
426
|
+
roomId: restId('ROOM', 'room-uuid'),
|
|
427
|
+
roomRef: '',
|
|
428
|
+
personId: restId('PEOPLE', 'person-uuid'),
|
|
429
|
+
personRef: '',
|
|
430
|
+
personEmail: 'user@example.com',
|
|
431
|
+
personDisplayName: 'User',
|
|
432
|
+
isModerator: false,
|
|
433
|
+
created: '2024-01-01T00:00:00Z',
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
it('decodes id, roomId and personId into raw uuid refs', () => {
|
|
437
|
+
const result = normalizeSdkMembership(membership)
|
|
438
|
+
|
|
439
|
+
expect(result.ref).toBe('membership-uuid')
|
|
440
|
+
expect(result.roomRef).toBe('room-uuid')
|
|
441
|
+
expect(result.personRef).toBe('person-uuid')
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
it('does not re-encode the already-REST ids', () => {
|
|
445
|
+
const result = normalizeSdkMembership(membership)
|
|
446
|
+
|
|
447
|
+
expect(result.id).toBe(restId('MEMBERSHIP', 'membership-uuid'))
|
|
448
|
+
expect(result.personId).toBe(restId('PEOPLE', 'person-uuid'))
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
describe('SDK and event refs agree for the same person', () => {
|
|
453
|
+
it('matches the bot identity ref against an event mention ref so self/mention detection holds', () => {
|
|
454
|
+
const personUuid = 'bot-person-uuid'
|
|
455
|
+
|
|
456
|
+
// given: the bot identity from a REST response (testAuth path)
|
|
457
|
+
const bot = normalizeSdkPerson({
|
|
458
|
+
id: restId('PEOPLE', personUuid),
|
|
459
|
+
ref: '',
|
|
460
|
+
emails: ['bot@webex.bot'],
|
|
461
|
+
displayName: 'Bot',
|
|
462
|
+
orgId: restId('ORGANIZATION', 'org-uuid'),
|
|
463
|
+
orgRef: '',
|
|
464
|
+
type: 'bot',
|
|
465
|
+
created: '2024-01-01T00:00:00Z',
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
// when: an event carries the same person as a raw Mercury uuid
|
|
469
|
+
const event = normalizeMessage({
|
|
470
|
+
id: 'msg-uuid',
|
|
471
|
+
roomId: 'room-uuid',
|
|
472
|
+
personId: personUuid,
|
|
473
|
+
personEmail: 'bot@webex.bot',
|
|
474
|
+
text: 'hi',
|
|
475
|
+
created: '2024-01-01T00:00:00Z',
|
|
476
|
+
mentionedPeople: [personUuid],
|
|
477
|
+
mentionedGroups: [],
|
|
478
|
+
files: [],
|
|
479
|
+
raw: RAW,
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
// then: both paths decode to the same raw uuid ref
|
|
483
|
+
expect(bot.ref).toBe(personUuid)
|
|
484
|
+
expect(event.personRef).toBe(personUuid)
|
|
485
|
+
expect(event.mentionedPeopleRefs.includes(bot.ref)).toBe(true)
|
|
486
|
+
})
|
|
207
487
|
})
|
|
@@ -7,12 +7,37 @@ import type {
|
|
|
7
7
|
RoomActivity,
|
|
8
8
|
} from 'webex-message-handler'
|
|
9
9
|
|
|
10
|
+
import type { WebexMembership, WebexMessage, WebexPerson } from './types'
|
|
11
|
+
|
|
10
12
|
export { fromRestId }
|
|
11
13
|
|
|
12
14
|
// Superset of webex-message-handler's toRestId union, which omits ATTACHMENT_ACTION
|
|
13
15
|
// (the resource type behind GET /v1/attachment/actions).
|
|
14
16
|
export type WebexRestIdType = 'MESSAGE' | 'PEOPLE' | 'ROOM' | 'ATTACHMENT_ACTION'
|
|
15
17
|
|
|
18
|
+
export interface DecodedWebexId {
|
|
19
|
+
cluster: string
|
|
20
|
+
type: string
|
|
21
|
+
uuid: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function toRef(id: string): string {
|
|
25
|
+
// fromRestId throws on values that are not base64-encoded ciscospark ids; fail
|
|
26
|
+
// open so a non-REST id (legacy/sentinel) yields the id itself instead of crashing.
|
|
27
|
+
if (!id || !decodeWebexId(id)) return id
|
|
28
|
+
return fromRestId(id)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Webex REST ids are base64(url) of `ciscospark://<cluster>/<TYPE>/<uuid>`; room
|
|
32
|
+
// cluster correction needs all three parts, not just the trailing ref value.
|
|
33
|
+
export function decodeWebexId(restId: string): DecodedWebexId | null {
|
|
34
|
+
if (!restId) return null
|
|
35
|
+
const decoded = Buffer.from(restId, 'base64').toString('utf-8')
|
|
36
|
+
const match = decoded.match(/^ciscospark:\/\/([^/]+)\/([^/]+)\/(.+)$/)
|
|
37
|
+
if (!match) return null
|
|
38
|
+
return { cluster: match[1], type: match[2], uuid: match[3] }
|
|
39
|
+
}
|
|
40
|
+
|
|
16
41
|
/**
|
|
17
42
|
* Encode a raw Mercury UUID as a Webex REST ID. Empty input is returned unchanged
|
|
18
43
|
* so an absent ID never becomes a bogus `ciscospark://us/{TYPE}/` value.
|
|
@@ -27,50 +52,117 @@ export function toRestId(uuid: string, type: WebexRestIdType): string {
|
|
|
27
52
|
}
|
|
28
53
|
|
|
29
54
|
export function normalizeMessage(message: DecryptedMessage): DecryptedMessage {
|
|
55
|
+
const id = toRestId(message.id, 'MESSAGE')
|
|
56
|
+
const parentId = message.parentId ? toRestId(message.parentId, 'MESSAGE') : message.parentId
|
|
57
|
+
const roomId = toRestId(message.roomId, 'ROOM')
|
|
58
|
+
const personId = toRestId(message.personId, 'PEOPLE')
|
|
59
|
+
const mentionedPeople = message.mentionedPeople.map((person) => toRestId(person, 'PEOPLE'))
|
|
30
60
|
return {
|
|
31
61
|
...message,
|
|
32
|
-
id
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
62
|
+
id,
|
|
63
|
+
ref: toRef(id),
|
|
64
|
+
parentId,
|
|
65
|
+
parentRef: parentId ? toRef(parentId) : parentId,
|
|
66
|
+
roomId,
|
|
67
|
+
roomRef: toRef(roomId),
|
|
68
|
+
personId,
|
|
69
|
+
personRef: toRef(personId),
|
|
70
|
+
mentionedPeople,
|
|
71
|
+
mentionedPeopleRefs: mentionedPeople.map(toRef),
|
|
37
72
|
}
|
|
38
73
|
}
|
|
39
74
|
|
|
40
75
|
export function normalizeDeletedMessage(message: DeletedMessage): DeletedMessage {
|
|
76
|
+
const messageId = toRestId(message.messageId, 'MESSAGE')
|
|
77
|
+
const roomId = toRestId(message.roomId, 'ROOM')
|
|
78
|
+
const personId = toRestId(message.personId, 'PEOPLE')
|
|
41
79
|
return {
|
|
42
|
-
messageId
|
|
43
|
-
|
|
44
|
-
|
|
80
|
+
messageId,
|
|
81
|
+
messageRef: toRef(messageId),
|
|
82
|
+
roomId,
|
|
83
|
+
roomRef: toRef(roomId),
|
|
84
|
+
personId,
|
|
85
|
+
personRef: toRef(personId),
|
|
45
86
|
}
|
|
46
87
|
}
|
|
47
88
|
|
|
48
89
|
export function normalizeMembership(activity: MembershipActivity): MembershipActivity {
|
|
49
|
-
// `id` stays raw: it is a Mercury activity UUID, not a REST membership ID
|
|
90
|
+
// `id` stays raw: it is a Mercury activity UUID, not a REST membership ID, so
|
|
91
|
+
// its ref is the id itself rather than a decoded REST id.
|
|
92
|
+
const actorId = toRestId(activity.actorId, 'PEOPLE')
|
|
93
|
+
const personId = toRestId(activity.personId, 'PEOPLE')
|
|
94
|
+
const roomId = toRestId(activity.roomId, 'ROOM')
|
|
50
95
|
return {
|
|
51
96
|
...activity,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
97
|
+
ref: activity.id,
|
|
98
|
+
actorId,
|
|
99
|
+
actorRef: toRef(actorId),
|
|
100
|
+
personId,
|
|
101
|
+
personRef: toRef(personId),
|
|
102
|
+
roomId,
|
|
103
|
+
roomRef: toRef(roomId),
|
|
55
104
|
}
|
|
56
105
|
}
|
|
57
106
|
|
|
58
107
|
export function normalizeAttachmentAction(action: AttachmentAction): AttachmentAction {
|
|
108
|
+
const id = toRestId(action.id, 'ATTACHMENT_ACTION')
|
|
109
|
+
const messageId = action.messageId ? toRestId(action.messageId, 'MESSAGE') : action.messageId
|
|
110
|
+
const personId = toRestId(action.personId, 'PEOPLE')
|
|
111
|
+
const roomId = toRestId(action.roomId, 'ROOM')
|
|
59
112
|
return {
|
|
60
113
|
...action,
|
|
61
|
-
id
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
114
|
+
id,
|
|
115
|
+
ref: toRef(id),
|
|
116
|
+
messageId,
|
|
117
|
+
messageRef: toRef(messageId),
|
|
118
|
+
personId,
|
|
119
|
+
personRef: toRef(personId),
|
|
120
|
+
roomId,
|
|
121
|
+
roomRef: toRef(roomId),
|
|
65
122
|
}
|
|
66
123
|
}
|
|
67
124
|
|
|
68
125
|
export function normalizeRoomActivity(activity: RoomActivity): RoomActivity {
|
|
69
|
-
// `id` stays raw: the Mercury conversation activity UUID has no
|
|
70
|
-
//
|
|
126
|
+
// `id` stays raw: the Mercury conversation activity UUID has no consumer-facing
|
|
127
|
+
// REST resource, so its ref is the id itself (the comparable REST id is `roomId`).
|
|
128
|
+
const roomId = toRestId(activity.roomId, 'ROOM')
|
|
129
|
+
const actorId = toRestId(activity.actorId, 'PEOPLE')
|
|
71
130
|
return {
|
|
72
131
|
...activity,
|
|
73
|
-
|
|
74
|
-
|
|
132
|
+
ref: activity.id,
|
|
133
|
+
roomId,
|
|
134
|
+
roomRef: toRef(roomId),
|
|
135
|
+
actorId,
|
|
136
|
+
actorRef: toRef(actorId),
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// SDK REST responses (people/messages/memberships) already carry REST-encoded ids,
|
|
141
|
+
// so unlike the event normalizers above we only attach raw uuid refs — no re-encoding.
|
|
142
|
+
export function normalizeSdkPerson(person: WebexPerson): WebexPerson {
|
|
143
|
+
return {
|
|
144
|
+
...person,
|
|
145
|
+
ref: toRef(person.id),
|
|
146
|
+
orgRef: toRef(person.orgId),
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function normalizeSdkMessage(message: WebexMessage): WebexMessage {
|
|
151
|
+
return {
|
|
152
|
+
...message,
|
|
153
|
+
ref: toRef(message.id),
|
|
154
|
+
roomRef: toRef(message.roomId),
|
|
155
|
+
personRef: toRef(message.personId),
|
|
156
|
+
parentRef: message.parentId ? toRef(message.parentId) : message.parentId,
|
|
157
|
+
mentionedPeopleRefs: message.mentionedPeople?.map(toRef),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function normalizeSdkMembership(membership: WebexMembership): WebexMembership {
|
|
162
|
+
return {
|
|
163
|
+
...membership,
|
|
164
|
+
ref: toRef(membership.id),
|
|
165
|
+
roomRef: toRef(membership.roomId),
|
|
166
|
+
personRef: toRef(membership.personId),
|
|
75
167
|
}
|
|
76
168
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { WebexClient } from './client'
|
|
2
2
|
export { WebexCredentialManager } from './credential-manager'
|
|
3
|
-
export { fromRestId, toRestId } from './id-normalizer'
|
|
4
|
-
export type { WebexRestIdType } from './id-normalizer'
|
|
3
|
+
export { decodeWebexId, fromRestId, toRef, toRestId } from './id-normalizer'
|
|
4
|
+
export type { DecodedWebexId, WebexRestIdType } from './id-normalizer'
|
|
5
5
|
export { WebexListener } from './listener'
|
|
6
6
|
export type { WebexListenerClient, WebexListenerEventMap, WebexListenerOptions } from './listener'
|
|
7
7
|
export { loginWithPassword } from './password-login'
|
|
@@ -81,8 +81,11 @@ describe('WebexListener', () => {
|
|
|
81
81
|
|
|
82
82
|
const expected = expect.objectContaining({
|
|
83
83
|
id: toRestId('message-123', 'MESSAGE'),
|
|
84
|
+
ref: 'message-123',
|
|
84
85
|
roomId: toRestId('room-123', 'ROOM'),
|
|
86
|
+
roomRef: 'room-123',
|
|
85
87
|
personId: toRestId('person-123', 'PEOPLE'),
|
|
88
|
+
personRef: 'person-123',
|
|
86
89
|
personEmail: 'user@example.com',
|
|
87
90
|
text: 'hello',
|
|
88
91
|
raw: RAW_ACTIVITY,
|
|
@@ -73,10 +73,13 @@ it('WebexSpaceSchema rejects invalid type', () => {
|
|
|
73
73
|
it('WebexMessageSchema validates valid message', () => {
|
|
74
74
|
const result = WebexMessageSchema.safeParse({
|
|
75
75
|
id: 'Y2lzY29zcGFyazovL3VzL01FU1NBR0UvbXNn',
|
|
76
|
+
ref: 'msg',
|
|
76
77
|
roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
78
|
+
roomRef: 'abc',
|
|
77
79
|
roomType: 'group',
|
|
78
80
|
text: 'Hello world',
|
|
79
81
|
personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
82
|
+
personRef: 'abc',
|
|
80
83
|
personEmail: 'user@example.com',
|
|
81
84
|
created: '2024-01-15T10:30:00.000Z',
|
|
82
85
|
})
|
|
@@ -86,17 +89,22 @@ it('WebexMessageSchema validates valid message', () => {
|
|
|
86
89
|
it('WebexMessageSchema validates message with optional fields', () => {
|
|
87
90
|
const result = WebexMessageSchema.safeParse({
|
|
88
91
|
id: 'Y2lzY29zcGFyazovL3VzL01FU1NBR0UvbXNn',
|
|
92
|
+
ref: 'msg',
|
|
89
93
|
roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
94
|
+
roomRef: 'abc',
|
|
90
95
|
roomType: 'group',
|
|
91
96
|
text: 'Hello world',
|
|
92
97
|
markdown: '**Hello world**',
|
|
93
98
|
html: '<strong>Hello world</strong>',
|
|
94
99
|
files: ['https://webexapis.com/v1/contents/file1'],
|
|
95
100
|
personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
101
|
+
personRef: 'abc',
|
|
96
102
|
personEmail: 'user@example.com',
|
|
97
103
|
created: '2024-01-15T10:30:00.000Z',
|
|
98
104
|
parentId: 'Y2lzY29zcGFyazovL3VzL01FU1NBR0UvcGFyZW50',
|
|
105
|
+
parentRef: 'parent',
|
|
99
106
|
mentionedPeople: ['Y2lzY29zcGFyazovL3VzL1BFT1BMRS9tZW50aW9u'],
|
|
107
|
+
mentionedPeopleRefs: ['mention'],
|
|
100
108
|
})
|
|
101
109
|
expect(result.success).toBe(true)
|
|
102
110
|
})
|
|
@@ -125,9 +133,11 @@ it('WebexMessageSchema rejects invalid roomType', () => {
|
|
|
125
133
|
it('WebexPersonSchema validates valid person', () => {
|
|
126
134
|
const result = WebexPersonSchema.safeParse({
|
|
127
135
|
id: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
136
|
+
ref: 'abc',
|
|
128
137
|
emails: ['user@example.com'],
|
|
129
138
|
displayName: 'Test User',
|
|
130
139
|
orgId: 'Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi9vcmc',
|
|
140
|
+
orgRef: 'org',
|
|
131
141
|
type: 'person',
|
|
132
142
|
created: '2024-01-01T00:00:00.000Z',
|
|
133
143
|
})
|
|
@@ -137,6 +147,7 @@ it('WebexPersonSchema validates valid person', () => {
|
|
|
137
147
|
it('WebexPersonSchema validates person with optional fields', () => {
|
|
138
148
|
const result = WebexPersonSchema.safeParse({
|
|
139
149
|
id: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
150
|
+
ref: 'abc',
|
|
140
151
|
emails: ['user@example.com', 'user@work.com'],
|
|
141
152
|
displayName: 'Test User',
|
|
142
153
|
nickName: 'Tester',
|
|
@@ -144,6 +155,7 @@ it('WebexPersonSchema validates person with optional fields', () => {
|
|
|
144
155
|
lastName: 'User',
|
|
145
156
|
avatar: 'https://example.com/avatar.jpg',
|
|
146
157
|
orgId: 'Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi9vcmc',
|
|
158
|
+
orgRef: 'org',
|
|
147
159
|
type: 'person',
|
|
148
160
|
created: '2024-01-01T00:00:00.000Z',
|
|
149
161
|
})
|
|
@@ -153,9 +165,11 @@ it('WebexPersonSchema validates person with optional fields', () => {
|
|
|
153
165
|
it('WebexPersonSchema validates bot type', () => {
|
|
154
166
|
const result = WebexPersonSchema.safeParse({
|
|
155
167
|
id: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9ib3Q',
|
|
168
|
+
ref: 'bot',
|
|
156
169
|
emails: ['bot@webex.bot'],
|
|
157
170
|
displayName: 'My Bot',
|
|
158
171
|
orgId: 'Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi9vcmc',
|
|
172
|
+
orgRef: 'org',
|
|
159
173
|
type: 'bot',
|
|
160
174
|
created: '2024-01-01T00:00:00.000Z',
|
|
161
175
|
})
|
|
@@ -185,8 +199,11 @@ it('WebexPersonSchema rejects invalid type', () => {
|
|
|
185
199
|
it('WebexMembershipSchema validates valid membership', () => {
|
|
186
200
|
const result = WebexMembershipSchema.safeParse({
|
|
187
201
|
id: 'Y2lzY29zcGFyazovL3VzL01FTUJFUlNISVAvbWVt',
|
|
202
|
+
ref: 'mem',
|
|
188
203
|
roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
204
|
+
roomRef: 'abc',
|
|
189
205
|
personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
206
|
+
personRef: 'abc',
|
|
190
207
|
personEmail: 'user@example.com',
|
|
191
208
|
personDisplayName: 'Test User',
|
|
192
209
|
isModerator: false,
|
|
@@ -198,8 +215,11 @@ it('WebexMembershipSchema validates valid membership', () => {
|
|
|
198
215
|
it('WebexMembershipSchema validates moderator membership', () => {
|
|
199
216
|
const result = WebexMembershipSchema.safeParse({
|
|
200
217
|
id: 'Y2lzY29zcGFyazovL3VzL01FTUJFUlNISVAvbWVt',
|
|
218
|
+
ref: 'mem',
|
|
201
219
|
roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
220
|
+
roomRef: 'abc',
|
|
202
221
|
personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
222
|
+
personRef: 'abc',
|
|
203
223
|
personEmail: 'moderator@example.com',
|
|
204
224
|
personDisplayName: 'Moderator User',
|
|
205
225
|
isModerator: true,
|