@tinyweb_dev/tinyquizz-react
v0.6.1
Published
React SDK to embed TinyQuizz games + server-side headless create/publish API
Downloads
1,418
Readme
@tinyweb_dev/tinyquizz-react
React SDK to embed TinyQuizz players and editors with a versioned iframe + postMessage contract.
Install
npm install @tinyweb_dev/tinyquizz-react
# or
yarn add @tinyweb_dev/tinyquizz-reactPeer dependencies: react and react-dom >= 17.
Quick start
import { TinyQuizz } from '@tinyweb_dev/tinyquizz-react';
export function LessonEmbed() {
return (
<TinyQuizz
publicLink="abc123"
licenseKey="tq_live_xxxx"
playerName="An"
externalUserId="student-42"
theme="space"
mode="race"
onComplete={(event) => {
console.log(event.payload.score, event.payload.total);
}}
onError={(event) => {
console.error(event.payload.code, event.payload.message);
}}
hideStudentList
hideHeader
/>
);
}For Duck Race, set hideStudentList to hide the student-list panel. The Fullscreen and New Race controls remain visible above the game.
Set hideHeader to hide the engine header chrome (title / subtitle / Edit button) for compact embeds.
Exercise editor embed
Create an exercise from a TinyQuizz game template inside the host application:
import {
EmbedEditorMode,
TinyQuizz,
TinyQuizzEditor,
} from '@tinyweb_dev/tinyquizz-react';
import { useState } from 'react';
export function LessonExerciseEditor() {
const [publicLink, setPublicLink] = useState<string | null>(null);
return (
<>
<TinyQuizzEditor
mode={EmbedEditorMode.CREATE}
gameTemplateSlug="dynamic-quiz"
initialTitle="Fractions grade 5"
lang="en"
// Partner LMS: silent auth — no Google login inside the iframe.
licenseKey="tq_live_oe_school"
onExerciseCreated={(event) => {
// Persist event.payload.exerciseId on the host lesson/activity.
console.log('Created exercise', event.payload.exerciseId);
}}
onSaved={(event) => {
// Content saved. status is usually "published" after the first Save
// in the embed editor (see Visibility below).
console.log('Saved', event.payload.exerciseId, event.payload.status);
}}
onPublished={(event) => {
// Fired when the exercise becomes link-playable.
// Use publicLink with <TinyQuizz /> for students.
setPublicLink(event.payload.publicLink);
}}
onCancelled={() => console.log('Cancelled')}
onAuthRequired={() => console.log('Sign-in required')}
/>
{publicLink ? (
<TinyQuizz publicLink={publicLink} height={640} />
) : null}
</>
);
}Edit an existing exercise by passing exerciseId:
<TinyQuizzEditor exerciseId="exercise-uuid" height={800} />Editor auth (partner LMS)
Pass licenseKey so the iframe exchanges it for a synthetic partner session
(POST /api/auth/partner-session) and skips Google login. Without a key,
the editor still shows the compact Google sign-in UI (direct embeds / demos).
Do not pass end-user JWTs or host cookies into the iframe. The only supported silent auth is the partner license key issued by TinyQuizz.
Save = playable (embed editor)
In the host embed editor there is a single Lưu / Save action (no separate Publish button):
- User edits content and clicks Lưu.
- TinyQuizz saves content, then publishes the exercise as
internalvisibility. - Host receives
editor-savedtheneditor-published(withpublicLink). - Students play via
<TinyQuizz publicLink={…} />(or the public embed URL).
| Event | When | Host should |
| --- | --- | --- |
| exercise-created | New exercise created from a game template | Store exerciseId on the lesson/activity |
| editor-saved | Content write succeeded | Sync title / status / questionCount |
| editor-published | Exercise is link-playable | Store publicLink and render the player |
Toast copy in the iframe: 「Đã lưu — có thể chơi ngay.」
Content that fails publish validation (e.g. empty quiz) stays draft; the host still gets editor-saved with status: "draft" and no editor-published until content is valid and Save succeeds again.
Visibility
Published exercises have one of:
| Value | Discovery (TinyQuizz site) | Play via publicLink |
| --- | --- | --- |
| public | Listed | Yes |
| internal | Not listed | Yes (anyone with the link) |
| private | Not listed | No (owner-only) |
Embed SDK default on Save: internal — host LMS exercises are playable by link without appearing on TinyQuizz Discovery.
TinyQuizz app Publish dialog lets creators choose public / internal / private (default public).
Player embed (TinyQuizz / /embed/:publicLink) works for public and internal. private returns not publicly accessible.
onSaved payload status is the lifecycle value (draft | published | archived), not the visibility enum. After a successful embed Save, expect status: "published" and use onPublished.publicLink for the player.
Imperative controls
import { useRef } from 'react';
import {
TinyQuizz,
TinyQuizzEditor,
type TinyQuizzEditorHandle,
type TinyQuizzHandle,
} from '@tinyweb_dev/tinyquizz-react';
export function ControlledEmbed() {
const playerRef = useRef<TinyQuizzHandle>(null);
const editorRef = useRef<TinyQuizzEditorHandle>(null);
return (
<>
<button type="button" onClick={() => playerRef.current?.start()}>
Start
</button>
<button
type="button"
onClick={() =>
playerRef.current?.setDuckNames(['An', 'Binh', 'Chi'])
}
>
Fill Duck Race roster (player)
</button>
<TinyQuizz ref={playerRef} publicLink="abc123" />
<TinyQuizzEditor
editorRef={editorRef}
mode="create"
gameTemplateSlug="duck-race"
onRequestClassRoster={() => {
// LMS host owns student data — push roster into the editor.
editorRef.current?.setDuckNames(['An', 'Binh', 'Chi']);
}}
/>
</>
);
}Protocol
- Player → host:
tinyquizz:ready | start | answer | complete | error - Exercise editor → host:
tinyquizz:editor-ready | exercise-created | editor-saved | editor-published | editor-cancelled | editor-auth-required | editor-request-class-roster - Host → player:
{ type: 'tinyquizz:command', action: 'start' | 'pause' | 'restart' | 'setPlayer' | 'setDuckNames' } - Host → editor:
{ type: 'tinyquizz:editor-command', action: 'setDuckNames' } setDuckNamespayload:{ names: string[] }(Duck Race class roster from the host LMS)- Messages are versioned (
version: 1) and scoped byinstanceIdso multiple embeds on one page stay isolated. - Editor events are sent only to the exact
hostOriginsupplied by the SDK.
Local demo
Run the TinyQuizz frontend and open /dev/embed-sdk. The editor tab creates an exercise from a selected game template or opens an existing exercise by exerciseId; the player tab accepts a public link.
Headless API (server-only)
Create and publish exercises without opening TinyQuizzEditor. Import the
/api subpath from a trusted host backend only.
Security: never put
licenseKeyin student / browser bundles. Browser code should only use<TinyQuizz publicLink={…} />.
import {
createTinyQuizzClient,
buildMultipleChoiceContent,
ExerciseVisibility,
GameTemplateSlug,
} from '@tinyweb_dev/tinyquizz-react/api';
const client = createTinyQuizzClient({
baseUrl: process.env.TINYQUIZZ_API_URL!, // e.g. https://api.tinyquizz.com
licenseKey: process.env.TINYQUIZZ_LICENSE_KEY!,
hostOrigin: 'https://exam.oceanedu.site', // partner allowlist
});
const content = buildMultipleChoiceContent({
questions: [
{
id: 'q1',
text: 'What color is the sky?',
answers: [
{ id: 'a', text: 'Blue', isCorrect: true },
{ id: 'b', text: 'Green', isCorrect: false },
],
},
],
});
const game = await client.exercises.createAndPublish({
template: GameTemplateSlug.MULTIPLE_CHOICE,
title: 'Colors Quiz',
visibility: ExerciseVisibility.INTERNAL, // default
content,
});
// → { exerciseId, publicLink, status: 'published', ... }
// Play: <TinyQuizz publicLink={game.publicLink} />| Method | Backend | Notes |
| --- | --- | --- |
| exercises.createAndPublish(input) | POST /api/partner/exercises/create-and-publish | One-shot; default visibility internal |
| exercises.create(input) | POST /api/partner/exercises | Draft only |
| exercises.update(id, input) | PUT /api/partner/exercises/:id | Title / content / settings |
| exercises.publish(id, input?) | PATCH /api/partner/exercises/:id/publish | Default visibility internal |
Content builders (light client validation; backend still normalizes):
buildMultipleChoiceContent→multiple-choicebuildDynamicQuizContent→dynamic-quiz/starter-quizbuildFlashcardContent→flashcardbuildWordSearchContent→word-searchbuildCrosswordContent→crossword
react / react-dom peer deps are optional when you only import /api.
Publish
This package is released from the monorepo root via .github/workflows/release_npm.yml on tags v*.
Changelog
0.6.0
- Additive: headless server client at
@tinyweb_dev/tinyquizz-react/api(createTinyQuizzClient, content builders, partner create/publish). - No breaking changes to embed components or the main entry export surface.
