06 Line

Call with the SDK

Place and answer calls from a web page with the browser SDK, in a few lines.

Complete example

06line/example-softphone is a working softphone in Next.js built on this page: clone it, put your API key in .env.local and run it.

@06line/sdk-browser does the plumbing of a call in a browser: the WebRTC session and its reconnections, the ephemeral keys, the microphone, the choice of devices, the volumes. You write the interface. If you prefer to drive the calls yourself, everything it does is described in Call with the API, which follows the same steps.

Install

npm install @06line/sdk-browser

Get a key

The browser never sees your API key. Add a route to your server that asks 06 Line for an ephemeral key and returns it as is, after checking that your user may call:

server: /api/06line-key
export async function POST() {
  const res = await fetch('https://api.06line.com/v1/ephemeral-keys', {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.SIXLINE_API_KEY}` },
  });

  return new Response(res.body, { status: res.status, headers: { 'Content-Type': 'application/json' } });
}

The SDK calls this route when it starts, then again about a minute before each 15-minute key expires.

Create the phone

import { createPhone } from '@06line/sdk-browser';

const phone = createPhone({
  getEphemeralKey: () => fetch('/api/06line-key', { method: 'POST' }).then((r) => r.json()),
});

phone.on('state', (state) => (statusEl.textContent = state)); // connecting, connected, reconnecting, closed

Let the user pick devices

const { inputs, outputs } = await phone.devices();
fillSelect(micSelect, inputs);
fillSelect(speakerSelect, outputs);
speakerSelect.hidden = !phone.canChooseOutput; // no setSinkId (some Safari versions): sound follows the system output

Device names stay empty until the user has granted microphone access once; phone.testMicrophone() is a good moment to ask, and lets them hear themselves before a real call.

Place a call

Browsers only let a page use the microphone and play sound after a user action, so start calls from a click. Each call takes its own microphone, speaker and volumes:

callButton.onclick = async () => {
  const call = await phone.call({
    to: numberInput.value,
    input: micSelect.value,
    output: phone.canChooseOutput ? speakerSelect.value : undefined,
    inputVolume: Number(micVolume.value), // 0 to 2, 1 = unchanged
    outputVolume: Number(speakerVolume.value),
  });

  call.on('state', (state) => (callStateEl.textContent = state)); // dialing, ringing, active, ended
  call.on('level', ({ input, output }) => drawMeters(input, output));
  call.on('ended', (reason) => console.log('call ended:', reason));

  // everything can change mid-call
  micSelect.onchange = () => call.setInput(micSelect.value);
  speakerSelect.onchange = () => call.setOutput(speakerSelect.value);
  micVolume.oninput = () => call.setInputVolume(Number(micVolume.value));
  speakerVolume.oninput = () => call.setOutputVolume(Number(speakerVolume.value));

  hangupButton.onclick = () => call.hangup();
  muteButton.onclick = () => call.mute(!call.muted);
};

With several lines, pass lineId; phone.lines lists them with their state. Without input or output, the call uses the devices of the previous call, else the system defaults; a remembered device that has been unplugged is forgotten. The microphone opens before the call is placed, so a refused microphone places no call.

A call does not survive a lost connection: if the network drops for long enough that the WebRTC session fails, the call ends once the phone has reconnected (see reconnection).

Answer incoming calls

answer() takes the same audio options:

Every open page of your company rings; the first to answer takes the call, and on the others it ends with answered_elsewhere.

phone.on('incoming', (call) => {
  showRinging(call.from);
  answerButton.onclick = () =>
    call.answer({ input: micSelect.value, output: phone.canChooseOutput ? speakerSelect.value : undefined });
  declineButton.onclick = () => call.hangup();
  call.on('ended', hideRinging);
});

SMS

The SDK is for calls. Incoming SMS and delivery reports reach your server through webhooks, and sending is a REST call your server makes with its API key.

Handling errors

Every failure is a PhoneError with a code. The ones a softphone should handle:

import { PhoneError } from '@06line/sdk-browser';

try {
  await phone.call({ to });
} catch (err) {
  if (err instanceof PhoneError && err.code === 'microphone_denied') return showMicHelp();
  if (err instanceof PhoneError && err.code === 'line_busy') return showMessage('Already on a call');
  throw err;
}

The full list is in the SDK reference.

On this page