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:
1
npm install @braze/javascript-sdk
Ou com yarn:
1
yarn add @braze/javascript-sdk
Com a API em nível de módulo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 na Braze: Uma conta na 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.
1
2
3
4
5
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
1
2
3
4
5
6
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 opt-out. 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
1
2
3
4
5
6
7
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 taxa integrada
3. PushManager (opcional) - Interface de notificação por push
1
2
3
4
5
6
7
8
9
10
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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():
1
2
3
4
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.