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