Passer au contenu

Guide du dépôt du SDK JavaScript

À propos du SDK JavaScript de Braze

Le SDK JavaScript de Braze vous aide à intégrer les fonctionnalités de communication, d’analyse et d’engagement des utilisateurs de Braze dans votre application.

Pour commencer, consultez les ressources suivantes :

Aperçu de l’architecture

Le SDK JavaScript de Braze est une bibliothèque indépendante de la plateforme, conçue pour fonctionner dans n’importe quel environnement JavaScript pur. Il ne contient pas d’API propres au navigateur ou à Node.js, ce qui permet de l’utiliser dans divers environnements d’exécution JavaScript.

Principes de conception clés :

  • Injection de dépendances : Le SDK nécessite des implémentations pour le stockage, le réseau et les informations sur l’appareil, au lieu d’utiliser des API propres à une plateforme.
  • Asynchrone avant tout : La plupart des méthodes de l’API sont asynchrones et renvoient des Promises ; certaines méthodes utilitaires (par exemple destroy, subscribeToInAppMessage, toggleLogging, setLogger) sont synchrones. Consultez les définitions TypeScript pour connaître les signatures exactes.
  • Session unique : L’API au niveau du module (initialize/destroy) gère une seule session SDK active à la fois.
  • Gestion interne des dépendances : Le SDK crée et gère des dépendances internes (UserManager, SessionManager, DataFlushController, etc.) à partir des implémentations fournies.

Démarrage rapide

Installez le SDK avec npm :

npm install @braze/javascript-sdk

Ou avec yarn :

yarn add @braze/javascript-sdk

Avec l’API au niveau du module :

import { initialize, openSession, changeUser } from '@braze/javascript-sdk';

await initialize({
  apiKey,
  baseUrl,
  options,
  sdkMetadata,
  deviceInfo,
  storageManager,
  networkManager,
});

await changeUser(userId);

await openSession();

Conditions préalables

Avant d’intégrer le SDK JavaScript de Braze, vous aurez besoin des éléments suivants :

  • Compte Braze : un compte Braze disposant d’un accès à l’API
  • Clé API : la clé API de votre application, disponible dans le tableau de bord de Braze
  • Endpoint SDK : l’URL de votre endpoint SDK Braze (par exemple, sdk.iad-01.braze.com)

Obtenir vos identifiants

  1. Clé API : dans votre tableau de bord de Braze, sous Paramètres → Clés API
  2. Endpoint SDK : dans Paramètres → Authentification SDK → Endpoints

Intégration

Appeler l’API

Utilisez l’API au niveau du module : appelez initialize() une seule fois, puis appelez les fonctions exportées. Pour changer de configuration, appelez d’abord destroy(), puis à nouveau 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);

Concepts fondamentaux

Implémentations requises

L’objet de configuration d’initialisation requiert storageManager. networkManager et pushManager sont facultatifs.

1. StorageManager - Interface de stockage clé-valeur asynchrone

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>;
}
  • Le paramètre isId indique un stockage d’ID persistant : lorsqu’il vaut true, le SDK stocke un identifiant persistant (ID d’appareil, ID utilisateur) ou l’indicateur de désabonnement. Les implémentations doivent conserver ces valeurs entre les redémarrages de l’application afin que le SDK puisse reconnaître le même appareil ou le même utilisateur. Lorsqu’il vaut false, la valeur correspond à des données de session ou de cache (événements, attributs, etc.) et peut être stockée uniquement en mémoire. Dans les environnements web, envisagez d’utiliser des cookies pour les clés stockées avec isId: true afin de garantir leur persistance d’une session à l’autre.
  • Doit gérer de manière asynchrone toutes les opérations de stockage

2. NetworkManager (facultatif) - Interface de requête HTTP POST

interface NetworkManager {
  postRequest(
    url: string,
    data: Partial<Record<string, unknown>>,
    headers?: globalThis.Headers | [string, string][]
  ): Promise<Partial<Record<string, unknown>>>;
}
  • L’implémentation par défaut utilise l’API fetch (nécessite fetch et URL globaux)
  • Peut être remplacée si fetch n’est pas l’API privilégiée
  • Remarque : le SDK intègre déjà une logique de nouvelle tentative et de limitation du débit

