コンテンツにスキップ

JavaScript SDK リポジトリガイド

Braze JavaScript SDKについて

Braze JavaScript SDKは、Brazeのメッセージング、分析、ユーザーエンゲージメント機能をアプリケーションに統合するのに役立ちます。

開始するには、以下のリソースを参照してください。

アーキテクチャの概要

Braze JavaScript SDKは、あらゆる純粋なJavaScript環境で動作するように設計されたプラットフォーム非依存のライブラリです。ブラウザやNode.js固有のAPIを含まないため、さまざまなJavaScriptランタイムでの使用に適しています。

主な設計原則:

  • 依存性の注入: SDKはプラットフォーム固有のAPIを使用する代わりに、ストレージ、ネットワーキング、デバイス情報の実装を必要とします
  • 非同期ファースト: ほとんどのAPIメソッドは非同期でPromiseを返します。一部のユーティリティメソッド(例:destroysubscribeToInAppMessagetoggleLoggingsetLogger)は同期的です。正確なシグネチャについてはTypeScript定義を参照してください。
  • シングルトンセッション: モジュールレベルのAPI(initialize/destroy)は、一度に1つのアクティブなSDKセッションを管理します。
  • 内部依存関係の管理: 提供された実装から内部依存関係(UserManager、SessionManager、DataFlushControllerなど)を作成・管理します

クイックスタート

npm で SDKをインストールします:

npm install @braze/javascript-sdk

または yarn を使用します:

yarn add @braze/javascript-sdk

モジュールレベル API の場合:

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

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

await changeUser(userId);

await openSession();

前提条件

Braze JavaScript SDKを統合する前に、以下が必要です。

  • Brazeアカウント: APIアクセスが可能なBrazeアカウント
  • APIキー: Brazeダッシュボードから取得したアプリのAPIキー
  • SDKエンドポイント: BrazeのSDKエンドポイントURL(例: sdk.iad-01.braze.com

認証情報の取得

  1. APIキー: Brazeダッシュボードの設定 > APIキーで確認できます
  2. SDKエンドポイント: 設定 > SDK認証 > エンドポイントで確認できます

インテグレーション

APIの呼び出し

モジュールレベルのAPIを使用します。initialize() を一度呼び出してから、エクスポートされた関数を呼び出します。設定を切り替えるには、まず destroy() を呼び出してから、再度 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);

コアコンセプト

必須の実装

initialize 設定オブジェクトには storageManager が必要です。networkManagerpushManager はオプションです。

1. StorageManager - 非同期キーバリューストレージインターフェイス

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>;
}
  • isId パラメーターは永続的なIDストレージを示します。true の場合、SDKは永続的な識別子(デバイスID、ユーザーID)またはオプトアウトフラグを保存しています。実装では、SDKが同じデバイス/ユーザーを認識できるように、アプリの再起動後もこれらを永続化する必要があります。false の場合、値はセッション/キャッシュデータ(イベント、属性など)であり、メモリ内のみでも構いません。Web環境では、クロスセッションの永続性を確保するために、isId: true で保存されるキーにはCookieの使用を検討してください。
  • すべてのストレージ操作で非同期操作を処理する必要があります

2. NetworkManager(オプション)- HTTP POSTリクエストインターフェイス

interface NetworkManager {
  postRequest(
    url: string,
    data: Partial<Record<string, unknown>>,
    headers?: globalThis.Headers | [string, string][]
  ): Promise<Partial<Record<string, unknown>>>;
}
  • デフォルトの実装は fetch APIを使用します(グローバルな fetchURL が必要です)
  • fetch が推奨されるAPIでない場合は上書きできます
  • 注: SDKにはリトライとレート制限のロジックがすでに組み込まれています

3. PushManager(オプション)- プッシュ通知インターフェイス

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;
}
  • プッシュ通知を実装する場合にのみ必要です

データフラッシュ

SDKは、キャッシュされたデータを10秒ごとに自動的にBrazeサーバーにフラッシュします(flushIntervalInSeconds で設定可能)。即時同期を強制するには requestImmediateDataFlush() を使用します。

インテグレーションパターン

メソッドシグネチャ、パラメーターと戻り値の型、および完全なAPIの詳細については、パッケージ内のTypeScript定義を参照してください。

基本的なインテグレーション

エラーハンドリングを含む完全な動作例:

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

カスタムストレージの実装

IndexedDBを使用した永続IDの完全なStorageManager実装:

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

カスタムネットワークの実装

すべての送信リクエストをログに記録するNetworkManager(SDKはエラーとリトライをすでに処理しています):

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

エラーハンドリング

完全なエラーハンドリングパターン:

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

購読管理

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

設定の切り替え: 一度にアクティブなセッションは1つだけ存在します。設定を切り替えるには、destroy() を呼び出してから initialize() を呼び出します:

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

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

一般的なユースケース

ユーザー識別と属性トラッキング

import {
  changeUser,
  setCustomUserAttribute,
  setUserEmail,
  setUserFirstName,
  setUserLastName,
} from '@braze/javascript-sdk';

// Identify user
await changeUser('user-123');

// Set standard attributes
await setUserEmail('user@example.com');
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']);

イベントログと分析

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

アプリ内メッセージの処理

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

エラーハンドリングとエッジケース

一般的なエラー条件

SDKが初期化されていない場合:

  • ほとんどのメソッドは、SDKが初期化されていない場合、スローではなく undefined を返します
  • initialize() は、すでに初期化されているかバリデーションに失敗した場合に false を返します
  • changeUser() はSDKが初期化されていない場合、何も行わずPromiseが解決されます
  • 戻り値を使用する前に、常に undefined をチェックしてください

