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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@oliv-e/vitals

v0.4.1

Published

Componentes React do Oliv-e Vitals

Downloads

66

Readme

@oliv-e/vitals

Biblioteca de componentes React e widget embutível (Custom Element) para iniciar a medição de sinais vitais via iFrame. Este projeto entrega:

  • Lib npm para React/TypeScript (ESM + CJS + tipos .d.ts)
  • Bundle standalone para integração via SCRIPT TAG ()

Sumário

  • Visão Geral
  • Estrutura do projeto
  • Uso como biblioteca React (npm)
  • Uso via SCRIPT TAG (widget standalone)
  • Atributos (obrigatórios e opcionais)
  • Build, testes e publicação
  • Tipos exportados
  • Eventos e comunicação
  • Configuração de estilos (Tailwind/CSS)
  • Dicas de troubleshooting

Visão Geral

  • A lib React expõe os componentes que constroem um iFrame apontando por padrão para https://vitals.oliv-e.health/widget/ e fazem o handshake com postMessage + MessageChannel para receber resultados. Essa URL pode ser customizada via WIDGET_BASE_URL (ver abaixo), mantendo o mesmo contrato.
  • Eventos padronizados propagados para window: vh:handshake, vh:onready, vh:result, vh:onsuccess, vh:onerror, vh:onevent.
  • O bundle standalone disponibiliza o Custom Element <vitals-widget> para integrar via SCRIPT TAG sem React.

Estrutura do projeto

.
├── dist/                      # Artefatos de build (lib + widget standalone)
├── src/
│   ├── index.ts               # Entry da lib (reexporta componentes e tipos)
│   └── widget/
│       ├── components/
│       │   ├── widget.tsx             # Componente que renderiza o iFrame e o handshake
│       │   └── widget-container.tsx   # Provider + composição do widget
│       │   └── widget-sdk.tsx         # Provider + composição do widget para aplicações React
│       ├── lib/
│       │   ├── context.ts             # Contexto do widget
│       │   ├── types.ts               # Tipos públicos exportados
│       │   └── use-widget-context.ts  # Hook utilitário
│       ├── styles/
│       │   └── style.css              # Estilos do widget (Tailwind @apply)
│       ├── custom-element.tsx         # Definição do <vitals-widget /> (Shadow DOM)
│       └── index.tsx                  # Entry para o bundle standalone
├── test/
│   └── index.html              # Página de teste local do widget standalone
├── rollup.config.mjs           # Build do widget standalone (IIFE)
├── vite.config.lib.ts          # Build da lib (ESM/CJS + d.ts)
├── tailwind.config.cjs         # Configuração Tailwind para build do CSS
├── postcss.config.cjs          # PostCSS + autoprefixer
├── .env.development            # Variáveis para build:widget ambiente dev
├── .env.production             # Variáveis para build:widget ambiente prod
├── package.json
└── README.md

Uso como biblioteca React (npm)

Instalação (após publicação):

pnpm add @oliv-e/vitals @tanstack/react-query

Nota: @tanstack/react-query é peerDependency do SDK.

Exemplo recomendado (default export):

import Vitals from '@oliv-e/vitals';

export default function Page() {
  return (
    <Vitals
      clientKey="<CLIENT_KEY>"
      user="14267197016"   // OBRIGATÓRIO: CPF
      birthyear={1999}
      height={170}
      weight={65}
      sex={1}
      bp_group="primary"
      bp_mode="normal"
      facing_mode="user"
    />
  );
}

Notas:

  • Obrigatório: clientKey (identificador público do parceiro) e user (CPF).
  • Opcionais: birthyear, height, weight, sex, bp_group, bp_mode, facing_mode, scanOnly.
  • sex: 0 = female, 1 = male.
  • facing_mode: 'user' | 'environment' (padrão: 'user').
  • scanOnly: quando true, o iFrame renderiza somente vídeo+canvas (sem botões/overlays) e delega o início (InitScan) e o redirecionamento de sucesso ao parent. O parent deve enviar o comando InitScan via MessageChannel e tratar o evento OnSuccess.

Resolução do parceiro (SDK)

  • O SDK busca os dados do parceiro via GET https://api-dev.oliv-e.health/company/widget/themes/${clientKey} com header x-partner-code: <clientKey>. Retornos 404 disparam o erro: O parceiro informado não está registrado para utilização..
  • Se retornar 404, será lançado o erro: O parceiro informado não está registrado para utilização..

Envio automático de sucesso (SDK)

  • Ao receber o evento de sucesso do iFrame, o SDK envia um POST para https://api-dev.oliv-e.health/clinical-metrics/partner/clinical-measurement/ com:
    • Header: x-partner-code: <clientKey> e Content-Type: application/json
    • Body: { ...dadosDoEvento, metadata: { Cpf: "<CPF do usuário>" } }
  • Falhas no POST não bloqueiam o fluxo; são logadas no console.

