Website widget
On a website, Lingara runs as a custom element, <lingara-embed>. The element draws Lingara’s practice in a frame served by Lingara, so your page never sees the learner’s data and Lingara’s page never sees yours.
Mount the element
Install @lingara/embed from npm, or load its module build from a CDN if your site has no bundler. Then put <lingara-embed> on the page and define it. The element takes:
plan-id, the lesson plan to practise, one the player owns;surface, what to show:practice, the default and the one surface there is;theme, one oflight,darkorauto;locale, the language of the frame’s own words, which otherwise follows the learner;title, the frame’s accessible name.
Styling stops at the theme. Your page cannot reach inside the frame, and the theme is the one setting it offers.
Give it tokens
Set the element’s tokenProvider to a function that asks your own server for a token. Your server mints it with the page’s origin, using a server kit, and answers with the token and its expiry. The element hands the token to the frame by message, and it never travels in a URL.
Register every origin the widget runs on before you mint for it, in your client’s settings in the console. The server kits page says how. The API refuses a token minted for any other origin, and the frame shows an error instead of practice.
A server kit can also write the whole snippet for you, element and token provider together, with your page’s script nonce if your content security policy needs one. The last sample below shows it.
Listen to what the frame says
The frame talks to the element over a versioned message protocol, and the element turns what your page needs into DOM events:
lingara:practice-completedwhen the learner finishes a set, with itsplanId,setNumber,correctandtotal;lingara:errorwhen the frame stops, with acodeandretryable. A retryable error means a fresh mount may succeed; when to try again is yours to decide.
The result of the practice reaches your server too, as described in practice results.
npm install @lingara/embed@0.1.0-alpha.6
# No bundler? One module script instead:
# <script type="module" src="https://cdn.jsdelivr.net/npm/@lingara/embed@0.1.0-alpha.6/dist/lingara-embed.min.js"></script><!-- plan-id is a lesson plan your server made for this player. -->
<lingara-embed surface="practice" plan-id="0b9d4f2e-3c1a-4e8b-9f6d-2a7c5e1b8d40" theme="auto" title="Practice"></lingara-embed>
<script type="module">
import { defineLingaraEmbed } from '@lingara/embed'
defineLingaraEmbed()
</script>// Your own endpoint, on your own server. It mints with this page's origin and
// returns exactly { token, expires_at }: the token (lgr_et_…) never touches a URL.
widget.tokenProvider = async () => {
const response = await fetch('/lingara/token', { method: 'POST', credentials: 'same-origin' })
if (!response.ok) throw new Error(`token endpoint answered ${response.status}`)
return response.json()
}widget.addEventListener('lingara:practice-completed', (event) => {
const { planId, setNumber, correct, total } = (event as CustomEvent).detail
console.log(`set ${setNumber} of ${planId}: ${correct}/${total}`)
})
widget.addEventListener('lingara:error', (event) => {
const { code, retryable } = (event as CustomEvent).detail
// `retryable` means a remount may succeed; when to try is yours to decide.
if (retryable) setTimeout(() => widget.reload(), 30_000)
else console.warn('Lingara practice stopped:', code)
})