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)
認証情報の取得
- APIキー: Brazeダッシュボードの設定 > APIキーで確認できます
- 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>>>;
}
- デフォルトの実装は
fetchAPIを使用します(グローバルな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);
}
カスタムストレージの実装
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()のコールバックを使用してください - ネットワークが利用できない場合、データはキューに入れられ、ネットワーク復旧時にフラッシュされます
実装に関する重要な注意事項
-
ほとんどのメソッドは非同期です: 非同期SDKメソッドはPromiseを返します(
awaitまたは.then()を使用してください)。一部の設定およびユーティリティメソッド(例:destroy、toggleLogging、setLogger)は同期的です。詳細についてはTypeScript定義またはクイックリファレンステーブルを参照してください。 -
メソッドが
undefinedを返す場合があります: SDKが初期化されていない場合、ほとんどのメソッドはスローではなくundefinedを返します。戻り値を使用する前にundefinedをチェックしてください。 -
メソッドが
nullを返す場合があります: 一部のメソッドは「見つからない」ことを示すためにnullを返します(例:getUserId()はユーザーが匿名の場合にnullを返します)。これはundefined(SDKが初期化されていない)とは異なります。 - ストレージキーは
isIdフラグを使用します: StorageManagerメソッドのisIdパラメーターは以下を区別します:- IDストレージ: セッション間で永続化する必要がある永続的な識別子(デバイスID、ユーザーID)
- オブジェクトストレージ: クリア可能なセッションスコープのデータ
-
SDKメタデータタグ:
sdkMetadata配列は、SDKを使用しているプラットフォーム/ラッパーを識別します(例:['npm']または[BrazeSdkMetadata.NPM])。有効なタグはBrazeSdkMetadata列挙型(npm、cdn、manu、shp、gg、kepなど)で定義されており、SDKはJavaScript SDKを示す'wjs'を自動的に追加します。 -
デフォルトのNetworkManager:
networkManagerが提供されない場合、SDKはグローバルなfetchとURLAPIを必要とするデフォルトの実装を使用します。これらが利用できない場合は、カスタム実装を提供してください。 -
PushManagerはオプションです: プッシュ通知機能が必要な場合にのみ
PushManagerを実装してください。それ以外の場合は省略できます。 -
破棄とクリーンアップ: SDKを解体する必要がある場合は
destroy()を呼び出します。一度にアクティブなセッションは1つだけ存在できます。再度initialize()を呼び出す前にdestroy()を呼び出す必要があります。これにより、タイマーが停止し、データがフラッシュされ、リソースが解放されます。 -
データフラッシュ: データは10秒ごとに自動的にフラッシュされます(設定可能)。即時同期には
requestImmediateDataFlush()を使用します。 -
セッション管理: 重複する匿名ユーザーの作成を避けるため、
changeUser()またはsetIdentifierToken()の後に必ずopenSession()を呼び出してください。 -
型安全性: SDKは完全な型定義を備えたTypeScriptで記述されています。最適なエクスペリエンスと型チェックのためにTypeScriptを使用してください。
- バリデーションルール: イベント名、属性キー、およびプロパティキーには厳格なバリデーションがあります(最大255文字、
$で始めることはできません。英数字と句読点のみ使用可能です)。無効な値は無視されるか、エラーが発生する場合があります。
enableLogging: true オプションを初期化オプションに渡します。これは開発時に役立ちますが、ページを本番環境にリリースする前に、このオプションを削除するか、代替のロガーを提供してください。
お問い合わせ
ご質問がある場合は、support@braze.com までお問い合わせください。
リポジトリの詳細とサンプルプロジェクトについては、https://github.com/braze-inc/braze-javascript-sdk を参照してください。