Consistency + cleanup pass

- Route Twilio Lookup v2 through the on-prem remote agent so all
  third-party calls (SIW, Google, Twilio) share the same network path
  and future IP allow-lists / corp proxies don't break it silently
- Delete unused GOOGLE_APPLICATION_CREDENTIALS / serviceAccountKeyPath
  wiring in config.js, .env.example, and README (never read by any code)
- Extract 150 lines of STORE_DEVICE_CUSTOMIZATIONS out of
  src/webex/devices.js into src/webex/deviceCustomizations.js so device
  config diffs are self-contained and devices.js stays focused on API
- runStep learns a {critical: true} option that re-throws on failure
  instead of the legacy always-swallow behavior; mark
  enableLocationForCalling critical in buildStore + stageStore so a
  failure there aborts the flow cleanly rather than cascading into
  dozens of downstream 404s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jmcqueen 2026-07-06 15:56:01 -04:00
parent 8b32975eaf
commit 461caa8086
9 changed files with 212 additions and 187 deletions

View file

@ -36,15 +36,10 @@ WS_TOKEN=change-me-to-a-long-random-string
# --- Google ---
# Simple REST API key used by Address Validation + Time Zone endpoints.
# All Google calls are proxied through the on-prem remote agent (see WS_*
# above) so requests originate from the IP subnet the key allow-lists.
GOOGLE_API_KEY=your-google-api-key
# Optional. Path to a Google service-account JSON key file (the one shaped
# like { type: "service_account", private_key: "-----BEGIN...", ... }).
# Not required for the REST calls above, but standard for any future code
# that uses google-auth-library / googleapis. Keep the file itself outside
# of git (config/google-service-account.json is already ignored).
GOOGLE_APPLICATION_CREDENTIALS=./config/google-service-account.json
# --- Runtime ---
NODE_ENV=production
LOG_LEVEL=info

View file

