Guía del repositorio del JavaScript SDK
Acerca del SDK de JavaScript de Braze
El SDK de JavaScript de Braze te ayuda a integrar en tu aplicación las capacidades de mensajería, análisis y participación de usuarios de Braze.
Para empezar, consulta los siguientes recursos:
Resumen de la arquitectura
El SDK de JavaScript de Braze es una biblioteca independiente de la plataforma diseñada para funcionar en cualquier entorno de JavaScript puro. No contiene API específicas del navegador ni de Node.js, lo que la hace adecuada para usarla en distintos entornos de ejecución de JavaScript.
Principios clave de diseño:
- Inyección de dependencias: El SDK requiere implementaciones para el almacenamiento, las redes y la información del dispositivo en lugar de usar API específicas de la plataforma
- Asincronía primero: La mayoría de los métodos de la API son asíncronos y devuelven promesas (Promises); algunos métodos de utilidad (por ejemplo,
destroy,subscribeToInAppMessage,toggleLogging,setLogger) son síncronos. Consulta las definiciones de TypeScript para conocer las firmas exactas. - Sesión única: La API a nivel de módulo (
initialize/destroy) gestiona una sola sesión activa del SDK a la vez. - Gestión interna de dependencias: Crea y gestiona dependencias internas (UserManager, SessionManager, DataFlushController, etc.) a partir de las implementaciones proporcionadas
Inicio rápido
Instala el SDK con npm:
npm install @braze/javascript-sdk
O con yarn:
yarn add @braze/javascript-sdk
Con la API a nivel 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();
Requisitos previos
Antes de integrar el SDK de JavaScript de Braze, necesitarás:
- Cuenta de Braze: Una cuenta de Braze con acceso a la API
- Clave de API: La clave de API de tu aplicación, disponible en el panel de Braze
- Punto final de SDK: La URL del punto final de SDK de Braze (por ejemplo,
sdk.iad-01.braze.com)
Obtener tus credenciales
- Clave de API: La encontrarás en el panel de Braze, en Configuración → Claves de API
- Punto final de SDK: Se encuentra en Configuración → Autenticación del SDK → Endpoints
Integración
Llamar a la API
Usa la API a nivel de módulo: llama a initialize() una vez y, a continuación, llama a las funciones exportadas. Para cambiar la configuración, llama primero a destroy() y luego otra vez a initialize().
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);
Conceptos básicos
Implementaciones obligatorias
El objeto de configuración de inicialización requiere storageManager. networkManager y pushManager son opcionales.
1. StorageManager: interfaz de almacenamiento asíncrono de clave-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>;
}
- El parámetro
isIdindica almacenamiento persistente de ID: cuando estrue, el SDK está almacenando un identificador persistente (ID del dispositivo, ID de usuario) o el indicador de exclusión. Las implementaciones deben conservar estos valores entre reinicios de la aplicación para que el SDK pueda reconocer el mismo dispositivo o usuario. Cuando esfalse, el valor son datos de sesión o de caché (eventos, atributos, etc.) y puede guardarse solo en memoria. En entornos web, considera usar cookies para las claves almacenadas conisId: truey así garantizar la persistencia entre sesiones. - Debe gestionar operaciones asíncronas en todas las operaciones de almacenamiento
2. NetworkManager (opcional): interfaz de solicitudes HTTP POST
interface NetworkManager {
postRequest(
url: string,
data: Partial<Record<string, unknown>>,
headers?: globalThis.Headers | [string, string][]
): Promise<Partial<Record<string, unknown>>>;
}
- La implementación predeterminada usa la API
fetch(requierefetchyURLglobales) - Puedes sobrescribirla si
fetchno es la API que prefieres - Nota: el SDK ya incluye lógica de reintentos y de límite de velocidad
3. PushManager (opcional): interfaz de notificaciones 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;
}
- Solo es obligatoria si implementas notificaciones push
Vaciado de datos
El SDK vacía automáticamente los datos en caché a los servidores de Braze cada 10 segundos (configurable mediante flushIntervalInSeconds). Usa requestImmediateDataFlush() para forzar una sincronización inmediata.
Patrones de integración
Para conocer las firmas de los métodos, los tipos de parámetros y de retorno, y todos los detalles de la API, consulta las definiciones de TypeScript incluidas en el paquete.
Integración básica
Ejemplo completo y funcional con gestión de errores:
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);
}
Implementación de almacenamiento personalizada
Implementación completa de StorageManager con IndexedDB para ID 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();
Implementación de red personalizada
NetworkManager que registra cada solicitud saliente (el SDK ya gestiona los errores y los reintentos):
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();
Gestión de errores
Patrones completos de gestión de errores:
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;
}
}
Gestión de suscripciones
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);
}
Cambiar la configuración: solo puede existir una sesión activa a la vez. Para cambiar de configuración, llama a destroy() y después a initialize():
import { destroy, initialize } from '@braze/javascript-sdk';
destroy();
await initialize({ /* new config */ });
Ejemplos comunes
Identificación de usuarios y seguimiento 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 y análisis
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);
});
Gestión de mensajes dentro de la aplicación
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);
});
Gestión de errores y casos límite
Condiciones de error comunes
SDK no inicializado:
- La mayoría de los métodos devuelven
undefined(en lugar de lanzar una excepción) cuando el SDK no está inicializado initialize()devuelvefalsesi ya está inicializado o si falla la validaciónchangeUser()no realiza ninguna acción y la promesa se resuelve si el SDK no está inicializado- Comprueba siempre si los valores de retorno son
undefinedantes de usarlos
Errores de validación:
- Clave de API o URL base no válidas:
initialize()devuelvefalsey registra un error - Nombres o claves de eventos no válidos: deben tener un máximo de 255 caracteres, no pueden empezar por
$y solo admiten caracteres alfanuméricos y signos de puntuación - Valores de atributos no válidos: cadenas de máximo 255 caracteres, sin saltos de línea, tabulaciones ni comillas dobles, y no pueden empezar por
$ - Códigos de moneda no válidos: los códigos no admitidos generan una advertencia y no se realiza ninguna acción
- Cantidad de compra no válida: debe estar entre 1 y 100; de lo contrario, se ignora
Errores de red:
- El método
postRequest()de NetworkManager debe gestionar los errores y rechazar las promesas adecuadamente - El controlador de vaciado de datos reintenta automáticamente las solicitudes fallidas
- Usa la devolución de llamada de
requestImmediateDataFlush()para detectar fallos en el vaciado
Errores de almacenamiento:
- Los métodos de StorageManager deben gestionar los errores de forma controlada
- Si el almacenamiento falla, es posible que el SDK no funcione correctamente
- El indicador
isIddetermina la persistencia: los ID se conservan entre sesiones y los objetos se limitan a la sesión
Casos límite de identificación de usuarios:
- No se puede volver a un usuario anónimo después de la identificación
- Cambiar de usuario finaliza la sesión actual e inicia una nueva
- El historial del usuario anónimo se conserva al identificarlo por primera vez
- El historial se fusiona si el usuario existe en otro dispositivo
Gestión de sesiones:
- Las sesiones caducan tras 30 minutos de inactividad (configurable)
openSession()devuelvetruepara una sesión nueva yfalsesi se reanuda- Debes llamar a
openSession()después dechangeUser()osetIdentifierToken()
Gestión de suscripciones:
- Las devoluciones de llamada de las suscripciones se ejecutan de forma síncrona cuando se producen los eventos
- Elimina las suscripciones para evitar fugas de memoria
removeAllSubscriptions()borra todas las suscripciones a la vez
Vaciado de datos:
- Vaciado automático cada 10 segundos (configurable, mínimo: 3 segundos)
- El vaciado puede fallar sin avisar; usa la devolución de llamada de
requestImmediateDataFlush() - Los datos se ponen en cola si la red no está disponible y se vacían cuando se restablece
Notas importantes de implementación
-
La mayoría de los métodos son asíncronos: los métodos asíncronos del SDK devuelven una Promise (usa
awaito.then()). Algunos métodos de configuración y utilidades (por ejemplo,destroy,toggleLogging,setLogger) son síncronos; consulta las definiciones de TypeScript o la tabla de referencia rápida para más detalles. -
Los métodos pueden devolver
undefined: si el SDK no está inicializado, la mayoría de los métodos devuelvenundefineden lugar de lanzar una excepción. Comprueba si el valor esundefinedantes de usar los valores de retorno. -
Los métodos pueden devolver
null: algunos métodos devuelvennullpara indicar “no encontrado” (por ejemplo,getUserId()devuelvenullsi el usuario es anónimo). Esto es distinto deundefined(SDK no inicializado). - Las claves de almacenamiento usan el indicador
isId: el parámetroisIdde los métodos de StorageManager distingue entre:- Almacenamiento de ID: identificadores persistentes (ID del dispositivo, ID de usuario) que deben conservarse entre sesiones
- Almacenamiento de objetos: datos limitados a la sesión que pueden borrarse
-
Etiquetas de metadatos del SDK: el array
sdkMetadataidentifica la plataforma o el envoltorio que usa el SDK (por ejemplo,['npm']o[BrazeSdkMetadata.NPM]). Las etiquetas válidas se definen en la enumeraciónBrazeSdkMetadata(comonpm,cdn,manu,shp,gg,kep), y el SDK añade automáticamente'wjs'para indicar el SDK de JavaScript. -
NetworkManager predeterminado: si no proporcionas
networkManager, el SDK usa una implementación predeterminada que requiere las API globalesfetchyURL. Proporciona una implementación personalizada si no están disponibles. -
PushManager es opcional: implementa
PushManagersolo si necesitas la funcionalidad de notificaciones push. En caso contrario, puedes omitirlo. -
Destrucción y limpieza: llama a
destroy()cuando necesites desmontar el SDK. Solo puede existir una sesión activa a la vez; debes llamar adestroy()antes de volver a llamar ainitialize(). Al hacerlo, se detienen los temporizadores, se vacían los datos y se liberan los recursos. -
Vaciado de datos: los datos se vacían automáticamente cada 10 segundos (configurable). Usa
requestImmediateDataFlush()para una sincronización inmediata. -
Gestión de sesiones: llama siempre a
openSession()después dechangeUser()osetIdentifierToken()para evitar crear usuarios anónimos duplicados. -
Seguridad de tipos: el SDK está escrito en TypeScript con definiciones de tipos completas. Usa TypeScript para disfrutar de la mejor experiencia y de la comprobación de tipos.
- Reglas de validación: los nombres de eventos, las claves de atributos y las claves de propiedades tienen una validación estricta (máximo 255 caracteres, no pueden empezar por
$y solo admiten caracteres alfanuméricos y signos de puntuación). Los valores no válidos pueden ignorarse o provocar errores.
Depuración y solución de problemas
Pasa la opción enableLogging: true a las opciones de inicialización. Esto resulta muy útil durante el desarrollo, pero asegúrate de eliminar esta opción o de proporcionar un registrador alternativo antes de publicar tu página en producción.
Contacto
Si tienes preguntas, ponte en contacto con [email protected].
Para obtener detalles del repositorio y proyectos de ejemplo, consulta https://github.com/braze-inc/braze-javascript-sdk.