Sending server-side data to video call participants with Daily

If you already work with Daily’s video call APIs you likely know about our ”app-message" events, which enables clients to send data to one or more other participants in a Daily call. We’ve featured client-side ”app-message”  events in several demos and tutorials, such as our spatialization series and our cursor sharing demo. We also use them internally for our Daily Prebuilt chat functionality.

Previously the only way to send these messages was from another client. But as I mentioned in my post discussing considerations that go into choosing between client-side ”app-message" events and WebSockets, some types of data are better delegated to a server. For example:

  • Data that cannot be entrusted to a client-side origin (e.g., information a malicious client shouldn’t be able to tamper with)
  • Data with a central, non-participant-specific source of truth (e.g., information a client isn’t actually able to deduce on its own)

In the past I might’ve suggested that you use something like a WebSocket to send data from a server to your Daily video call participants. Now you can accomplish this with Daily’s /rooms/:name/send-app-message REST API endpoint. This endpoint allows you to send ”app-message” events to Daily video call participants from your server-side logic and handle them just like you would any other "app-message" on the clients—no separate client-side code path for server-side message handling required.

To demonstrate the different ways of sending these message events to Daily video call participants I’ve created a small App message demo using Daily Prebuilt. In this demo you can play around with sending messages to different participants from both the client and the server.

GIF showing two clients sending client and server-side app messages to each other.
Sending `"app-message"` events between participants.

Of course, in a real-life application, your server will likely be sending these messages to clients based on server-side events as opposed to a button click. I’ve just used a button here for the purpose of letting you get hands-on with the functionality.

You’ll find all the code for this demo on GitHub (be sure to check out the README if you want to run it locally). Since in this post I’m focusing on the new server-side /send-app-message endpoint for sending data to clients, I’ll go through that implementation below.

💡
The server-side POST request in this demo is instrumented through a Netlify function, but the logic of the request would be the same with other stateless functions (like AWS Lambda) or a more traditional server implementation.

Here’s how my server-side Netlify function is sending an "app-message" event to the specified room. You’ll find explanatory comments inline:

// sendAppMessage() uses Daily's REST API to send an `"app-message"`
// event to one or all Daily participant clients.
// https://docs.daily.co/reference/rest-api/rooms/send-app-message
async function sendAppMessage(
  apiKey: string,
  roomName: string,
  recipient: string = '*'
) {
  // Prepare request body.
  // We include the recipient twice because the property
  // in "data" will be accessible through the `"app-message"`
  // event payload. The recipient can use it to tailor their
  // message handling based on whether they were the only recipient.
  const req = {
    data: {
      recipient,
    },
    recipient,
  };
  const data = JSON.stringify(req);

  // Prepare headers containing Daily API key
  const headers = {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  };

  const url = `${dailyAPIUrl}/rooms/${roomName}/send-app-message`;

  const errMsg = 'failed to send app message';
  const res = await axios.post(url, data, { headers });
  if (res.status !== 200 || !res.data) {
    console.error(
      'Unexpected app message endpoint response:',
      res.status,
      res.data
    );
    throw new Error(errMsg);
  }
}

Once a client receives the event you can handle it like you would any other Daily event, by first creating a handler on the Daily call frame:

  const callFrame = DailyIframe.createFrame(callContainer, {
    showLeaveButton: true,
  }) 
 callFrame
    .on("app-message", (ev) => {
      handleAppMessage(callFrame, ev)
    })

In the handler above, I format the “Hello” message that shows up in the application based on the recipient and sender. If the fromId is ”API", I know the sender is my server-side function:

function handleAppMessage(call: DailyCall, ev?: DailyEventObjectAppMessage) {
  if (!ev) return;
  const contentEle = <HTMLElement>document.getElementById('content');
  const line = getMsg(call.participants(), ev.data.recipient, ev.fromId);
  contentEle.appendChild(line);
}

// getMsg() formats a message to display to the user
// based on the "app-message"
function getMsg(
  participants: DailyParticipantsObject,
  recipient: string,
  fromId: string
): HTMLSpanElement {
  const line = document.createElement('span');
  const date = new Date();
  const t = date.toLocaleTimeString();
  let msg = `[${t}] Hello, `;

  // Customize message with the user's name or ID if it was sent
  // directly to them and not all participants.
  const recipientData = recipient;
  let name = 'all';
  if (recipientData !== '*') {
    const lp = participants.local;
    if (lp.user_name) {
      name = lp.user_name;
    } else {
      name = lp.session_id;
    }
  }

  // Customize who the message was from based on whether it
  // came from another participant or the API.
  msg += ` ${name}, from `;
  if (fromId === 'API') {
    msg += 'the server!';
    line.classList.add('serverMsg');
  } else {
    line.classList.add('clientMsg');
    const from = fromId;
    const p = participants[from];
    if (p?.user_name) {
      msg += p.user_name;
    } else {
      msg += from;
    }
  }
  line.innerText = msg;
  return line;
}

The result is a client-side handler which parses who the data came from as well as who its recipient was, and formats the greeting accordingly:

Server and client-side greetings
Server and client-side greetings

And that’s it! The server-side app messages can go through exactly the same flow as the ones sent from other clients, distinguishable by the “API” fromId.

Conclusion

I was really excited to see this new feature in our REST API. It makes Daily developers’ lives a little easier by enabling them to send server-side data to selected Daily participants without having to maintain a separate messaging implementation.

If you have any questions, comments, or ideas, head over to our WebRTC community. I’d love to hear more about how you’re currently handling transmitting data between clients and servers in your application.

Never miss a story

Get the latest direct to your inbox.