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をインストールします:
1
npm install @braze/javascript-sdk
またはyarnで:
1
yarn add @braze/javascript-sdk
モジュールレベルAPIを使用する場合:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()を呼び出します。
1
2
3
4
5
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);
コアコンセプト
必須の実装
初期化設定オブジェクトにはstorageManagerが必須です。networkManagerとpushManagerはオプションです。
1. StorageManager - 非同期キーバリューストレージインターフェイス
1
2
3
4
5
6
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リクエストインターフェイス
1
2
3
4
5
6
7
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(オプション)- プッシュ通知インターフェイス
1
2
3
4
5
6
7
8
9
10
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定義を参照してください。
基本的な統合
エラーハンドリングを含む完全な動作例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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実装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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はエラーとリトライをすでに処理しています):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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();
エラーハンドリング
完全なエラーハンドリングパターン:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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;
}
}
サブスクリプション管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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()を呼び出します:
1
2
3
4
import { destroy, initialize } from '@braze/javascript-sdk';
destroy();
await initialize({ /* new config */ });
一般的なユースケース
ユーザー識別と属性トラッキング
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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']);
イベントログと分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
});
アプリ内メッセージの処理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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が初期化されていない場合はno-opとなり、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を渡します。これは開発時に有用ですが、本番環境にページをリリースする前に、このオプションを削除するか、代替のロガーを提供してください。
お問い合わせ
ご質問がある場合は、[email protected]までお問い合わせください。
リポジトリの詳細とサンプルプロジェクトについては、https://github.com/braze-inc/braze-javascript-sdkを参照してください。