React Query (TanStack) – opcional

  • O SDK aceita um queryClient (prop) para compartilhar o cache com a aplicação host.
  • Se não for fornecido, o SDK cria um QueryClient interno e envolve o widget em QueryClientProvider.
  • Para evitar múltiplos caches e requisições duplicadas, recomenda-se passar o QueryClient da aplicação host.

Exemplos:

Usando QueryClient da aplicação:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import Vitals from '@oliv-e/vitals';

const qc = new QueryClient();

export default function Page() {
  return (
    <QueryClientProvider client={qc}>
      <Vitals clientKey="<CLIENT_KEY>" user="14267197016" />
    </QueryClientProvider>
  );
}

Passando via prop:

import { QueryClient } from '@tanstack/react-query';
import VitalsWidget from '@oliv-e/vitals-widget';

const qc = new QueryClient();

export default function Page() {
return <Vitals clientKey="<CLIENT_KEY>" user="14267197016" queryClient={qc} />;
}

Exemplo mínimo (clientKey + user):

import VitalsWidget from '@oliv-e/vitals-widget';

export default function Page() {
return <Vitals clientKey="your_client_key" user="14267197016" />;
}

CSS

  • O pacote exporta um CSS compilado (dist/vitals-widget.css). Na maioria dos bundlers, importar a lib já injeta o CSS. Caso seu bundler não carregue CSS de node_modules automaticamente, importe explicitamente:
import '@oliv-e/vitals/dist/vitals.css';

Uso avançado (opcional) com WidgetContainer

  • Caso você já possua o trio (client, name, theme), é possível montar o widget diretamente:
import { WidgetContainer } from '@oliv-e/vitals';

export default function Page() {
  return (
    <WidgetContainer
      client="..."
      name="..."
      theme="standard"
      birthyear={1999}
      height={170}
      weight={65}
      sex={1}
      bp_group="primary"
      bp_mode="normal"
      facing_mode="user"
    />
  );
}

Tipos exportados úteis:

import type {
  VitalsConfig,
  Sex,
  FacingMode,
  BPMode,
  BPGroup,
  WidgetEventMap,
  WidgetSDK as WidgetSDKProps
} from '@oliv-e/vitals-widget';

Observação: o componente React padrão exportado chama-se Vitals (default export).

Provider e Hooks (novo em 0.3.0)

Para integrações com mais controle de layout/estado (como sobrepor UI própria, ex.: Tauri), você pode usar o Provider e os hooks exportados.

  • VitalsProvider: resolve o parceiro (via clientKey), registra listeners e publica OnSuccess automaticamente; provê o WidgetContext para os filhos
  • useVitalsContext: acessa a configuração resolvida (client, name, theme, user, etc.)
  • useVitalsEvents: inscreve-se nos eventos do widget e expõe controles initScan e stopScan
  • Widget: componente que renderiza somente o iFrame (sem Providers); útil dentro do Provider

Exemplo:

import { VitalsProvider, Widget, useVitalsEvents } from '@oliv-e/vitals';
import '@oliv-e/vitals/dist/vitals.css';

function Overlay() {
  const { isReady, lastResult, initScan } = useVitalsEvents();
  return (
    <div className="absolute inset-0 pointer-events-none">
      <button className="pointer-events-auto" disabled={!isReady} onClick={initScan}>
        Iniciar medição
      </button>
      {/* Exemplo simples de progresso vindo de lastResult */}
      <pre>{JSON.stringify(lastResult)}</pre>
    </div>
  );
}

export default function Page() {
  return (
    <VitalsProvider clientKey="<CLIENT_KEY>" user="14267197016" scanOnly>
      <div className="relative">
        <Widget />
        <Overlay />
      </div>
    </VitalsProvider>
  );
}

Notas:

  • scanOnly é recomendado quando você fornece sua própria UI (o iFrame renderiza apenas vídeo/canvas)
  • Você também pode obter as funções via ref no VitalsProvider (mesmo contrato de WidgetControls)

Uso via SCRIPT TAG (widget standalone)

Build do widget (dev/prod):

pnpm build:widget
pnpm build:widget:production

Servir e testar:

pnpm serve:widget   # serve ./dist na porta 3334
pnpm serve          # serve ./test na porta 3333

Abra http://localhost:3333/test/index.html — o HTML de teste carrega http://localhost:3334/widget.js e instância o Custom Element:

<script async src="http://localhost:3334/widget.js"></script>
<vitals-widget
  client-key="<CLIENT_KEY>"
  user="14267197016"
  birthyear="1999"
  height="175"
  weight="55"
  sex="1"
  bp-group="primary"
  bp-mode="normal"
  facing-mode="user"
