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

@bfocus/react-native-widget

v0.1.0

Published

Widget de suporte do bFocus (chamados, chat ao vivo e release notes) para React Native e Expo: botão com badge, pílula de versão, banner de ciência e push.

Readme

@bfocus/react-native-widget

English summary. Support widget of bFocus for React Native and Expo: tickets, live chat and release notes, the same screens as the web widget, rendered in a react-native-webview. Pure JS (no native code of its own), Expo compatible. Includes a floating button with badge, a version pill, a non-dismissible acknowledgement banner and FCM push handling (you bring the token; the package does not depend on Firebase). Install with npm i @bfocus/react-native-widget react-native-webview @react-native-async-storage/async-storage, wrap your app in <BFocusProvider config={…}> and add <BFocusLauncher />. Only the public key bf_pk_… and the userHash (HMAC computed on your server) go into the app. MIT license.

Widget de suporte do bFocus (chamados, chat ao vivo e release notes) para apps React Native e Expo. O pacote mostra o mesmo embed.html / release-notes.html do widget web numa WebView e cuida só do que é nativo: botão com badge, pílula de versão, banner de ciência, links, arquivos, push e a consulta periódica do estado.

  • JS puro, sem código nativo próprio: funciona no Expo (dev client ou Expo Go com react-native-webview).
  • Protocolo: Host Protocol v1. O cabeçalho X-bFocus-Client vai como react-native/0.1.0.

Instalação

npm i @bfocus/react-native-widget react-native-webview @react-native-async-storage/async-storage
# Expo
npx expo install @bfocus/react-native-widget react-native-webview @react-native-async-storage/async-storage

Dependências

| Pacote | Tipo | Para quê | |---|---|---| | react ≥ 18 | peer | useSyncExternalStore | | react-native ≥ 0.71 | peer | UI, Linking, AppState, BackHandler | | react-native-webview ≥ 13 | peer | a tela do widget | | @react-native-async-storage/async-storage | peer opcional | guarda o last_seen do "•" e o token de push. Sem ele, use storage na config; senão fica só em memória (o "•" não persiste entre aberturas do app) |

O pacote não tem outras dependências.

