@memberjunction/ai-bridge-webex 0.0.1 → 5.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -43
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/register-native.d.ts +18 -0
- package/dist/register-native.d.ts.map +1 -0
- package/dist/register-native.js +31 -0
- package/dist/register-native.js.map +1 -0
- package/dist/webex-bridge.d.ts +156 -0
- package/dist/webex-bridge.d.ts.map +1 -0
- package/dist/webex-bridge.js +361 -0
- package/dist/webex-bridge.js.map +1 -0
- package/dist/webex-meeting-controls.d.ts +80 -0
- package/dist/webex-meeting-controls.d.ts.map +1 -0
- package/dist/webex-meeting-controls.js +130 -0
- package/dist/webex-meeting-controls.js.map +1 -0
- package/dist/webex-native-sdk.d.ts +283 -0
- package/dist/webex-native-sdk.d.ts.map +1 -0
- package/dist/webex-native-sdk.js +305 -0
- package/dist/webex-native-sdk.js.map +1 -0
- package/dist/webex-sdk.d.ts +174 -0
- package/dist/webex-sdk.d.ts.map +1 -0
- package/dist/webex-sdk.js +36 -0
- package/dist/webex-sdk.js.map +1 -0
- package/package.json +32 -7
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `WebexBridge` — the **Cisco Webex** platform Realtime Bridge driver, connecting the
|
|
3
|
+
* one realtime agent engine to a **Webex meeting**. Implements the {@link BaseRealtimeBridge} contract
|
|
4
|
+
* against an injectable {@link IWebexMeetingSdk} seam so it builds + unit-tests with NO network and NO
|
|
5
|
+
* real Cisco Webex Meetings SDK (the Gemini `connectLiveSession` testability pattern). Structurally
|
|
6
|
+
* mirrors `ZoomBridge` / `TeamsBridge` (the reference drivers) — only the platform names + join-coordinate
|
|
7
|
+
* parsing differ.
|
|
8
|
+
*
|
|
9
|
+
* Webex capability coverage (per the §8 seed row): on-demand + scheduled + invite join, inbound routing,
|
|
10
|
+
* audio in/out (video/screen directional flags carried by transport), a diarized roster
|
|
11
|
+
* (`SpeakerDiarization`), participant mute + Webex meeting chat (via the Meeting Controls channel). Native
|
|
12
|
+
* raised-hand is ⚠️ partial over the Meetings SDK — wired where the platform surfaces it, tolerant of it
|
|
13
|
+
* never firing. Telephony features (DTMF / transfer / recording) are NOT Webex-meeting features here, so
|
|
14
|
+
* those virtual base methods keep throwing `BridgeCapabilityNotSupportedError`.
|
|
15
|
+
*
|
|
16
|
+
* @module @memberjunction/ai-bridge-webex
|
|
17
|
+
* @author MemberJunction.com
|
|
18
|
+
*/
|
|
19
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
20
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
21
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
22
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
23
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
24
|
+
};
|
|
25
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
26
|
+
import { LogError } from '@memberjunction/core';
|
|
27
|
+
import { BaseRealtimeBridge, } from '@memberjunction/ai-bridge-base';
|
|
28
|
+
import { WebexMeetingControlsEventSource } from './webex-meeting-controls.js';
|
|
29
|
+
/**
|
|
30
|
+
* The `DriverClass` key {@link WebexBridge} registers under. A `MJ: AI Bridge Providers` row with
|
|
31
|
+
* `DriverClass = 'WebexBridge'` resolves to this driver via the `ClassFactory`.
|
|
32
|
+
*/
|
|
33
|
+
export const WEBEX_BRIDGE_DRIVER_CLASS = 'WebexBridge';
|
|
34
|
+
/**
|
|
35
|
+
* Maps a Webex member role to the bridge's {@link BridgeParticipantRole}. The bot (`IsSelf`) is
|
|
36
|
+
* surfaced as `'Agent'`; Webex hosts/cohosts map to host/co-host.
|
|
37
|
+
*/
|
|
38
|
+
function mapParticipantRole(role, isSelf) {
|
|
39
|
+
if (isSelf) {
|
|
40
|
+
return 'Agent';
|
|
41
|
+
}
|
|
42
|
+
switch (role) {
|
|
43
|
+
case 'Host':
|
|
44
|
+
return 'Host';
|
|
45
|
+
case 'Cohost':
|
|
46
|
+
return 'CoHost';
|
|
47
|
+
default:
|
|
48
|
+
return 'Participant';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Maps a Webex member onto the bridge's {@link BridgeParticipantInfo}. */
|
|
52
|
+
function toBridgeParticipant(p) {
|
|
53
|
+
return {
|
|
54
|
+
ExternalId: p.ParticipantId,
|
|
55
|
+
DisplayName: p.DisplayName,
|
|
56
|
+
Role: mapParticipantRole(p.Role, p.IsSelf),
|
|
57
|
+
IsAgent: p.IsSelf === true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Realtime Bridge driver for **Cisco Webex meetings**.
|
|
62
|
+
*
|
|
63
|
+
* Construct the driver with the default constructor (the engine's `ClassFactory` path) — it lazily
|
|
64
|
+
* builds the Webex SDK from the {@link sdkFactory} at {@link Connect} time. Tests inject a `FakeWebexSdk`
|
|
65
|
+
* by overriding the factory via {@link SetSdkFactory} (the creation seam) before connecting.
|
|
66
|
+
*
|
|
67
|
+
* Registered via `@RegisterClass(BaseRealtimeBridge, 'WebexBridge')`.
|
|
68
|
+
*/
|
|
69
|
+
let WebexBridge = class WebexBridge extends BaseRealtimeBridge {
|
|
70
|
+
constructor() {
|
|
71
|
+
super(...arguments);
|
|
72
|
+
/** The live Webex SDK seam for this session, created at {@link Connect}. */
|
|
73
|
+
this.sdk = null;
|
|
74
|
+
/** The bot's own member id in the joined meeting (set at {@link Connect}). */
|
|
75
|
+
this.botParticipantId = null;
|
|
76
|
+
/** The Meeting Controls event source for this session (only when diarization is supported). */
|
|
77
|
+
this.meetingControls = null;
|
|
78
|
+
/**
|
|
79
|
+
* The SDK creation seam. Defaults to a factory that throws an explicit "bind the real Webex SDK"
|
|
80
|
+
* error, since this package ships WITHOUT the real SDK adapter (a deployment concern). Production
|
|
81
|
+
* sets a real factory via {@link SetSdkFactory}; tests inject a `FakeWebexSdk`.
|
|
82
|
+
*/
|
|
83
|
+
this.sdkFactory = () => {
|
|
84
|
+
throw new Error('WebexBridge has no Cisco Webex SDK bound. Call WebexBridge.SetSdkFactory(...) with a factory ' +
|
|
85
|
+
'that builds an IWebexMeetingSdk over the real Cisco Webex Meetings SDK / Webex bot framework, ' +
|
|
86
|
+
'or inject a fake in tests.');
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Sets the {@link WebexMeetingSdkFactory} this driver uses to construct its SDK seam at connect —
|
|
91
|
+
* the creation seam (mirroring Gemini's overridable `connectLiveSession`). Production binds the
|
|
92
|
+
* real Cisco Webex Meetings SDK adapter here; tests inject a `FakeWebexSdk`.
|
|
93
|
+
*
|
|
94
|
+
* @param factory The factory that builds the {@link IWebexMeetingSdk} for a session.
|
|
95
|
+
*/
|
|
96
|
+
SetSdkFactory(factory) {
|
|
97
|
+
this.sdkFactory = factory;
|
|
98
|
+
}
|
|
99
|
+
// ── Abstract — every bridge MUST implement ───────────────────────────────────────
|
|
100
|
+
/**
|
|
101
|
+
* Joins the Webex meeting and brings the bot online. Captures the capability context, builds the SDK
|
|
102
|
+
* from the {@link sdkFactory}, wires the inbound raw-audio path and meeting-ended callback, joins,
|
|
103
|
+
* and (when the provider diarizes) constructs + seeds the Meeting Controls event source.
|
|
104
|
+
*
|
|
105
|
+
* @param ctx The bridge context (features, provider name, the meeting link as `Address`, config).
|
|
106
|
+
* @returns The bot member + meeting (external connection) identifiers, persisted by the engine.
|
|
107
|
+
*/
|
|
108
|
+
async Connect(ctx) {
|
|
109
|
+
this.applyContext(ctx);
|
|
110
|
+
this.RequireFeature('AudioIn'); // a Webex meeting bridge requires bidirectional audio at minimum
|
|
111
|
+
this.RequireFeature('AudioOut');
|
|
112
|
+
this.sdk = this.sdkFactory(ctx.Configuration);
|
|
113
|
+
this.wireInboundAudio(this.sdk);
|
|
114
|
+
this.sdk.onMeetingEnded(() => this.handleMeetingEnded());
|
|
115
|
+
// Roster diarization is optional per provider; only stand up the Meeting Controls source when
|
|
116
|
+
// the platform advertises it (the engine also gates participant tracking on this flag).
|
|
117
|
+
if (this.features.SpeakerDiarization === true) {
|
|
118
|
+
this.meetingControls = new WebexMeetingControlsEventSource(this.sdk);
|
|
119
|
+
}
|
|
120
|
+
// The driver owns the single SDK subscription set and fans out to BOTH its own roster handler
|
|
121
|
+
// and the Meeting Controls source (the SDK seam is latest-handler-wins, so there is one owner).
|
|
122
|
+
this.wireRoster(this.sdk);
|
|
123
|
+
const result = await this.sdk.join(this.buildJoinArgs(ctx));
|
|
124
|
+
this.botParticipantId = result.BotParticipantId;
|
|
125
|
+
// Seed the initial roster now that the bot has joined.
|
|
126
|
+
await this.refreshRoster(this.sdk);
|
|
127
|
+
return { BotParticipantId: result.BotParticipantId, ExternalConnectionId: result.MeetingId };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Leaves the Webex meeting and releases all SDK resources. Tolerant of teardown errors.
|
|
131
|
+
*
|
|
132
|
+
* @param _reason Why the disconnect happened (Webex teardown is uniform; the bot simply leaves).
|
|
133
|
+
*/
|
|
134
|
+
async Disconnect(_reason) {
|
|
135
|
+
const sdk = this.sdk;
|
|
136
|
+
this.sdk = null;
|
|
137
|
+
this.botParticipantId = null;
|
|
138
|
+
this.meetingControls = null;
|
|
139
|
+
this.mediaHandler = undefined;
|
|
140
|
+
this.participantHandler = undefined;
|
|
141
|
+
if (sdk) {
|
|
142
|
+
try {
|
|
143
|
+
await sdk.leave();
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
LogError(`[WebexBridge] leave() failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Sends an outbound media frame into the meeting. Audio is fed to the SDK's outbound audio-track
|
|
152
|
+
* path; video/screen frames are gated by the directional capability flags (the transport carries
|
|
153
|
+
* them, and the SDK send is a deployment-time binding TODO — today the models emit audio).
|
|
154
|
+
*
|
|
155
|
+
* @param track The outbound track the frame targets.
|
|
156
|
+
* @param frame The media frame to send.
|
|
157
|
+
*/
|
|
158
|
+
SendMedia(track, frame) {
|
|
159
|
+
if (!this.sdk) {
|
|
160
|
+
return; // not connected — drop
|
|
161
|
+
}
|
|
162
|
+
switch (track) {
|
|
163
|
+
case 'audio-out': {
|
|
164
|
+
const pcm = this.framePcm(frame);
|
|
165
|
+
if (pcm) {
|
|
166
|
+
this.sdk.sendAudioFrame(pcm);
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'video-out':
|
|
171
|
+
case 'screen-out':
|
|
172
|
+
// Directional video/screen capability is declared; the SDK send binding lands with the
|
|
173
|
+
// first realtime model that emits these tracks (TODO at deployment). No-op for now.
|
|
174
|
+
break;
|
|
175
|
+
default:
|
|
176
|
+
// An inbound track was passed to SendMedia — ignore (defensive).
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Registers the inbound-media handler. The driver forwards each raw per-participant audio frame
|
|
182
|
+
* (with its speaker label) to this handler; the engine routes it to `IRealtimeSession.SendInput`.
|
|
183
|
+
*
|
|
184
|
+
* @param handler Invoked with each inbound media frame.
|
|
185
|
+
*/
|
|
186
|
+
OnMedia(handler) {
|
|
187
|
+
this.mediaHandler = handler;
|
|
188
|
+
}
|
|
189
|
+
// ── Capability-gated virtuals Webex supports (gated by SupportedFeatures) ──────────
|
|
190
|
+
/**
|
|
191
|
+
* Returns the current Webex member roster (gated by `SpeakerDiarization`). Re-asserts the flag
|
|
192
|
+
* (defense-in-depth) so even an engine-bypassing caller cannot pull a roster a disabled provider
|
|
193
|
+
* forbids.
|
|
194
|
+
*
|
|
195
|
+
* @returns The current members.
|
|
196
|
+
* @throws {BridgeCapabilityNotSupportedError} when diarization is not enabled for the provider.
|
|
197
|
+
*/
|
|
198
|
+
async GetParticipants() {
|
|
199
|
+
this.RequireFeature('SpeakerDiarization');
|
|
200
|
+
if (!this.sdk) {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
const participants = await this.sdk.getParticipants();
|
|
204
|
+
return participants.map(toBridgeParticipant);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Registers a roster-change handler (gated by `SpeakerDiarization`). The driver fires it from the
|
|
208
|
+
* SDK's member join/leave stream with the full current roster.
|
|
209
|
+
*
|
|
210
|
+
* @param handler Invoked with the updated participant list on each change.
|
|
211
|
+
* @throws {BridgeCapabilityNotSupportedError} when diarization is not enabled.
|
|
212
|
+
*/
|
|
213
|
+
OnParticipantChange(handler) {
|
|
214
|
+
this.RequireFeature('SpeakerDiarization');
|
|
215
|
+
this.participantHandler = handler;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Returns the Webex Meeting Controls event source for this session (roster · hand-raise · speaking ·
|
|
219
|
+
* mute), or `null` when diarization is off (no roster to facilitate). The engine wires the Meeting
|
|
220
|
+
* Controls channel from this.
|
|
221
|
+
*/
|
|
222
|
+
GetMeetingControlsEventSource() {
|
|
223
|
+
return this.meetingControls;
|
|
224
|
+
}
|
|
225
|
+
// ── Webex-native surfaces (used by the channel plane / facilitator) ──────────────
|
|
226
|
+
/**
|
|
227
|
+
* Posts a message to the Webex meeting space chat. Exposed for the channel plane / turn-taking hybrid
|
|
228
|
+
* mode (the social-cost-free "raise hand"). Best-effort — a chat failure is logged, never fatal.
|
|
229
|
+
*
|
|
230
|
+
* @param text The chat message to post.
|
|
231
|
+
*/
|
|
232
|
+
async PostChatMessage(text) {
|
|
233
|
+
if (!this.sdk) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
await this.sdk.postChatMessage(text);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
LogError(`[WebexBridge] postChatMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/** The bot's member id in the joined meeting (or `null` before {@link Connect}). */
|
|
244
|
+
get BotParticipantId() {
|
|
245
|
+
return this.botParticipantId;
|
|
246
|
+
}
|
|
247
|
+
// ── internals ────────────────────────────────────────────────────────────────────
|
|
248
|
+
/** Wires the SDK's inbound raw-audio callback to a diarized inbound {@link BridgeMediaFrame}. */
|
|
249
|
+
wireInboundAudio(sdk) {
|
|
250
|
+
sdk.onAudioFrame((frame) => {
|
|
251
|
+
this.mediaHandler?.({
|
|
252
|
+
Track: 'audio-in',
|
|
253
|
+
Bytes: frame.Pcm,
|
|
254
|
+
SpeakerLabel: frame.ParticipantId,
|
|
255
|
+
TimestampMs: frame.TimestampMs ?? Date.now(),
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Wires the SDK's member join/leave + hand-raise streams ONCE (the driver is the single owner;
|
|
261
|
+
* the SDK seam is latest-handler-wins). Each roster change fans out to both the driver's own
|
|
262
|
+
* roster-change handler and the Meeting Controls source; hand-raise signals (⚠️ partial on Webex)
|
|
263
|
+
* feed the source.
|
|
264
|
+
*/
|
|
265
|
+
wireRoster(sdk) {
|
|
266
|
+
sdk.onParticipantJoin(() => void this.refreshRoster(sdk));
|
|
267
|
+
sdk.onParticipantLeave(() => void this.refreshRoster(sdk));
|
|
268
|
+
sdk.onHandRaise((id, raised) => this.meetingControls?.IngestHandRaise(id, raised));
|
|
269
|
+
}
|
|
270
|
+
/** Pulls the current roster from the SDK and fans it out to both the participant handler + Meeting Controls. */
|
|
271
|
+
async refreshRoster(sdk) {
|
|
272
|
+
try {
|
|
273
|
+
const participants = await sdk.getParticipants();
|
|
274
|
+
this.participantHandler?.(participants.map(toBridgeParticipant));
|
|
275
|
+
this.meetingControls?.IngestRoster(participants);
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
LogError(`[WebexBridge] roster refresh failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/** Handles the SDK's meeting-ended signal: surface an empty roster so the engine sees everyone gone. */
|
|
282
|
+
handleMeetingEnded() {
|
|
283
|
+
this.participantHandler?.([]);
|
|
284
|
+
this.meetingControls?.IngestRoster([]);
|
|
285
|
+
}
|
|
286
|
+
/** Builds the SDK join args from the bridge context (Address is the meeting link). */
|
|
287
|
+
buildJoinArgs(ctx) {
|
|
288
|
+
const config = ctx.Configuration ?? {};
|
|
289
|
+
return {
|
|
290
|
+
MeetingLink: ctx.Address,
|
|
291
|
+
MeetingNumber: typeof config.MeetingNumber === 'string' ? config.MeetingNumber : this.parseMeetingNumber(ctx.Address),
|
|
292
|
+
BotDisplayName: typeof config.BotDisplayName === 'string' ? config.BotDisplayName : `${ctx.ProviderName} Agent`,
|
|
293
|
+
AccessToken: typeof config.AccessToken === 'string' ? config.AccessToken : undefined,
|
|
294
|
+
SiteUrl: typeof config.SiteUrl === 'string' ? config.SiteUrl : undefined,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Extracts the Webex meeting number from a meeting link when present, else `undefined`. Webex meeting
|
|
299
|
+
* numbers are 9–11 digit strings carried in the join URL (e.g. as a `MTID`/`/j.php?MTID=...` query
|
|
300
|
+
* param or a trailing numeric segment of a personal-room / scheduled link). Tolerant — a non-matching
|
|
301
|
+
* address yields `undefined` so the SDK can resolve coordinates itself.
|
|
302
|
+
*/
|
|
303
|
+
parseMeetingNumber(address) {
|
|
304
|
+
if (!address) {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
const decoded = (() => {
|
|
308
|
+
try {
|
|
309
|
+
return decodeURIComponent(address);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return address;
|
|
313
|
+
}
|
|
314
|
+
})();
|
|
315
|
+
// A 9–11 digit run is the Webex meeting number; prefer the longest match in the link.
|
|
316
|
+
const matches = decoded.match(/\d{9,11}/g);
|
|
317
|
+
if (!matches || matches.length === 0) {
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
return matches.reduce((longest, cur) => (cur.length > longest.length ? cur : longest));
|
|
321
|
+
}
|
|
322
|
+
/** Returns the PCM bytes of an outbound audio frame, preferring the binary payload. */
|
|
323
|
+
framePcm(frame) {
|
|
324
|
+
if (frame.Bytes) {
|
|
325
|
+
return frame.Bytes;
|
|
326
|
+
}
|
|
327
|
+
if (frame.Base64) {
|
|
328
|
+
return this.decodeBase64(frame.Base64);
|
|
329
|
+
}
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
/** Decodes a base64 payload to an `ArrayBuffer` (Node `Buffer` fast path, `atob` fallback). */
|
|
333
|
+
decodeBase64(base64) {
|
|
334
|
+
if (typeof Buffer !== 'undefined') {
|
|
335
|
+
const buf = Buffer.from(base64, 'base64');
|
|
336
|
+
// Copy into a fresh ArrayBuffer (Buffer.buffer is ArrayBufferLike under newer @types/node).
|
|
337
|
+
const out = new Uint8Array(buf.byteLength);
|
|
338
|
+
out.set(buf);
|
|
339
|
+
return out.buffer;
|
|
340
|
+
}
|
|
341
|
+
const binary = atob(base64);
|
|
342
|
+
const bytes = new Uint8Array(binary.length);
|
|
343
|
+
for (let i = 0; i < binary.length; i++) {
|
|
344
|
+
bytes[i] = binary.charCodeAt(i);
|
|
345
|
+
}
|
|
346
|
+
return bytes.buffer;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
WebexBridge = __decorate([
|
|
350
|
+
RegisterClass(BaseRealtimeBridge, WEBEX_BRIDGE_DRIVER_CLASS)
|
|
351
|
+
], WebexBridge);
|
|
352
|
+
export { WebexBridge };
|
|
353
|
+
/**
|
|
354
|
+
* Tree-shaking-prevention loader. Modern bundlers cannot see the `@RegisterClass` dynamic registration
|
|
355
|
+
* of {@link WebexBridge} and may eliminate it. Import and call this no-op from a static code path
|
|
356
|
+
* (the package entry point does) so the `ClassFactory` can resolve `'WebexBridge'`.
|
|
357
|
+
*/
|
|
358
|
+
export function LoadWebexBridge() {
|
|
359
|
+
// Intentionally empty — referencing the module is what prevents tree-shaking.
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=webex-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webex-bridge.js","sourceRoot":"","sources":["../src/webex-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapts the {@link IWebexMeetingSdk} member / hand-raise / speaking stream into an
|
|
3
|
+
* {@link IBridgeMeetingControlsEventSource} so the bridge engine can wire the **Meeting Controls**
|
|
4
|
+
* facilitator channel (roster · hand-raise queue · who's-speaking · mute) for a Webex session. This is
|
|
5
|
+
* the §4b "the bridge contributes a channel" pattern realized for Webex — no channel logic lives here,
|
|
6
|
+
* only the platform→channel signal mapping.
|
|
7
|
+
*/
|
|
8
|
+
import { IBridgeMeetingControlsEventSource, BridgeMeetingParticipant, BridgeMeetingControlsCapability } from '@memberjunction/ai-bridge-base';
|
|
9
|
+
import { IWebexMeetingSdk, WebexParticipant } from './webex-sdk.js';
|
|
10
|
+
/** Maps one Webex member onto the channel's {@link BridgeMeetingParticipant} shape. */
|
|
11
|
+
export declare function toMeetingParticipant(p: WebexParticipant): BridgeMeetingParticipant;
|
|
12
|
+
/**
|
|
13
|
+
* Webex's adapter to the Meeting Controls channel's event source. It maintains a live roster and
|
|
14
|
+
* mirrors native hand-raise + diarized speaking signals, and actuates mute through the SDK.
|
|
15
|
+
*
|
|
16
|
+
* **The driver owns the SDK subscriptions** (the SDK seam is "latest handler wins", so it can have
|
|
17
|
+
* only one subscriber per event) and **feeds this source imperatively** via {@link IngestRoster} /
|
|
18
|
+
* {@link IngestHandRaise} / {@link IngestSpeaking}. This source only needs the SDK for the one *action*
|
|
19
|
+
* it actuates — {@link MuteParticipant}. The Meeting Controls channel subscribes the three `On*`
|
|
20
|
+
* streams in turn.
|
|
21
|
+
*
|
|
22
|
+
* The driver constructs ONE of these per session (only when the provider has `SpeakerDiarization`, i.e.
|
|
23
|
+
* there is a roster to facilitate) and returns it from `WebexBridge.GetMeetingControlsEventSource`.
|
|
24
|
+
*/
|
|
25
|
+
export declare class WebexMeetingControlsEventSource implements IBridgeMeetingControlsEventSource {
|
|
26
|
+
private readonly sdk;
|
|
27
|
+
/** Live roster keyed by member id (lowercased), kept current by the driver's {@link IngestRoster}. */
|
|
28
|
+
private readonly roster;
|
|
29
|
+
private rosterHandler?;
|
|
30
|
+
private speakingHandler?;
|
|
31
|
+
private handRaiseHandler?;
|
|
32
|
+
/** Webex supports host/cohost-driven mute, so the facilitator `MuteParticipant` tool is offered. */
|
|
33
|
+
readonly Capabilities: ReadonlyArray<BridgeMeetingControlsCapability>;
|
|
34
|
+
/**
|
|
35
|
+
* @param sdk The Webex SDK seam used only to actuate {@link MuteParticipant} — all *perception* is
|
|
36
|
+
* fed in by the driver via the `Ingest*` methods so the SDK keeps a single subscriber per event.
|
|
37
|
+
*/
|
|
38
|
+
constructor(sdk: IWebexMeetingSdk);
|
|
39
|
+
/**
|
|
40
|
+
* Replaces the roster with a fresh snapshot from the driver and emits it to the channel. Called by
|
|
41
|
+
* the driver after the initial join and on every member join/leave.
|
|
42
|
+
*
|
|
43
|
+
* @param participants The full current Webex roster.
|
|
44
|
+
*/
|
|
45
|
+
IngestRoster(participants: WebexParticipant[]): void;
|
|
46
|
+
/**
|
|
47
|
+
* Forwards a native hand-raise/lower signal from the driver to the channel.
|
|
48
|
+
*
|
|
49
|
+
* ⚠️ Webex raised-hand is partial; this may never fire on clients/builds that don't surface the
|
|
50
|
+
* event over the Meetings SDK. The channel degrades gracefully (its software hand-raise still works).
|
|
51
|
+
*
|
|
52
|
+
* @param participantId The member whose hand changed.
|
|
53
|
+
* @param raised Whether the hand is now raised.
|
|
54
|
+
*/
|
|
55
|
+
IngestHandRaise(participantId: string, raised: boolean): void;
|
|
56
|
+
/**
|
|
57
|
+
* Forwards a diarized speaking-set change from the driver to the channel.
|
|
58
|
+
*
|
|
59
|
+
* @param participantIds The ids currently speaking.
|
|
60
|
+
*/
|
|
61
|
+
IngestSpeaking(participantIds: string[]): void;
|
|
62
|
+
/**
|
|
63
|
+
* Convenience alias for {@link IngestSpeaking}, kept because "notify" reads naturally at the
|
|
64
|
+
* driver's inbound-audio call site.
|
|
65
|
+
*
|
|
66
|
+
* @param participantIds The ids currently speaking.
|
|
67
|
+
*/
|
|
68
|
+
NotifySpeaking(participantIds: string[]): void;
|
|
69
|
+
/** @inheritdoc */
|
|
70
|
+
OnRosterChange(handler: (participants: BridgeMeetingParticipant[]) => void): void;
|
|
71
|
+
/** @inheritdoc */
|
|
72
|
+
OnSpeakingChange(handler: (participantIds: string[]) => void): void;
|
|
73
|
+
/** @inheritdoc */
|
|
74
|
+
OnHandRaiseChange(handler: (participantId: string, raised: boolean) => void): void;
|
|
75
|
+
/** @inheritdoc */
|
|
76
|
+
MuteParticipant(participantId: string): Promise<void>;
|
|
77
|
+
private emitRoster;
|
|
78
|
+
private key;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=webex-meeting-controls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webex-meeting-controls.d.ts","sourceRoot":"","sources":["../src/webex-meeting-controls.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACH,iCAAiC,EACjC,wBAAwB,EACxB,+BAA+B,EAElC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAwB,MAAM,aAAa,CAAC;AAqBvF,uFAAuF;AACvF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,gBAAgB,GAAG,wBAAwB,CAOlF;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,+BAAgC,YAAW,iCAAiC;IAezE,OAAO,CAAC,QAAQ,CAAC,GAAG;IAdhC,sGAAsG;IACtG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+C;IAEtE,OAAO,CAAC,aAAa,CAAC,CAAqD;IAC3E,OAAO,CAAC,eAAe,CAAC,CAAqC;IAC7D,OAAO,CAAC,gBAAgB,CAAC,CAAmD;IAE5E,oGAAoG;IACpG,SAAgB,YAAY,EAAE,aAAa,CAAC,+BAA+B,CAAC,CAAY;IAExF;;;OAGG;gBAC0B,GAAG,EAAE,gBAAgB;IAIlD;;;;;OAKG;IACI,YAAY,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,IAAI;IAQ3D;;;;;;;;OAQG;IACI,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAIpE;;;;OAIG;IACI,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI;IAIrD;;;;;OAKG;IACI,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI;IAMrD,kBAAkB;IACX,cAAc,CAAC,OAAO,EAAE,CAAC,YAAY,EAAE,wBAAwB,EAAE,KAAK,IAAI,GAAG,IAAI;IAMxF,kBAAkB;IACX,gBAAgB,CAAC,OAAO,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,IAAI;IAI1E,kBAAkB;IACX,iBAAiB,CAAC,OAAO,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IAIzF,kBAAkB;IACL,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlE,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,GAAG;CAGd"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapts the {@link IWebexMeetingSdk} member / hand-raise / speaking stream into an
|
|
3
|
+
* {@link IBridgeMeetingControlsEventSource} so the bridge engine can wire the **Meeting Controls**
|
|
4
|
+
* facilitator channel (roster · hand-raise queue · who's-speaking · mute) for a Webex session. This is
|
|
5
|
+
* the §4b "the bridge contributes a channel" pattern realized for Webex — no channel logic lives here,
|
|
6
|
+
* only the platform→channel signal mapping.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Maps a Webex member role to the bridge's meeting-participant role. Webex has no distinct "Agent"
|
|
10
|
+
* role; the bot is flagged via `IsSelf` and surfaced as `'Agent'`. Webex hosts/cohosts map to the
|
|
11
|
+
* bridge's host/co-host roles (the closest facilitator-authority equivalents).
|
|
12
|
+
*/
|
|
13
|
+
function mapRole(role, isSelf) {
|
|
14
|
+
if (isSelf) {
|
|
15
|
+
return 'Agent';
|
|
16
|
+
}
|
|
17
|
+
switch (role) {
|
|
18
|
+
case 'Host':
|
|
19
|
+
return 'Host';
|
|
20
|
+
case 'Cohost':
|
|
21
|
+
return 'CoHost';
|
|
22
|
+
default:
|
|
23
|
+
return 'Participant';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Maps one Webex member onto the channel's {@link BridgeMeetingParticipant} shape. */
|
|
27
|
+
export function toMeetingParticipant(p) {
|
|
28
|
+
return {
|
|
29
|
+
ParticipantId: p.ParticipantId,
|
|
30
|
+
DisplayName: p.DisplayName,
|
|
31
|
+
Role: mapRole(p.Role, p.IsSelf),
|
|
32
|
+
IsAgent: p.IsSelf === true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Webex's adapter to the Meeting Controls channel's event source. It maintains a live roster and
|
|
37
|
+
* mirrors native hand-raise + diarized speaking signals, and actuates mute through the SDK.
|
|
38
|
+
*
|
|
39
|
+
* **The driver owns the SDK subscriptions** (the SDK seam is "latest handler wins", so it can have
|
|
40
|
+
* only one subscriber per event) and **feeds this source imperatively** via {@link IngestRoster} /
|
|
41
|
+
* {@link IngestHandRaise} / {@link IngestSpeaking}. This source only needs the SDK for the one *action*
|
|
42
|
+
* it actuates — {@link MuteParticipant}. The Meeting Controls channel subscribes the three `On*`
|
|
43
|
+
* streams in turn.
|
|
44
|
+
*
|
|
45
|
+
* The driver constructs ONE of these per session (only when the provider has `SpeakerDiarization`, i.e.
|
|
46
|
+
* there is a roster to facilitate) and returns it from `WebexBridge.GetMeetingControlsEventSource`.
|
|
47
|
+
*/
|
|
48
|
+
export class WebexMeetingControlsEventSource {
|
|
49
|
+
/**
|
|
50
|
+
* @param sdk The Webex SDK seam used only to actuate {@link MuteParticipant} — all *perception* is
|
|
51
|
+
* fed in by the driver via the `Ingest*` methods so the SDK keeps a single subscriber per event.
|
|
52
|
+
*/
|
|
53
|
+
constructor(sdk) {
|
|
54
|
+
this.sdk = sdk;
|
|
55
|
+
/** Live roster keyed by member id (lowercased), kept current by the driver's {@link IngestRoster}. */
|
|
56
|
+
this.roster = new Map();
|
|
57
|
+
/** Webex supports host/cohost-driven mute, so the facilitator `MuteParticipant` tool is offered. */
|
|
58
|
+
this.Capabilities = ['Mute'];
|
|
59
|
+
}
|
|
60
|
+
// ── Driver-fed perception (the driver owns the SDK subscriptions) ─────────────────
|
|
61
|
+
/**
|
|
62
|
+
* Replaces the roster with a fresh snapshot from the driver and emits it to the channel. Called by
|
|
63
|
+
* the driver after the initial join and on every member join/leave.
|
|
64
|
+
*
|
|
65
|
+
* @param participants The full current Webex roster.
|
|
66
|
+
*/
|
|
67
|
+
IngestRoster(participants) {
|
|
68
|
+
this.roster.clear();
|
|
69
|
+
for (const p of participants) {
|
|
70
|
+
this.roster.set(this.key(p.ParticipantId), toMeetingParticipant(p));
|
|
71
|
+
}
|
|
72
|
+
this.emitRoster();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Forwards a native hand-raise/lower signal from the driver to the channel.
|
|
76
|
+
*
|
|
77
|
+
* ⚠️ Webex raised-hand is partial; this may never fire on clients/builds that don't surface the
|
|
78
|
+
* event over the Meetings SDK. The channel degrades gracefully (its software hand-raise still works).
|
|
79
|
+
*
|
|
80
|
+
* @param participantId The member whose hand changed.
|
|
81
|
+
* @param raised Whether the hand is now raised.
|
|
82
|
+
*/
|
|
83
|
+
IngestHandRaise(participantId, raised) {
|
|
84
|
+
this.handRaiseHandler?.(participantId, raised);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Forwards a diarized speaking-set change from the driver to the channel.
|
|
88
|
+
*
|
|
89
|
+
* @param participantIds The ids currently speaking.
|
|
90
|
+
*/
|
|
91
|
+
IngestSpeaking(participantIds) {
|
|
92
|
+
this.speakingHandler?.(participantIds);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Convenience alias for {@link IngestSpeaking}, kept because "notify" reads naturally at the
|
|
96
|
+
* driver's inbound-audio call site.
|
|
97
|
+
*
|
|
98
|
+
* @param participantIds The ids currently speaking.
|
|
99
|
+
*/
|
|
100
|
+
NotifySpeaking(participantIds) {
|
|
101
|
+
this.IngestSpeaking(participantIds);
|
|
102
|
+
}
|
|
103
|
+
// ── IBridgeMeetingControlsEventSource ────────────────────────────────────────────
|
|
104
|
+
/** @inheritdoc */
|
|
105
|
+
OnRosterChange(handler) {
|
|
106
|
+
this.rosterHandler = handler;
|
|
107
|
+
// Emit the current roster immediately so a late subscriber is not blank.
|
|
108
|
+
this.emitRoster();
|
|
109
|
+
}
|
|
110
|
+
/** @inheritdoc */
|
|
111
|
+
OnSpeakingChange(handler) {
|
|
112
|
+
this.speakingHandler = handler;
|
|
113
|
+
}
|
|
114
|
+
/** @inheritdoc */
|
|
115
|
+
OnHandRaiseChange(handler) {
|
|
116
|
+
this.handRaiseHandler = handler;
|
|
117
|
+
}
|
|
118
|
+
/** @inheritdoc */
|
|
119
|
+
async MuteParticipant(participantId) {
|
|
120
|
+
await this.sdk.muteParticipant(participantId);
|
|
121
|
+
}
|
|
122
|
+
// ── internals ────────────────────────────────────────────────────────────────────
|
|
123
|
+
emitRoster() {
|
|
124
|
+
this.rosterHandler?.(Array.from(this.roster.values()));
|
|
125
|
+
}
|
|
126
|
+
key(participantId) {
|
|
127
|
+
return participantId.trim().toLowerCase();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=webex-meeting-controls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webex-meeting-controls.js","sourceRoot":"","sources":["../src/webex-meeting-controls.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH;;;;GAIG;AACH,SAAS,OAAO,CAAC,IAA0B,EAAE,MAA2B;IACpE,IAAI,MAAM,EAAE,CAAC;QACT,OAAO,OAAO,CAAC;IACnB,CAAC;IACD,QAAQ,IAAI,EAAE,CAAC;QACX,KAAK,MAAM;YACP,OAAO,MAAM,CAAC;QAClB,KAAK,QAAQ;YACT,OAAO,QAAQ,CAAC;QACpB;YACI,OAAO,aAAa,CAAC;IAC7B,CAAC;AACL,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,oBAAoB,CAAC,CAAmB;IACpD,OAAO;QACH,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QAC/B,OAAO,EAAE,CAAC,CAAC,MAAM,KAAK,IAAI;KAC7B,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,+BAA+B;IAWxC;;;OAGG;IACH,YAA6B,GAAqB;QAArB,QAAG,GAAH,GAAG,CAAkB;QAdlD,sGAAsG;QACrF,WAAM,GAAG,IAAI,GAAG,EAAoC,CAAC;QAMtE,oGAAoG;QACpF,iBAAY,GAAmD,CAAC,MAAM,CAAC,CAAC;IAMnC,CAAC;IAEtD,qFAAqF;IAErF;;;;;OAKG;IACI,YAAY,CAAC,YAAgC;QAChD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;QACxE,CAAC;QACD,IAAI,CAAC,UAAU,EAAE,CAAC;IACtB,CAAC;IAED;;;;;;;;OAQG;IACI,eAAe,CAAC,aAAqB,EAAE,MAAe;QACzD,IAAI,CAAC,gBAAgB,EAAE,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IAED;;;;OAIG;IACI,cAAc,CAAC,cAAwB;QAC1C,IAAI,CAAC,eAAe,EAAE,CAAC,cAAc,CAAC,CAAC;IAC3C,CAAC;IAED;;;;;OAKG;IACI,cAAc,CAAC,cAAwB;QAC1C,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;IACxC,CAAC;IAED,oFAAoF;IAEpF,kBAAkB;IACX,cAAc,CAAC,OAA2D;QAC7E,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;QAC7B,yEAAyE;QACzE,IAAI,CAAC,UAAU,EAAE,CAAC;IACtB,CAAC;IAED,kBAAkB;IACX,gBAAgB,CAAC,OAA2C;QAC/D,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC;IACnC,CAAC;IAED,kBAAkB;IACX,iBAAiB,CAAC,OAAyD;QAC9E,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC;IACpC,CAAC;IAED,kBAAkB;IACX,KAAK,CAAC,eAAe,CAAC,aAAqB;QAC9C,MAAM,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;IAClD,CAAC;IAED,oFAAoF;IAE5E,UAAU;QACd,IAAI,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC3D,CAAC;IAEO,GAAG,CAAC,aAAqB;QAC7B,OAAO,aAAa,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9C,CAAC;CACJ"}
|