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をインストールします:
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);
コアコンセプト
必須の実装
initialize 設定オブジェクトには 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);
}
カスタムストレージの実装
IndexedDBを使用した永続IDの完全な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('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']);
イベントログと分析
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が初期化されていない場合、何も行わず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 を参照してください。