Inspiration
We've always wanted to make presentations more exciting for audience members.
In the past keeping engagement up meant taking focus away from the presentation, like playing a little game, or broadly asking the audience a question... but this can be difficult in hybrid work environments, or can result in results being weighed towards more vocal individuals. If everybody is shouting out their answers, it can be hard for the audience to know what anybody else has said. So perhaps the presenter takes what they happened to hear the loudest. It's not ideal, but there was no better way.
Canva Live appears to help a lot with these problems but appears more tailored towards discussion than interacting directly with the audience.
What it does
With Interactives presenters can capture engagement in a way that means that everybody gets a say.
Interactives allows you to make your presentations come to life by showing your audience's input right in the presentation. The designer can easily add quizzes, polls, and word clouds to drive engagement. With the help of AI, they can also quickly generate quizzes based on their design's content.
How we built it
Interactives is built on entirely serverless infrastructure.
Using Cloudflare Workers and Durable Objects, data is stored as close to users as possible, usually in the same country. We've found during our testing that requests almost always take less than 100ms. There's no "database", at least not in the typical sense. We're not running a SQL or even a NoSQL database server.
Interactives
Each interactive is a Durable Object, which contains its state. We used Durable Objects because they are perfect for making real-time experiences. This came in handy when we realized that updating the URL of an embed app element in Canva meant that the underlying iframe had to be reloaded. We didn't want the user to wait long for interactives to update on screen, as there's a lot of extra stuff that Canva has to do when you update an embed app element. Canva also rate limits updates to app elements. To solve both of these problems the iframe is only reloaded the first time you make a change. After that, the interactive connects to the Durable Object via a WebSocket, and any updates the user makes are pushed in real time.
An interactive can be anything - so we built interactives in a way that new ones can added without much effort.
Rooms
A presenter creates a room (that audience members can connect to), which is also just a Durable Object. They are also created as close to the user as possible. Audience members connect to the room using a join code (typically 6 digits long). Each room has its own unique join code.
To ensure costs remain as low as possible, we used the WebSocket Hibernation API.
We wanted to make sure that if a presenter is using Interactives in a way that they may either accidentally or purposefully capture PII - there was a simple way to permanently erase that data. To achieve this, we delete all the storage associated with the Durable Object whenever a presenter presses the "End" button below the room join code/QR code.
Join codes
Join codes pose interesting challenges. We wanted codes that were short and were guaranteed to be unique. We also wanted to ensure that generating join codes was as fast as it could be. This isn't trivial when you want to be as close to users as possible. So we create a Durable Object (yep, another one!) that uses the end-user's country code (eg. NZ or US) as the name. That means that the first time the Durable Object is referenced, Cloudflare decides where it should live, and because the user is making their request from within that country, it likely chooses to run the Durable Object from within that country. Now we have a Durable Object close to the user, we use its low-latency storage APIs to maintain an atomic counter that can be used to derive a unique code. Then we prefix the code with the country code, and we have ourselves a globally unique join code!
We use Cloudflare Workers KV to store the join codes, and the ID of the Durable Object of the room it's for. That way, if a user from the other side of the world uses the join code, it can be accessed using Cloudflare's globally distributed key-value store, and not via the Durable Object (which might be far from them). This cuts down the latency, which you feel when you join a room.
AI
We use the latest OpenAI models to generate quiz questions and answers based on text content the designer selects within their Canva design. Of course, it's important with any AI product (especially a free one like ours) to ensure that users don't abuse this. We implemented rate limiting, so we can keep costs under control. Rate limiting was achieved... you guessed it!... using Durable Objects. Each user is given a Durable Object that maintains their credits. If a particular user is using the app in its intended way and needs their quota raised, we built a system for doing so. This can only be done manually at this stage.
We also knew that sometimes, individual pages might be quite text-heavy. So we allocate a deterministic credit cost associated with each AI request and display this to the user so it's not a surprise to them when they end up using their credits faster than expected.
Another consideration with how we implemented rate limiting was user experience. We've all used tools that when you get rate limited, you have to wait a whole day for it to reset. We chose an algorithm that gradually renews the user's credits over time. Meaning if you run out of your daily quota, you don't need to wait a whole day for them to all replenish. For now, we've configured this to gain 1 credit every hour. But this is completely configurable.
Web app
We intentionally kept the web app simple. We wanted all eyes to be on presenters when there's nothing for the audience members to interact with. We also kept things lightweight. We can imagine the app being used by thousands of users all on crappy hotel WiFi. We don't bloat the client with tracking code.
Designers can configure the font, colors, and a name for their rooms. This allows them to match the presentations and/or their company's branding.
Authentication
We don't require designers to log in to use Interactives. Using the Canva guidelines, we verify users are who they say they are using JWTs. This initially seemed like it could be a bit of a challenge to get working with Cloudflare Workers, as I couldn't easily find a JWKS package on npm that didn't only work on Node.js. But we found that using a package for this wasn't necessary, because using the Caches API to cache Canva's JWKS response, and using the Web Crypto API to import the keys was all that was needed. Yay for web standards!
Challenges we ran into
Iframely
Pretty early on we knew we had to figure out whether it was even possible to embed rich media (HTML) into a Canva design. We saw that Canva uses a third party called Iframely for this. We couldn't find a way to get started building without us being an Iframely publisher, so we reached out to Iframely, and explained the situation - they kindly helped us get approved.
Duplicated elements
Without a user opening our Canva app with an app element selected (so we could update the embed URL) there is no way for us to know from within the embed that an element has been duplicated. This meant that we needed to find a novel solution that wasn't tied to a design ID and worked regardless of if an interactive was added to a design via our Canva app, or if it was duplicated from somewhere else, i.e. if the user had duplicated the element, a page containing the element, or a design containing the element. To get around this, once an interactive embed URL is created, it can only be edited until the app element loses focus, after which it is set in stone so that editing an interactive does not also edit any interactives that were created by duplicating it. Editing an existing interactive in a design requires a whole new interactive ID to be created. This new interactive ID replaces the existing one, and again can only be edited until the app element loses focus.
Supporting duplicated elements made it so that a room can not be tied to a single design. Interactives that the presenter views in other designs will appear in the room until the presenter ends the room via a room joiner. We hope that in the future Canva makes additional information, such as a design ID, available within embedded elements without relying on the design ID of the design that the element was initially created in being passed in the URL, which would be incorrect after duplicating a design.
Canva App UI Kit
Building with Canva's SDK was a great experience overall, however, we ran into a couple of small issues along the way.
We noticed that there were inconsistencies between some of the design principals provided by Canva, and with how their components worked in practice. This left us worried about which one was correct; the documentation or the component?
We couldn't find a way to add a tooltip to a custom component. There were some components we wanted to build but ended up using one of their components that did have a tooltip, in a way that may not have been intended.
Some UI elements (from the Canva app itself) just simply didn't exist in the App UI Kit. We ended up building them ourselves from other components. This worked well, but it still would have been nice to have those components built out already. Building them ourselves means that if Canva updates their UI, our UI won't reflect those changes.
Accomplishments that we're proud of
This is the first Canva app we've built, and we feel pretty confident in the user experience. From not needing to login, to displaying changes to interactives in real-time, we feel like users will really like using Interactives. And we hope their audiences do too!
What we learned
Using Iframely required us to implement oEmbed for our web app, which turned out to be a fun learning experience.
This may sound like we're just sucking up, but we genuinely had the best experience building all of our creative materials, eg. the hackathon submission video, using Canva. We will likely keep using Canva for projects going forward.
What's next for Interactives
We certainly want to grow the types of Interactives we support. This will probably require us to implement a way to gather user feedback - but we didn't have time to do this for the hackathon.
In order to cover the AI costs, we will likely need to charge for credits, but we would like to always provide a free tier.
Built With
- canva-apps-sdk
- cloudflare
- cloudflare-durable-objects
- cloudflare-kv
- cloudflare-pages
- cloudflare-workers
- open-ai
- react
- remix
- typescript
Log in or sign up for Devpost to join the conversation.