Wiring UI
Start with the smallest possible browser flow, then adapt it to your UI framework.
Smallest Example
This example starts recognition from a single button and logs the recognized campaign name.
html
<button id="start-button" type="button">Start Recognition</button>
<script type="module">
import { AudioRecognizer } from "@sorisdk/web-audio";
const startButton = document.querySelector("#start-button");
const recognizer = new AudioRecognizer({
appId: "YOUR_APP_ID",
ephemeralKey: async () => {
const response = await fetch("/api/ephemeral-key", {
method: "POST"
});
if (!response.ok) {
throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
}
const { ephemeral_key } = await response.json();
return ephemeral_key;
}
});
recognizer.on("campaign", (event) => {
console.log(event.campaign.name);
});
startButton?.addEventListener("click", async () => {
await recognizer.start();
});
</script>This example assumes you already have an ephemeral key endpoint. See Ephemeral Key for the standard setup.
Advanced Examples
The following examples render recognized campaigns in the UI. If you use React, Vue, or another Vite-based framework, configure the wasm loader in Setup first.
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SORI Web SDK</title>
<style>
#campaign-list {
padding: 0;
list-style: none;
}
#campaign-list li + li {
margin-top: 8px;
}
</style>
</head>
<body>
<main>
<button id="start-button" type="button">Start Recognition</button>
<ul id="campaign-list"></ul>
</main>
<script type="module">
import { AudioRecognizer } from "@sorisdk/web-audio";
const startButton = document.querySelector("#start-button");
const campaignList = document.querySelector("#campaign-list");
const recognizer = new AudioRecognizer({
appId: "YOUR_APP_ID",
ephemeralKey: async () => {
const response = await fetch("/api/ephemeral-key", {
method: "POST"
});
if (!response.ok) {
throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
}
const { ephemeral_key } = await response.json();
return ephemeral_key;
}
});
recognizer.on("campaign", (event) => {
console.log(event.campaign.name);
if (campaignList) {
const item = document.createElement("li");
const link = document.createElement("a");
link.href = String(event.campaign.action_url);
link.textContent = String(event.campaign.title);
link.target = "_blank";
link.rel = "noreferrer";
item.append(link);
campaignList.prepend(item);
}
});
startButton?.addEventListener("click", async () => {
await recognizer.start();
});
globalThis.addEventListener("beforeunload", () => {
void recognizer.destroy();
});
</script>
</body>
</html>tsx
import { useEffect, useRef, useState } from "react";
import { AudioRecognizer } from "@sorisdk/web-audio";
type CampaignCard = {
title: string;
imageUrl: string;
actionUrl: string;
};
export default function App() {
const recognizerRef = useRef<AudioRecognizer | null>(null);
const [campaigns, setCampaigns] = useState<CampaignCard[]>([]);
useEffect(() => {
const recognizer = new AudioRecognizer({
appId: "YOUR_APP_ID",
ephemeralKey: async () => {
const response = await fetch("/api/ephemeral-key", {
method: "POST"
});
if (!response.ok) {
throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
}
const { ephemeral_key } = await response.json();
return ephemeral_key;
}
});
recognizer.on("campaign", (event) => {
console.log(event.campaign.name);
setCampaigns((current) => [
{
title: String(event.campaign.title),
imageUrl: String(event.campaign.image_url),
actionUrl: String(event.campaign.action_url)
},
...current
]);
});
recognizerRef.current = recognizer;
return () => {
void recognizer.destroy();
recognizerRef.current = null;
};
}, []);
return (
<main>
<button
type="button"
onClick={() => {
void recognizerRef.current?.start();
}}
>
Start Recognition
</button>
<div>
{campaigns.map((campaign, index) => (
<a
key={`${campaign.title}-${index}`}
href={campaign.actionUrl}
target="_blank"
rel="noreferrer"
>
<img src={campaign.imageUrl} alt={campaign.title} />
<strong>{campaign.title}</strong>
</a>
))}
</div>
</main>
);
}vue
<script setup lang="ts">
import { onBeforeUnmount } from "vue";
import { ref } from "vue";
import { AudioRecognizer } from "@sorisdk/web-audio";
const campaigns = ref<
Array<{ title: string; imageUrl: string; actionUrl: string }>
>([]);
const recognizer = new AudioRecognizer({
appId: "YOUR_APP_ID",
ephemeralKey: async () => {
const response = await fetch("/api/ephemeral-key", {
method: "POST"
});
if (!response.ok) {
throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
}
const { ephemeral_key } = await response.json();
return ephemeral_key;
}
});
recognizer.on("campaign", (event) => {
console.log(event.campaign.name);
campaigns.value.unshift({
title: String(event.campaign.title),
imageUrl: String(event.campaign.image_url),
actionUrl: String(event.campaign.action_url)
});
});
async function startRecognition() {
await recognizer.start();
}
onBeforeUnmount(() => {
void recognizer.destroy();
});
</script>
<template>
<main>
<button type="button" @click="startRecognition">Start Recognition</button>
<div class="campaign-grid">
<a
v-for="campaign in campaigns"
:key="`${campaign.actionUrl}-${campaign.title}`"
:href="campaign.actionUrl"
target="_blank"
rel="noreferrer"
class="campaign-card"
>
<img :src="campaign.imageUrl" :alt="campaign.title" />
<strong>{{ campaign.title }}</strong>
</a>
</div>
</main>
</template>
<style scoped>
.campaign-grid {
display: grid;
gap: 12px;
margin-top: 16px;
}
.campaign-card {
display: grid;
gap: 8px;
color: inherit;
text-decoration: none;
}
</style>svelte
<script lang="ts">
import { onMount } from "svelte";
import { AudioRecognizer } from "@sorisdk/web-audio";
let campaigns: Array<{ title: string; imageUrl: string; actionUrl: string }> = [];
let recognizer: AudioRecognizer;
onMount(() => {
recognizer = new AudioRecognizer({
appId: "YOUR_APP_ID",
ephemeralKey: async () => {
const response = await fetch("/api/ephemeral-key", {
method: "POST"
});
if (!response.ok) {
throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
}
const { ephemeral_key } = await response.json();
return ephemeral_key;
}
});
recognizer.on("campaign", (event) => {
console.log(event.campaign.name);
campaigns = [
{
title: String(event.campaign.title),
imageUrl: String(event.campaign.image_url),
actionUrl: String(event.campaign.action_url)
},
...campaigns
];
});
return () => {
void recognizer.destroy();
};
});
async function startRecognition() {
await recognizer.start();
}
</script>
<main>
<button type="button" on:click={startRecognition}>Start Recognition</button>
<div class="campaign-grid">
{#each campaigns as campaign}
<a
href={campaign.actionUrl}
target="_blank"
rel="noreferrer"
class="campaign-card"
>
<img src={campaign.imageUrl} alt={campaign.title} />
<strong>{campaign.title}</strong>
</a>
{/each}
</div>
</main>
<style>
.campaign-grid {
display: grid;
gap: 12px;
margin-top: 16px;
}
.campaign-card {
display: grid;
gap: 8px;
color: inherit;
text-decoration: none;
}
</style>Notes
- Request microphone access only from a user action such as a button click.
- If your page or component can be removed, call
destroy()during cleanup. - If you need a richer control flow, add
stop()and other event handlers after this minimal wiring works.
