// services/voicePin/resetVoicemailPin.js // PUT /v1/telephony/config/people/{personId}/voicemail/passcode // // Admin API to set a custom voicemail PIN (spark-admin:telephony_config_write). // Do NOT use people/{id}/features/voicemail/passcode — that path does not exist. // For org-default reset only: POST people/{id}/features/voicemail/actions/resetPin/invoke import { logger } from '../../utils/logger.js'; import { generatePin } from './generatePin.js'; const LOG_SCOPE = 'resetvmpin:service'; const VM_PIN_LENGTH = 6; const MAX_PUT_ATTEMPTS = 5; function vmPasscodeEndpoint(personId) { return `telephony/config/people/${personId}/voicemail/passcode`; } /** * Reset a user's voicemail mailbox PIN. * * @param {string} personId * @param {object} [opts] * @param {object} [opts.webexClient] injectable client for tests * @returns {Promise} new PIN (never log this value) */ export async function resetVoicemailPin(personId, opts = {}) { if (!personId) { throw new Error('personId is required'); } const client = opts.webexClient || (await import('../../integrations/webex/WebexClient.js')).default; const endpoint = vmPasscodeEndpoint(personId); let lastError = null; for (let attempt = 0; attempt < MAX_PUT_ATTEMPTS; attempt += 1) { const passcode = generatePin({ minLength: VM_PIN_LENGTH, maxLength: VM_PIN_LENGTH }); try { await client.request('PUT', endpoint, { passcode }); logger(LOG_SCOPE, `Voicemail PIN reset succeeded for person ${personId}`, 'info'); return passcode; } catch (err) { lastError = err; const status = err?.response?.status; if (status === 400 && attempt < MAX_PUT_ATTEMPTS - 1) { logger(LOG_SCOPE, `Voicemail PIN rejected for ${personId} (attempt ${attempt + 1}), retrying`, 'warn'); continue; } throw err; } } throw lastError || new Error('Voicemail PIN reset failed'); }