/>

Nota: obrigatórios: client-key e user (CPF). Opcionais: birthyear, height, weight, sex, bp-group, bp-mode, facing-mode (padrão: 'user').

Resolução do parceiro (Web Component)

  • O Web Component utiliza um mapeamento local (clients.json). O client-key deve existir nesse arquivo para que o widget funcione.

Exemplo mínimo (client-key + user):

<script async src="http://localhost:3334/widget.js"></script>
<vitals-widget client-key="your_client_key" user="14267197016"></vitals-widget>

Publicação em CDN (ex.: jsDelivr):

<script async src="https://cdn.jsdelivr.net/npm/@oliv-e/vitals@<versao>/dist/widget.js"></script>

Nota: o CSS do widget é carregado automaticamente dentro do Shadow DOM com base no nome do script (widget.jswidget.css). Se precisar forçar um caminho absoluto, defina a variável de ambiente WIDGET_CSS_URL durante o build do bundle standalone.

Variáveis de ambiente do widget standalone

WIDGET_BASE_URL (desenvolvimento/local)

Durante o desenvolvimento, é comum servir o widget diretamente do projeto 3 (olive-face-heart-app/public/widget). Você pode apontar o SDK para esse servidor local definindo WIDGET_BASE_URL no build do SDK ou no ambiente em que o bundle React será servido.

