Lingara Lingara Documentation Guides API Bibliothèques Applications Créer Intégration Application web
Langue: Français

Widget de site web

Cette page est traduite de l'anglais. En cas de différence, la page anglaise fait foi. Lire la page en anglais

Sur un site web, Lingara s’exécute sous forme d’élément personnalisé, <lingara-embed>. L’élément dessine la pratique de Lingara dans un cadre servi par Lingara : votre page ne voit donc jamais les données de l’apprenant, et la page de Lingara ne voit jamais les vôtres.

Monter l’élément

Installez @lingara/embed depuis npm, ou chargez sa version module depuis un CDN si votre site n’a pas de bundler. Placez ensuite <lingara-embed> sur la page et définissez-le. L’élément accepte :

  • plan-id, le plan de cours à pratiquer, que le joueur doit posséder ;
  • surface, ce qu’il faut afficher : practice, la valeur par défaut et la seule surface existante ;
  • theme, parmi light, dark ou auto ;
  • locale, la langue des textes propres au cadre, qui sinon suit celle de l’apprenant ;
  • title, le nom accessible du cadre.

La personnalisation du style s’arrête au thème. Votre page ne peut pas atteindre l’intérieur du cadre, et le thème est le seul réglage qu’il propose.

Lui donner des jetons

Définissez le tokenProvider de l’élément sur une fonction qui demande un jeton à votre propre serveur. Votre serveur l’émet avec l’origine de la page, en utilisant un kit serveur, et répond avec le jeton et son expiration. L’élément transmet le jeton au cadre par message, et il ne transite jamais dans une URL.

Enregistrez chaque origine sur laquelle le widget s’exécute avant d’émettre un jeton pour elle, dans les paramètres de votre client dans la console. La page kits serveur explique comment. L’API refuse un jeton émis pour toute autre origine, et le cadre affiche une erreur au lieu de la pratique.

Un kit serveur peut aussi écrire pour vous l’extrait complet, élément et fournisseur de jetons ensemble, avec le nonce de script de votre page si votre politique de sécurité du contenu en exige un. Le dernier exemple ci-dessous le montre.

Écouter ce que dit le cadre

Le cadre communique avec l’élément via un protocole de messages versionné, et l’élément transforme ce dont votre page a besoin en événements DOM :

  • lingara:practice-completed lorsque l’apprenant termine une série, avec ses planId, setNumber, correct et total ;
  • lingara:error lorsque le cadre s’arrête, avec un code et retryable. Une erreur réessayable signifie qu’un nouveau montage peut réussir ; c’est à vous de décider quand réessayer.

Le résultat de la pratique parvient aussi à votre serveur, comme le décrit résultats de pratique.

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)
})
Langage de l'exemple de code

TypeScript

// The page that mounts the widget: the token never appears in it.
export function practicePage(planId: string, cspNonce: string): string {
  const widget = widgetSnippet({ planId, tokenEndpoint: '/lingara/token', theme: 'auto', locale: 'fr', title: 'Pratique', nonce: cspNonce })
  return `<!doctype html><title>Practice</title>${widget}`
}

Rust

// The element, its script build and a token provider that POSTs to your
// token route. The token never appears in the markup.
let html = widget_snippet(&SnippetOptions {
    locale: Some("fr".into()),
    nonce: Some(csp_nonce.into()), // your page's CSP nonce, for a strict script-src
    ..SnippetOptions::new(plan_id, "/lingara/token")
})?;

Go

// The plan your server made for this player, and the token route above.
// Pass the request's CSP nonce when your script-src allows no inline
// script; the token itself never appears in the markup.
theme := "auto"
html, err := lingaraembed.WidgetSnippet(lingaraembed.SnippetOptions{
	PlanID:        planID,
	TokenEndpoint: "/lingara/token",
	Theme:         &theme,
	Nonce:         &cspNonce,
})
if err != nil {
	return "", err // a *lingaraembed.SnippetError names the refused option
}
return template.HTML(html), nil // already escaped by the kit

Java

// planId is the lesson plan your server made for this player. The element
// POSTs to tokenEndpoint with same-origin cookies: the token never appears
// in the markup. Pass this request's CSP nonce if your page sets one.
String html =
    WidgetSnippet.render(
        SnippetOptions.builder(planId, "/lingara/token").locale("fr").nonce(cspNonce).build());

Kotlin

// planId is the lesson plan your server made for this player. The element
// POSTs to tokenEndpoint with same-origin cookies: the token never appears
// in the markup. Pass this request's CSP nonce if your page sets one.
val html =
    widgetSnippet(
        SnippetOptions(planId, tokenEndpoint = "/lingara/token").apply {
            locale = "fr"
            nonce = cspNonce
        },
    )

Ruby

# The plan your server made for this player, rendered into your page.
# nonce: your page's CSP nonce, when its script-src allows no inline script.
def self.widget(plan_id, csp_nonce)
  Lingara::Embed.widget_snippet(plan_id: plan_id, token_endpoint: "/lingara/token", locale: "fr", nonce: csp_nonce)
end

PHP

use Lingara\Embed\SnippetOptions;
use Lingara\Embed\WidgetSnippet;

// In your page's template: the plan your server made for this player, and
// your token route. Pass the request's CSP nonce if your script-src allows
// no inline script. A bad option throws SnippetException naming its field.
function practiceWidget(string $planId, string $cspNonce): string
{
    return WidgetSnippet::render(new SnippetOptions(
        planId: $planId,
        tokenEndpoint: '/lingara/token',
        theme: 'auto',
        nonce: $cspNonce,
    ));
}

C#

app.MapGet("/practice/{planId}", (string planId, HttpContext http) =>
{
    var widget = WidgetSnippet.Render(new SnippetOptions
    {
        PlanId = planId,                              // the plan your server made for this player
        TokenEndpoint = "/lingara/token",             // same origin: the element sends this page's cookies
        Nonce = http.Items["csp-nonce"] as string,     // your CSP's nonce, if script-src has one
    });
    return Results.Content($"<!doctype html><title>Practice</title>\n{widget}", "text/html");
}).RequireAuthorization();