Guia do repositório do JavaScript SDK
Sobre o SDK JavaScript da Braze
O SDK JavaScript da Braze 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 SDK JavaScript da Braze é uma biblioteca independente de plataforma, projetada para funcionar em qualquer ambiente JavaScript puro. Ele não contém APIs específicas de navegador ou de Node.js, por isso pode ser usado em vários runtimes JavaScript.
Princípios de design:
- Injeção de dependências: O SDK exige implementações de armazenamento, rede e informações do dispositivo em vez de usar APIs específicas de plataforma
- Assíncrono por padrão: A maioria dos métodos da API é assíncrona e retorna Promises. Alguns métodos utilitários (por exemplo,
destroy,subscribeToInAppMessage,toggleLoggingesetLogger) são síncronos. Consulte as definições TypeScript para ver as 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 SDK JavaScript da Braze, você precisará de:
- Conta Braze: Uma conta Braze com acesso à API
- Chave de API: A chave de API do seu app, disponível 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: Disponível no dashboard da Braze, em Configurações → Chaves de API
- Endpoint do SDK: Localizado em Configurações → Autenticação do SDK → Endpoints
Integração
Chamada da API
Use a API no nível do módulo: chame initialize() uma vez e depois chame as funções exportadas. Para trocar a configuração, chame destroy() primeiro e depois initialize() de novo.
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 de inicialização exige storageManager. networkManager e pushManager são opcionais.
1. StorageManager - Interface assíncrona de armazenamento 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: quando étrue, o SDK está armazenando um identificador persistente (ID do dispositivo, ID do usuário) ou a flag de opt-out. As implementações devem manter esses valores entre reinicializações do app para que o SDK reconheça o mesmo dispositivo ou usuário. Quando éfalse, o valor é um dado de sessão ou cache (eventos, atributos etc.) e pode ficar apenas na memória. Em ambientes web, considere usar cookies para as chaves armazenadas comisId: truepara garantir a persistência entre sessões. - Deve lidar com operações assíncronas em 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(exigefetcheURLglobais) - Pode ser substituída se
fetchnão for a API preferida - Nota: o SDK já tem lógica de nova tentativa 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;
}
- Só é necessário se você for implementar notificações por push
Envio de dados
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 uma sincronização imediata.
Padrões de integração
Para assinaturas de métodos, tipos de parâmetros e de retorno e detalhes completos da API, consulte as definições de TypeScript no pacote.
Integração básica
Exemplo completo e funcional 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 de 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 enviada (o SDK já trata erros e novas tentativas):
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);
}
Troca de configuração: só existe uma sessão ativa por vez. Para trocar de configuração, 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ários 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(sem lançar exceção) quando o SDK não está inicializado initialize()retornafalsese o SDK já estiver inicializado ou se a validação falharchangeUser()não faz nada e a promise é resolvida se o SDK não estiver inicializado- Sempre verifique se há valores de retorno
undefinedantes de usá-los
Falhas de validação:
- Chave de API ou URL base inválida:
initialize()retornafalsee registra um erro - Nomes ou chaves de eventos inválidos: devem ter no máximo 255 caracteres, não podem começar com
$e só podem conter caracteres alfanuméricos e pontuação - Valores de atributos inválidos: strings com no máximo 255 caracteres, sem quebras de linha, tabulações ou aspas duplas, e não podem começar com
$ - Códigos de moeda inválidos: códigos não suportados geram um alerta e nenhuma ação é realizada
- Quantidade de compra inválida: deve ser de 1 a 100; caso contrário, é ignorada
Erros de rede:
- O método
postRequest()do NetworkManager deve tratar erros e rejeitar as promises adequadamente - O controlador de envio de dados repete automaticamente as requisições que falharam
- Use o retorno de chamada de
requestImmediateDataFlush()para detectar falhas no envio
Erros de armazenamento:
- Os métodos do StorageManager devem tratar erros sem interromper a execução
- Se o armazenamento falhar, o SDK pode não funcionar corretamente
- A flag
isIddetermina a persistência: os IDs persistem entre sessões e os objetos ficam restritos à sessão
Casos extremos de identificação de usuários:
- Não é possível voltar a um usuário anônimo depois da 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 na primeira identificação
- O histórico é mesclado se o usuário já existir em outro dispositivo
Gerenciamento de sessões:
- As sessões expiram após 30 minutos de inatividade (configurável)
openSession()retornatruepara uma nova sessão efalsese a sessão for retomada- É necessário chamar
openSession()depois dechangeUser()ousetIdentifierToken()
Gerenciamento de inscrições:
- Os retornos de chamada das inscrições são chamados de forma síncrona quando os eventos ocorrem
- Remova as inscrições para evitar vazamentos de memória
removeAllSubscriptions()remove todas as inscrições de uma só vez
Envio de dados:
- Envio automático a cada 10 segundos (configurável, mínimo de 3 segundos)
- O envio pode falhar sem aviso; use o retorno de chamada de
requestImmediateDataFlush() - Os dados ficam em fila se a rede estiver indisponível e são enviados quando a conexão é restabelecida
Notas importantes de implementação
-
A maioria dos métodos é assíncrona: os 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 de TypeScript ou a tabela de referência rápida para mais detalhes. -
Os métodos podem retornar
undefined: se o SDK não estiver inicializado, a maioria dos métodos retornaundefinedem vez de lançar uma exceção. Verifique se háundefinedantes de usar os valores de retorno. -
Os 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). - As chaves de armazenamento usam a flag
isId: o parâmetroisIdnos métodos do StorageManager diferencia:- Armazenamento de IDs: identificadores persistentes (ID do dispositivo, ID do usuário) que devem persistir entre sessões
- Armazenamento de objetos: dados restritos à sessão que podem ser apagados
-
Tags de metadados do SDK: o array
sdkMetadataidentifica a plataforma ou o wrapper que usa o SDK (por exemplo,['npm']ou[BrazeSdkMetadata.NPM]). As tags válidas são definidas pelo enumBrazeSdkMetadata(comonpm,cdn,manu,shp,gg,kep), e o SDK adiciona'wjs'automaticamente para indicar o SDK de JavaScript. -
NetworkManager padrão: se
networkManagernão for informado, o SDK usa uma implementação padrão que exige as APIs globaisfetcheURL. Forneça uma implementação personalizada se elas não estiverem disponíveis. -
O PushManager é opcional: só implemente o
PushManagerse precisar da funcionalidade de notificações por push. Caso contrário, ele pode ser omitido. -
Destruição e limpeza: chame
destroy()quando precisar encerrar o SDK. Só pode existir uma sessão ativa por vez, então é necessário chamardestroy()antes de chamarinitialize()de novo. Isso interrompe os timers, envia os dados e libera os recursos. -
Envio de dados: os dados são enviados automaticamente a cada 10 segundos (configurável). Use
requestImmediateDataFlush()para uma sincronização imediata. -
Gerenciamento de sessões: sempre chame
openSession()depois dechangeUser()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 ter a melhor experiência e a verificação de tipos.
- Regras de validação: nomes de eventos, chaves de atributos e chaves de propriedades passam por uma validação rigorosa (no máximo 255 caracteres, não podem começar com
$e só podem conter caracteres alfanuméricos e 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 durante o desenvolvimento, mas lembre-se de remover essa opção ou fornecer um registrador alternativo antes de lançar sua página em produção.
Contato
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.