Exemplo (servindo o widget em http://localhost:8080/widget/):

# .env.development
WIDGET_BASE_URL=http://localhost:8080/widget/

O SDK validará o targetOrigin com base nessa URL para o handshake via MessageChannel. WIDGET_NAME: nome do arquivo JS de saída (ex.: vitals-widget.js)

  • WIDGET_CSS_URL (opcional): URL absoluta do CSS; se não definido, o CSS é resolvido dinamicamente com base na URL do script atual.
  • WIDGET_BASE_URL (novo em 0.4.0): URL base do iFrame que hospeda o widget (padrão seguro: https://vitals.oliv-e.health/widget/). Útil para desenvolvimento/local.

Build, testes e publicação

Scripts principais:

  • pnpm build → build da lib (ESM, CJS, d.ts) via vite.config.lib.ts
  • pnpm build:lib → build somente da lib
  • pnpm build:widget → build do bundle standalone (IIFE)
  • pnpm build:widget:production → build do bundle standalone em modo prod (.env.production)
  • pnpm serve → serve página de teste ./test (porta 3333)
  • pnpm serve:widget → serve ./dist (porta 3334)
  • pnpm typecheck → checagem de tipos
  • pnpm lint / pnpm format

Publicação no npm (escopo @oliv-e):

  1. Crie/tenha acesso à organização oliv-e no npm (reserva o escopo @oliv-e).
  2. Garanta login e 2FA conforme sua política.
  3. O package já possui:
    • "files": ["dist"]
    • "types": "./dist/index.d.ts"
    • "exports" para ESM/CJS/Types
    • "prepublishOnly": "pnpm run build && pnpm run build:widget"
  4. Publique:
npm publish --access public
# ou, se 2FA write:
# npm publish --access public --otp <codigo>

Atributos (obrigatórios e opcionais)

React (componente) vs Custom Element (HTML)

  • clientKey (React) / client-key (Custom Element) – obrigatório. Identificador público do parceiro. Não inclua segredos.
  • user (React e Custom Element) – obrigatório. CPF do usuário.
  • queryClient (React) – opcional. Instância do QueryClient para compartilhamento de cache; se ausente, o SDK cria um internamente.
  • birthyear – opcional. Ano de nascimento (number no React; string numérica no HTML).
  • height – opcional. Altura em cm (number no React; string no HTML).
  • weight – opcional. Peso em kg (number no React; string no HTML).
  • sex – opcional. 0 = female, 1 = male.
  • bp_group – opcional. 'primary' | 'normal' | 'prehypertension' | 'hypertension'.
  • bp_mode – opcional. 'normal' | 'binary' | 'ternary'.
  • facing_mode (React) / facing-mode (Custom Element) – opcional. Padrão: 'user'.

Notas

  • Parâmetros são enviados ao iFrame apenas quando definidos; valores ausentes não são incluídos na query string.

Tipos exportados

// Tipos base exportados pela biblioteca
export type Sex = 0 | 1; // 0=female, 1=male
export type FacingMode = 'user' | 'environment';

// Mantemos valores mais amplos para compatibilidade com integrações existentes
export type BPMode = 'normal' | 'binary' | 'ternary';
export type BPGroup = 'primary' | 'normal' | 'prehypertension' | 'hypertension';

export interface OnErrorPayload {
  code: string | null;
  step: 'sdk_init' | 'startPreview' | 'startMeasuring' | 'onEvent' | 'modal' | null;
  reason: 'FACE_LOSS' | 'ABNORMAL_RESULT' | 'MAX_MEASURE_TIME' | 'connect_failed' | 'path_not_found' | 'reject' | null;
  message?: string | null;
}
export interface OnReadyPayload { ready: boolean; source?: string }

export interface VitalsConfig {
  client: string;
  name: string;
  theme: string;
  // CPF do usuário (obrigatório)
  user: string;
  height?: number;
  weight?: number;
  birthyear?: number;
  sex?: 0 | 1;
  bp_mode?: 'normal' | 'binary' | 'ternary';
  bp_group?: 'primary' | 'normal' | 'prehypertension' | 'hypertension';
  facing_mode?: 'user' | 'environment';
  scanOnly?: boolean;
}

export type WidgetEventName =
  | 'vh:handshake'
  | 'vh:result'
  | 'vh:dispose'
  | 'vh:onerror'
  | 'vh:onsuccess'
  | 'vh:onready'
  | 'vh:onevent';
export interface HandshakePayload { ok: boolean }
export type ResultPayload = unknown;

export interface WidgetEventMap {
  'vh:handshake': CustomEvent<HandshakePayload>;
  'vh:result': CustomEvent<ResultPayload>;
  'vh:dispose': CustomEvent<void>;
  'vh:onerror': CustomEvent<OnErrorPayload>;
  'vh:onsuccess': CustomEvent<unknown>;
  'vh:onready': CustomEvent<OnReadyPayload>;
  'vh:onevent': CustomEvent<unknown>;
}

export interface WidgetContainerProps extends VitalsConfig {}
export interface WidgetProps {}

Eventos e comunicação

API imperativa e comandos (start/stop)

  • Via ref do componente (recomendado). Observação: initScan só envia a mensagem após o iFrame emitir OnReady; antes disso, a chamada é ignorada.
import { useRef } from 'react';
import Vitals, { type WidgetControls } from '@oliv-e/vitals';

export default function Page() {
  const ref = useRef<WidgetControls>(null);
  return (
    <>
      <Vitals ref={ref} clientKey="<CLIENT_KEY>" user="14267197016" />
      <button onClick={() => ref.current?.initScan()}>Iniciar</button>
      <button onClick={() => ref.current?.stopScan()}>Parar</button>
    </>
  );
}

Também é possível via eventos de janela (legado):

  • vh:startScan → envia InitScan ao iFrame (use quando scanOnly=true para iniciar sob demanda)
  • vh:stopScan → envia StopScan ao iFrame

Exemplo:

window.dispatchEvent(new Event('vh:startScan'));

O componente React <Widget /> cria um MessageChannel com o iFrame. O iFrame envia mensagens que são propagadas para window como eventos customizados:

  • vh:handshake – canal pronto
  • vh:onready – iFrame pronto para iniciar a medição (equivalente ao evento OnReady do widget)
  • vh:result – resultados de medição contínuos (payload depende do backend)
  • vh:onsuccess – medição finalizada com sucesso (payload: resultado final)
  • vh:onerror – erros operacionais/SDK (payload com code/step/reason/message)
  • vh:onevent – espelha eventos gerais do SDK (ex.: camera_ready, connection_close)
  • vh:dispose – canal encerrado

Exemplo de listener no app host:

useEffect(() => {
  function onResult(e: CustomEvent) {
    console.log('Resultado:', e.detail);
  }
  window.addEventListener('vh:result' as any, onResult as any);
  return () => window.removeEventListener('vh:result' as any, onResult as any);
}, []);

Configuração de estilos (Tailwind/CSS)

  • Os estilos do widget são gerados em build a partir de src/widget/styles/style.css (usa @apply).
  • tailwind.config.cjs define corePlugins.preflight = false para evitar reset global.
  • A lib exporta dist/vitals-widget.css. Se o host não importar CSS de node_modules automaticamente, importe manualmente.

Dicas de troubleshooting

  • Scope not found ao publicar: crie a org oliv-e no npm ou use seu escopo de usuário.
  • CSS: o build gera dist/vitals.css para a lib; para o widget standalone, o CSS é injetado automaticamente.
  • Tipos não aparecem no pacote: rode pnpm build antes do publish (gera dist/index.d.ts).
  • SSR: os acessos a window ocorrem dentro de efeitos; evite renderizar o iFrame no server.
  • Múltiplos QueryClients: se o app host já usa React Query e o SDK criar outro, haverá caches isolados e possivelmente chamadas duplicadas. Prefira passar o QueryClient do app via prop queryClient ou envolver o SDK em um único QueryClientProvider no topo.

Licença

MIT. Veja LICENSE.