Antes de começar

  1. No bFocus, em Integrações → Widget, copie a chave pública bf_pk_….
  2. Em Integrações → Apps nativos, cadastre o id do app (app://com.empresa.app). É o bundle id no iOS e o package name no Android.
  3. Se o tenant exige sessão verificada, o userHash é obrigatório (veja abaixo).

appId

O React Native não expõe o id do app sem módulo nativo, então appId é obrigatório. Pegue-o assim:

import * as Application from 'expo-application' // Expo
const appId = Application.applicationId!         // "com.empresa.app"

import DeviceInfo from 'react-native-device-info' // RN puro
const appId = DeviceInfo.getBundleId()

Ou escreva o valor fixo. A origem enviada é app://<appId em minúsculas>.

Uso

import { BFocus, BFocusProvider, BFocusLauncher, BFocusReleaseBadge } from '@bfocus/react-native-widget'

const config = {
  publishableKey: 'bf_pk_live_…',
  appId: 'com.empresa.app',
  user: { externalId: 'USR-1', name: 'Ana Souza', email: '[email protected]' },
  customer: { externalId: 'ACME-1', name: 'Acme Ltda' },
  userHashProvider: async () => (await api.post('/bfocus/user-hash')).userHash,
  product: 'erp',
  onError: (code, detail) => console.warn(code, detail),
}

export default function App() {
  return (
    // Na raiz do app: o widget abre numa camada em tela cheia por cima de tudo.
    <BFocusProvider config={config}>
      <Navigation />
      <BFocusLauncher />            {/* botão flutuante com badge */}
    </BFocusProvider>
  )
}

// Em qualquer tela:
<BFocusReleaseBadge />             // pílula "v4.2.0" com ponto de novidade; toca → histórico
BFocus.open()                      // lista
BFocus.open('new')                 // novo chamado
BFocus.open({ ticketId: 'abc' })   // um chamado
BFocus.open('chat')                // chat ao vivo

Prefere o próprio botão? Use BFocus.open() e o hook useBFocusBadge() (ou o callback onBadgeChanged). O <BFocusProvider> continua necessário para mostrar a WebView.

Configuração

| Campo | Obrigatório | Padrão | Descrição | |---|---|---|---| | publishableKey | sim | — | bf_pk_… | | appId | sim | — | bundle id / package name | | user | sim | — | {externalId, name?, email?, phone?} | | customer | sim | — | {externalId, name?, document?, email?, phone?, website?} | | userHash | não | — | HMAC calculado no servidor | | userHashProvider | não | — | função assíncrona que devolve o userHash. É chamada no init e de novo, uma vez, após WIDGET_USER_HASH_INVALID | | product | não | — | slug do produto | | audience | não | — | external | internal | both | | locale | não | idioma do aparelho (Intl) | pt_BR | en | es (aceita pt-BR, es-AR…) | | showReleaseNotes | não | true | false esconde o splash de novidades dentro dos chamados | | autoShowReleaseBanner | não | true | abre sozinho o banner de ciência. Com false, use BFocus.openReleaseNotesBanner() | | apiBaseUrl / embedBaseUrl | não | produção | homologação e testes (http:// só em 127.0.0.1/localhost) | | pollIntervalSeconds | não | 60 | intervalo do launcher-state (mínimo 15) | | storage | não | AsyncStorage | {getItem, setItem, removeItem} assíncrono | | fetch | não | fetch global | para proxies e testes | | onBadgeChanged(label) | não | — | '' | '•' | 'N' | '99+' | | onReleaseNotesChanged({label, dot, bannerIds}) | não | — | a pílula mudou | | onError(code, detail?) | não | — | veja Erros | | onOpen / onClose | não | — | o widget abriu / fechou |

Os eventos também existem como BFocus.addListener('badgeChanged' | 'releaseNotesChanged' | 'error' | 'open' | 'close', cb), que devolve { remove() }.

Métodos

| Método | Efeito | |---|---| | BFocus.init(config) | valida (lança BFocusConfigError na hora) e inicia. O <BFocusProvider config> chama sozinho | | open(target?) | list (padrão), new, chat, {ticketId}, {conversationId} | | close() | fecha, mantendo a WebView para reabrir rápido | | openReleaseNotesHistory() | histórico de versões | | openReleaseNotesBanner() | banner de ciência com a fila atual | | openInBrowser(target?) | modo navegador: abre o widget em página inteira no navegador do sistema (host=browser) | | refresh() | consulta o launcher-state agora | | logout() | para a consulta, cancela o push, limpa o estado local e o localStorage do embed | | registerPushToken(token, platform?) | registra o token FCM (platform padrão: Platform.OS, só android/ios) | | handlePush(data) → boolean | true se a notificação é do bFocus; nesse caso abre o widget no item certo |

<BFocusProvider>

| Prop | Padrão | | |---|---|---| | config | — | chama BFocus.init (e de novo quando muda). Troca de usuário recria a WebView | | presentation | overlay | overlay: camada em tela cheia com a WebView mantida entre aberturas. modal: Modal do RN, que fica acima de modais nativos (react-navigation presentation: 'modal'), mas recarrega a cada abertura | | webViewProps | — | props extras das WebViews (ex.: webviewDebuggingEnabled) |

O botão voltar do Android fecha o widget. O banner de ciência abre em Modal de tela cheia que não fecha por gesto nem pelo voltar; o usuário tem de confirmar no próprio banner.

userHash (identidade verificada)

O userHash é hex(HMAC-SHA256(segredo, "v1:" + user.externalId + ":" + customer.externalId)), calculado no seu servidor com o segredo bf_whs_…. Use os SDKs do bFocus:

// Node: @bfocus/sdk
import { signWidgetIdentity } from '@bfocus/sdk'
const userHash = signWidgetIdentity(process.env.BFOCUS_WIDGET_SECRET!, user.id, user.companyId)
# Python: bfocus
from bfocus import sign_widget_identity
user_hash = sign_widget_identity(os.environ["BFOCUS_WIDGET_SECRET"], user.id, user.company_id)

Exponha isso numa rota autenticada do seu backend e devolva o hash ao app pelo userHashProvider.

Push (FCM)

O pacote não depende do Firebase: o seu app já tem o FCM e entrega o token e o data. Cadastre a credencial FCM do app em Integrações → Apps nativos.

import messaging from '@react-native-firebase/messaging'

const token = await messaging().getToken()
await BFocus.registerPushToken(token)            // espera a primeira consulta do widget
messaging().onTokenRefresh((t) => BFocus.registerPushToken(t))

// Toque na notificação (app em segundo plano ou fechado):
messaging().onNotificationOpenedApp((m) => BFocus.handlePush(m.data))
const initial = await messaging().getInitialNotification()
if (initial) BFocus.handlePush(initial.data)     // funciona mesmo antes do init

handlePush aceita o mapa data ou a mensagem inteira ({data: …}). Com expo-notifications, passe response.notification.request.content.data. Formato do data do bFocus: bfocus: "1", type (ticket.reply | chat.message | ticket.status), ticket_id e, no chat, conversation_id. O token é registrado de novo se o usuário mudar, e o logout() cancela o registro.

Erros

onError(code, detail) recebe:

| Código | Quando | |---|---| | WIDGET_USER_HASH_INVALID | userHash recusado. Com userHashProvider, o pacote pede outro hash uma vez e repete | | WIDGET_VERIFIED_SESSION_REQUIRED | o tenant exige userHash e ele não veio | | WIDGET_CONFIG_FAILED | o embed não carregou a configuração (chave, origem não cadastrada, widget desligado) | | outros 4xx do launcher-state | o código vem da API (ex.: origem app:// não cadastrada) | | WIDGET_CONFIG_INVALID | só pelo <BFocusProvider>: a config não passou na validação |

Erros de rede e 5xx são silenciosos: o próximo ciclo tenta de novo. Sem conexão, a tela do widget mostra "Sem conexão" com "Tentar novamente".

Comportamento

  • Consulta: POST /api/v1/widget/launcher-state logo após o init e a cada 60 s, só com o app em primeiro plano e o widget fechado. Ao fechar, consulta na hora. A primeira chamada cria o usuário no bFocus: nada mais sai antes dela (nem o push, nem a WebView).
  • "•" do botão: o last_seen fica gravado por chave + usuário + cliente. Na primeira vez só grava; depois acende quando há evento novo. Com o widget aberto, o badge segue os não lidos.
  • Links e arquivos: a WebView só navega na origem do embed. Links externos e downloads (URL pré-assinada) abrem no navegador do sistema. O <input type=file> usa o seletor nativo do react-native-webview; para a câmera no Android, declare a permissão CAMERA no app.
  • Segurança da ponte: mensagens só da página na origem de embedBaseUrl e só dos tipos do protocolo. Tipo desconhecido é ignorado.

Segurança

  • No app vão só a chave pública bf_pk_… e o userHash. Nunca bf_whs_… (segredo do widget), bf_live_… ou bf_sk_…: o init recusa essas chaves.
  • A origem app://… é só um rótulo; a proteção real é o userHash com require_verified_session ligado no tenant.

Testes e desenvolvimento

npm install
npm run typecheck && npm run build && npm test

Os testes de integração sobem widgets-native/conformance/mock-server.mjs (monorepo) e são pulados fora dele. Defina BFOCUS_MOCK_SERVER=/caminho/mock-server.mjs para rodá-los.

Se o seu app testa com Jest e o preset do React Native, inclua o pacote no transformIgnorePatterns (o lib/ é ESM), por exemplo node_modules/(?!(@react-native|react-native|@bfocus/react-native-widget)/).

Licença

MIT — Copyright (c) 2026 Berni Software.