@ -10,14 +10,9 @@ greetings, build auto-attendants, and clean up user licensing.
- A Webex bot token, a Webex integration (service account) with the scopes
currently used by admin API calls, a Twilio lookup account, an SIW basic-auth
user, and a Google API key with Address Validation + Time Zone enabled.
- An on-prem host that can reach Store Info Web and Google APIs from an
IP-whitelisted subnet, to run the remote agent (see
- An on-prem host that can reach Store Info Web, Google APIs, and Twilio
Lookup from an IP-whitelisted subnet, to run the remote agent (see
[Remote agent (SIW + Google)](#remote-agent-siw--google) below).
- Optional: a Google service-account JSON key. Only the REST API key is
required today, but if/when you add code that uses `google-auth-library`,
save the JSON at `config/google-service-account.json` and set
`GOOGLE_APPLICATION_CREDENTIALS` in `.env` to point at it. The file is
git-ignored.
## Local setup

View file

@ -73,13 +73,6 @@ export const config = {
google: {
apiKey: required('GOOGLE_API_KEY'),
// Path to a service-account JSON key file. Optional — only set when
// future code needs google-auth-library-style credentials. The file
// itself lives outside of git.
serviceAccountKeyPath: (() => {
const raw = optional('GOOGLE_APPLICATION_CREDENTIALS', '');
return raw ? resolvePath(raw) : null;
})(),
},
};

View file

@ -30,8 +30,11 @@ export async function buildStoreLocation(bot, locationInfo, userInfo) {
`<blockquote class='success'>Created location ${location.name}.</blockquote>`,
);
await runStep(bot, 'Enabled location for Webex Calling', () =>
enableLocationForCalling(location),
await runStep(
bot,
'Enabled location for Webex Calling',
() => enableLocationForCalling(location),
{ critical: true },
);
await runStep(bot, 'Updated location Webex Calling connection', () =>
updateLocationRouteGroup(location),

View file

@ -27,8 +27,11 @@ export async function stageStoreLocation(bot, locationInfo, userInfo) {
`<blockquote class='success'>Created location ${location.name}.</blockquote>`,
);
await runStep(bot, 'Enabled location for Webex Calling', () =>
enableLocationForCalling(location),
await runStep(
bot,
'Enabled location for Webex Calling',
() => enableLocationForCalling(location),
{ critical: true },
);
await runStep(bot, 'Updated internal dialing', () => updateInternalDialing(location));
await runStep(bot, 'Updated location outgoing permission', () =>

View file

@ -2,11 +2,24 @@ import { logger } from '../logger.js';
/**
* Run a provisioning step, log its outcome to console + bot chat, and return
* the step's resolved value (or undefined on failure). Never throws matches
* the legacy behavior where each step's error was captured but did not halt
* the flow.
* the step's resolved value.
*
* Options:
* - successMessage: text to show on success (defaults to description)
* - failureMessage: text to show on failure (defaults to "Error: {description}")
* - critical: when true, a failure re-throws so the caller aborts the flow.
* Use for steps that everything downstream depends on (e.g. enabling a
* new location for Webex Calling every subsequent Calling API call will
* 404 otherwise). Non-critical steps swallow the error and return undefined,
* matching legacy behavior so a single flaky API call doesn't abort the
* entire provision.
*/
export async function runStep(bot, description, fn, { successMessage, failureMessage } = {}) {
export async function runStep(
bot,
description,
fn,
{ successMessage, failureMessage, critical = false } = {},
) {
try {
const result = await fn();
const msg = successMessage ?? description;
@ -14,8 +27,10 @@ export async function runStep(bot, description, fn, { successMessage, failureMes
return result;
} catch (error) {
logger.error(`${description} failed:`, error);
const msg = failureMessage ?? `Error: ${description}`;
const baseMsg = failureMessage ?? `Error: ${description}`;
const msg = critical ? `${baseMsg} — aborting (critical step).` : baseMsg;
bot?.say('markdown', `<blockquote class='failure'>${msg}</blockquote>`);
if (critical) throw error;
return undefined;
}
}

View file

@ -1,7 +1,15 @@
import { config } from '../config.js';
import { requestJson } from '../http.js';
import { proxyRequest } from '../services/websocket.js';
function twilioHeaders() {
// Twilio's Lookup v2 has no IP allow-list requirement today, but every other
// third-party call in this bot (SIW, Google) is already proxied through the
// on-prem agent. Doing the same for Twilio keeps the network surface
// consistent — if Twilio ever adds IP restrictions, or the bot moves behind
// a corporate egress proxy, this call keeps working without a code change.
// `insecure` is not set here: Twilio uses public CAs that Node trusts, and
// the agent's host reaches lookups.twilio.com without an SSL-inspecting hop.
function twilioAuthHeaders() {
const auth = Buffer.from(`${config.twilio.accountSid}:${config.twilio.authToken}`).toString(
'base64',
);
@ -14,5 +22,10 @@ function twilioHeaders() {
*/
export async function validatePhoneNumber(phoneNumber) {
const url = `https://lookups.twilio.com/v2/PhoneNumbers/${encodeURIComponent(phoneNumber)}`;
return requestJson('GET', url, undefined, { headers: twilioHeaders() });
const response = await proxyRequest({
method: 'GET',
url,
headers: twilioAuthHeaders(),
});
return response?.data;
}

View file

@ -0,0 +1,160 @@
// Per-device-family calling customizations applied to every store location
// as part of buildStore / stageStore. Sent verbatim to
// POST /telephony/config/jobs/devices/callDeviceSettings under `customizations`.
//
// This is intentionally a data-only module so device config changes are a
// clean, self-contained diff. Anything that changes here changes what gets
// pushed to phones store-wide, so review carefully.
export const STORE_DEVICE_CUSTOMIZATIONS = {
ata: {
audioCodecPriority: {
selection: 'REGIONAL',
primary: 'G711a',
secondary: 'G711u',
tertiary: 'G729a',
},
ataDtmfMode: 'STRICT',
ataDtmfMethod: 'AVT',
cdpEnabled: true,
lldpEnabled: true,
qosEnabled: true,
vlan: { enabled: false, value: 1 },
webAccessEnabled: false,
nightlyResyncEnabled: true,
snmp: {
enabled: false,
trustedIP: '0.0.0.0/0.0.0.0',
getCommunity: 'public',
setCommunity: 'private',
snmpV3Enabled: false,
},
},
dect: {
audioCodecPriority: {
selection: 'REGIONAL',
primary: 'G729',
secondary: 'G711a',
tertiary: 'G711u',
},
cdpEnabled: true,
lldpEnabled: true,
qosEnabled: true,
vlan: { enabled: false, value: 0 },
webAccessEnabled: true,
nightlyResyncEnabled: true,
},
mpp: {
pnacEnabled: true,
audioCodecPriority: {
selection: 'REGIONAL',
primary: 'OPUS',
secondary: 'G722',
tertiary: 'G711u',
},
backlightTimer: 'FIVE_MIN',
background: { image: 'NONE' },
displayNameFormat: 'PERSON_NUMBER',
cdpEnabled: true,
defaultLoggingLevel: 'STANDARD',
dndServicesEnabled: false,
acd: { enabled: false, displayCallqueueAgentSoftkeys: 'LAST_PAGE' },
shortInterdigitTimer: 3,
longInterdigitTimer: 5,
lineKeyLabelFormat: 'PERSON_EXTENSION',
lineKeyLEDPattern: 'DEFAULT',
lldpEnabled: true,
mppUserWebAccessEnabled: false,
offHookTimer: 30,
phoneLanguage: 'PERSON_LANGUAGE',
poeMode: 'NORMAL',
qosEnabled: true,
screenTimeout: { enabled: false, value: 300 },
vlan: { enabled: false, value: 1, pcPort: 1 },
wifiNetwork: { enabled: false, authenticationMethod: 'NONE' },
callHistory: 'WEBEX_UNIFIED_CALL_HISTORY',
contacts: 'XSI_DIRECTORY',
webexMeetingsEnabled: false,
usbPorts: { enabled: false, sideUsbEnabled: false, rearUsbEnabled: false },
volumeSettings: {
ringerVolume: 9,
speakerVolume: 11,
handsetVolume: 10,
headsetVolume: 10,
eHookEnabled: true,
allowEndUserOverrideEnabled: false,
},
cfExpandedSoftKey: 'ALL_CALL_FORWARDS',
httpProxy: {
mode: 'OFF',
autoDiscoveryEnabled: true,
port: '3128',
authSettingsEnabled: false,
},
bluetooth: { enabled: false, mode: 'PHONE' },
passThroughPortEnabled: false,
userPasswordOverrideEnabled: false,
activeCallFocusEnabled: false,
peerFirmwareEnabled: true,
noiseCancellation: { enabled: true, allowEndUserOverrideEnabled: false },
dialAssistEnabled: true,
callsPerLine: 4,
nightlyResyncEnabled: true,
missedCallNotificationEnabled: true,
softKeyLayout: {
softKeyMenu: {
idleKeyList:
'guestin|;guestout|;acd_login|;acd_logout|;astate|;redial|;newcall|;cfwd|;recents|;dnd|;unpark|;psk1|;gpickup|;pickup|;dir|4;miss|5;selfview|;messages|;meetings',
offHookKeyList: 'endcall|1;redial|2;dir|3;lcr|4;unpark|5;pickup|6;gpickup|7',
dialingInputKeyList: 'dial|1;cancel|2;delchar|3;left|5;right|6',
progressingKeyList: 'endcall|2',
connectedKeyList:
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume;adhocparticipants',
connectedVideoKeyList:
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume;adhocparticipants',
startTransferKeyList: 'endcall|2;xfer|3',
startConferenceKeyList: 'endcall|2;conf|3',
conferencingKeyList: 'endcall;join;crdstart;crdstop;crdpause;crdresume',
releasingKeyList: 'endcall|2',
holdKeyList: 'resume|1;endcall|2;newcall|3;redial|4;dir|5;adhocparticipants',
ringingKeyList: 'answer|1;ignore|2',
sharedActiveKeyList: 'newcall|1;psk1|2;dir|3;back|4',
sharedHeldKeyList: 'resume|1;dir|4',
},
psk: { psk1: 'fnc=sd;ext=*11;nme=Call Pull' },
softKeyMenuDefaults: {
idleKeyList:
'guestin|;guestout|;acd_login|;acd_logout|;astate|;redial|;newcall|;cfwd|;recents|;dnd|;unpark|;psk1|;gpickup|;pickup|;dir|4;miss|5;selfview|;messages',
offHookKeyList: 'endcall|1;redial|2;dir|3;lcr|4;unpark|5;pickup|6;gpickup|7',
dialingInputKeyList: 'dial|1;cancel|2;delchar|3;left|5;right|6',
progressingKeyList: 'endcall|2',
connectedKeyList:
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume',
connectedVideoKeyList:
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume',
startTransferKeyList: 'endcall|2;xfer|3',
startConferenceKeyList: 'endcall|2;conf|3',
conferencingKeyList: 'endcall;join;crdstart;crdstop;crdpause;crdresume',
releasingKeyList: 'endcall|2',
holdKeyList: 'resume|1;endcall|2;newcall|3;redial|4;dir|5',
ringingKeyList: 'answer|1;ignore|2',
sharedActiveKeyList: 'newcall|1;psk1|2;dir|3;back|4',
sharedHeldKeyList: 'resume|1;dir|4',
},
pskDefaults: { psk1: 'fnc=sd;ext=*11;nme=Call Pull' },
},
backgroundImage8875: 'VIOLET_DARK',
backlightTimer68XX78XX: 'ALWAYS_ON',
voiceFeedbackAccessibilityEnabled: true,
},
wifi: {
audioCodecPriority: {
selection: 'REGIONAL',
primary: 'OPUS',
secondary: 'G722',
tertiary: 'G711u',
},
ldap: {},
webAccess: { enabled: true },
},
};

View file

@ -1,157 +1,5 @@
import { webexJson } from './client.js';
const STORE_DEVICE_CUSTOMIZATIONS = {
ata: {
audioCodecPriority: {
selection: 'REGIONAL',
primary: 'G711a',
secondary: 'G711u',
tertiary: 'G729a',
},
ataDtmfMode: 'STRICT',
ataDtmfMethod: 'AVT',
cdpEnabled: true,
lldpEnabled: true,
qosEnabled: true,
vlan: { enabled: false, value: 1 },
webAccessEnabled: false,
nightlyResyncEnabled: true,
snmp: {
enabled: false,
trustedIP: '0.0.0.0/0.0.0.0',
getCommunity: 'public',
setCommunity: 'private',
snmpV3Enabled: false,
},
},
dect: {
audioCodecPriority: {
selection: 'REGIONAL',
primary: 'G729',
secondary: 'G711a',
tertiary: 'G711u',
},
cdpEnabled: true,
lldpEnabled: true,
qosEnabled: true,
vlan: { enabled: false, value: 0 },
webAccessEnabled: true,
nightlyResyncEnabled: true,
},
mpp: {
pnacEnabled: true,
audioCodecPriority: {
selection: 'REGIONAL',
primary: 'OPUS',
secondary: 'G722',
tertiary: 'G711u',
},
backlightTimer: 'FIVE_MIN',
background: { image: 'NONE' },
displayNameFormat: 'PERSON_NUMBER',
cdpEnabled: true,
defaultLoggingLevel: 'STANDARD',
dndServicesEnabled: false,
acd: { enabled: false, displayCallqueueAgentSoftkeys: 'LAST_PAGE' },
shortInterdigitTimer: 3,
longInterdigitTimer: 5,
lineKeyLabelFormat: 'PERSON_EXTENSION',
lineKeyLEDPattern: 'DEFAULT',
lldpEnabled: true,
mppUserWebAccessEnabled: false,
offHookTimer: 30,
phoneLanguage: 'PERSON_LANGUAGE',
poeMode: 'NORMAL',
qosEnabled: true,
screenTimeout: { enabled: false, value: 300 },
vlan: { enabled: false, value: 1, pcPort: 1 },
wifiNetwork: { enabled: false, authenticationMethod: 'NONE' },
callHistory: 'WEBEX_UNIFIED_CALL_HISTORY',
contacts: 'XSI_DIRECTORY',
webexMeetingsEnabled: false,
usbPorts: { enabled: false, sideUsbEnabled: false, rearUsbEnabled: false },
volumeSettings: {
ringerVolume: 9,
speakerVolume: 11,
handsetVolume: 10,
headsetVolume: 10,
eHookEnabled: true,
allowEndUserOverrideEnabled: false,
},
cfExpandedSoftKey: 'ALL_CALL_FORWARDS',
httpProxy: {
mode: 'OFF',
autoDiscoveryEnabled: true,
port: '3128',
authSettingsEnabled: false,
},
bluetooth: { enabled: false, mode: 'PHONE' },
passThroughPortEnabled: false,
userPasswordOverrideEnabled: false,
activeCallFocusEnabled: false,
peerFirmwareEnabled: true,
noiseCancellation: { enabled: true, allowEndUserOverrideEnabled: false },
dialAssistEnabled: true,
callsPerLine: 4,
nightlyResyncEnabled: true,
missedCallNotificationEnabled: true,
softKeyLayout: {
softKeyMenu: {
idleKeyList:
'guestin|;guestout|;acd_login|;acd_logout|;astate|;redial|;newcall|;cfwd|;recents|;dnd|;unpark|;psk1|;gpickup|;pickup|;dir|4;miss|5;selfview|;messages|;meetings',
offHookKeyList: 'endcall|1;redial|2;dir|3;lcr|4;unpark|5;pickup|6;gpickup|7',
dialingInputKeyList: 'dial|1;cancel|2;delchar|3;left|5;right|6',
progressingKeyList: 'endcall|2',
connectedKeyList:
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume;adhocparticipants',
connectedVideoKeyList:
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume;adhocparticipants',
startTransferKeyList: 'endcall|2;xfer|3',
startConferenceKeyList: 'endcall|2;conf|3',
conferencingKeyList: 'endcall;join;crdstart;crdstop;crdpause;crdresume',
releasingKeyList: 'endcall|2',
holdKeyList: 'resume|1;endcall|2;newcall|3;redial|4;dir|5;adhocparticipants',
ringingKeyList: 'answer|1;ignore|2',
sharedActiveKeyList: 'newcall|1;psk1|2;dir|3;back|4',
sharedHeldKeyList: 'resume|1;dir|4',
},
psk: { psk1: 'fnc=sd;ext=*11;nme=Call Pull' },
softKeyMenuDefaults: {
idleKeyList:
'guestin|;guestout|;acd_login|;acd_logout|;astate|;redial|;newcall|;cfwd|;recents|;dnd|;unpark|;psk1|;gpickup|;pickup|;dir|4;miss|5;selfview|;messages',
offHookKeyList: 'endcall|1;redial|2;dir|3;lcr|4;unpark|5;pickup|6;gpickup|7',
dialingInputKeyList: 'dial|1;cancel|2;delchar|3;left|5;right|6',
progressingKeyList: 'endcall|2',
connectedKeyList:
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume',
connectedVideoKeyList:
'hold;endcall;xfer;conf;xferLx;confLx;bxfer;phold;redial;dir;park;crdstart;crdstop;crdpause;crdresume',
startTransferKeyList: 'endcall|2;xfer|3',
startConferenceKeyList: 'endcall|2;conf|3',
conferencingKeyList: 'endcall;join;crdstart;crdstop;crdpause;crdresume',
releasingKeyList: 'endcall|2',
holdKeyList: 'resume|1;endcall|2;newcall|3;redial|4;dir|5',
ringingKeyList: 'answer|1;ignore|2',
sharedActiveKeyList: 'newcall|1;psk1|2;dir|3;back|4',
sharedHeldKeyList: 'resume|1;dir|4',
},
pskDefaults: { psk1: 'fnc=sd;ext=*11;nme=Call Pull' },
},
backgroundImage8875: 'VIOLET_DARK',
backlightTimer68XX78XX: 'ALWAYS_ON',
voiceFeedbackAccessibilityEnabled: true,
},
wifi: {
audioCodecPriority: {
selection: 'REGIONAL',
primary: 'OPUS',
secondary: 'G722',
tertiary: 'G711u',
},
ldap: {},
webAccess: { enabled: true },
},
};
import { STORE_DEVICE_CUSTOMIZATIONS } from './deviceCustomizations.js';
export async function scheduleStoreDeviceSettings(location) {
const body = {