Skip to main content

Hi

I have call using ring central sdk and that is the my code.

I have changes based on the ES module and also tried with the commonjs so based on that face both way get the same error.

So can you please check and let me know it’s giving me error.

This is my js code 

const RingCentral = require('@ringcentral/sdk').SDK;

const WebPhone = require('ringcentral-web-phone').default;

const fs = require('fs');

const { AudioContext } = require('node-web-audio-api');

const WebSocket = require('ws');

require('dotenv').config();

 

// Fallback: Set global.WebSocket

global.WebSocket = WebSocket;

 

const CLIENT_ID = process.env.RC_CLIENT_ID;

const CLIENT_SECRET = process.env.RC_CLIENT_SECRET;

const SERVER_URL = process.env.RC_SERVER_URL || 'https://platform.ringcentral.com';

const JWT = process.env.RC_JWT;

 

async function getSipInfo(platform) {

    console.log('Creating SIP registration...');

    try {

        const response = await platform.post('/restapi/v1.0/client-info/sip-provision', {

            sipInfo: Â{ transport: 'WSS' }]

        });

        const sipInfo = await response.json();

        console.log('Full SIP registration response:', JSON.stringify(sipInfo, null, 2));

        if (!sipInfo || !sipInfo.sipInfo || !sipInfo.sipInfo 0]) {

            throw new Error('Invalid SIP provisioning response: sipInfo array is empty or missing');

        }

        const sipData = sipInfo.sipInfo}0];

        if (!sipData.domain) {

            throw new Error('SIP provisioning response missing domain');

        }

        if (!sipData.username) {

            throw new Error('SIP provisioning response missing username');

        }

        if (!sipData.password) {

            throw new Error('SIP provisioning response missing password');

        }

        // Normalize sipInfo for ringcentral-web-phone

        const normalizedSipInfo = {

            ...sipInfo,

            sipInfo: undefined, // Remove nested sipInfo array

            domain: sipData.domain,

            username: sipData.username,

            password: sipData.password,

            outboundProxy: sipData.outboundProxy || 'sip.ringcentral.com:5090',

            transport: sipData.transport || 'WSS',

            authorizationId: sipData.authorizationId || sipData.username // Fallback to username

        };

        console.log('Normalized SIP info:', JSON.stringify(normalizedSipInfo, null, 2));

        return normalizedSipInfo;

    } catch (error) {

        console.error('Error creating SIP registration:', {

            message: error.message,

            status: error.response ? error.response.status : 'N/A',

            statusText: error.response ? error.response.statusText : 'N/A',

            response: error.response ? await error.response.text() : 'No response'

        });

        throw error;

    }

}

 

async function testWebSocket(wsUrl, sipInfo) {

    console.log('Testing WebSocket connection to:', wsUrl);

    try {

        await new Promise((resolve, reject) => {

            const ws = new WebSocket(wsUrl, 'sip'], {

                headers: {

                    'Origin': 'http://localhost',

                    'User-Agent': 'ringcentral-web-phone/2.2.7',

                    'Sec-WebSocket-Protocol': 'sip',

                    'Authorization': `Basic ${Buffer.from(`${sipInfo.username}:${sipInfo.password}`).toString('base64')}`

                }

            });

            ws.on('open', () => {

                console.log('WebSocket connection successful to:', wsUrl);

                ws.close();

                resolve();

            });

            ws.on('error', (error) => {

                console.error('WebSocket connection failed:', error.message);

                reject(new Error(`WebSocket connection failed: ${error.message}`));

            });

            ws.on('unexpected-response', (request, response) => {

                console.error('WebSocket unexpected response:', {

                    statusCode: response.statusCode,

                    statusMessage: response.statusMessage,

                    headers: response.headers

                });

                reject(new Error(`WebSocket unexpected response: ${response.statusCode} ${response.statusMessage}`));

            });

        });

    } catch (error) {

        console.error('WebSocket test failed:', error.message);

        return false;

    }

    return true;

}

 