バリデーションの失敗:

  • 無効なAPIキーまたはベースURL: initialize()false を返し、エラーをログに記録します
  • 無効なイベント名/キー: 最大255文字、$ で始めることはできません。英数字と句読点のみ使用可能です
  • 無効な属性値: 文字列は最大255文字、改行/タブ/ダブルクォートは使用できません。$ で始めることはできません
  • 無効な通貨コード: サポートされていないコードは警告が表示され、アクションは実行されません
  • 無効な購入数量: 1〜100の範囲でなければなりません。範囲外の場合は無視されます

ネットワークエラー:

  • NetworkManagerの postRequest() はエラーを適切に処理し、Promiseを拒否する必要があります
  • データフラッシュコントローラーは失敗したリクエストを自動的にリトライします
  • フラッシュの失敗を検出するには requestImmediateDataFlush() のコールバックを使用します

ストレージエラー:

  • StorageManagerのメソッドはエラーを適切に処理する必要があります
  • ストレージが失敗した場合、SDKが正しく機能しない可能性があります
  • isId フラグが永続性を決定します: IDはセッション間で永続化され、オブジェクトはセッションスコープです

ユーザー識別のエッジケース:

  • 識別後に匿名ユーザーに戻すことはできません
  • ユーザーの切り替えにより、現在のセッションが終了し、新しいセッションが開始されます
  • 初回識別時に匿名ユーザーの履歴が保持されます
  • 別のデバイスにユーザーが存在する場合、履歴がマージされます

セッション管理:

  • セッションは30分間の非アクティブ後にタイムアウトします(設定可能)
  • openSession() は新しいセッションの場合 true を、再開の場合 false を返します
  • changeUser() または setIdentifierToken() の後に openSession() を呼び出す必要があります

購読管理:

  • 購読コールバックはイベント発生時に同期的に呼び出されます
  • メモリリークを防ぐために購読を解除してください
  • removeAllSubscriptions() はすべての購読を一度にクリアします

データフラッシュ:

  • 10秒ごとに自動フラッシュが行われます(設定可能、最小: 3秒)
  • フラッシュはサイレントに失敗する場合があります - requestImmediateDataFlush() のコールバックを使用してください
  • ネットワークが利用できない場合、データはキューに入れられ、ネットワーク復旧時にフラッシュされます

実装に関する重要な注意事項

  1. ほとんどのメソッドは非同期です: 非同期SDKメソッドはPromiseを返します(await または .then() を使用してください)。一部の設定およびユーティリティメソッド(例: destroytoggleLoggingsetLogger)は同期的です。詳細についてはTypeScript定義またはクイックリファレンステーブルを参照してください。

  2. メソッドが undefined を返す場合があります: SDKが初期化されていない場合、ほとんどのメソッドはスローではなく undefined を返します。戻り値を使用する前に undefined をチェックしてください。

  3. メソッドが null を返す場合があります: 一部のメソッドは「見つからない」ことを示すために null を返します(例: getUserId() はユーザーが匿名の場合に null を返します)。これは undefined(SDKが初期化されていない)とは異なります。

  4. ストレージキーは isId フラグを使用します: StorageManagerメソッドの isId パラメーターは以下を区別します:
    • IDストレージ: セッション間で永続化する必要がある永続的な識別子(デバイスID、ユーザーID)
    • オブジェクトストレージ: クリア可能なセッションスコープのデータ
  5. SDKメタデータタグ: sdkMetadata 配列は、SDKを使用しているプラットフォーム/ラッパーを識別します(例: ['npm'] または [BrazeSdkMetadata.NPM])。有効なタグは BrazeSdkMetadata 列挙型(npmcdnmanushpggkep など)で定義されており、SDKはJavaScript SDKを示す 'wjs' を自動的に追加します。

  6. デフォルトのNetworkManager: networkManager が提供されない場合、SDKはグローバルな fetchURL APIを必要とするデフォルトの実装を使用します。これらが利用できない場合は、カスタム実装を提供してください。

  7. PushManagerはオプションです: プッシュ通知機能が必要な場合にのみ PushManager を実装してください。それ以外の場合は省略できます。

  8. 破棄とクリーンアップ: SDKを解体する必要がある場合は destroy() を呼び出します。一度にアクティブなセッションは1つだけ存在できます。再度 initialize() を呼び出す前に destroy() を呼び出す必要があります。これにより、タイマーが停止し、データがフラッシュされ、リソースが解放されます。

  9. データフラッシュ: データは10秒ごとに自動的にフラッシュされます(設定可能)。即時同期には requestImmediateDataFlush() を使用します。

  10. セッション管理: 重複する匿名ユーザーの作成を避けるため、changeUser() または setIdentifierToken() の後に必ず openSession() を呼び出してください。

  11. 型安全性: SDKは完全な型定義を備えたTypeScriptで記述されています。最適なエクスペリエンスと型チェックのためにTypeScriptを使用してください。

  12. バリデーションルール: イベント名、属性キー、およびプロパティキーには厳格なバリデーションがあります(最大255文字、$ で始めることはできません。英数字と句読点のみ使用可能です)。無効な値は無視されるか、エラーが発生する場合があります。

enableLogging: true オプションを初期化オプションに渡します。これは開発時に役立ちますが、ページを本番環境にリリースする前に、このオプションを削除するか、代替のロガーを提供してください。

お問い合わせ

ご質問がある場合は、support@braze.com までお問い合わせください。

リポジトリの詳細とサンプルプロジェクトについては、https://github.com/braze-inc/braze-javascript-sdk を参照してください。

New Stuff!