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:
parent
8b32975eaf
commit
461caa8086
9 changed files with 212 additions and 187 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
})(),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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', () =>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
160
src/webex/deviceCustomizations.js
Normal file
160
src/webex/deviceCustomizations.js
Normal 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 },
|
||||
},
|
||||
};
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue