@memberjunction/server 5.44.0 → 5.45.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/dist/agentSessions/ReturningVisitorRecap.d.ts +39 -0
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js +198 -0
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -0
- package/dist/agentSessions/SessionJanitor.d.ts +13 -0
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +65 -7
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +37 -0
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +10 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +16 -0
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +7 -0
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +13 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/index.d.ts +1 -1
- package/dist/auth/magicLink/index.d.ts.map +1 -1
- package/dist/auth/magicLink/index.js +1 -1
- package/dist/auth/magicLink/index.js.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.d.ts +38 -1
- package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.js +42 -19
- package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +26 -0
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/config.d.ts +1883 -144
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +160 -36
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +45 -2
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +522 -0
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +2860 -0
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +13 -0
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +22 -0
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +4 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -3
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +6 -0
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts +26 -0
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -0
- package/dist/realtimeWidget/WidgetRouter.js +182 -0
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -0
- package/dist/realtimeWidget/WidgetSessionService.d.ts +265 -0
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -0
- package/dist/realtimeWidget/WidgetSessionService.js +477 -0
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -0
- package/dist/realtimeWidget/host-identity.d.ts +40 -0
- package/dist/realtimeWidget/host-identity.d.ts.map +1 -0
- package/dist/realtimeWidget/host-identity.js +58 -0
- package/dist/realtimeWidget/host-identity.js.map +1 -0
- package/dist/realtimeWidget/index.d.ts +10 -0
- package/dist/realtimeWidget/index.d.ts.map +1 -0
- package/dist/realtimeWidget/index.js +9 -0
- package/dist/realtimeWidget/index.js.map +1 -0
- package/dist/realtimeWidget/visitorIdentity.d.ts +76 -0
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -0
- package/dist/realtimeWidget/visitorIdentity.js +202 -0
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -0
- package/dist/realtimeWidget/widgetCore.d.ts +106 -0
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -0
- package/dist/realtimeWidget/widgetCore.js +173 -0
- package/dist/realtimeWidget/widgetCore.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +51 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.js +79 -0
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -0
- package/dist/resolvers/ComponentRegistryResolver.d.ts +7 -0
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +31 -8
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/EntityPermissionResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityPermissionResolver.js +1 -2
- package/dist/resolvers/EntityPermissionResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +9 -6
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +15 -13
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +47 -3
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RingCentralTelephonyResolver.d.ts +27 -0
- package/dist/resolvers/RingCentralTelephonyResolver.d.ts.map +1 -0
- package/dist/resolvers/RingCentralTelephonyResolver.js +87 -0
- package/dist/resolvers/RingCentralTelephonyResolver.js.map +1 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts +9 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +20 -4
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.js +2 -0
- package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
- package/dist/resolvers/TeamsMeetingsResolver.d.ts +28 -0
- package/dist/resolvers/TeamsMeetingsResolver.d.ts.map +1 -0
- package/dist/resolvers/TeamsMeetingsResolver.js +91 -0
- package/dist/resolvers/TeamsMeetingsResolver.js.map +1 -0
- package/dist/resolvers/TelephonyResolver.d.ts +26 -0
- package/dist/resolvers/TelephonyResolver.d.ts.map +1 -0
- package/dist/resolvers/TelephonyResolver.js +86 -0
- package/dist/resolvers/TelephonyResolver.js.map +1 -0
- package/dist/resolvers/TestQuerySQLResolver.d.ts +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +2 -3
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/VonageTelephonyResolver.d.ts +26 -0
- package/dist/resolvers/VonageTelephonyResolver.d.ts.map +1 -0
- package/dist/resolvers/VonageTelephonyResolver.js +86 -0
- package/dist/resolvers/VonageTelephonyResolver.js.map +1 -0
- package/dist/rest/MediaStreamHandler.js +4 -1
- package/dist/rest/MediaStreamHandler.js.map +1 -1
- package/dist/telephony/RingCentralTelephonyService.d.ts +115 -0
- package/dist/telephony/RingCentralTelephonyService.d.ts.map +1 -0
- package/dist/telephony/RingCentralTelephonyService.js +262 -0
- package/dist/telephony/RingCentralTelephonyService.js.map +1 -0
- package/dist/telephony/TeamsMeetingsRouter.d.ts +40 -0
- package/dist/telephony/TeamsMeetingsRouter.d.ts.map +1 -0
- package/dist/telephony/TeamsMeetingsRouter.js +96 -0
- package/dist/telephony/TeamsMeetingsRouter.js.map +1 -0
- package/dist/telephony/TeamsMeetingsService.d.ts +113 -0
- package/dist/telephony/TeamsMeetingsService.d.ts.map +1 -0
- package/dist/telephony/TeamsMeetingsService.js +196 -0
- package/dist/telephony/TeamsMeetingsService.js.map +1 -0
- package/dist/telephony/TwilioTelephonyRouter.d.ts +36 -0
- package/dist/telephony/TwilioTelephonyRouter.d.ts.map +1 -0
- package/dist/telephony/TwilioTelephonyRouter.js +143 -0
- package/dist/telephony/TwilioTelephonyRouter.js.map +1 -0
- package/dist/telephony/TwilioTelephonyService.d.ts +95 -0
- package/dist/telephony/TwilioTelephonyService.d.ts.map +1 -0
- package/dist/telephony/TwilioTelephonyService.js +193 -0
- package/dist/telephony/TwilioTelephonyService.js.map +1 -0
- package/dist/telephony/VonageTelephonyRouter.d.ts +41 -0
- package/dist/telephony/VonageTelephonyRouter.d.ts.map +1 -0
- package/dist/telephony/VonageTelephonyRouter.js +201 -0
- package/dist/telephony/VonageTelephonyRouter.js.map +1 -0
- package/dist/telephony/VonageTelephonyService.d.ts +90 -0
- package/dist/telephony/VonageTelephonyService.d.ts.map +1 -0
- package/dist/telephony/VonageTelephonyService.js +191 -0
- package/dist/telephony/VonageTelephonyService.js.map +1 -0
- package/dist/telephony/calendar-scheduler.d.ts +67 -0
- package/dist/telephony/calendar-scheduler.d.ts.map +1 -0
- package/dist/telephony/calendar-scheduler.js +133 -0
- package/dist/telephony/calendar-scheduler.js.map +1 -0
- package/dist/telephony/index.d.ts +29 -0
- package/dist/telephony/index.d.ts.map +1 -0
- package/dist/telephony/index.js +38 -0
- package/dist/telephony/index.js.map +1 -0
- package/dist/telephony/media-upgrade-router.d.ts +33 -0
- package/dist/telephony/media-upgrade-router.d.ts.map +1 -0
- package/dist/telephony/media-upgrade-router.js +56 -0
- package/dist/telephony/media-upgrade-router.js.map +1 -0
- package/dist/telephony/ringcentral-runtime.d.ts +14 -0
- package/dist/telephony/ringcentral-runtime.d.ts.map +1 -0
- package/dist/telephony/ringcentral-runtime.js +18 -0
- package/dist/telephony/ringcentral-runtime.js.map +1 -0
- package/dist/telephony/teams-meetings-runtime.d.ts +16 -0
- package/dist/telephony/teams-meetings-runtime.d.ts.map +1 -0
- package/dist/telephony/teams-meetings-runtime.js +20 -0
- package/dist/telephony/teams-meetings-runtime.js.map +1 -0
- package/dist/telephony/teamsAcsMediaRegistry.d.ts +69 -0
- package/dist/telephony/teamsAcsMediaRegistry.d.ts.map +1 -0
- package/dist/telephony/teamsAcsMediaRegistry.js +118 -0
- package/dist/telephony/teamsAcsMediaRegistry.js.map +1 -0
- package/dist/telephony/telephony-runtime.d.ts +14 -0
- package/dist/telephony/telephony-runtime.d.ts.map +1 -0
- package/dist/telephony/telephony-runtime.js +18 -0
- package/dist/telephony/telephony-runtime.js.map +1 -0
- package/dist/telephony/twilioMediaRegistry.d.ts +60 -0
- package/dist/telephony/twilioMediaRegistry.d.ts.map +1 -0
- package/dist/telephony/twilioMediaRegistry.js +106 -0
- package/dist/telephony/twilioMediaRegistry.js.map +1 -0
- package/dist/telephony/vonage-runtime.d.ts +14 -0
- package/dist/telephony/vonage-runtime.d.ts.map +1 -0
- package/dist/telephony/vonage-runtime.js +18 -0
- package/dist/telephony/vonage-runtime.js.map +1 -0
- package/dist/telephony/vonageMediaRegistry.d.ts +78 -0
- package/dist/telephony/vonageMediaRegistry.d.ts.map +1 -0
- package/dist/telephony/vonageMediaRegistry.js +158 -0
- package/dist/telephony/vonageMediaRegistry.js.map +1 -0
- package/package.json +88 -83
- package/src/__tests__/RunAIAgentResolver.streaming.test.ts +128 -0
- package/src/__tests__/magicLink.test.ts +53 -3
- package/src/__tests__/search-knowledge-tags.test.ts +9 -9
- package/src/__tests__/teams-meetings-service.test.ts +65 -0
- package/src/__tests__/teamsAcsMediaRegistry.test.ts +82 -0
- package/src/__tests__/twilio-telephony-service.test.ts +23 -0
- package/src/__tests__/twilioMediaRegistry.test.ts +103 -0
- package/src/__tests__/vonageMediaRegistry.test.ts +180 -0
- package/src/__tests__/widget.test.ts +204 -0
- package/src/__tests__/widgetGuestElevation.test.ts +57 -0
- package/src/__tests__/widgetHostIdentity.test.ts +117 -0
- package/src/agentSessions/ReturningVisitorRecap.ts +242 -0
- package/src/agentSessions/SessionJanitor.ts +66 -6
- package/src/agentSessions/SessionManager.ts +38 -0
- package/src/auth/magicLink/MagicLinkRouter.ts +17 -0
- package/src/auth/magicLink/MagicLinkService.ts +13 -3
- package/src/auth/magicLink/index.ts +1 -0
- package/src/auth/magicLink/magicLinkCore.ts +48 -20
- package/src/auth/magicLink/types.ts +26 -0
- package/src/config.ts +172 -38
- package/src/context.ts +50 -3
- package/src/generated/generated.ts +1991 -1
- package/src/generic/ResolverBase.ts +28 -0
- package/src/generic/RunViewResolver.ts +4 -0
- package/src/index.ts +109 -3
- package/src/integration/CustomColumnPromoter.ts +6 -0
- package/src/realtimeWidget/WidgetRouter.ts +204 -0
- package/src/realtimeWidget/WidgetSessionService.ts +714 -0
- package/src/realtimeWidget/host-identity.ts +84 -0
- package/src/realtimeWidget/index.ts +15 -0
- package/src/realtimeWidget/visitorIdentity.ts +258 -0
- package/src/realtimeWidget/widgetCore.ts +231 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +115 -0
- package/src/resolvers/ComponentRegistryResolver.ts +36 -10
- package/src/resolvers/EntityPermissionResolver.ts +1 -2
- package/src/resolvers/QuerySystemUserResolver.ts +14 -10
- package/src/resolvers/RealtimeClientSessionResolver.ts +59 -2
- package/src/resolvers/RingCentralTelephonyResolver.ts +64 -0
- package/src/resolvers/RunAIAgentResolver.ts +32 -8
- package/src/resolvers/SearchKnowledgeResolver.ts +2 -0
- package/src/resolvers/TeamsMeetingsResolver.ts +68 -0
- package/src/resolvers/TelephonyResolver.ts +63 -0
- package/src/resolvers/TestQuerySQLResolver.ts +2 -3
- package/src/resolvers/VonageTelephonyResolver.ts +63 -0
- package/src/rest/MediaStreamHandler.ts +4 -1
- package/src/telephony/RingCentralTelephonyService.ts +342 -0
- package/src/telephony/TeamsMeetingsRouter.ts +124 -0
- package/src/telephony/TeamsMeetingsService.ts +268 -0
- package/src/telephony/TwilioTelephonyRouter.ts +184 -0
- package/src/telephony/TwilioTelephonyService.ts +261 -0
- package/src/telephony/VonageTelephonyRouter.ts +239 -0
- package/src/telephony/VonageTelephonyService.ts +259 -0
- package/src/telephony/calendar-scheduler.ts +176 -0
- package/src/telephony/index.ts +43 -0
- package/src/telephony/media-upgrade-router.ts +62 -0
- package/src/telephony/ringcentral-runtime.ts +22 -0
- package/src/telephony/teams-meetings-runtime.ts +24 -0
- package/src/telephony/teamsAcsMediaRegistry.ts +152 -0
- package/src/telephony/telephony-runtime.ts +22 -0
- package/src/telephony/twilioMediaRegistry.ts +137 -0
- package/src/telephony/vonage-runtime.ts +22 -0
- package/src/telephony/vonageMediaRegistry.ts +200 -0
- package/dist/agents/skip-agent.d.ts +0 -111
- package/dist/agents/skip-agent.d.ts.map +0 -1
- package/dist/agents/skip-agent.js +0 -1487
- package/dist/agents/skip-agent.js.map +0 -1
- package/dist/agents/skip-sdk.d.ts +0 -261
- package/dist/agents/skip-sdk.d.ts.map +0 -1
- package/dist/agents/skip-sdk.js +0 -909
- package/dist/agents/skip-sdk.js.map +0 -1
- package/src/agents/skip-agent.ts +0 -1594
- package/src/agents/skip-sdk.ts +0 -1210
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Host wiring for the scheduled/invite-driven meeting join loop (program M2).
|
|
3
|
+
*
|
|
4
|
+
* Composes the offline-complete seams from `@memberjunction/ai-bridge-server` into a running
|
|
5
|
+
* background loop, mirroring the {@link SessionJanitor} startup pattern (run once at boot, then on an
|
|
6
|
+
* interval):
|
|
7
|
+
*
|
|
8
|
+
* 1. {@link CalendarWatcher} polls each agent identity's calendar (via the injected
|
|
9
|
+
* {@link CalendarSourceResolver}, bound here to the real {@link GraphCalendarClient} /
|
|
10
|
+
* {@link GoogleCalendarClient}) and creates a `Scheduled` `AIAgentSessionBridge` for every invite
|
|
11
|
+
* addressed to the agent.
|
|
12
|
+
* 2. {@link ScheduledBridgeRunner} starts each bridge whose `ScheduledStartTime` is due, building the
|
|
13
|
+
* realtime session + the provider bind-sdk via the per-provider meeting service (Teams today).
|
|
14
|
+
*
|
|
15
|
+
* Calendar credentials are PRE-RESOLVED upstream (the bot's Graph access token comes from the Teams
|
|
16
|
+
* meetings config — `botAccessToken`, resolved via MJ config, never inline). Google calendar polling
|
|
17
|
+
* binds only when a Google auth client is supplied; otherwise those identities are skipped (the watcher
|
|
18
|
+
* tolerates a `null` source per identity). Live verification requires a real tenant + the Azure app /
|
|
19
|
+
* Google service account — the same access the meeting bridges need.
|
|
20
|
+
*
|
|
21
|
+
* @module @memberjunction/server/telephony
|
|
22
|
+
*/
|
|
23
|
+
import { LogStatus, LogError } from '@memberjunction/core';
|
|
24
|
+
import { CalendarWatcher, ScheduledBridgeRunner, GraphCalendarSource, GraphCalendarClient, } from '@memberjunction/ai-bridge-server';
|
|
25
|
+
/** The Teams bridge driver class — identities/bridges on this provider poll Graph + join via the Teams service. */
|
|
26
|
+
const TEAMS_PROVIDER_DRIVER = 'TeamsBridge';
|
|
27
|
+
/** Default cadence (ms) for the calendar sweep + due-bridge run. 60s balances freshness vs. API quota. */
|
|
28
|
+
const DEFAULT_POLL_INTERVAL_MS = 60_000;
|
|
29
|
+
/**
|
|
30
|
+
* Builds the {@link CalendarSourceResolver} that binds the real calendar clients per identity. Graph
|
|
31
|
+
* (Teams/Microsoft) identities bind a {@link GraphCalendarClient} authenticated with the bot's
|
|
32
|
+
* pre-resolved Graph token; any identity without configured credentials resolves to `null` and is
|
|
33
|
+
* skipped this sweep (the watcher tolerates that). Google binding activates when a Google auth client
|
|
34
|
+
* is wired into the deployment (none configured today → Google-backed identities are skipped).
|
|
35
|
+
*/
|
|
36
|
+
export function buildCalendarSourceResolver(teamsConfig) {
|
|
37
|
+
return (_identity, provider) => {
|
|
38
|
+
const driver = provider?.DriverClass?.trim();
|
|
39
|
+
if (driver === TEAMS_PROVIDER_DRIVER && teamsConfig.botAccessToken) {
|
|
40
|
+
return new GraphCalendarSource(new GraphCalendarClient({ Credentials: { AccessToken: teamsConfig.botAccessToken } }));
|
|
41
|
+
}
|
|
42
|
+
// Google Calendar is a deliberate, CLOSED seam — intentionally not bound here. The
|
|
43
|
+
// GoogleCalendarClient is built + unit-tested, but binding it would be dead code today:
|
|
44
|
+
// 1. there is no Google Meet *join* path for a detected invite to trigger (the scheduled-join
|
|
45
|
+
// factory is Teams-only), and
|
|
46
|
+
// 2. Google's Meet Media API is Developer-Preview + allowlist-gated regardless (every meeting
|
|
47
|
+
// participant must be enrolled), so it isn't a shippable target.
|
|
48
|
+
// To activate: define a Google auth client from config AND wire a Google Meet join. Until then
|
|
49
|
+
// Google identities are skipped rather than erroring the sweep.
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Builds the {@link ScheduledBridgeSessionFactory} that turns a due bridge into start params. Resolves
|
|
55
|
+
* the bridge's agent (via its `AIAgentSession`) and, for a Teams bridge, delegates to the shared
|
|
56
|
+
* {@link TeamsMeetingsService} so the scheduled join reuses the exact provider/session/bind-sdk path as
|
|
57
|
+
* an on-demand join. Returns `null` (skip) for a provider with no scheduled-join wiring yet, or when the
|
|
58
|
+
* bridge has no join address.
|
|
59
|
+
*/
|
|
60
|
+
export function buildScheduledSessionFactory(teamsService) {
|
|
61
|
+
return async (ctx) => {
|
|
62
|
+
const joinUrl = ctx.BridgeRow.Address?.trim();
|
|
63
|
+
if (!joinUrl) {
|
|
64
|
+
LogStatus(`[CalendarScheduler] bridge ${ctx.BridgeRow.ID} has no join address; skipping.`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (ctx.Provider.DriverClass?.trim() !== TEAMS_PROVIDER_DRIVER) {
|
|
68
|
+
LogStatus(`[CalendarScheduler] no scheduled-join wiring for provider '${ctx.Provider.Name}'; skipping bridge ${ctx.BridgeRow.ID}.`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const agentID = await resolveAgentID(ctx.BridgeRow.AgentSessionID, ctx.ContextUser, ctx.MetadataProvider);
|
|
72
|
+
if (!agentID) {
|
|
73
|
+
LogStatus(`[CalendarScheduler] could not resolve agent for session ${ctx.BridgeRow.AgentSessionID}; skipping.`);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const params = await teamsService.BuildScheduledStartParams({
|
|
77
|
+
agentID,
|
|
78
|
+
joinUrl,
|
|
79
|
+
agentSessionID: ctx.BridgeRow.AgentSessionID,
|
|
80
|
+
contextUser: ctx.ContextUser,
|
|
81
|
+
provider: ctx.MetadataProvider,
|
|
82
|
+
});
|
|
83
|
+
return params;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Loads the `AIAgentSession` to resolve the agent the scheduled bridge belongs to. */
|
|
87
|
+
async function resolveAgentID(agentSessionID, contextUser, provider) {
|
|
88
|
+
const session = await provider.GetEntityObject('MJ: AI Agent Sessions', contextUser);
|
|
89
|
+
if (!(await session.Load(agentSessionID))) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return session.AgentID;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Starts the calendar/scheduled-bridge loop: sweeps calendars and runs due bridges once immediately,
|
|
96
|
+
* then on a fixed interval. Mirrors {@link SessionJanitor.Start}. A sweep/run failure is logged and the
|
|
97
|
+
* loop continues — a transient calendar/transport error never kills the scheduler.
|
|
98
|
+
*/
|
|
99
|
+
export function StartCalendarScheduler(options) {
|
|
100
|
+
const watcher = new CalendarWatcher({
|
|
101
|
+
SourceResolver: buildCalendarSourceResolver(options.TeamsConfig),
|
|
102
|
+
ContextUser: options.ContextUser,
|
|
103
|
+
MetadataProvider: options.Provider,
|
|
104
|
+
});
|
|
105
|
+
const runner = new ScheduledBridgeRunner({
|
|
106
|
+
SessionFactory: buildScheduledSessionFactory(options.TeamsService),
|
|
107
|
+
ContextUser: options.ContextUser,
|
|
108
|
+
MetadataProvider: options.Provider,
|
|
109
|
+
});
|
|
110
|
+
const tick = async () => {
|
|
111
|
+
try {
|
|
112
|
+
const swept = await watcher.Sweep();
|
|
113
|
+
const ran = await runner.RunDueBridges();
|
|
114
|
+
if (swept.BridgesCreated > 0 || ran.Started > 0) {
|
|
115
|
+
LogStatus(`[CalendarScheduler] swept ${swept.BridgesCreated} new bridge(s); started ${ran.Started} due bridge(s).`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
LogError(`[CalendarScheduler] sweep/run pass failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
void tick(); // run-once at boot
|
|
123
|
+
const timer = setInterval(() => void tick(), options.PollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
124
|
+
// Don't let the poll timer keep the process alive at shutdown (the loop is best-effort background work).
|
|
125
|
+
timer.unref?.();
|
|
126
|
+
LogStatus(`[CalendarScheduler] started (every ${(options.PollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS) / 1000}s).`);
|
|
127
|
+
return {
|
|
128
|
+
Stop() {
|
|
129
|
+
clearInterval(timer);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=calendar-scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar-scheduler.js","sourceRoot":"","sources":["../../src/telephony/calendar-scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Public exports for the MJAPI telephony + meetings ingress.
|
|
3
|
+
*
|
|
4
|
+
* Each vendor ingress is a quartet: a per-call media registry, a bridge-orchestration service, an
|
|
5
|
+
* Express/WSS router (the `create*Handler` factory + mount-path consts), and a process-wide runtime
|
|
6
|
+
* holder (`Set*`/`Get*`). The routers + runtimes are star-exported; the service + registry modules are
|
|
7
|
+
* re-exported by name to avoid star-export ambiguity on their internal, per-vendor-identical helper
|
|
8
|
+
* types (`InboundCallInput`, `InboundCallResult`, `ITelephonyMediaSocket`). Those internal types are
|
|
9
|
+
* not part of the package's public surface — code that needs them imports from the source module.
|
|
10
|
+
*
|
|
11
|
+
* @module @memberjunction/server/telephony
|
|
12
|
+
*/
|
|
13
|
+
export * from './media-upgrade-router.js';
|
|
14
|
+
export { TwilioCallMediaRegistry } from './twilioMediaRegistry.js';
|
|
15
|
+
export { TwilioTelephonyService, type TwilioTelephonyServiceDeps } from './TwilioTelephonyService.js';
|
|
16
|
+
export * from './TwilioTelephonyRouter.js';
|
|
17
|
+
export * from './telephony-runtime.js';
|
|
18
|
+
export { VonageCallMediaRegistry } from './vonageMediaRegistry.js';
|
|
19
|
+
export { VonageTelephonyService, type VonageTelephonyServiceDeps } from './VonageTelephonyService.js';
|
|
20
|
+
export * from './VonageTelephonyRouter.js';
|
|
21
|
+
export * from './vonage-runtime.js';
|
|
22
|
+
export { RingCentralTelephonyService, type RingCentralTelephonyServiceDeps } from './RingCentralTelephonyService.js';
|
|
23
|
+
export * from './ringcentral-runtime.js';
|
|
24
|
+
export * from './teamsAcsMediaRegistry.js';
|
|
25
|
+
export * from './TeamsMeetingsService.js';
|
|
26
|
+
export * from './TeamsMeetingsRouter.js';
|
|
27
|
+
export * from './teams-meetings-runtime.js';
|
|
28
|
+
export * from './calendar-scheduler.js';
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telephony/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,cAAc,2BAA2B,CAAC;AAG1C,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,KAAK,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AACtG,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AAGvC,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,KAAK,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AACtG,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AAMpC,OAAO,EAAE,2BAA2B,EAAE,KAAK,+BAA+B,EAAE,MAAM,kCAAkC,CAAC;AACrH,cAAc,0BAA0B,CAAC;AAGzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAG5C,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Public exports for the MJAPI telephony + meetings ingress.
|
|
3
|
+
*
|
|
4
|
+
* Each vendor ingress is a quartet: a per-call media registry, a bridge-orchestration service, an
|
|
5
|
+
* Express/WSS router (the `create*Handler` factory + mount-path consts), and a process-wide runtime
|
|
6
|
+
* holder (`Set*`/`Get*`). The routers + runtimes are star-exported; the service + registry modules are
|
|
7
|
+
* re-exported by name to avoid star-export ambiguity on their internal, per-vendor-identical helper
|
|
8
|
+
* types (`InboundCallInput`, `InboundCallResult`, `ITelephonyMediaSocket`). Those internal types are
|
|
9
|
+
* not part of the package's public surface — code that needs them imports from the source module.
|
|
10
|
+
*
|
|
11
|
+
* @module @memberjunction/server/telephony
|
|
12
|
+
*/
|
|
13
|
+
// ── Shared WebSocket-upgrade dispatcher (routes GraphQL + all media sockets by path) ──────
|
|
14
|
+
export * from './media-upgrade-router.js';
|
|
15
|
+
// ── Twilio Programmable Voice + Media Streams ──────────────────────────────────────────
|
|
16
|
+
export { TwilioCallMediaRegistry } from './twilioMediaRegistry.js';
|
|
17
|
+
export { TwilioTelephonyService } from './TwilioTelephonyService.js';
|
|
18
|
+
export * from './TwilioTelephonyRouter.js';
|
|
19
|
+
export * from './telephony-runtime.js';
|
|
20
|
+
// ── Vonage Voice + WebSocket-media ─────────────────────────────────────────────────────
|
|
21
|
+
export { VonageCallMediaRegistry } from './vonageMediaRegistry.js';
|
|
22
|
+
export { VonageTelephonyService } from './VonageTelephonyService.js';
|
|
23
|
+
export * from './VonageTelephonyRouter.js';
|
|
24
|
+
export * from './vonage-runtime.js';
|
|
25
|
+
// ── RingCentral SIP softphone (the only RingCentral transport with bidirectional call audio) ──
|
|
26
|
+
// No media registry / webhook router: the softphone is an outbound SIP registration that receives inbound
|
|
27
|
+
// INVITEs directly. The service owns the registration; the runtime holder shares it with the outbound
|
|
28
|
+
// PlaceRingCentralCall resolver.
|
|
29
|
+
export { RingCentralTelephonyService } from './RingCentralTelephonyService.js';
|
|
30
|
+
export * from './ringcentral-runtime.js';
|
|
31
|
+
// ── Teams meetings (Microsoft Graph cloud-communications + ACS application-hosted media) ──
|
|
32
|
+
export * from './teamsAcsMediaRegistry.js';
|
|
33
|
+
export * from './TeamsMeetingsService.js';
|
|
34
|
+
export * from './TeamsMeetingsRouter.js';
|
|
35
|
+
export * from './teams-meetings-runtime.js';
|
|
36
|
+
// ── Scheduled / invite-driven meeting join loop (M2: calendar watcher + due-bridge runner) ──
|
|
37
|
+
export * from './calendar-scheduler.js';
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/telephony/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,6FAA6F;AAC7F,cAAc,2BAA2B,CAAC;AAE1C,0FAA0F;AAC1F,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAmC,MAAM,6BAA6B,CAAC;AACtG,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AAEvC,0FAA0F;AAC1F,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAmC,MAAM,6BAA6B,CAAC;AACtG,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AAEpC,iGAAiG;AACjG,0GAA0G;AAC1G,sGAAsG;AACtG,iCAAiC;AACjC,OAAO,EAAE,2BAA2B,EAAwC,MAAM,kCAAkC,CAAC;AACrH,cAAc,0BAA0B,CAAC;AAEzC,6FAA6F;AAC7F,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAE5C,+FAA+F;AAC/F,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Single WebSocket-upgrade dispatcher for the shared HTTP server.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: in `ws` 8.x, every `new WebSocketServer({ server })` attaches its OWN `upgrade`
|
|
5
|
+
* listener that calls `handleUpgrade` UNCONDITIONALLY, and `handleUpgrade` does `abortHandshake(400)` for
|
|
6
|
+
* any request whose path it doesn't own. So two `{server}`-bound servers on one HTTP server (e.g. the
|
|
7
|
+
* GraphQL subscriptions socket on `/` AND a telephony Media-Streams socket on `/telephony/.../media`)
|
|
8
|
+
* fight on every upgrade — the non-owning server 400s the request. That's exactly what made Twilio's
|
|
9
|
+
* `<Connect><Stream>` fail with error 31920 ("Stream WebSocket failed to connect").
|
|
10
|
+
*
|
|
11
|
+
* The fix: telephony/meetings media servers are created with `{ noServer: true }` (no auto-listener) and
|
|
12
|
+
* register their path here; at boot we strip the auto-listeners and install ONE `upgrade` listener that
|
|
13
|
+
* routes by path — GraphQL on its root path, each media socket on its own. When no media routes are
|
|
14
|
+
* registered (telephony disabled), we leave the GraphQL server's own listener untouched (zero change).
|
|
15
|
+
*
|
|
16
|
+
* @module @memberjunction/server/telephony
|
|
17
|
+
*/
|
|
18
|
+
import type { Server as HttpServer } from 'node:http';
|
|
19
|
+
import type { WebSocketServer } from 'ws';
|
|
20
|
+
/** Registers a media WebSocket route. Called by each vendor router's attach step. */
|
|
21
|
+
export declare function RegisterMediaUpgradeRoute(path: string, wss: WebSocketServer): void;
|
|
22
|
+
/**
|
|
23
|
+
* Installs the single path-routing `upgrade` dispatcher. No-op when no media routes are registered, so
|
|
24
|
+
* the GraphQL server keeps its own listener untouched. Otherwise it removes the auto-attached listeners
|
|
25
|
+
* (GraphQL's included) and dispatches every upgrade by pathname — GraphQL on `graphqlPath`, each media
|
|
26
|
+
* socket on its registered path; unknown paths are destroyed.
|
|
27
|
+
*
|
|
28
|
+
* @param httpServer the shared HTTP server.
|
|
29
|
+
* @param graphqlWss the GraphQL subscriptions WebSocketServer (kept, re-dispatched on its path).
|
|
30
|
+
* @param graphqlPath the GraphQL root path (the path GraphQL's socket listens on).
|
|
31
|
+
*/
|
|
32
|
+
export declare function InstallMediaUpgradeDispatcher(httpServer: HttpServer, graphqlWss: WebSocketServer, graphqlPath: string): void;
|
|
33
|
+
//# sourceMappingURL=media-upgrade-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-upgrade-router.d.ts","sourceRoot":"","sources":["../../src/telephony/media-upgrade-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAmB,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAK1C,qFAAqF;AACrF,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,IAAI,CAElF;AAED;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAqB5H"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Single WebSocket-upgrade dispatcher for the shared HTTP server.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: in `ws` 8.x, every `new WebSocketServer({ server })` attaches its OWN `upgrade`
|
|
5
|
+
* listener that calls `handleUpgrade` UNCONDITIONALLY, and `handleUpgrade` does `abortHandshake(400)` for
|
|
6
|
+
* any request whose path it doesn't own. So two `{server}`-bound servers on one HTTP server (e.g. the
|
|
7
|
+
* GraphQL subscriptions socket on `/` AND a telephony Media-Streams socket on `/telephony/.../media`)
|
|
8
|
+
* fight on every upgrade — the non-owning server 400s the request. That's exactly what made Twilio's
|
|
9
|
+
* `<Connect><Stream>` fail with error 31920 ("Stream WebSocket failed to connect").
|
|
10
|
+
*
|
|
11
|
+
* The fix: telephony/meetings media servers are created with `{ noServer: true }` (no auto-listener) and
|
|
12
|
+
* register their path here; at boot we strip the auto-listeners and install ONE `upgrade` listener that
|
|
13
|
+
* routes by path — GraphQL on its root path, each media socket on its own. When no media routes are
|
|
14
|
+
* registered (telephony disabled), we leave the GraphQL server's own listener untouched (zero change).
|
|
15
|
+
*
|
|
16
|
+
* @module @memberjunction/server/telephony
|
|
17
|
+
*/
|
|
18
|
+
/** path → the `{ noServer:true }` WebSocketServer that owns it. */
|
|
19
|
+
const mediaRoutes = new Map();
|
|
20
|
+
/** Registers a media WebSocket route. Called by each vendor router's attach step. */
|
|
21
|
+
export function RegisterMediaUpgradeRoute(path, wss) {
|
|
22
|
+
mediaRoutes.set(path, wss);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Installs the single path-routing `upgrade` dispatcher. No-op when no media routes are registered, so
|
|
26
|
+
* the GraphQL server keeps its own listener untouched. Otherwise it removes the auto-attached listeners
|
|
27
|
+
* (GraphQL's included) and dispatches every upgrade by pathname — GraphQL on `graphqlPath`, each media
|
|
28
|
+
* socket on its registered path; unknown paths are destroyed.
|
|
29
|
+
*
|
|
30
|
+
* @param httpServer the shared HTTP server.
|
|
31
|
+
* @param graphqlWss the GraphQL subscriptions WebSocketServer (kept, re-dispatched on its path).
|
|
32
|
+
* @param graphqlPath the GraphQL root path (the path GraphQL's socket listens on).
|
|
33
|
+
*/
|
|
34
|
+
export function InstallMediaUpgradeDispatcher(httpServer, graphqlWss, graphqlPath) {
|
|
35
|
+
if (mediaRoutes.size === 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
httpServer.removeAllListeners('upgrade');
|
|
39
|
+
const dispatch = (wss, req, socket, head) => {
|
|
40
|
+
wss.handleUpgrade(req, socket, head, (ws) => wss.emit('connection', ws, req));
|
|
41
|
+
};
|
|
42
|
+
httpServer.on('upgrade', (req, socket, head) => {
|
|
43
|
+
const pathname = (req.url ?? '').split('?')[0];
|
|
44
|
+
if (pathname === graphqlPath) {
|
|
45
|
+
dispatch(graphqlWss, req, socket, head);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const wss = mediaRoutes.get(pathname);
|
|
49
|
+
if (wss) {
|
|
50
|
+
dispatch(wss, req, socket, head);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
socket.destroy();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=media-upgrade-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-upgrade-router.js","sourceRoot":"","sources":["../../src/telephony/media-upgrade-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,mEAAmE;AACnE,MAAM,WAAW,GAAG,IAAI,GAAG,EAA2B,CAAC;AAEvD,qFAAqF;AACrF,MAAM,UAAU,yBAAyB,CAAC,IAAY,EAAE,GAAoB;IACxE,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,6BAA6B,CAAC,UAAsB,EAAE,UAA2B,EAAE,WAAmB;IAClH,IAAI,WAAW,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;IACX,CAAC;IACD,UAAU,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,CAAC,GAAoB,EAAE,GAAoB,EAAE,MAAc,EAAE,IAAY,EAAQ,EAAE;QAChG,GAAG,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IAClF,CAAC,CAAC;IACF,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAoB,EAAE,MAAc,EAAE,IAAY,EAAE,EAAE;QAC5E,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;YAC3B,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;YACxC,OAAO;QACX,CAAC;QACD,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,GAAG,EAAE,CAAC;YACN,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;YACjC,OAAO;QACX,CAAC;QACD,MAAM,CAAC,OAAO,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Process-wide holder for the startup-constructed RingCentral telephony service, so the
|
|
3
|
+
* outbound `PlaceRingCentralCall` GraphQL resolver reaches the SAME {@link RingCentralTelephonyService}
|
|
4
|
+
* (and thus the same long-lived SIP-softphone registration) that the inbound INVITE coordinator uses. Set
|
|
5
|
+
* once during server boot; read by the resolver.
|
|
6
|
+
*
|
|
7
|
+
* @module @memberjunction/server/telephony
|
|
8
|
+
*/
|
|
9
|
+
import type { RingCentralTelephonyService } from './RingCentralTelephonyService.js';
|
|
10
|
+
/** Binds the startup-constructed RingCentral telephony service (called from server boot). */
|
|
11
|
+
export declare function SetRingCentralTelephonyService(service: RingCentralTelephonyService): void;
|
|
12
|
+
/** Returns the bound RingCentral telephony service, or `undefined` when telephony is not configured. */
|
|
13
|
+
export declare function GetRingCentralTelephonyService(): RingCentralTelephonyService | undefined;
|
|
14
|
+
//# sourceMappingURL=ringcentral-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ringcentral-runtime.d.ts","sourceRoot":"","sources":["../../src/telephony/ringcentral-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAIpF,6FAA6F;AAC7F,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,2BAA2B,GAAG,IAAI,CAEzF;AAED,wGAAwG;AACxG,wBAAgB,8BAA8B,IAAI,2BAA2B,GAAG,SAAS,CAExF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Process-wide holder for the startup-constructed RingCentral telephony service, so the
|
|
3
|
+
* outbound `PlaceRingCentralCall` GraphQL resolver reaches the SAME {@link RingCentralTelephonyService}
|
|
4
|
+
* (and thus the same long-lived SIP-softphone registration) that the inbound INVITE coordinator uses. Set
|
|
5
|
+
* once during server boot; read by the resolver.
|
|
6
|
+
*
|
|
7
|
+
* @module @memberjunction/server/telephony
|
|
8
|
+
*/
|
|
9
|
+
let ringCentralService;
|
|
10
|
+
/** Binds the startup-constructed RingCentral telephony service (called from server boot). */
|
|
11
|
+
export function SetRingCentralTelephonyService(service) {
|
|
12
|
+
ringCentralService = service;
|
|
13
|
+
}
|
|
14
|
+
/** Returns the bound RingCentral telephony service, or `undefined` when telephony is not configured. */
|
|
15
|
+
export function GetRingCentralTelephonyService() {
|
|
16
|
+
return ringCentralService;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=ringcentral-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ringcentral-runtime.js","sourceRoot":"","sources":["../../src/telephony/ringcentral-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,IAAI,kBAA2D,CAAC;AAEhE,6FAA6F;AAC7F,MAAM,UAAU,8BAA8B,CAAC,OAAoC;IAC/E,kBAAkB,GAAG,OAAO,CAAC;AACjC,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,8BAA8B;IAC1C,OAAO,kBAAkB,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Process-wide holder for the startup-constructed Teams meetings service, so the
|
|
3
|
+
* `StartTeamsMeetingSession` GraphQL resolver reaches the SAME {@link TeamsMeetingsService} (and thus the
|
|
4
|
+
* same {@link TeamsAcsMediaRegistry} the ACS media adapter attaches transports to) that the Graph
|
|
5
|
+
* change-notification webhook router uses. Set once during server boot; read by the resolver + the router.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors `telephony-runtime.ts` (the Twilio equivalent).
|
|
8
|
+
*
|
|
9
|
+
* @module @memberjunction/server/telephony
|
|
10
|
+
*/
|
|
11
|
+
import type { TeamsMeetingsService } from './TeamsMeetingsService.js';
|
|
12
|
+
/** Binds the startup-constructed Teams meetings service (called from server boot). */
|
|
13
|
+
export declare function SetTeamsMeetingsService(service: TeamsMeetingsService): void;
|
|
14
|
+
/** Returns the bound Teams meetings service, or `undefined` when Teams meetings are not configured. */
|
|
15
|
+
export declare function GetTeamsMeetingsService(): TeamsMeetingsService | undefined;
|
|
16
|
+
//# sourceMappingURL=teams-meetings-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teams-meetings-runtime.d.ts","sourceRoot":"","sources":["../../src/telephony/teams-meetings-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAItE,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAE3E;AAED,uGAAuG;AACvG,wBAAgB,uBAAuB,IAAI,oBAAoB,GAAG,SAAS,CAE1E"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Process-wide holder for the startup-constructed Teams meetings service, so the
|
|
3
|
+
* `StartTeamsMeetingSession` GraphQL resolver reaches the SAME {@link TeamsMeetingsService} (and thus the
|
|
4
|
+
* same {@link TeamsAcsMediaRegistry} the ACS media adapter attaches transports to) that the Graph
|
|
5
|
+
* change-notification webhook router uses. Set once during server boot; read by the resolver + the router.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors `telephony-runtime.ts` (the Twilio equivalent).
|
|
8
|
+
*
|
|
9
|
+
* @module @memberjunction/server/telephony
|
|
10
|
+
*/
|
|
11
|
+
let teamsService;
|
|
12
|
+
/** Binds the startup-constructed Teams meetings service (called from server boot). */
|
|
13
|
+
export function SetTeamsMeetingsService(service) {
|
|
14
|
+
teamsService = service;
|
|
15
|
+
}
|
|
16
|
+
/** Returns the bound Teams meetings service, or `undefined` when Teams meetings are not configured. */
|
|
17
|
+
export function GetTeamsMeetingsService() {
|
|
18
|
+
return teamsService;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=teams-meetings-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teams-meetings-runtime.js","sourceRoot":"","sources":["../../src/telephony/teams-meetings-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,IAAI,YAA8C,CAAC;AAEnD,sFAAsF;AACtF,MAAM,UAAU,uBAAuB,CAAC,OAA6B;IACjE,YAAY,GAAG,OAAO,CAAC;AAC3B,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,uBAAuB;IACnC,OAAO,YAAY,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Per-call ACS application-hosted-media pump registry for the Teams meetings ingress.
|
|
3
|
+
*
|
|
4
|
+
* This is the server half of the Teams audio plane — the analogue of {@link TwilioCallMediaRegistry} for
|
|
5
|
+
* telephony. It implements the Teams provider package's `IAcsMediaPump` seam (which `PumpBackedAcsMedia` →
|
|
6
|
+
* `RealTeamsBindings` drive) on top of the live ACS application-hosted-media audio socket(s) the server owns.
|
|
7
|
+
*
|
|
8
|
+
* Like telephony, there is a lifecycle gap: when a meeting bridge session starts, `RealTeamsBindings`
|
|
9
|
+
* registers its inbound-audio handler before the ACS media socket has connected its inbound stream. The
|
|
10
|
+
* registry bridges that gap by buffering handler registrations + outbound PCM per call id and binding them to
|
|
11
|
+
* the media transport once it attaches (the server's native ACS media adapter calls {@link AttachTransport}
|
|
12
|
+
* when the application-hosted-media socket is up for the call).
|
|
13
|
+
*
|
|
14
|
+
* Pure + unit-testable: the media transport is injected as the minimal {@link IAcsMediaTransport} surface, so
|
|
15
|
+
* the registry's buffering / dispatch logic is exercised with a fake transport and no ACS install or network.
|
|
16
|
+
*
|
|
17
|
+
* @module @memberjunction/server/telephony
|
|
18
|
+
*/
|
|
19
|
+
import type { AcsInboundAudioFrame, IAcsMediaPump } from '@memberjunction/ai-bridge-teams';
|
|
20
|
+
/**
|
|
21
|
+
* The minimal ACS media-transport surface the registry drives — a structural subset of the server's native
|
|
22
|
+
* application-hosted-media socket adapter, so the registry never imports the ACS SDK and is testable with a
|
|
23
|
+
* fake. Outbound frames are raw PCM16 `ArrayBuffer`s for the call.
|
|
24
|
+
*/
|
|
25
|
+
export interface IAcsMediaTransport {
|
|
26
|
+
/** Sends one outbound PCM16 frame on the call's application-hosted-media outbound audio socket. */
|
|
27
|
+
sendAudioFrame(pcm: ArrayBuffer): void;
|
|
28
|
+
/** Closes the call's media transport (best-effort; called on call end). */
|
|
29
|
+
close(): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Coordinates ACS application-hosted-media transports with the bridge bindings, keyed by Graph call id. One
|
|
33
|
+
* instance per server, shared between the meetings service (which binds `PumpBackedAcsMedia` over this as the
|
|
34
|
+
* `IAcsMediaPump`) and the server's native ACS media adapter (which attaches transports + dispatches inbound
|
|
35
|
+
* audio).
|
|
36
|
+
*/
|
|
37
|
+
export declare class TeamsAcsMediaRegistry implements IAcsMediaPump {
|
|
38
|
+
readonly SampleRate: number;
|
|
39
|
+
private readonly channels;
|
|
40
|
+
/**
|
|
41
|
+
* @param SampleRate The ACS audio-socket sample rate in Hz (the application-hosted-media PCM rate the
|
|
42
|
+
* server negotiates). Drives T0 resampling in `RealTeamsBindings`. Defaults to 16000.
|
|
43
|
+
*/
|
|
44
|
+
constructor(SampleRate?: number);
|
|
45
|
+
/** Ensures a channel exists for a call (called when a meeting bridge session for the call starts). */
|
|
46
|
+
RegisterCall(callId: string): void;
|
|
47
|
+
/** @inheritdoc — buffers when the transport has not yet attached; sends immediately once it has. */
|
|
48
|
+
Send(callId: string, pcm: ArrayBuffer): void;
|
|
49
|
+
/** @inheritdoc — stores the handler so frames delivered before AND after transport attach reach it. */
|
|
50
|
+
OnFrame(callId: string, handler: (frame: AcsInboundAudioFrame) => void): void;
|
|
51
|
+
/** @inheritdoc — stores the hand-raise handler (Teams hand-raise is partial; tolerated if it never fires). */
|
|
52
|
+
OnHandRaise(callId: string, handler: (participantId: string, raised: boolean) => void): void;
|
|
53
|
+
/**
|
|
54
|
+
* Binds a connected ACS media transport to a call, flushing any outbound PCM produced while the transport
|
|
55
|
+
* was still connecting. Called by the server's native ACS media adapter once the application-hosted-media
|
|
56
|
+
* socket is up for the call.
|
|
57
|
+
*/
|
|
58
|
+
AttachTransport(callId: string, transport: IAcsMediaTransport): void;
|
|
59
|
+
/** Dispatches one inbound per-participant audio frame to all registered handlers for the call. */
|
|
60
|
+
DispatchInbound(callId: string, frame: AcsInboundAudioFrame): void;
|
|
61
|
+
/** Dispatches one raised-hand signal to all registered hand-raise handlers for the call. */
|
|
62
|
+
DispatchHandRaise(callId: string, participantId: string, raised: boolean): void;
|
|
63
|
+
/** Tears down a call's channel and closes its transport (called on call end / meeting ended). */
|
|
64
|
+
EndCall(callId: string): void;
|
|
65
|
+
/** Whether a channel is currently tracked for the call (test/observability helper). */
|
|
66
|
+
HasCall(callId: string): boolean;
|
|
67
|
+
private ensureChannel;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=teamsAcsMediaRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teamsAcsMediaRegistry.d.ts","sourceRoot":"","sources":["../../src/telephony/teamsAcsMediaRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAE3F;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAC/B,mGAAmG;IACnG,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAAC;IACvC,2EAA2E;IAC3E,KAAK,IAAI,IAAI,CAAC;CACjB;AAaD;;;;;GAKG;AACH,qBAAa,qBAAsB,YAAW,aAAa;aAO3B,UAAU;IANtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAEhE;;;OAGG;gBACyB,UAAU,SAAQ;IAE9C,sGAAsG;IAC/F,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAMzC,oGAAoG;IAC7F,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,IAAI;IASnD,uGAAuG;IAChG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,IAAI;IAIpF,8GAA8G;IACvG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IAMnG;;;;OAIG;IACI,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,GAAG,IAAI;IAS3E,kGAAkG;IAC3F,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB,GAAG,IAAI;IAUzE,4FAA4F;IACrF,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAUtF,iGAAiG;IAC1F,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAapC,uFAAuF;IAChF,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIvC,OAAO,CAAC,aAAa;CAQxB"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Per-call ACS application-hosted-media pump registry for the Teams meetings ingress.
|
|
3
|
+
*
|
|
4
|
+
* This is the server half of the Teams audio plane — the analogue of {@link TwilioCallMediaRegistry} for
|
|
5
|
+
* telephony. It implements the Teams provider package's `IAcsMediaPump` seam (which `PumpBackedAcsMedia` →
|
|
6
|
+
* `RealTeamsBindings` drive) on top of the live ACS application-hosted-media audio socket(s) the server owns.
|
|
7
|
+
*
|
|
8
|
+
* Like telephony, there is a lifecycle gap: when a meeting bridge session starts, `RealTeamsBindings`
|
|
9
|
+
* registers its inbound-audio handler before the ACS media socket has connected its inbound stream. The
|
|
10
|
+
* registry bridges that gap by buffering handler registrations + outbound PCM per call id and binding them to
|
|
11
|
+
* the media transport once it attaches (the server's native ACS media adapter calls {@link AttachTransport}
|
|
12
|
+
* when the application-hosted-media socket is up for the call).
|
|
13
|
+
*
|
|
14
|
+
* Pure + unit-testable: the media transport is injected as the minimal {@link IAcsMediaTransport} surface, so
|
|
15
|
+
* the registry's buffering / dispatch logic is exercised with a fake transport and no ACS install or network.
|
|
16
|
+
*
|
|
17
|
+
* @module @memberjunction/server/telephony
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Coordinates ACS application-hosted-media transports with the bridge bindings, keyed by Graph call id. One
|
|
21
|
+
* instance per server, shared between the meetings service (which binds `PumpBackedAcsMedia` over this as the
|
|
22
|
+
* `IAcsMediaPump`) and the server's native ACS media adapter (which attaches transports + dispatches inbound
|
|
23
|
+
* audio).
|
|
24
|
+
*/
|
|
25
|
+
export class TeamsAcsMediaRegistry {
|
|
26
|
+
/**
|
|
27
|
+
* @param SampleRate The ACS audio-socket sample rate in Hz (the application-hosted-media PCM rate the
|
|
28
|
+
* server negotiates). Drives T0 resampling in `RealTeamsBindings`. Defaults to 16000.
|
|
29
|
+
*/
|
|
30
|
+
constructor(SampleRate = 16000) {
|
|
31
|
+
this.SampleRate = SampleRate;
|
|
32
|
+
this.channels = new Map();
|
|
33
|
+
}
|
|
34
|
+
/** Ensures a channel exists for a call (called when a meeting bridge session for the call starts). */
|
|
35
|
+
RegisterCall(callId) {
|
|
36
|
+
this.ensureChannel(callId);
|
|
37
|
+
}
|
|
38
|
+
// ── IAcsMediaPump ─────────────────────────────────────────────────────────────
|
|
39
|
+
/** @inheritdoc — buffers when the transport has not yet attached; sends immediately once it has. */
|
|
40
|
+
Send(callId, pcm) {
|
|
41
|
+
const channel = this.ensureChannel(callId);
|
|
42
|
+
if (channel.transport) {
|
|
43
|
+
channel.transport.sendAudioFrame(pcm);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
channel.outboundBuffer.push(pcm);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** @inheritdoc — stores the handler so frames delivered before AND after transport attach reach it. */
|
|
50
|
+
OnFrame(callId, handler) {
|
|
51
|
+
this.ensureChannel(callId).frameHandlers.push(handler);
|
|
52
|
+
}
|
|
53
|
+
/** @inheritdoc — stores the hand-raise handler (Teams hand-raise is partial; tolerated if it never fires). */
|
|
54
|
+
OnHandRaise(callId, handler) {
|
|
55
|
+
this.ensureChannel(callId).handRaiseHandlers.push(handler);
|
|
56
|
+
}
|
|
57
|
+
// ── native-adapter side ─────────────────────────────────────────────────────
|
|
58
|
+
/**
|
|
59
|
+
* Binds a connected ACS media transport to a call, flushing any outbound PCM produced while the transport
|
|
60
|
+
* was still connecting. Called by the server's native ACS media adapter once the application-hosted-media
|
|
61
|
+
* socket is up for the call.
|
|
62
|
+
*/
|
|
63
|
+
AttachTransport(callId, transport) {
|
|
64
|
+
const channel = this.ensureChannel(callId);
|
|
65
|
+
channel.transport = transport;
|
|
66
|
+
const buffered = channel.outboundBuffer.splice(0, channel.outboundBuffer.length);
|
|
67
|
+
for (const pcm of buffered) {
|
|
68
|
+
transport.sendAudioFrame(pcm);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Dispatches one inbound per-participant audio frame to all registered handlers for the call. */
|
|
72
|
+
DispatchInbound(callId, frame) {
|
|
73
|
+
const channel = this.channels.get(callId);
|
|
74
|
+
if (!channel) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
for (const handler of channel.frameHandlers) {
|
|
78
|
+
handler(frame);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Dispatches one raised-hand signal to all registered hand-raise handlers for the call. */
|
|
82
|
+
DispatchHandRaise(callId, participantId, raised) {
|
|
83
|
+
const channel = this.channels.get(callId);
|
|
84
|
+
if (!channel) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const handler of channel.handRaiseHandlers) {
|
|
88
|
+
handler(participantId, raised);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Tears down a call's channel and closes its transport (called on call end / meeting ended). */
|
|
92
|
+
EndCall(callId) {
|
|
93
|
+
const channel = this.channels.get(callId);
|
|
94
|
+
if (!channel) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
channel.transport?.close();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
/* best-effort */
|
|
102
|
+
}
|
|
103
|
+
this.channels.delete(callId);
|
|
104
|
+
}
|
|
105
|
+
/** Whether a channel is currently tracked for the call (test/observability helper). */
|
|
106
|
+
HasCall(callId) {
|
|
107
|
+
return this.channels.has(callId);
|
|
108
|
+
}
|
|
109
|
+
ensureChannel(callId) {
|
|
110
|
+
let channel = this.channels.get(callId);
|
|
111
|
+
if (!channel) {
|
|
112
|
+
channel = { frameHandlers: [], handRaiseHandlers: [], outboundBuffer: [] };
|
|
113
|
+
this.channels.set(callId, channel);
|
|
114
|
+
}
|
|
115
|
+
return channel;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=teamsAcsMediaRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teamsAcsMediaRegistry.js","sourceRoot":"","sources":["../../src/telephony/teamsAcsMediaRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Process-wide holder for the startup-constructed telephony service, so the
|
|
3
|
+
* outbound `PlaceTwilioCall` GraphQL resolver reaches the SAME {@link TwilioTelephonyService}
|
|
4
|
+
* (and thus the same {@link TwilioCallMediaRegistry} the Media-Streams WSS attaches sockets to)
|
|
5
|
+
* that the inbound webhook router uses. Set once during server boot; read by the resolver.
|
|
6
|
+
*
|
|
7
|
+
* @module @memberjunction/server/telephony
|
|
8
|
+
*/
|
|
9
|
+
import type { TwilioTelephonyService } from './TwilioTelephonyService.js';
|
|
10
|
+
/** Binds the startup-constructed Twilio telephony service (called from server boot). */
|
|
11
|
+
export declare function SetTwilioTelephonyService(service: TwilioTelephonyService): void;
|
|
12
|
+
/** Returns the bound Twilio telephony service, or `undefined` when telephony is not configured. */
|
|
13
|
+
export declare function GetTwilioTelephonyService(): TwilioTelephonyService | undefined;
|
|
14
|
+
//# sourceMappingURL=telephony-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telephony-runtime.d.ts","sourceRoot":"","sources":["../../src/telephony/telephony-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAI1E,wFAAwF;AACxF,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAE/E;AAED,mGAAmG;AACnG,wBAAgB,yBAAyB,IAAI,sBAAsB,GAAG,SAAS,CAE9E"}
|