Guia do repositório do JavaScript SDK
Sobre o Braze JavaScript SDK
O Braze JavaScript SDK ajuda você a integrar recursos de envio de mensagens, análise de dados e engajamento de usuários da Braze ao seu aplicativo.
Para começar, consulte os seguintes recursos:
Visão geral da arquitetura
O Braze JavaScript SDK é uma biblioteca independente de plataforma projetada para funcionar em qualquer ambiente JavaScript puro. Ele não contém APIs específicas de navegador ou Node.js, o que o torna adequado para uso em diversos runtimes JavaScript.
Princípios de design principais:
- Injeção de dependência: O SDK requer implementações para armazenamento, rede e informações do dispositivo em vez de usar APIs específicas de plataforma
- Async-First: A maioria dos métodos da API é assíncrona e retorna Promises; alguns métodos utilitários (por exemplo,
destroy,subscribeToInAppMessage,toggleLogging,setLogger) são síncronos. Consulte as definições TypeScript para assinaturas exatas. - Sessão singleton: A API em nível de módulo (
initialize/destroy) gerencia uma sessão ativa do SDK por vez. - Gerenciamento interno de dependências: Cria e gerencia dependências internas (UserManager, SessionManager, DataFlushController, etc.) a partir das implementações fornecidas
Início rápido
Instale o SDK com npm:
npm install @braze/javascript-sdk
Ou com yarn:
yarn add @braze/javascript-sdk
Com a API em nível de módulo:
import { initialize, openSession, changeUser } from '@braze/javascript-sdk';
await initialize({
apiKey,
baseUrl,
options,
sdkMetadata,
deviceInfo,
storageManager,
networkManager,
});
await changeUser(userId);
await openSession();
Pré-requisitos
Antes de integrar o Braze JavaScript SDK, você precisará de:
- Conta Braze: Uma conta Braze com acesso à API
- Chave de API: A chave de API do seu app no dashboard da Braze
- Endpoint do SDK: A URL do endpoint do SDK da Braze (por exemplo,
sdk.iad-01.braze.com)
Obtendo suas credenciais
- Chave de API: Encontrada no dashboard da Braze em Configurações → Chaves de API
- Endpoint do SDK: Localizado em Configurações → Autenticação do SDK → Endpoints
Integração
Chamando a API
Use a API em nível de módulo: chame initialize() uma vez e depois chame as funções exportadas. Para trocar a configuração, chame destroy() primeiro e depois initialize() novamente.
import { initialize, logPurchase, changeUser } from '@braze/javascript-sdk';
await initialize({ apiKey, baseUrl, options, ... });
await changeUser('user-123');
await logPurchase('sku-1', 9.99, 'USD', 1);
Conceitos principais
Implementações obrigatórias
O objeto de configuração do initialize requer storageManager. networkManager e pushManager são opcionais.
1. StorageManager — Interface de armazenamento assíncrono chave-valor
interface StorageManager {
store(key: string, value: string, isId?: boolean): Promise<void>;
remove(key: string, isId?: boolean): Promise<void>;
retrieve(key: string, isId?: boolean): Promise<string | null>;
clearData(storageKeys: string[]): Promise<void>;
}
- O parâmetro
isIdindica armazenamento persistente de ID: quandotrue, o SDK está armazenando um identificador persistente (ID do dispositivo, ID do usuário) ou a flag de descadastramento. As implementações devem persistir esses dados entre reinicializações do app para que o SDK possa reconhecer o mesmo dispositivo/usuário. Quandofalse, o valor é dado de sessão/cache (eventos, atributos, etc.) e pode ficar apenas em memória. Para ambientes web, considere usar cookies para chaves armazenadas comisId: truepara garantir persistência entre sessões. - Deve lidar com operações assíncronas para todas as operações de armazenamento
2. NetworkManager (opcional) — Interface de requisição HTTP POST
interface NetworkManager {
postRequest(
url: string,
data: Partial<Record<string, unknown>>,
headers?: globalThis.Headers | [string, string][]
): Promise<Partial<Record<string, unknown>>>;
}
- A implementação padrão usa a API
fetch(requerfetcheURLglobais) - Pode ser substituída se
fetchnão for a API preferida - Nota: O SDK já possui lógica de retry e limite de frequência integrada
3. PushManager (opcional) — Interface de notificação por push
interface PushManager {
isPushBlocked(): boolean | undefined;
isPushPermissionGranted(): boolean | undefined;
isPushSupported(): boolean | undefined;
registerPush(
successCallback?: (endpoint: string, publicKey: string, userAuth: string) => void,
deniedCallback?: (temporaryDenial: boolean) => void,
): void;
unregisterPush(successCallback?: () => void, errorCallback?: () => void): void;
}
- Necessário apenas se você estiver implementando notificações por push
Envio de dados (Data Flushing)
O SDK envia automaticamente os dados em cache para os servidores da Braze a cada 10 segundos (configurável via flushIntervalInSeconds). Use requestImmediateDataFlush() para forçar a sincronização imediata.
Padrões de integração
Para assinaturas de métodos, tipos de parâmetros e retorno, e detalhes completos da API, consulte as definições TypeScript no pacote.
Integração básica
Exemplo funcional completo com tratamento de erros:
import {
initialize,
openSession,
changeUser,
logCustomEvent,
type StorageManager,
type DeviceInfo
} from '@braze/javascript-sdk';
// Implement required StorageManager interface (in-memory only; does not persist data).
// This example treats all keys equally and ignores the isId parameter
// See "Complete StorageManager implementation with IndexedDB" below for an
// example where we properly handle isId
class InMemoryStorageManager implements StorageManager {
private storage = new Map<string, string>();
async store(key: string, value: string, isId?: boolean): Promise<void> {
this.storage.set(key, value);
}
async retrieve(key: string, isId?: boolean): Promise<string | null> {
return this.storage.get(key) ?? null;
}
async remove(key: string, isId?: boolean): Promise<void> {
this.storage.delete(key);
}
async clearData(storageKeys: string[]): Promise<void> {
for (const key of storageKeys) {
this.storage.delete(key);
}
}
}
const storageManager: StorageManager = new InMemoryStorageManager();
// Provide device information (use your platform's APIs for non-browser)
const deviceInfo: DeviceInfo = {
os: 'my-runtime-os',
language: 'en',
timezone: 'UTC',
browser: 'Chrome', // Optional
browserVersion: '120', // Optional
userAgent: "some-user-agent" // Optional
};
// Browser-only example (uncomment and adapt if you are running in a web browser)
// const deviceInfo: DeviceInfo = {
// os: navigator.platform || 'Unknown',
// language: navigator.language || 'en',
// timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
// browser: 'Chrome', // Optional
// browserVersion: '120', // Optional
// userAgent: navigator.userAgent // Optional
// };
// Initialize SDK
try {
const initialized = await initialize({
apiKey: 'YOUR-API-KEY-HERE',
baseUrl: 'sdk.iad-01.braze.com', // Your Braze SDK endpoint
options: {
sdkVersion: '1.0.0',
enableLogging: true, // Remove in production
sessionTimeoutInSeconds: 1800, // 30 minutes
flushIntervalInSeconds: 10
},
sdkMetadata: ['npm'], // Identify your platform
deviceInfo,
storageManager
});
if (!initialized) {
console.error('Failed to initialize Braze SDK');
return;
}
// Identify user (wait for promise to resolve)
await changeUser('user-123');
// Open session (must be after changeUser)
const isNewSession = await openSession();
console.log('Session opened:', isNewSession ? 'new' : 'resumed');
// Log events
await logCustomEvent('app_opened', {
source: 'homepage',
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Braze SDK error:', error);
}
Implementação de armazenamento personalizado
Implementação completa do StorageManager com IndexedDB para IDs persistentes:
import type { StorageManager } from '@braze/javascript-sdk';
class IndexedDBStorageManager implements StorageManager {
private dbName = 'braze-storage';
private storeName = 'braze-ids';
private memoryCache = new Map<string, string>();
private db: IDBDatabase | null = null;
private dbInitPromise: Promise<void> | null = null;
private async initDB(): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName);
}
};
});
}
private async ensureDB(): Promise<void> {
if (this.dbInitPromise !== null) {
return this.dbInitPromise;
}
this.dbInitPromise = this.initDB();
return this.dbInitPromise;
}
async store(key: string, value: string, isId?: boolean): Promise<void> {
await this.ensureDB();
this.memoryCache.set(key, value);
if (isId && this.db) {
try {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
await new Promise<void>((resolve, reject) => {
const request = store.put(value, key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
} catch (error) {
console.error('Failed to store ID in IndexedDB:', error);
}
}
}
async retrieve(key: string, isId?: boolean): Promise<string | null> {
await this.ensureDB();
if (this.memoryCache.has(key)) {
return this.memoryCache.get(key) || null;
}
if (isId && this.db) {
try {
const transaction = this.db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
return new Promise<string | null>((resolve, reject) => {
const request = store.get(key);
request.onsuccess = () => {
const value = request.result;
if (value) {
this.memoryCache.set(key, value);
}
resolve(value || null);
};
request.onerror = () => reject(request.error);
});
} catch (error) {
console.error('Failed to retrieve ID from IndexedDB:', error);
return null;
}
}
return null;
}
async remove(key: string, isId?: boolean): Promise<void> {
await this.ensureDB();
this.memoryCache.delete(key);
if (isId && this.db) {
try {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
await new Promise<void>((resolve, reject) => {
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
} catch (error) {
console.error('Failed to remove ID from IndexedDB:', error);
}
}
}
async clearData(storageKeys: string[]): Promise<void> {
await this.ensureDB();
for (const key of storageKeys) {
this.memoryCache.delete(key);
}
if (this.db) {
try {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
await Promise.all(
storageKeys.map(
(key) =>
new Promise<void>((resolve, reject) => {
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
})
)
);
} catch (error) {
console.error('Failed to clear data from IndexedDB:', error);
}
}
}
}
const storageManager = new IndexedDBStorageManager();
Implementação de rede personalizada
NetworkManager que registra cada requisição de saída (o SDK já lida com erros e retries):
import type { NetworkManager } from '@braze/javascript-sdk';
function logRequest(url: string, data: Partial<Record<string, unknown>>): void {
// Send to your analytics, monitoring, or logging backend
console.log('Braze SDK request', { url, data });
}
class LoggingNetworkManager implements NetworkManager {
async postRequest(
url: string,
data: Partial<Record<string, unknown>>,
headers?: Headers | [string, string][]
): Promise<Partial<Record<string, unknown>>> {
logRequest(url, data);
const requestHeaders = new Headers(headers);
requestHeaders.set('Content-Type', 'application/json');
const response = await fetch(url, {
method: 'POST',
headers: requestHeaders,
body: JSON.stringify(data),
});
const result = await response.json();
return result as Partial<Record<string, unknown>>;
}
}
const networkManager = new LoggingNetworkManager();
Tratamento de erros
Padrões completos de tratamento de erros:
import {
getUserId,
logCustomEvent,
initialize,
} from '@braze/javascript-sdk';
// Pattern 1: Check for undefined (SDK not initialized)
async function getDevice() {
const deviceId = await getDeviceId();
if (deviceId === undefined) {
console.warn('SDK not initialized');
return null;
}
return deviceId;
}
// Pattern 2: Try-catch for methods that may throw
async function logEventSafely() {
try {
const success = await logCustomEvent('button_clicked', { button: 'submit' });
if (success === undefined) {
console.warn('SDK not initialized, event not logged');
} else if (success) {
console.log('Event logged successfully');
} else {
console.warn('Event failed to enqueue');
}
} catch (error) {
console.error('Error logging event:', error);
// Handle error (e.g., retry, queue for later)
}
}
// Pattern 3: Handle null vs undefined distinction
async function checkUser() {
const userId = await getUserId();
if (userId === undefined) {
// SDK not initialized
console.warn('SDK not initialized');
} else if (userId === null) {
// Current user is anonymous
console.log('Current user is anonymous');
} else {
// User is identified
console.log(`User ID is ${userId}`);
}
}
// Pattern 4: Handle initialization errors
async function initializeSafely() {
try {
const initialized = await initialize({
apiKey: 'YOUR-API-KEY',
baseUrl: 'sdk.iad-01.braze.com',
options: { sdkVersion: '1.0.0' },
sdkMetadata: ['npm'],
deviceInfo: { os: 'iOS', language: 'en', timezone: 'UTC' },
storageManager: myStorageManager
});
if (!initialized) {
console.error('Failed to initialize SDK');
// Check if already initialized, disabled, or validation failed
return false;
}
return true;
} catch (error) {
console.error('Initialization error:', error);
return false;
}
}
Gerenciamento de inscrições
import {
ControlMessage,
logInAppMessageImpression,
removeSubscription,
subscribeToInAppMessage,
} from '@braze/javascript-sdk';
const displayMessage = (inAppMessage) => {
// Add custom code to display in-app messages
}
// Subscribe to in-app messages
const subscriptionId = subscribeToInAppMessage((inAppMessage) => {
if (inAppMessage instanceof ControlMessage) {
return; // Skip control messages
}
displayMessage(inAppMessage);
logInAppMessageImpression(inAppMessage);
});
// Later, remove subscription if it was successfully created
if (subscriptionId) {
removeSubscription(subscriptionId);
}
Trocando a configuração: Apenas uma sessão ativa existe por vez. Para trocar configurações, chame destroy() e depois initialize():
import { destroy, initialize } from '@braze/javascript-sdk';
destroy();
await initialize({ /* new config */ });
Casos de uso comuns
Identificação de usuário e rastreamento de atributos
import {
changeUser,
setCustomUserAttribute,
setUserEmail,
setUserFirstName,
setUserLastName,
} from '@braze/javascript-sdk';
// Identify user
await changeUser('user-123');
// Set standard attributes
await setUserEmail('[email protected]');
await setUserFirstName('John');
await setUserLastName('Doe');
// Set custom attributes
await setCustomUserAttribute('subscription_tier', 'premium');
await setCustomUserAttribute('last_login', new Date());
await setCustomUserAttribute('tags', ['vip', 'early-adopter']);
Registro de eventos e análise de dados
import {
logCustomEvent,
logPurchase,
requestImmediateDataFlush,
} from '@braze/javascript-sdk';
await logCustomEvent('product_viewed', {
product_id: '123',
category: 'electronics',
price: 99.99
});
await logPurchase('product-123', 99.99, 'USD', 1, {
category: 'electronics'
});
// Flushing these events to the server will happen periodically,
// however you can manually trigger a flush if necessary
requestImmediateDataFlush((success) => {
console.log('Data flushed:', success);
});
Tratamento de mensagens no app
import {
ControlMessage,
logInAppMessageImpression,
subscribeToInAppMessage,
} from '@braze/javascript-sdk';
const displayInAppMessage = async (inAppMessage) => {
// Add custom code to display in-app messages
}
subscribeToInAppMessage(async (inAppMessage) => {
if (inAppMessage instanceof ControlMessage) {
return;
}
await displayInAppMessage(inAppMessage);
await logInAppMessageImpression(inAppMessage);
});
Tratamento de erros e casos extremos
Condições de erro comuns
SDK não inicializado:
- A maioria dos métodos retorna
undefined(não lança exceção) quando o SDK não está inicializado initialize()retornafalsese já estiver inicializado ou se a validação falharchangeUser()é um no-op e a promise é resolvida se o SDK não estiver inicializado- Sempre verifique se os valores de retorno são
undefinedantes de usá-los
Falhas de validação:
- Chave de API ou URL base inválida:
initialize()retornafalse, registra erro - Nomes de eventos/chaves inválidos: Devem ter no máximo 255 caracteres, não podem começar com
$, apenas alfanuméricos + pontuação - Valores de atributos inválidos: Strings com no máximo 255 caracteres, sem quebras de linha/tabulações/aspas duplas, não podem começar com
$ - Códigos de moeda inválidos: Códigos não suportados resultam em aviso, nenhuma ação é tomada
- Quantidade de compra inválida: Deve ser de 1 a 100, caso contrário é ignorada
Erros de rede:
- O
postRequest()do NetworkManager deve tratar erros e rejeitar promises adequadamente - O controlador de envio de dados faz retry automaticamente em requisições com falha
- Use o retorno de chamada de
requestImmediateDataFlush()para detectar falhas de envio
Erros de armazenamento:
- Os métodos do StorageManager devem tratar erros de forma adequada
- Se o armazenamento falhar, o SDK pode não funcionar corretamente
- A flag
isIddetermina a persistência: IDs persistem entre sessões, objetos têm escopo de sessão
Casos extremos de identificação de usuário:
- Não é possível reverter para usuário anônimo após a identificação
- A troca de usuário encerra a sessão atual e inicia uma nova sessão
- O histórico do usuário anônimo é preservado ao identificar pela primeira vez
- O histórico é mesclado se o usuário existir em outro dispositivo
Gerenciamento de sessão:
- As sessões expiram após 30 minutos de inatividade (configurável)
openSession()retornatruepara nova sessão,falsese retomada- Deve chamar
openSession()apóschangeUser()ousetIdentifierToken()
Gerenciamento de inscrições:
- Os retornos de chamada de inscrição são chamados de forma síncrona quando os eventos ocorrem
- Remova inscrições para evitar vazamentos de memória
removeAllSubscriptions()limpa todas as inscrições de uma vez
Envio de dados:
- Envio automático a cada 10 segundos (configurável, mínimo: 3 segundos)
- O envio pode falhar silenciosamente — use o retorno de chamada de
requestImmediateDataFlush() - Os dados são enfileirados se a rede estiver indisponível e enviados quando a rede for restaurada
Notas importantes de implementação
-
A maioria dos métodos é assíncrona: Métodos assíncronos do SDK retornam uma Promise (use
awaitou.then()). Alguns métodos de configuração e utilitários (por exemplo,destroy,toggleLogging,setLogger) são síncronos; consulte as definições TypeScript ou a tabela de referência rápida para detalhes. -
Métodos podem retornar
undefined: Se o SDK não estiver inicializado, a maioria dos métodos retornaundefinedem vez de lançar exceção. Verifique se o valor éundefinedantes de usar os valores de retorno. -
Métodos podem retornar
null: Alguns métodos retornamnullpara indicar “não encontrado” (por exemplo,getUserId()retornanullse o usuário for anônimo). Isso é diferente deundefined(SDK não inicializado). - Chaves de armazenamento usam a flag
isId: O parâmetroisIdnos métodos do StorageManager distingue entre:- Armazenamento de ID: Identificadores persistentes (ID do dispositivo, ID do usuário) que devem persistir entre sessões
- Armazenamento de objetos: Dados com escopo de sessão que podem ser limpos
-
Tags de metadados do SDK: O array
sdkMetadataidentifica a plataforma/wrapper que está usando o SDK (por exemplo,['npm']ou[BrazeSdkMetadata.NPM]). Tags válidas são definidas pelo enumBrazeSdkMetadata(comonpm,cdn,manu,shp,gg,kep), e o SDK adiciona automaticamente'wjs'para indicar JavaScript SDK. -
NetworkManager padrão: Se
networkManagernão for fornecido, o SDK usa uma implementação padrão que requer as APIs globaisfetcheURL. Forneça uma implementação personalizada se essas APIs não estiverem disponíveis. -
PushManager é opcional: Implemente o
PushManagerapenas se você precisar de funcionalidade de notificação por push. Caso contrário, pode ser omitido. -
Destroy e limpeza: Chame
destroy()quando precisar encerrar o SDK. Apenas uma sessão ativa pode existir por vez; você deve chamardestroy()antes de chamarinitialize()novamente. Isso interrompe timers, envia dados pendentes e libera recursos. -
Envio de dados: Os dados são enviados automaticamente a cada 10 segundos (configurável). Use
requestImmediateDataFlush()para sincronização imediata. -
Gerenciamento de sessão: Sempre chame
openSession()apóschangeUser()ousetIdentifierToken()para evitar a criação de usuários anônimos duplicados. -
Segurança de tipos: O SDK é escrito em TypeScript com definições de tipos completas. Use TypeScript para a melhor experiência e verificação de tipos.
- Regras de validação: Nomes de eventos, chaves de atributos e chaves de propriedades têm validação rigorosa (máximo de 255 caracteres, não podem começar com
$, apenas alfanuméricos + pontuação). Valores inválidos podem ser ignorados ou causar erros.
Depuração / Solução de problemas
Passe a opção enableLogging: true nas opções de inicialização. Isso é útil para desenvolvimento, mas certifique-se de remover essa opção ou fornecer um logger alternativo antes de publicar sua página em produção.
Fale com a gente
Se você tiver dúvidas, entre em contato com [email protected].
Para detalhes do repositório e projetos de exemplo, consulte https://github.com/braze-inc/braze-javascript-sdk.