npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@studiolxd/react-scorm

v1.0.2

Published

Headless React + TypeScript SCORM 1.2/2004 runtime integration library

Readme

🌐 English · Español · Français · Português · Deutsch · Polski

@studiolxd/react-scorm

Uma biblioteca headless React + TypeScript para integração com o runtime SCORM. Fornece um <ScormProvider> e o hook useScorm() para comunicação com um LMS via SCORM 1.2 ou SCORM 2004 (4ª Edição).

Principais funcionalidades:

  • Cobertura completa dos padrões SCORM 1.2 e 2004
  • Headless (sem UI) — você constrói a interface
  • Tipagem TypeScript estrita para todos os caminhos CMI e APIs
  • Tratamento de erros baseado em Result (sem lançamentos implícitos)
  • Sem estado automático, sem persistência, sem salvamento automático
  • Modo mock em memória para desenvolvimento local
  • Gerenciamento de ciclo de vida opcional com useScormAutoTerminate

Instalação

npm install @studiolxd/react-scorm

React 18+ é necessário como peer dependency.

Início Rápido

import { ScormProvider, useScorm } from '@studiolxd/react-scorm';

function App() {
  return (
    <ScormProvider version="1.2" options={{ noLmsBehavior: 'mock', debug: true }}>
      <CourseContent />
    </ScormProvider>
  );
}

function CourseContent() {
  const { api, status } = useScorm();

  if (!api) return <p>No SCORM API available</p>;

  const handleStart = () => {
    const result = api.initialize();
    if (!result.ok) console.error(result.error);
  };

  const handleComplete = () => {
    api.setScore({ raw: 95, min: 0, max: 100 });
    api.setComplete();
    api.commit();
  };

  return (
    <div>
      <p>API found: {String(status.apiFound)}</p>
      <button onClick={handleStart}>Start</button>
      <button onClick={handleComplete}>Complete</button>
    </div>
  );
}

ScormProvider

O componente <ScormProvider> localiza a API do LMS e a disponibiliza via contexto. Ele não inicializa automaticamente — você deve chamar api.initialize() explicitamente.

<ScormProvider
  version="1.2"  // or "2004"
  options={{
    noLmsBehavior: 'error',  // 'error' | 'mock' | 'throw'
    debug: false,             // enable console logging
    maxParentDepth: 10,       // max window.parent traversal depth
    checkOpener: true,        // check window.opener
  }}
>
  {children}
</ScormProvider>

Opções de noLmsBehavior

| Valor | Comportamento | |-------|---------------| | "error" (padrão) | api é null, status.apiFound é false. As operações não podem ser chamadas. | | "mock" | Usa uma API SCORM mock em memória. Ideal para desenvolvimento local e testes. | | "throw" | Lança um ScormError durante a renderização. Envolva com um Error Boundary do React. |

Modo "throw" lança de forma síncrona dentro do useMemo. Sem um Error Boundary, o erro se propagará e derrubará toda a subárvore. Sempre envolva o <ScormProvider> em um Error Boundary ao usar esse modo.

useScorm()

const { status, api, raw } = useScorm();

| Campo | Tipo | Descrição | |-------|------|-----------| | status | ScormStatus | Status da conexão: apiFound, initialized, terminated, version, noLmsBehavior | | api | IScormApi \| null | API de alto nível agnóstica à versão. null quando nenhuma API é encontrada com o comportamento "error". | | raw | IScormDriver \| null | Driver de baixo nível para chamadas diretas à API (escape hatch). |

Nota: status.initialized e status.terminated são sempre false no valor de contexto — o provider não rastreia o estado do runtime. Se você precisar de estado reativo de inicialização/encerramento, mantenha-o você mesmo com useState após chamar api.initialize() e api.terminate().

API de Alto Nível

Todos os métodos retornam Result<T, ScormError> — verifique result.ok antes de acessar o valor.

Ciclo de Vida

api.initialize()   // Result<true, ScormError>
api.terminate()    // Result<true, ScormError>
api.commit()       // Result<true, ScormError>

Status