async function playAudio(session, audioFile) {

    console.log(`Streaming PCM file: ${audioFile}`);

    try {

        if (!fs.existsSync(audioFile)) {

            throw new Error(`PCM file not found: ${audioFile}`);

        }

        const audioContext = new AudioContext();

        const pcmData = fs.readFileSync(audioFile);

        const audioBuffer = await audioContext.decodeAudioData(pcmData.buffer);

        const source = audioContext.createBufferSource();

        source.buffer = audioBuffer;

        const destination = audioContext.createMediaStreamDestination();

        source.connect(destination);

        source.start();

        session.mediaHandler.getLocalStream().addTrack(destination.stream.getAudioTracks()a0]);

        console.log(`Streaming PCM file: ${audioFile}`);

        await new Promise(resolve => source.onended = resolve);

    } catch (error) {

        console.error(`Error streaming PCM file ${audioFile}:`, error.message, error.stack);

        throw error;

    }

}

 

async function startWebRTCCall(fromNumber, toNumber, audioFiles) {

    console.log(`Starting WebRTC call from ${fromNumber} to ${toNumber}`);

    console.log('Environment variables:', {

        CLIENT_ID: CLIENT_ID ? 'Set' : 'Missing',

        CLIENT_SECRET: CLIENT_SECRET ? 'Set' : 'Missing',

        SERVER_URL,

        JWT: JWT ? 'Set' : 'Missing'

    });

    console.log('WebSocket module loaded:', !!WebSocket);

    console.log('WebSocket version:', require('ws/package.json').version);

    console.log('Global WebSocket set:', !!global.WebSocket);

 

    if (!CLIENT_ID || !CLIENT_SECRET || !JWT) {

        console.error('Missing RingCentral credentials');

        process.exit(1);

    }

 

    try {

        const rcsdk = new RingCentral({ clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, server: SERVER_URL });

        const platform = rcsdk.platform();

        console.log('Logging in to RingCentral...');

        await platform.login({ jwt: JWT });

        console.log('Authenticated with RingCentral');

 

        const sipInfo = await getSipInfo(platform);

 

        // Test WebSocket connection

        const wsUrl = `wss://${sipInfo.outboundProxy || 'sip.ringcentral.com:5090'}`;

        const wsUrlFallback = 'wss://sip.ringcentral.com:5090';

        let wsSuccess = await testWebSocket(wsUrl, sipInfo);

        if (!wsSuccess && wsUrl !== wsUrlFallback) {

            console.log('Retrying WebSocket connection with fallback URL:', wsUrlFallback);

            wsSuccess = await testWebSocket(wsUrlFallback, sipInfo);

        }

        if (!wsSuccess) {

            console.warn('WebSocket test failed, proceeding with WebPhone initialization...');

        }

 

        console.log('Initializing WebPhone with WebSocket...');

        const webPhoneOptions = {

            sipInfo,

            WebSocket,

            logLevel: 2

        };

        console.log('WebPhone options:', JSON.stringify(webPhoneOptions, (key, value) => {

            if (key === 'WebSocket') return typeof value;

            return value;

        }, 2));

        const webPhone = new WebPhone(webPhoneOptions);

        console.log('WebPhone instance created:', !!webPhone);

        console.log('WebPhone internal state before start:', {

            hasSipClient: !!webPhone.sipClient,

            hasUserAgent: !!webPhone.userAgent,

            sipClientState: webPhone.sipClient ? Object.keys(webPhone.sipClient) : 'undefined'

        });

 

        console.log('Starting Web Phone...');

        try {

            await webPhone.start();

            console.log('Web Phone started successfully');

        } catch (startError) {

            console.error('Failed to start WebPhone:', startError.message, startError.stack);

            throw new Error(`WebPhone start failed: ${startError.message}`);

        }

 

        console.log('WebPhone state after start:', {

            hasUserAgent: !!webPhone.userAgent,

            userAgentProperties: webPhone.userAgent ? Object.keys(webPhone.userAgent) : 'undefined',

            sipClientState: webPhone.sipClient ? Object.keys(webPhone.sipClient) : 'undefined'

        });

        if (!webPhone.userAgent) {

            throw new Error('WebPhone userAgent is undefined after start');

        }

 

        console.log(`Initiating call to ${toNumber}`);

        const session = await webPhone.userAgent.invite(toNumber, {

            fromNumber: fromNumber,

            homeCountryId: '44'

        });

        console.log(`Call initiated. Session ID: ${session.id}`);

 

        session.on('accepted', async () => {

            console.log('Call answered! Starting audio playback...');

            for (const audioFile of audioFiles) {

                await playAudio(session, audioFile);

            }

            console.log('All audio played. Ending call...');

            await session.hangup();

            console.log('Call ended successfully.');

            await webPhone.stop();

            await platform.logout();

        });

 

        session.on('progress', () => {

            console.log('Call in progress (ringing)...');

        });

 

        session.on('rejected', (reason) => {

            console.error('Call rejected:', JSON.stringify(reason, null, 2));

            webPhone.stop();

            platform.logout();

            process.exit(1);

        });

 

        session.on('terminated', (reason) => {

            console.log('Call terminated:', JSON.stringify(reason, null, 2));

            webPhone.stop();

            platform.logout();

            process.exit(1);

        });

 

        session.on('failed', (error) => {

            console.error('Call failed:', error.message, error.stack);

            webPhone.stop();

            platform.logout();

            process.exit(1);

        });

 

        webPhone.userAgent.on('invite', () => console.log('Received INVITE'));

        webPhone.userAgent.on('connecting', () => console.log('WebRTC connecting...'));

    } catch (error) {

        console.error('WebRTC error:', error.message, error.stack);

        process.exit(1);

    }

}

 

