コンテンツにスキップ

JavaScript SDK リポジトリガイド

Braze JavaScript SDKについて

Braze JavaScript SDKを使用すると、Brazeのメッセージング、分析、ユーザーエンゲージメントの機能をアプリケーションに統合できます。

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

アーキテクチャの概要

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

主な設計原則:

  • 依存性の注入:SDKは、プラットフォーム固有のAPIを使用する代わりに、ストレージ、ネットワーク、デバイス情報の実装を必要とします
  • 非同期ファースト:ほとんどのAPIメソッドは非同期で、Promiseを返します。一部のユーティリティメソッド(destroy、subscribeToInAppMessage、toggleLogging、setLogger など)は同期的です。正確なシグネチャについては、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 が必要です。networkManager と pushManager はオプションです。

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 を使用します (グローバルな fetch と URL が必要です)
  • 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);
}

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

永続的な ID 向けに IndexedDB を使用した 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('[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']);

イベントのログ記録と分析

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 を返します
  • SDKが初期化されていない場合、changeUser() は何も実行せず、Promise は解決されます
  • 戻り値を使用する前に、必ず undefined でないことを確認してください

検証の失敗:

  • 無効な API キーまたはベース URL:initialize() は false を返し、エラーをログに記録します
  • 無効なイベント名またはキー:最大 255 文字で、$ で始めることはできず、英数字と句読点のみ使用できます
  • 無効な属性値:文字列は最大 255 文字で、改行、タブ、二重引用符は使用できず、$ で始めることはできません
  • 無効な通貨コード:サポートされていないコードは警告となり、何も実行されません
  • 無効な購入数量:1~100 である必要があり、それ以外は無視されます

ネットワークエラー:

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

ストレージエラー:

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

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

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

セッション管理:

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

サブスクリプション管理:

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

データのフラッシュ:

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

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

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

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

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

  4. ストレージキーは isId フラグを使用します:StorageManager メソッドの isId パラメーターは、次の 2 つを区別します。
    • ID ストレージ:セッションをまたいで保持する必要がある永続的な識別子 (デバイス ID、ユーザー ID)
    • オブジェクトストレージ:消去可能なセッションスコープのデータ
  5. SDK メタデータタグ:sdkMetadata 配列は、SDKを使用しているプラットフォームやラッパーを識別します (たとえば、['npm'] や [BrazeSdkMetadata.NPM])。有効なタグは BrazeSdkMetadata 列挙型で定義されており (npm、cdn、manu、shp、gg、kep など)、SDKは JavaScript SDK であることを示すために 'wjs' を自動的に追加します。

  6. デフォルトの NetworkManager:networkManager が指定されていない場合、SDKはグローバルな fetch API と URL 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 オプションを渡します。これは開発時に役立ちますが、ページを本番環境にリリースする前に、このオプションを削除するか、代替ロガーを指定してください。

お問い合わせ

ご不明な点がございましたら、[email protected] までお問い合わせください。

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

New Stuff!