api.setComplete()    // 1.2: cmi.core.lesson_status="completed" | 2004: cmi.completion_status="completed"
api.setIncomplete()
api.setPassed()      // 1.2: cmi.core.lesson_status="passed"    | 2004: cmi.success_status="passed"
api.setFailed()      // 1.2: cmi.core.lesson_status="failed"    | 2004: cmi.success_status="failed"
api.getCompletionStatus()
api.getSuccessStatus()

Pontuação

api.setScore({ raw: 85, min: 0, max: 100, scaled: 0.85 })  // scaled is 2004 only
api.getScore()  // Result<ScoreData, ScormError>

Validação: raw, min e max devem ser números finitos (NaN/Infinity são rejeitados com o código de erro 405). scaled deve estar no intervalo [-1, 1] (código de erro 407 se fora do intervalo). Para SCORM 1.2, scaled é silenciosamente ignorado.

Localização e Dados de Suspensão

api.setLocation('page-5')
api.getLocation()
api.setSuspendData(JSON.stringify({ progress: [1, 2, 3] }))
api.getSuspendData()

Tempo de Sessão

api.setSessionTime(90000)  // 1.2: "00:01:30.00" | 2004: "PT1M30S"
api.getTotalTime()

Informações do Aprendiz (somente leitura)

api.getLearnerId()
api.getLearnerName()
api.getLaunchData()
api.getMode()
api.getCredit()
api.getEntry()

Objetivos

api.setObjective(0, {
  id: 'obj-1',
  scoreRaw: 90,
  scoreScaled: 0.9,       // 2004 only
  status: 'passed',       // 1.2
  successStatus: 'passed', // 2004
  completionStatus: 'completed', // 2004
})
api.getObjective(0)       // Result<ObjectiveRecord, ScormError>
api.getObjectiveCount()   // Result<number, ScormError>

Interações

api.recordInteraction(0, {
  id: 'q1',
  type: 'choice',
  learnerResponse: 'a',
  correctResponses: ['b'],
  result: 'incorrect',
  weighting: 1,
  description: 'Question text',  // 2004 only
})
api.getInteractionCount()

Comentários

// SCORM 1.2: appends to a single cmi.comments string (newline-separated)
// SCORM 2004: creates indexed entries in cmi.comments_from_learner
api.addLearnerComment('Great course', 'page-3', '2024-01-15T10:00:00Z')
                      // comment        location  timestamp (all optional except comment)

// Count methods: always return 0 for SCORM 1.2
api.getLearnerCommentCount()  // Result<number, ScormError>
api.getLmsCommentCount()      // Result<number, ScormError> — LMS comments read-only

Preferências

api.setPreference('language', 'en')
api.getPreferences()  // Result<Record<string, string>, ScormError>

Progresso (somente SCORM 2004)

api.setProgressMeasure(0.75)  // value must be in range [0, 1] — error 407 otherwise
                               // no-op (returns ok) for SCORM 1.2

Saída

api.setExit('suspend')  // 1.2: cmi.core.exit | 2004: cmi.exit
                        // Common values: 'suspend', 'logout', 'time-out', ''

Dados do Aluno (somente leitura, definido pelo LMS)

api.getMasteryScore()     // 1.2: cmi.student_data.mastery_score | 2004: cmi.scaled_passing_score
api.getMaxTimeAllowed()   // 1.2: cmi.student_data.max_time_allowed | 2004: cmi.max_time_allowed
api.getTimeLimitAction()  // 1.2: cmi.student_data.time_limit_action | 2004: cmi.time_limit_action

Navegação (somente SCORM 2004)

api.setNavRequest('continue')              // no-op (returns ok) for SCORM 1.2
api.getNavRequestValid('continue')         // 'continue' | 'previous'
api.getNavRequestValid('previous')

Acesso Direto (escape hatch)

api.getRaw('cmi.core.lesson_status')
api.setRaw('cmi.core.lesson_status', 'completed')

Tratamento de Erros

Todas as operações retornam Result<T, ScormError> em vez de lançar exceções:

const result = api.setComplete();
if (result.ok) {
  console.log('Success:', result.value);
} else {
  console.error('Error:', result.error.code, result.error.errorString);
  console.error('Operation:', result.error.operation);
  console.error('Path:', result.error.path);
  console.error('Diagnostic:', result.error.diagnostic);
}