const args = process.argv.slice(2);

if (args.length < 3) {

    console.error('Usage: node ai-agent-webrtc.js <fromNumber> <toNumber> <audioFile1>,<audioFile2>,...');

    process.exit(1);

}

 

const fromNumber, toNumber, audioFilesStr] = args;

const audioFiles = audioFilesStr.split(',').filter(f => {

    const exists = fs.existsSync(f);

    if (!exists) console.error(`Audio file not found: ${f}`);

    return exists;

});

if (audioFiles.length === 0) {

    console.error('No valid audio files provided:', audioFilesStr);

    process.exit(1);

}

console.log('Audio files to play:', audioFiles);

startWebRTCCall(fromNumber, toNumber, audioFiles);

(commonjs error)
Error Output:
================
WebRTC error: WebPhone userAgent is undefined after start Error: WebPhone userAgent is undefined after start


Based on the ES module error

Also sometime face this error also

 

Please let me know why this error comes,

Thanks.

First of all, streaming local audio file to remote is not supported by the webPhone SDK.  If that is what you want to do, stop wasting your time.

Secondly, if you want to use the latest version of the webPhone SDK, read and follow its readme file carefully: https://github.com/ringcentral/ringcentral-web-phone/blob/main/README.md
For example, in order to create a new web phone instance, there is NO need to specify a WebSocket object.

Last by not least, streaming local audio files to remote is supported by the softphone SDK: https://github.com/ringcentral/ringcentral-softphone-ts

Here are the differences between the two SDKs:

  1. webPhone SDK is for human beings, it streams audio from/to your microphone/speaker. Normally there should be a human being to speak to the microphone and to listen from the speaker.
  2. softphone SDK is for robots/scripts, it could stream or capture audio streams, but it doesn’t directly interact with your microphone/speaker. Your app needs to have logic about how to prepare outgoing stream (like in your case, read from a file) and how to process inbound stream. 

Hi


As you mentioned above using softphone-ts i have tried and it will be giving me same error.

const { Softphone } = require('ringcentral-softphone-ts');

const fs = require('fs');

const { AudioContext } = require('node-web-audio-api');

require('dotenv').config();

 

const CLIENT_ID = process.env.RC_CLIENT_ID;

const CLIENT_SECRET = process.env.RC_CLIENT_SECRET;

const SERVER_URL = process.env.RC_SERVER_URL || 'https://platform.ringcentral.com';

const JWT = process.env.RC_JWT;

 

async function getSipInfo() {

    // Simulate SIP info retrieval (replace with actual API call if needed)

    return {

        username: process.env.RC_SIP_USERNAME || 'your_sip_username',

        password: process.env.RC_SIP_PASSWORD || 'your_sip_password',

        domain: process.env.RC_SIP_DOMAIN || 'sip.ringcentral.com',

        outboundProxy: process.env.RC_SIP_OUTBOUND_PROXY || 'sip.ringcentral.com:5090',

        transport: 'WSS'

    };

}

 

async function playAudio(session, audioFile) {

    console.log(`Streaming PCM file: ${audioFile}`);

    try {

        if (!fs.existsSync(audioFile)) {

            throw new Error(`PCM file not found: ${audioFile}`);

        }

        const audioContext = new AudioContext();

        const pcmData = fs.readFileSync(audioFile);

        const audioBuffer = await audioContext.decodeAudioData(pcmData.buffer);

        const source = audioContext.createBufferSource();

        source.buffer = audioBuffer;

        source.start(0);

 

        // Connect to the softphone's media stream

        const mediaStream = audioContext.createMediaStreamDestination();

        source.connect(mediaStream);

        session.setOutboundMediaStream(mediaStream.stream);

 

        console.log(`Started streaming PCM file: ${audioFile}`);

        await new Promise(resolve => source.onended = resolve);

    } catch (error) {

        console.error(`Error streaming PCM file ${audioFile}:`, error.message, error.stack);

        throw error;

    }

}

 