3. PushManager (facultatif) - Interface de notification 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;
}
  • Requis uniquement si vous mettez en place des notifications push

Vidage des données

Le SDK vide automatiquement les données en cache vers les serveurs de Braze toutes les 10 secondes (configurable via flushIntervalInSeconds). Utilisez requestImmediateDataFlush() pour forcer une synchronisation immédiate.

Modèles d’intégration

Pour les signatures de méthodes, les types de paramètres et de retour ainsi que le détail complet de l’API, consultez les définitions TypeScript du paquet.

Intégration de base

Exemple fonctionnel complet avec gestion des erreurs :

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);
}

Implémentation de stockage personnalisée

Implémentation complète de StorageManager avec IndexedDB pour les ID persistants :

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();

Implémentation réseau personnalisée

NetworkManager qui enregistre chaque requête sortante (le SDK gère déjà les erreurs et les nouvelles tentatives) :

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();

Gestion des erreurs

Modèles complets de gestion des erreurs :

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;
  }
}

Gestion des abonnements

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);
}

Changer de configuration : une seule session active peut exister à la fois. Pour changer de configuration, appelez destroy() puis initialize() :

import { destroy, initialize } from '@braze/javascript-sdk';

destroy();
await initialize({ /* new config */ });

Cas d’usage courants

Identification des utilisateurs et suivi des attributs

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']);

Enregistrement des événements et analyse

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);
});

Gestion des messages in-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);
});

Gestion des erreurs et cas limites

Conditions d’erreur courantes

SDK non initialisé :

  • La plupart des méthodes renvoient undefined (sans lever d’exception) lorsque le SDK n’est pas initialisé
  • initialize() renvoie false si le SDK est déjà initialisé ou si la validation échoue
  • changeUser() n’a aucun effet et la promesse est résolue si le SDK n’est pas initialisé
  • Vérifiez toujours les valeurs de retour undefined avant de les utiliser

Échecs de validation :

  • Clé API ou URL de base non valide : initialize() renvoie false et enregistre une erreur
  • Noms ou clés d’événement non valides : 255 caractères maximum, ne peuvent pas commencer par $, caractères alphanumériques et ponctuation uniquement
  • Valeurs d’attribut non valides : chaînes de caractères de 255 caractères maximum, sans sauts de ligne, tabulations ni guillemets doubles, ne pouvant pas commencer par $
  • Codes de devise non valides : les codes non pris en charge génèrent un avertissement et aucune action n’est effectuée
  • Quantité d’achat non valide : doit être comprise entre 1 et 100, sinon elle est ignorée

Erreurs réseau :

  • La méthode postRequest() de NetworkManager doit gérer les erreurs et rejeter les promesses de manière appropriée
  • Le contrôleur de vidage des données relance automatiquement les requêtes ayant échoué
  • Utilisez le rappel de requestImmediateDataFlush() pour détecter les échecs de vidage

Erreurs de stockage :

  • Les méthodes de StorageManager doivent gérer les erreurs de manière élégante
  • Si le stockage échoue, le SDK risque de ne pas fonctionner correctement
  • L’indicateur isId détermine la persistance : les ID persistent d’une session à l’autre, tandis que les objets sont limités à la session

Cas limites liés à l’identification des utilisateurs :

  • Impossible de revenir à un utilisateur anonyme après l’identification
  • Un changement d’utilisateur met fin à la session en cours et en démarre une nouvelle
  • L’historique de l’utilisateur anonyme est conservé lors de la première identification
  • L’historique est fusionné si l’utilisateur existe sur un autre appareil

Gestion des sessions :

  • Les sessions expirent après 30 minutes d’inactivité (configurable)
  • openSession() renvoie true pour une nouvelle session et false si la session est reprise
  • Vous devez appeler openSession() après changeUser() ou setIdentifierToken()

