Ir para o conteúdo

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, toggleLogging e setLogger) 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

  1. Chave de API: Disponível no dashboard da Braze, em Configurações → Chaves de API
  2. 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 isId indica 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 com isId: true para 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 (exige fetch e URL globais)
  • Pode ser substituída se fetch nã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() retorna false se o SDK já estiver inicializado ou se a validação falhar
  • changeUser() não faz nada e a promise é resolvida se o SDK não estiver inicializado
  • Sempre verifique se há valores de retorno undefined antes de usá-los

Falhas de validação:

  • Chave de API ou URL base inválida: initialize() retorna false e 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 isId determina 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() retorna true para uma nova sessão e false se a sessão for retomada
  • É necessário chamar openSession() depois de changeUser() ou setIdentifierToken()

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

  1. A maioria dos métodos é assíncrona: os métodos assíncronos do SDK retornam uma Promise (use await ou .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.

  2. Os métodos podem retornar undefined: se o SDK não estiver inicializado, a maioria dos métodos retorna undefined em vez de lançar uma exceção. Verifique se há undefined antes de usar os valores de retorno.

  3. Os métodos podem retornar null: alguns métodos retornam null para indicar “não encontrado” (por exemplo, getUserId() retorna null se o usuário for anônimo). Isso é diferente de undefined (SDK não inicializado).

  4. As chaves de armazenamento usam a flag isId: o parâmetro isId nos 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
  5. Tags de metadados do SDK: o array sdkMetadata identifica a plataforma ou o wrapper que usa o SDK (por exemplo, ['npm'] ou [BrazeSdkMetadata.NPM]). As tags válidas são definidas pelo enum BrazeSdkMetadata (como npm, cdn, manu, shp, gg, kep), e o SDK adiciona 'wjs' automaticamente para indicar o SDK de JavaScript.

  6. NetworkManager padrão: se networkManager não for informado, o SDK usa uma implementação padrão que exige as APIs globais fetch e URL. Forneça uma implementação personalizada se elas não estiverem disponíveis.

  7. O PushManager é opcional: só implemente o PushManager se precisar da funcionalidade de notificações por push. Caso contrário, ele pode ser omitido.

  8. Destruição e limpeza: chame destroy() quando precisar encerrar o SDK. Só pode existir uma sessão ativa por vez, então é necessário chamar destroy() antes de chamar initialize() de novo. Isso interrompe os timers, envia os dados e libera os recursos.

  9. Envio de dados: os dados são enviados automaticamente a cada 10 segundos (configurável). Use requestImmediateDataFlush() para uma sincronização imediata.

  10. Gerenciamento de sessões: sempre chame openSession() depois de changeUser() ou setIdentifierToken() para evitar a criação de usuários anônimos duplicados.

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

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

New Stuff!