async function startSoftphoneCall(fromNumber, toNumber, audioFiles) {

    console.log(`Starting softphone call from ${fromNumber} to ${toNumber}`);

    console.log('Environment variables:', {

        CLIENT_ID: CLIENT_ID ? 'Set' : 'Missing',

        CLIENT_SECRET: CLIENT_SECRET ? 'Set' : 'Missing',

        SERVER_URL,

        JWT: JWT ? 'Set' : 'Missing'

    });

 

    if (!CLIENT_ID || !CLIENT_SECRET || !JWT) {

        console.error('Missing RingCentral credentials');

        process.exit(1);

    }

 

    try {

        const sipInfo = await getSipInfo();

 

        const softphone = new Softphone({

            clientId: CLIENT_ID,

            clientSecret: CLIENT_SECRET,

            server: SERVER_URL,

            jwt: JWT,

            sipInfo: {

                username: sipInfo.username,

                password: sipInfo.password,

                wsServer: `wss://${sipInfo.outboundProxy}`,

                domain: sipInfo.domain

            },

            logLevel: 'debug'

        });

 

        console.log('Initializing softphone...');

        await softphone.init();

 

        console.log('Registering softphone...');

        await softphone.register();

 

        console.log(`Making call to ${toNumber} from ${fromNumber}...`);

        const session = await softphone.call(toNumber, { fromNumber });

 

        session.on('accepted', async () => {

            console.log('Call answered! Starting audio playback...');

            for (const audioFile of audioFiles) {

                await playAudio(session, audioFile);

            }

            console.log('All audio played. Ending call...');

            await session.hangup();

            console.log('Call ended successfully.');

            await softphone.unregister();

        });

 

        session.on('progress', () => console.log('Call in progress (ringing)...'));

        session.on('rejected', (reason) => {

            console.error('Call rejected:', reason);

            softphone.unregister();

            process.exit(1);

        });

        session.on('failed', (error) => {

            console.error('Call failed:', error.message, error.stack);

            softphone.unregister();

            process.exit(1);

        });

    } catch (error) {

        console.error('Softphone error:', error.message, error.stack);

        process.exit(1);

    }

}

 

const args = process.argv.slice(2);

if (args.length < 3) {

    console.error('Usage: node ai-agent-webrtc.js <fromNumber> <toNumber> <audioFile1>,<audioFile2>,...');

    process.exit(1);

}

 

const ofromNumber, toNumber, audioFilesStr] = args;

const audioFiles = audioFilesStr.split(',').filter(f => {

    const exists = fs.existsSync(f);

    if (!exists) console.error(`Audio file not found: ${f}`);

    return exists;

});

if (audioFiles.length === 0) {

    console.error('No valid audio files provided:', audioFilesStr);

    process.exit(1);

}

 

console.log('Audio files to play:', audioFiles);

startSoftphoneCall(fromNumber, toNumber, audioFiles);

error is same as above

 

Please give me why this issue occurs here.


Thanks.


Please follow the softphone SDK README file carefully. 

You cannot just copy paste your existing code and hoping it would work with a different SDK.

I see lots of things that are incompatible with the softphone SDK. For example, how to contruct a new softphone instance: https://github.com/ringcentral/ringcentral-softphone-ts?tab=readme-ov-file#usage

How to send audio: https://github.com/ringcentral/ringcentral-softphone-ts?tab=readme-ov-file#stream-local-audio-to-remote-peer

They are so different from your code. There is no way for your code to work if you don’t change it.


I had initially tried to reuse existing logic from a previous implementation, which was based on a different approach, and I now realize that it's not compatible with the structure and expectations of the ringcentral-softphone-ts SDK.

I appreciate you pointing me to the correct sections of the README. I'll go through the SDK documentation more carefully and refactor my code accordingly, particularly focusing on:

  • Proper instantiation of the Softphone object as outlined in the Usage section.

  • Correct handling of audio streaming as shown in Stream local audio to remote peer.

it’s clear now that my current implementation won’t work without aligning fully with the SDK’s architecture. I’ll make the necessary changes and test again.


Reply