Gestion des abonnements :

  • Les rappels d’abonnement sont appelés de manière synchrone lorsque des événements se produisent
  • Supprimez les abonnements pour éviter les fuites de mémoire
  • removeAllSubscriptions() supprime tous les abonnements en une seule fois

Vidage des données :

  • Vidage automatique toutes les 10 secondes (configurable, minimum : 3 secondes)
  • Le vidage peut échouer silencieusement : utilisez le rappel de requestImmediateDataFlush()
  • Les données sont mises en file d’attente si le réseau n’est pas disponible, puis vidées lorsque le réseau est rétabli

Remarques importantes sur le déploiement

  1. La plupart des méthodes sont asynchrones : les méthodes asynchrones du SDK renvoient une promesse (utilisez await ou .then()). Certaines méthodes de configuration et utilitaires (par exemple, destroy, toggleLogging, setLogger) sont synchrones ; consultez les définitions TypeScript ou le tableau de référence rapide pour plus de détails.

  2. Les méthodes peuvent renvoyer undefined : si le SDK n’est pas initialisé, la plupart des méthodes renvoient undefined au lieu de lever une exception. Vérifiez la présence de undefined avant d’utiliser les valeurs de retour.

  3. Les méthodes peuvent renvoyer null : certaines méthodes renvoient null pour indiquer « introuvable » (par exemple, getUserId() renvoie null si l’utilisateur est anonyme). Cette valeur est différente de undefined (SDK non initialisé).

  4. Les clés de stockage utilisent l’indicateur isId : le paramètre isId des méthodes de StorageManager distingue :
    • Le stockage d’ID : identifiants persistants (ID d’appareil, ID utilisateur) qui doivent persister d’une session à l’autre
    • Le stockage d’objets : données limitées à la session, qui peuvent être effacées
  5. Tags de métadonnées du SDK : le tableau sdkMetadata identifie la plateforme ou le wrapper qui utilise le SDK (par exemple, ['npm'] ou [BrazeSdkMetadata.NPM]). Les tags valides sont définis par l’enum BrazeSdkMetadata (comme npm, cdn, manu, shp, gg, kep), et le SDK ajoute automatiquement 'wjs' pour indiquer le SDK JavaScript.

  6. NetworkManager par défaut : si networkManager n’est pas fourni, le SDK utilise une implémentation par défaut qui nécessite les API globales fetch et URL. Fournissez une implémentation personnalisée si celles-ci ne sont pas disponibles.

  7. PushManager est facultatif : n’implémentez PushManager que si vous avez besoin de la fonctionnalité de notification push. Il peut être omis dans le cas contraire.

  8. Destruction et nettoyage : appelez destroy() lorsque vous devez arrêter le SDK. Une seule session active peut exister à la fois ; vous devez appeler destroy() avant de rappeler initialize(). Cette opération arrête les minuteurs, vide les données et libère les ressources.

  9. Vidage des données : les données sont automatiquement vidées toutes les 10 secondes (configurable). Utilisez requestImmediateDataFlush() pour une synchronisation immédiate.

  10. Gestion des sessions : appelez toujours openSession() après changeUser() ou setIdentifierToken() pour éviter de créer des utilisateurs anonymes en double.

  11. Sécurité des types : le SDK est écrit en TypeScript et fournit des définitions de types complètes. Utilisez TypeScript pour profiter de la meilleure expérience et de la vérification des types.

  12. Règles de validation : les noms d’événements, les clés d’attributs et les clés de propriétés sont soumis à une validation stricte (255 caractères maximum, ne peuvent pas commencer par $, caractères alphanumériques et ponctuation uniquement). Les valeurs non valides peuvent être ignorées ou provoquer des erreurs.

Débogage / Résolution des problèmes

Ajoutez l’option enableLogging: true aux options d’initialisation. Elle est très utile pendant le développement, mais veillez à la supprimer ou à fournir un autre enregistreur avant de mettre votre page en production.

Contact

Si vous avez des questions, veuillez contacter [email protected].

Pour les détails du dépôt et les exemples de projets, consultez https://github.com/braze-inc/braze-javascript-sdk.

New Stuff!