ScormError inclui: version, operation, path, code, errorString, diagnostic, exception, apiFound, initialized.

Funções auxiliares: isOk(), isErr(), unwrap(), unwrapOr().

Segurança: errorString e diagnostic são strings provenientes diretamente do LMS. Não as renderize via innerHTML ou qualquer API DOM sem sanitização — trate-as como entrada não confiável e faça escape de HTML antes de qualquer inserção no DOM.

useScormSession (estado reativo opcional)

useScorm() mantém intencionalmente status.initialized como um snapshot estático — o provider não rastreia o estado do ciclo de vida. Se você precisar de initialized e terminated como estado reativo do React (para acionar re-renderizações), use useScormSession() no lugar.

import { useScormSession } from '@studiolxd/react-scorm';

function Course() {
  const { initialized, initialize, terminate, api } = useScormSession();

  useEffect(() => { initialize(); }, [initialize]);

  if (!initialized) return <p>Connecting…</p>;
  return <CourseContent api={api} onFinish={terminate} />;
}

useScormSession() é um superconjunto de useScorm() — retorna tudo que useScorm() retorna (api, status, raw) mais:

| Campo | Tipo | Descrição | |-------|------|-----------| | initialized | boolean | true após initialize() ser executado com sucesso. Reativo. | | terminated | boolean | true após terminate() ser executado com sucesso. Reativo. | | initialize() | Result<true, ScormError> \| undefined | Chama api.initialize() e atualiza o estado. undefined se não houver API. | | terminate() | Result<true, ScormError> \| undefined | Chama api.terminate() e atualiza o estado. undefined se não houver API. | | commit() | Result<true, ScormError> \| undefined | Chama api.commit(). undefined se não houver API. |

Nota: Quando noLmsBehavior é 'error' e nenhum LMS é encontrado, api é null e os três métodos retornam undefined. Verifique status.apiFound se precisar distinguir esse caso.

useScormAutoTerminate (opcional)

Gerencia o ciclo de vida SCORM automaticamente:

function CourseContent() {
  const { api } = useScorm();
  useScormAutoTerminate({
    trackSessionTime: true,  // default: true
    handleUnload: true,      // listen to beforeunload/pagehide
    handleFreeze: true,      // listen to freeze event
  });

  // api is auto-initialized on mount
  // auto-commits + terminates on unmount/unload
}

Construtores de Caminhos

Funções auxiliares tipadas para construir caminhos CMI indexados:

import { scorm12ObjectivePath, scorm2004InteractionPath } from '@studiolxd/react-scorm';

scorm12ObjectivePath(0, 'score.raw')       // "cmi.objectives.0.score.raw"
scorm2004InteractionPath(1, 'learner_response')  // "cmi.interactions.1.learner_response"

Formatadores de Tempo

import { formatScorm12Time, formatScorm2004Time } from '@studiolxd/react-scorm';

formatScorm12Time(90000)   // "00:01:30.00"
formatScorm2004Time(90000) // "PT1M30S"

Testes

Use noLmsBehavior: 'mock' para testes. O mock utiliza um armazenamento em memória com comportamento real do driver.

// In tests
<ScormProvider version="1.2" options={{ noLmsBehavior: 'mock' }}>
  <ComponentUnderTest />
</ScormProvider>

Você também pode usar as classes mock diretamente:

import { MockScorm12Api, Scorm12Driver, ScormApi, createLogger } from '@studiolxd/react-scorm';

const mockApi = new MockScorm12Api();
const driver = new Scorm12Driver(mockApi, createLogger(false));
const api = new ScormApi(driver);
api.initialize();
// ... test your logic

TypeScript

Todos os caminhos CMI são estritamente tipados:

import type { Scorm12CmiPath, Scorm2004CmiPath } from '@studiolxd/react-scorm';

// These types catch typos at compile time:
const path: Scorm12CmiPath = 'cmi.core.lesson_status';  // OK
// const bad: Scorm12CmiPath = 'cmi.core.typo';          // ERROR

Documentação Adicional

Licença

MIT