Ir al contenido

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

  1. Clave de API: La encontrarás en el panel de Braze, en Configuración → Claves de API
  2. 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 isId indica almacenamiento persistente de ID: cuando es true, 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 es false, 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 con isId: true y 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 (requiere fetch y URL globales)
  • Puedes sobrescribirla si fetch no 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() devuelve false si ya está inicializado o si falla la validación
  • changeUser() no realiza ninguna acción y la promesa se resuelve si el SDK no está inicializado
  • Comprueba siempre si los valores de retorno son undefined antes de usarlos

Errores de validación:

  • Clave de API o URL base no válidas: initialize() devuelve false y 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 isId determina 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() devuelve true para una sesión nueva y false si se reanuda
  • Debes llamar a openSession() después de changeUser() o setIdentifierToken()

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

  1. La mayoría de los métodos son asíncronos: los métodos asíncronos del SDK devuelven una Promise (usa await o .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.

  2. Los métodos pueden devolver undefined: si el SDK no está inicializado, la mayoría de los métodos devuelven undefined en lugar de lanzar una excepción. Comprueba si el valor es undefined antes de usar los valores de retorno.

  3. Los métodos pueden devolver null: algunos métodos devuelven null para indicar “no encontrado” (por ejemplo, getUserId() devuelve null si el usuario es anónimo). Esto es distinto de undefined (SDK no inicializado).

  4. Las claves de almacenamiento usan el indicador isId: el parámetro isId de 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
  5. Etiquetas de metadatos del SDK: el array sdkMetadata identifica la plataforma o el envoltorio que usa el SDK (por ejemplo, ['npm'] o [BrazeSdkMetadata.NPM]). Las etiquetas válidas se definen en la enumeración BrazeSdkMetadata (como npm, cdn, manu, shp, gg, kep), y el SDK añade automáticamente 'wjs' para indicar el SDK de JavaScript.

  6. NetworkManager predeterminado: si no proporcionas networkManager, el SDK usa una implementación predeterminada que requiere las API globales fetch y URL. Proporciona una implementación personalizada si no están disponibles.

  7. PushManager es opcional: implementa PushManager solo si necesitas la funcionalidad de notificaciones push. En caso contrario, puedes omitirlo.

  8. Destrucción y limpieza: llama a destroy() cuando necesites desmontar el SDK. Solo puede existir una sesión activa a la vez; debes llamar a destroy() antes de volver a llamar a initialize(). Al hacerlo, se detienen los temporizadores, se vacían los datos y se liberan los recursos.

  9. Vaciado de datos: los datos se vacían automáticamente cada 10 segundos (configurable). Usa requestImmediateDataFlush() para una sincronización inmediata.

  10. Gestión de sesiones: llama siempre a openSession() después de changeUser() o setIdentifierToken() para evitar crear usuarios anónimos duplicados.

  11. 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.

  12. 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.

New Stuff!