Web SDK - ネイティブJavaScript実装ガイド

ドキュメント

概要

情報Web Attribution は企業向けの機能です。お客様のアカウントでこの機能を有効にするには、カスタマーサクセスマネージャーにお問い合わせください。

このガイドでは、ネイティブJavaScriptを使用したSingular WebSDKの実装方法を説明します。この方法は最も信頼性の高いトラッキングを提供し、一般的な広告ブロッカーによってブロックされないため、ほとんどの実装に推奨されます。

重要

  • ネイティブ JavaScript と Google タグマネージャの両方の方法を実装しないでください。重複トラッキングを避けるため、どちらか一方のみを選択してください。
  • Singular WebSDKはユーザーのブラウザでクライアントサイドで動作するように設計されています。正しく機能するためにはlocalStorageや DOM(Document Object Model)などのブラウザ機能にアクセスする必要があります。サーバー環境ではブラウザAPIにアクセスできないため、トラッキングに失敗します。

前提条件

開始する前に、以下を確認してください:

  • SDKキーとSDKシークレット
  • プロダクトID
    • 何ですか?お客様のウェブサイトのユニークな名前です。理想的には逆DNS形式(例:com.website-name )を使用します。
    • なぜ重要なのか:このIDはウェブサイトをSingularのAppとして関連付け、SingularのAppsページに記載されているWeb AppbundleIDと一致する必要があります。
  • ウェブサイトのHTMLコードを編集する権限。
  • ページの<head> セクションに JavaScript を追加するアクセス権。
  • 追跡したいイベントのリスト。Singular 標準イベントをご覧ください:全イベントリストと分野別推奨イベントをご覧ください。

実装ステップ


ステップ1:SDKライブラリスクリプトの追加

ウェブサイトの各ページの<head> セクションに以下のコードスニペットを追加します。できるだけ早い段階で、理想的には<head> タグの上部付近に配置します。

ヒントスクリプトを早めに追加することで、ページのソースコードにあるSingular関数でSingular JavaScriptライブラリが利用できるようになります。

Latest VersionSpecific VersionUsing NPMNext.js / React
<script src="https://web-sdk-cdn.singular.net/singular-sdk/latest/singular-sdk.js"></script>

ステップ2: SDKの初期化

  • ブラウザにページがロードされるたびに、SDKを常に初期化します。
  • 初期化はすべてのSingularアトリビューションとイベントトラッキング機能に必要です。
  • 初期化は __PAGE_VISIT__ イベントが発生します。
  • この __PAGE_VISIT__イベントは、以下の条件が満たされたときにサーバーサイドで新しいセッションを生成するために使用されます:
    • ユーザーが URL に新しい広告データ(UTM や WP パラメータなど)を持って到着した場合。
    • 前のセッションの有効期限が切れた(30分間操作がなかった場合)。
    • セッションは、ユーザーのリテンションを測定し、リエンゲージメントアトリビューションをサポートするために使用されます。
  1. 初期化関数を作成し、ページロード後の DOM Ready でこの関数を呼び出します。
  2. 他のSingularイベントが報告される前に初期化が行われるようにします。
  3. シングルページアプリケーション(SPA)の場合は、最初のページロード時にSingular SDKを初期化し、新しいページビューを表すルート変更ごとにSingular Page Visist関数window.singularSdk.pageVisit()
Basic InitializationNext.js / React

基本的なDOMレディ初期化

DOMContentLoadedinitSingularSDK()を呼び出すイベントリスナーを追加します。

/**
 * Initializes the Singular SDK with the provided configuration.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * initSingularSDK(); // Initializes SDK with default config
 */
function initSingularSDK() {
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId');
  window.singularSdk.init(config);
}

/**
 * Triggers Singular SDK initialization when the DOM is fully parsed.
 */
document.addEventListener('DOMContentLoaded', function() {
  initSingularSDK();
});

グローバルプロパティによる基本的な初期化

Singular SDKは初期化されないため、ブラウザのlocalstorage 、グローバルプロパティを設定するカスタム関数を実装する必要があります。カスタム関数setGlobalPropertyBeforeInit() を参照してください。実装すると、SDKの初期化前に以下の概要に従ってプロパティを設定できます。

/**
 * Initializes the Singular SDK with the provided configuration.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * initSingularSDK(); // Initializes SDK with default config
 */
function initSingularSDK() {
  var sdkKey = 'sdkKey';
  var sdkSecret = 'sdkSecret';
  var productId = 'productId';
  
  // Set global properties before SDK initialization
  setGlobalPropertyBeforeInit(sdkKey, productId, 'global_prop_1', 'test', false);
  setGlobalPropertyBeforeInit(sdkKey, productId, 'global_prop_2', 'US', true);
  
  // Initialize SDK
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId');
  window.singularSdk.init(config);
}

/**
 * Triggers Singular SDK initialization when the DOM is fully parsed.
 */
document.addEventListener('DOMContentLoaded', function() {
  initSingularSDK();
});

シングルページアプリケーション(SPA)ルーティングによる基本的な初期化

シナリオ 何をすべきか

最初のページロード

window.singularSdk.init(config) を呼び出します。

新しいルート/ページへの移動

window.singularSdk.pageVisit() を呼び出す。

SPAの初期ロード時

window.singularSdk.pageVisit() をコールしない (初期化は最初のページ訪問イベントを提供する)。

SPAの例 (React)

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

/**
 * Initializes the Singular SDK with the provided configuration.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * // Initialize the Singular SDK
 * initSingularSDK();
 */
function initSingularSDK() {
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId');
  window.singularSdk.init(config);
}

/**
 * Tracks a page visit event with the Singular SDK on route changes.
 * @example
 * // Track a page visit
 * trackPageVisit();
 */
function trackPageVisit() {
  window.singularSdk.pageVisit();
}

/**
 * A React component that initializes the Singular SDK on mount and tracks page visits on route changes.
 * @returns {JSX.Element} The component rendering the SPA content.
 * @example
 * // Use in a React SPA with react-router-dom
 * function App() {
 *   const location = useLocation();
 *   useEffect(() => {
 *     initSingularSDK();
 *   }, []);
 *   useEffect(() => {
 *     trackPageVisit();
 *   }, [location.pathname]);
 *   return <div>Your SPA Content</div>;
 * }
 */
function App() {
  const location = useLocation();

  useEffect(() => {
    initSingularSDK();
  }, []); // Run once on mount for SDK initialization

  useEffect(() => {
    trackPageVisit();
  }, [location.pathname]); // Run on route changes for page visits

  return (
    <div>Your SPA Content</div>
  );
}

export default App;

初期化コールバックを使った基本的な初期化

SDKの準備完了後にコードを実行する必要がある場合(たとえば、Singular Device IDを取得する場合など)、.withInitFinishedCallback() でコールバックを設定します:

/**
 * Initializes the Singular SDK with a callback to handle initialization completion.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * initSingularSDK(); // Initializes SDK and logs device ID
 */
function initSingularSDK() {
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId')
    .withInitFinishedCallback(function(initParams) {
      var singularDeviceId = initParams.singularDeviceId;
      // Example: Store device ID for analytics
      console.log('Singular Device ID:', singularDeviceId);
      // Optionally store in localStorage or use for event tracking
      // localStorage.setItem('singularDeviceId', singularDeviceId);
    });

  window.singularSdk.init(config);
}

/**
 * Triggers Singular SDK initialization when the DOM is fully parsed.
 */
document.addEventListener('DOMContentLoaded', function() {
  initSingularSDK();
});
  • を実際のSDKキーに置き換えてください。 'sdkKey' を実際のSDKキーに置き換えてください。
  • 実際のSDKキーに置き換えます。 'sdkSecret' を実際のSDKシークレットに置き換えます。
  • 実際の製品IDに置き換えます。 'productId' を実際のプロダクトIDに置き換えます。 com.website-name のようになり、SingularプラットフォームのAppsページにあるBundleIDの値と一致する必要があります。

設定オプション

.with メソッドを連結して追加機能を有効にすることで、WebSDK のセットアップを強化できます。

たとえば、Singular Device ID (SDID) をCookieに保存してクロスサブドメイントラッキングをサポートしたり、アクティブなログイン状態のサイト訪問者のためにカスタムユーザ ID を含めることができます:

Config with Options
var domain = 'website-name.com';
var config = new SingularConfig('sdkKey','sdkSecret','productId')
  .withAutoPersistentSingularDeviceId(domain)
  .withCustomUserId(userId);

SingularConfig メソッドリファレンス

使用可能なすべての ".with" メソッドを以下に示します。

メソッド 説明 詳細
.withCustomUserId(customId) ユーザーIDをSingularに送る ユーザーIDの設定
.withProductName(productName) 製品の表示名(オプション
.withLogLevel(logLevel) ロギングレベルの設定:0 - なし(デフォルト)、1 - 警告、2 - 情報、3 - デバッグ。
.withSessionTimeoutInMinutes(timeout) セッションのタイムアウトを分単位で設定(デフォルト:30分)

.withAutoPersistentSingularDeviceId(domain) 自動クロスサブドメイントラッキングを有効にする クッキーを使用した自動持続

.withPersistentSingularDeviceId(singularDeviceId)

手動でのクロスサブドメイントラッキングを有効にする 単一デバイスIDを手動で設定する
.withInitFinishedCallback(callback) SDK初期化完了時にコールバックを呼び出す 初期化完了時にコールバック関数を呼び出す

開発者向けチェックリスト

  • SDKの認証情報とプロダクトIDを集めます。
  • カスタム設定(ユーザーID、タイムアウトなど)が必要かどうかを決定します。
  • 上記の例を使って Singular Intitialization 関数と SingularConfig オブジェクトを作成します。
  • 初期化がページロード時に一度だけトリガーされることを必ずテストしてください。

ヒント Organic Searchトラッキングのような高度な設定の場合、Singular SDKが初期化される前にクエリーパラメーターを調整するなどのカスタムJavaScriptを実装する必要があるかもしれません。 変更が正しく取り込まれるように、カスタムコードがSingular初期化関数の前に実行されるようにしてください。オーガニック検索トラッキングの実装方法の詳細はこちらをご覧ください。


ステップ3:イベントのトラッキング

SDKを初期化した後、ユーザーがウェブサイト上で重要なアクションを行ったときにカスタムイベントをトラッキングすることができます。

重要Singularは重複イベントをブロックしません!ページのリフレッシュや重複を防止するのは開発者の責任です。誤った収益データを防ぐために、収益イベント専用の重複排除方法を組み込むことをお勧めします。例については、下記の "ステップ5:重複イベントの防止 "を参照してください

Basic EventConversion EventRevenue Event

基本的なイベントトラッキング

シンプルなイベントをトラッキングするか、有効なJSONを使用してカスタム属性を追加し、イベントに関するより多くのコンテキストを提供します:

// Basic event tracking
var eventName = 'page_view';
window.singularSdk.event(eventName);
      
// Optional: With attributes for more context
var eventName = 'sng_content_view';
var attributes = {
  key1: 'value1', // First custom attribute
  key2: 'value2'  // Second custom attribute
};
window.singularSdk.event(eventName, attributes);

一般的なイベント実装パターン

Page Load EventsButton Click EventsForm Submission Events

ページロードイベント

ページロードイベントトラッキングメカニズムは、JavaScriptを使用してウェブページが完全にロードされたタイミングを監視し、Singular SDKを使用して分析イベントをトリガーします。具体的には、window.addEventListener('load', ...) メソッドを活用し、すべてのページリソース(HTML、画像、スクリプトなど)がロードされたことを検出します。このイベントが発生すると、Singular SDKのevent 関数が呼び出され、関連する属性を持つカスタムイベントがログに記録され、ページビューや登録などのユーザーアクションの監視など、アナリティクス目的のユーザーインタラクションの追跡が可能になります。この仕組みはウェブ解析でよく使われ、ページ訪問や特定のアクションなど、ユーザーの行動に関するデータを取得し、カスタマイズ可能な属性で詳細な洞察を得ることができます。

このメカニズムは、以下に示すように、カスタム属性を持つ特定のイベント(例えば、page_view イベント)を追跡するために適応させることができます。イベント名と属性を別々に定義することで、コード構造はスッキリとモジュール化され、明快さと保守性が保たれています。

/**
 * Tracks a registration event with the Singular SDK when the page fully loads.
 * @param {string} eventName - The name of the event to track (e.g., 'page_view').
 * @param {Object} attributes - A JSON object with custom event attributes.
 * @param {string} attributes.key1 - First custom attribute (e.g., 'value1').
 * @param {string} attributes.key2 - Second custom attribute (e.g., 'value2').
 */
window.addEventListener('load', function() {
  var eventName = 'page_view';
  var attributes = {
    key1: 'value1', // First custom attribute
    key2: 'value2'  // Second custom attribute
  };
  window.singularSdk.event(eventName, attributes);
});

ステップ4:顧客ユーザーIDの設定

Singular SDKのメソッドを使用して、内部ユーザーIDをSingularに送信することができます。

注意: Singularのクロスデバイスソリューションを使用する場合は、すべてのプラットフォームでユーザーIDを収集する必要があります。

  • ユーザーIDはどのような識別子でもかまいませんが、PII(個人を特定できる情報)を公開するべきではありません。 例えば、ユーザーのメールアドレス、ユーザー名、電話番号は使用しないでください。Singularは、ファーストパーティデータにのみユニークなハッシュ値を使用することを推奨します。
  • Singularに渡すユーザーIDは、すべてのプラットフォーム(ウェブ/モバイル/PC/コンソール/オフライン)で同じ内部ユーザーIDを使用する必要があります。
  • Singularはユーザーレベルのエクスポート、ETL、内部BIポストバック(設定されている場合)にユーザーIDを含めます。ユーザーIDはファーストパーティデータであり、Singularが他者と共有することはありません。
  • ユーザーIDの値は、Singular SDKメソッドで設定されると、logout() メソッドで設定が解除されるまで、またはブラウザのローカルストレージが削除されるまで保持されます。ウェブサイトを閉じたり更新しても、ユーザーIDは解除されません。
  • プライベート/インコグニートモードでは、ブラウザが閉じられるとローカルストレージが自動的に削除されるため、SDKはユーザーIDを永続化できません。

ユーザIDを設定するには、login() メソッドを使用します。ユーザIDを解除するには(ユーザがアカウントを「ログアウト」した場合など)、logout() メソッドを呼び出します。

注:複数のユーザが1つのデバイスを使用する場合、ログインとログアウトのたびにユーザIDを設定および解除するログアウトフローを実装することを推奨します。

ウェブサイト上でSingular SDKが初期化されたときにすでにユーザーIDがわかっている場合は、設定オブジェクトにユーザーIDを設定します。こうすることで、Singularは最初のセッションからユーザーIDを持つことができます。しかし、ユーザーIDは通常、ユーザーが登録するかログインを実行するまで使用できません。その場合は、登録フローが完了した後にlogin() を呼び出してください。

ヒントモバイルSDKで使用するのと同じカスタマ・ユーザIDを使用してください。これにより、クロスデバイスのアトリビューションが可能になり、プラットフォーム間でのユーザー行動の完全なビューが提供されます。

カスタマー・ユーザーIDのベスト・プラクティス

  • ユーザーがログインまたはサインアップすると同時に設定する。
  • ウェブとモバイルのプラットフォームで同じIDを使用する。
  • 個人を特定できる情報(電子メール、電話番号)を使用しない。
  • 社内ユーザーIDまたはデータベースIDを使用する
  • 数字であっても、文字列として送信する:
Set the User IDUnset the User ID
// After user logs in or signs up
var userId = 'user_12345';
window.singularSdk.login(userId);

ステップ5:重複イベントの防止

重要です!これは最も一般的な実装ミスの1つです。適切な安全策を講じないと、イベントが複数回発生し、メトリクスが膨れ上がる可能性があります。

ウェブアプリケーションで収益イベントの重複を防ぐことは、正確な分析とコンバージョンの過剰報告を避けるために重要です。目標は、ユーザーがページを再読み込みしたり、不注意で複数の送信をトリガーした場合でも、各ユニークなイベントがセッションまたはページ訪問につき1回しか発生しないようにすることです。

  • イベントの詳細とオプションのカスタム属性に基づいて)各イベントに固有のキーが生成されます。
  • このキーはブラウザの sessionStorage(永続的な重複排除の場合は localStorage)に保存される。
  • 送信する前に、コードは同じペイロードを持つ収益イベントがすでに発生したかどうかをチェックする。
  • もしそうでなければ、イベントを送信し、キーを保存する。
  • そうであれば、繰り返しをブロックし、ユーザーまたは開発者に通知する。
Using Session Storage Method

セッション保存メソッドの使用

// Sends a revenue event to Singular WebSDK, preventing duplicates in the same session
// @param {string} eventName - Name of the revenue event (defaults to "web_purchase")
// @param {number} amount - Revenue amount (defaults to 0)
// @param {string} currency - Currency code (defaults to "USD", normalized to uppercase)
// @param {object} [attributes] - Optional key-value pairs for additional event data
function sendRevenueEvent(eventName, amount, currency, attributes) {
  // Normalize inputs: set defaults and ensure currency is uppercase
  eventName = eventName ? eventName : "web_purchase";
  currency = currency ? currency.toUpperCase() : "USD";
  amount = amount ? amount : 0;

  // Create a payload object to standardize event data
  var payload = {
    eventName: eventName,
    amount: amount,
    currency: currency,
    attributes: attributes ? attributes : null // Null if no attributes provided
  };

  // Generate a unique key for sessionStorage by hashing the payload
  // This ensures deduplication of identical events in the same session
  var storageKey = 'singular_revenue_' + btoa(JSON.stringify(payload));

  // Check if the event was already sent in this session to prevent duplicates
  if (!sessionStorage.getItem(storageKey)) {
    // Mark event as sent in sessionStorage
    sessionStorage.setItem(storageKey, 'true');

    // Send revenue event to Singular SDK, including attributes if provided and valid
    if (attributes && typeof attributes === 'object' && Object.keys(attributes).length > 0) {
      window.singularSdk.revenue(eventName, currency, amount, attributes);
    } else {
      // Fallback to basic revenue event without attributes
      window.singularSdk.revenue(eventName, currency, amount);
    }
    // Log event details for debugging
    console.log("Revenue event sent:", payload);
  } else {
    // Log and alert if a duplicate event is detected
    console.log("Duplicate revenue event prevented:", payload);
    alert("Duplicate revenue event prevented!");
  }
}

ステップ6:実装のテスト

SDKを実装したら、ブラウザの開発者ツールを使用して、SDKが正しく動作することを確認します。

SDKのロードを確認する

  1. ブラウザでウェブサイトを開きます。
  2. デベロッパーツールを開く(F12または右クリック→Inspect)
  3. コンソールタブに移動します。
  4. typeof singularSdk と入力し、Enterキーを押します。
  5. undefined "ではなく、"function "オブジェクトが表示されるはずです。

ネットワークリクエストの確認

  1. デベロッパーツールを開く(F12)
  2. ネットワークタブに移動する
  3. ページをリロードする
  4. singular またはsdk-apiでフィルターをかける
  5. sdk-api-v1.singular.netへのリクエストを探す
  6. リクエストをクリックして詳細を見る
  7. ステータスコードが200であることを確認する
  8. リクエストのペイロードにプロダクトIDが含まれていることを確認します。これは、ペイロードの "i" パラメータにあります。

成功! sdk-api-v1.singular.net へのリクエストがステータスコード 200 で表示されたら、SDK は Singular に正常にデータを送信しています。

イベントの確認

  1. ウェブサイト上でイベントをトリガーします(ボタンのクリック、フォームの送信など)。
  2. ネットワークタブで、sdk-api-v1.singular.netへの新しいリクエストを探します。
  3. リクエストをクリックし、ペイロードまたはリクエストタブを表示します。
  4. イベント名「n 」パラメータがリクエストに表示されていることを確認する。

    event_test.png

エンドツーエンドのテストには、テストコンソールを使用します。

Singularダッシュボードで利用可能なSDKコンソールを使用して、Web SDKの統合をテストできます。

  1. Singularプラットフォームで、Developer Tools > Testing Consoleに進みます。
  2. Add Deviceをクリックします。
  3. プラットフォーム「Web」を選択し、SingularデバイスIDを入力します。
  4. ブラウザのペイロードからSingularデバイスIDを取得します。上記のスクリーンショットを確認し、イベント上のSDID を見つけてください。
  5. 別ウィンドウでテストしている間、テストコンソールはアクティブ状態のままでなければなりません。コンソールが閉じている間やオフラインの間は、トリガーされたイベントは表示されません。
  6. SDID を追加したら、Web ページをリロードしていくつかのイベントをトリガーすることができます。SDKコンソールには「__PAGE_VISIT__」やその他のイベント(トリガーされた場合)が表示されます。
  7. エクスポートログ(Reports & Insights Export Logs)を利用することも、テスト結果を検証する方法です。このデータセットは1時間遅延します。

ステップ7:Web-to-Appフォワーディングの実装

ウェブからアプリへのアトリビューション転送

Singular WebSDK を使用して、ウェブサイトからモバイルアプリへのユーザージャーニーを追跡し、モバイルアプリのインストールと再エンゲージメントへの正確なウェブキャンペーン帰属を可能にします。以下の手順に従って、デスクトップユーザー向けのQRコードサポートを含む、ウェブからアプリへの転送を設定してください。

  1. ウェブサイトからモバイルアプリへのアトリビューション転送ガイドに従って、モバイルウェブアトリビューション用にSingular WebSDKを設定してください。
  2. デスクトップ Web-to-App トラッキングの場合:
    • 上記のガイドのセットアップを完了してください。
    • Singular Mobile Web-to-App Link(例:https://yourlink.sng.link/... )とWebSDK関数buildWebToAppLink(link)QRCode.jsのようなダイナミックQRコードライブラリを使用します。
    • リンクをエンコードしたQRコードをウェブページに生成します。デスクトップユーザーはモバイルデバイスでQRコードをスキャンしてアプリを開き、アトリビューション用のキャンペーンデータを渡すことができます。
    • QRコード生成とウェブからアプリへのリンク処理の実例については、Singular WebSDKデモをご覧ください。

ヒントモバイルのアプリ内ブラウザのウェブビュー(Facebook、Instagram、TikTokで使用されているような)は、ユーザーがデバイスのネイティブブラウザに移動するとSingularデバイスIDが変更され、アトリビューションが中断される可能性があります。

これを防ぐには、各広告ネットワークで常に適切な Singular トラッキングリンク形式を使用してください:


Singular WebSDKデモ

下記はドキュメント化されたSingular WebSDK APIを使用したシンプルで包括的な実装です。このサンプルでは、カスタムイベントコンバージョンイベント収益イベント、そしてWebSDK がサポートする Web-to-App 転送を使用した Web-to-App リンクサポートを明確に分離しています。

このコードをローカルの HTML ファイルで実行したり、サイト内で変更して高度な統合やトラブルシューティングを行うことができます。

Singular WebSDK デモ (ソースコード)
#

以下のコードをコピーして HTML ファイルとして貼り付け、ブラウザで開いてください。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  <title>Singular WebSDK Demo</title>
  <script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>
  <script src="https://web-sdk-cdn.singular.net/singular-sdk/latest/singular-sdk.js"></script>

  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', Roboto, sans-serif;
      line-height: 1.6;
      color: #1a1d29;
      background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
      padding: 40px 20px;
      min-height: 100vh;
    }

    .container {
      max-width: 800px;
      margin: 0 auto;
      background: white;
      padding: 48px;
      border-radius: 16px;
      box-shadow: 0 4px 24px rgba(30, 41, 59, 0.08);
      border: 1px solid rgba(148, 163, 184, 0.1);
    }

    .header-brand {
      display: flex;
      align-items: center;
      margin-bottom: 32px;
      padding-bottom: 24px;
      border-bottom: 2px solid #f1f5f9;
    }

    .brand-dot {
      width: 12px;
      height: 12px;
      background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
      border-radius: 50%;
      margin-right: 12px;
    }

    h1 {
      font-size: 32px;
      font-weight: 700;
      color: #0f172a;
      letter-spacing: -0.025em;
    }

    h2 {
      font-size: 24px;
      font-weight: 600;
      margin: 48px 0 24px 0;
      color: #1e293b;
      padding-bottom: 12px;
      border-bottom: 2px solid #e2e8f0;
      position: relative;
    }

    h2:before {
      content: '';
      position: absolute;
      bottom: -2px;
      left: 0;
      width: 60px;
      height: 2px;
      background: linear-gradient(90deg, #6366f1, #8b5cf6);
    }

    p {
      margin-bottom: 18px;
      color: #475569;
      font-size: 15px;
    }

    strong {
      color: #1e293b;
      font-weight: 600;
    }

    .form-group {
      margin-bottom: 24px;
    }

    .radio-group {
      display: flex;
      gap: 24px;
      margin-bottom: 24px;
      padding: 16px;
      background: #f8fafc;
      border-radius: 12px;
      border: 1px solid #e2e8f0;
    }

    .radio-group label {
      display: flex;
      align-items: center;
      gap: 10px;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
      color: #475569;
      transition: color 0.2s ease;
    }

    .radio-group label:hover {
      color: #1e293b;
    }

    input[type="radio"] {
      width: 20px;
      height: 20px;
      cursor: pointer;
      accent-color: #6366f1;
    }

    input[type="text"] {
      width: 100%;
      padding: 14px 18px;
      font-size: 15px;
      font-weight: 400;
      border: 2px solid #e2e8f0;
      border-radius: 12px;
      transition: all 0.2s ease;
      background: #fafbfc;
      color: #1e293b;
    }

    input[type="text"]:focus {
      outline: none;
      border-color: #6366f1;
      background: white;
      box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
    }

    input[type="text"]::placeholder {
      color: #94a3b8;
      font-weight: 400;
    }

    button {
      padding: 14px 32px;
      font-size: 15px;
      font-weight: 700;
      color: #fff;
      background: linear-gradient(90deg, #2363f6 0%, #1672fe 100%);
      border: none;
      border-radius: 4px;  /* Pill shape */
      cursor: pointer;
      box-shadow: 0 2px 8px rgba(35, 99, 246, 0.12);
      transition: background 0.2s, transform 0.2s;
      letter-spacing: 0.03em;
    }
    button:hover {
      background: linear-gradient(90deg, #1672fe 0%, #2363f6 100%);
      transform: translateY(-2px);
      box-shadow: 0 8px 24px rgba(22, 114, 254, 0.18);
    }
    button:active {
      transform: translateY(0);
    }

    a {
      color: #6366f1;
      text-decoration: none;
      font-weight: 600;
      transition: all 0.2s ease;
      position: relative;
    }

    a:hover {
      color: #4f46e5;
    }

    a:after {
      content: '';
      position: absolute;
      width: 0;
      height: 2px;
      bottom: -2px;
      left: 0;
      background: #6366f1;
      transition: width 0.2s ease;
    }

    a:hover:after {
      width: 100%;
    }

    ol {
      margin-left: 10px;
      margin-bottom: 18px;
    }

    ol li {
      margin-left: 10px;
    }

    .info-box {
      background: linear-gradient(135deg, #eff6ff 0%, #f0f9ff 100%);
      border-left: 4px solid #6366f1;
      padding: 20px;
      margin: 24px 0;
      border-radius: 12px;
      border: 1px solid rgba(99, 102, 241, 0.1);
    }

    .info-box p {
      color: #1e40af;
      margin-bottom: 0;
    }

    .section-label {
      font-size: 12px;
      font-weight: 700;
      color: #6b7280;
      margin-bottom: 10px;
      text-transform: uppercase;
      letter-spacing: 1px;
    }

    .button-group {
      display: flex;
      align-items: center;
      gap: 16px;
      flex-wrap: wrap;
    }

    .analytics-badge {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
      color: white;
      padding: 6px 12px;
      border-radius: 20px;
      font-size: 12px;
      font-weight: 600;
      margin-bottom: 16px;
    }

    .feature-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 16px;
      margin: 24px 0;
    }

    .feature-item {
      padding: 16px;
      background: #f8fafc;
      border-radius: 10px;
      border: 1px solid #e2e8f0;
      text-align: center;
    }

    .feature-item strong {
      display: block;
      color: #6366f1;
      font-size: 14px;
      margin-bottom: 4px;
    }

    .metric-dot {
      width: 8px;
      height: 8px;
      background: #10b981;
      border-radius: 50%;
      display: inline-block;
      margin-right: 8px;
      animation: pulse 2s infinite;
    }

    @keyframes pulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.5; }
    }
  </style>

</head>

<body>
  <div class="container">
    <div class="header-brand">
      <div class="brand-dot"></div>
      <h1>Singular WebSDK Demo</h1>
    </div>

    <p>
      Below is a simple but comprehensive implementation using the documented Singular WebSDK API. 
      This sample provides clear separation for <strong>custom events</strong>, <strong>conversion events</strong>, <strong>revenue events</strong>, and <strong>web-to-app link support using WebSDK supported Web-to-App forwarding</strong>. </p>
      <p>You can run this code in a local HTML file or modify it in your site for advanced integration and troubleshooting.
    </p>

    <h2>SDK Initialization Testing</h2>
    <ol>
      <li>Open your browsers developer tools.</li>
      <li>Inspect the Network tab and filter for "sdk-api-v1.singular.net"</li>
      <li>Refresh this page, and examin the payload of the request.</li>
    </ol>


    <hr>

    <h2>User Authentication</h2>
    <p>User authentication is key to supporting cross-device attribution. Pass the common User ID value that would also be sent to Singular from mobile SDKs for cross-device continuity.</p>
    <div class="form-group">
      <div class="section-label">Simulate User Authentication</div>
      <input type="text" id="userId" placeholder="Enter a User ID"/>
    </div>
    <div class="form-group">
      <div class="button-group">
        <button onclick="setUserId()">Set the User ID (Login)</button>
        <button onclick="unsetUserId()">Unset the User ID (Logout)</button>
      </div>
    </div>

    <hr>

    <h2>SDK Event Testing</h2>
    <div class="form-group">
      <div class="section-label">Event Type Selection</div>
      <div class="radio-group">
        <label>
          <input type="radio" name="eventType" value="custom" checked onchange="handleRevenueFields(this)">
          <span>Custom Event</span>
        </label>
        <label>
          <input type="radio" name="eventType" value="conversion" onchange="handleRevenueFields(this)">
          <span>Conversion Event</span>
        </label>
        <label>
          <input type="radio" name="eventType" value="revenue" onchange="handleRevenueFields(this)">
          <span>Revenue Event</span>
        </label>
      </div>
    </div>

    <div class="form-group">
      <div class="section-label">Event Configuration</div>
      <input type="text" id="eventName" placeholder="Enter event name"/>
    </div>

    <div class="form-group" id="currencyGroup" style="display:none">
      <input type="text" id="currency" placeholder="Currency code (e.g., USD, EUR)"/>
    </div>

    <div class="form-group" id="amountGroup" style="display:none">
      <input type="text" id="amount" placeholder="Revenue amount (e.g., 29.99)"/>
    </div>

    <div class="form-group" id="attributesGroup" style="display:none">
      <input type="text" id="attributes" placeholder='Optional attributes (JSON, e.g. {"color":"blue"})'/>
    </div>

    <div class="form-group">
      <button id="send" onclick="sendCustomEvent()">Send Event to Singular</button>
    </div>

    <hr>
    <h2>Web-to-App Forwarding</h2>
    <div class="form-group">
    <ol>
      <li><a href="https://support.singular.net/hc/ja/articles/360042283811" target="_blank">Learn more about Web-to-App Forwarding</a></li>
    <li>In the page source code you will find the mobile web-to-app base link "<strong>https://seteam.sng.link/D0mvs/y3br?_smtype=3</strong>" used for demonstration purposes.</li>
    <li>Replace all occurances of the link with your own Singular Mobile Web-to-App link to test functionality.</li>
    <li>Add Singular Web Parameters to the URL and refresh the page. (example: "<strong>?wpsrc=SingularTest&wpcn=MyCampaign</strong>").</li>
  </ol>

    <hr>
    <div class="section-label">Mobile Web-to-App Test</div>
    <div class="form-group">
      <div class="button-group">
        <button onclick="displayWebLink()">Display Constructed Web-to-App Link</button>
        <button onclick="testWebLink()">Test Web-to-App Link</button>
      </div>
    </div>
  </div>
    <hr>
    <div class="section-label">Desktop Web-to-App Configuration</div>
    <div class="form-group">
    <p>Use a dynamic QR Code generation library like <a href="https://davidshimjs.github.io/qrcodejs/" target="_blank">QRCode.js</a> to build and display a QR code using the Singular Mobile Web-to-App link with constructed campaign parameters.</p>
    <div id="qrcode"></div>
    </div>

  </div>
  
  <script>
    /**
     * Initializes the Singular SDK with the provided configuration.
     * @param {string} sdkKey - The SDK key for Singular (e.g., 'se_team_9b3431b0').
     * @param {string} sdkSecret - The SDK secret for Singular (e.g., 'bcdee06e8490949422c071437da5c5ed').
     * @param {string} productId - The product ID for Singular (e.g., 'com.website').
     * @example
     * // Initialize the Singular SDK
     * initSingularSDK('se_team_9b3431b0', 'bcdee06e8490949422c071437da5c5ed', 'com.website');
     */
    function initSingularSDK() {
      var sdkKey = 'se_team_9b3431b0';
      var sdkSecret = 'bcdee06e8490949422c071437da5c5ed';
      var productId = 'com.website';
      
      var config = new SingularConfig(sdkKey, sdkSecret, productId)
        .withInitFinishedCallback(function(initParams) {
          var singularDeviceId = initParams.singularDeviceId;
          console.log('Singular Device ID:', singularDeviceId);
          // Optionally store for use in events
          // sessionStorage.setItem('singularDeviceId', singularDeviceId);
        });

      window.singularSdk.init(config);
      generateDesktopWebToAppQRCode(); // Generate QR code after initialization
    }

    /**
     * Triggers Singular SDK initialization when the DOM is fully parsed.
     * @example
     * document.addEventListener('DOMContentLoaded', function() {
     *   initSingularSDK();
     * });
     */
    document.addEventListener('DOMContentLoaded', function() {
      initSingularSDK();
    });


    // Fires a custom event using the Singular SDK
    function sendCustomEvent() {
      var eventName = document.getElementById('eventName').value;
      window.singularSdk.event(eventName);
    }

    // Fires a conversion event using the Singular SDK
    function sendConversionEvent() {
      var eventName = document.getElementById('eventName').value;
      window.singularSdk.conversionEvent(eventName);
    }

    // Fires a revenue event only once per session with deduplication
    // Parameters:
    //   - eventName: string (name of event), default "web_purchase" if blank
    //   - amount: revenue amount, default 0 if blank
    //   - currency: string, defaults to "USD" if blank
    //   - attributes: optional object with extra payload
    function sendRevenueEvent(eventName, amount, currency, attributes) {
      // Fill in defaults and normalize values
      eventName = eventName ? eventName : "web_purchase";
      currency = currency ? currency.toUpperCase() : "USD";
      amount = amount ? amount : 0;

      // Create a unique key based on event data for deduplication
      // btoa(JSON.stringify(...)) ensures order consistency in local/sessionStorage
      var payload = {
        eventName: eventName,
        amount: amount,
        currency: currency,
        attributes: attributes ? attributes : null
      };
      var storageKey = 'singular_revenue_' + btoa(JSON.stringify(payload));

      // Only fire event if no identical event has already fired in this tab/session
      if (!sessionStorage.getItem(storageKey)) {
        sessionStorage.setItem(storageKey, 'true');
        if (attributes && typeof attributes === 'object' && Object.keys(attributes).length > 0) {
          // Fire event with attributes payload
          window.singularSdk.revenue(eventName, currency, amount, attributes);
        } else {
          // Fire simple revenue event
          window.singularSdk.revenue(eventName, currency, amount);
        }
        console.log("Revenue event sent:", payload);
      } else {
        // Duplicate detected, don't send to SDK
        console.log("Duplicate revenue event prevented:", payload);
        alert("Duplicate revenue event prevented!");
      }
    }

    // Collects form values, validates attributes JSON, and calls sendRevenueEvent()
    // Ensures only valid values are sent and blocks on malformed JSON
    function fireFormRevenueEvent() {
      var eventName = document.getElementById('eventName').value;
      var currency = document.getElementById('currency').value;
      var amount = document.getElementById('amount').value;
      
      var attributesRaw = document.getElementById('attributes').value;
      var attributes = null;
      if (attributesRaw) {
        try {
          // Try to parse the optional attributes field from JSON string
          attributes = JSON.parse(attributesRaw);
        } catch (e) {
          alert("Optional attributes must be valid JSON.");
          return; // Stop if invalid JSON
        }
      }

      // Calls main revenue logic for deduplication and event sending
      sendRevenueEvent(eventName, amount, currency, attributes);
    }

    // Controls which form fields are visible depending on selected event type
    // Revenue events require currency, amount, and attributes fields
    function handleRevenueFields(sender) {
      var isRevenue = sender.value === "revenue";
      document.getElementById("currencyGroup").style.display = isRevenue ? "block" : "none";
      document.getElementById("amountGroup").style.display = isRevenue ? "block" : "none";
      document.getElementById("attributesGroup").style.display = isRevenue ? "block" : "none";

      // Dynamically assign the event button's onclick handler
      var send = document.getElementById("send");
      send.onclick = (sender.value === "custom") ? sendCustomEvent
        : (sender.value === "conversion") ? sendConversionEvent
        : fireFormRevenueEvent; // Only fires revenue logic for "revenue"
    }

    // Opens demo Singular web-to-app link in a new tab/window
    function testWebLink() {
      var singularWebToAppBaseLink = 'https://seteam.sng.link/D0mvs/y3br?_smtype=3';
      window.open(singularWebToAppBaseLink);
    }

    // Displays constructed Singular web-to-app link with campaign parameters
    function displayWebLink() {
      var singularWebToAppBaseLink = 'https://seteam.sng.link/D0mvs/y3br?_smtype=3';
      var builtLink = window.singularSdk.buildWebToAppLink(singularWebToAppBaseLink);
      console.log("Singular Web-to-App Link: ", builtLink);
      alert("Singular Web-to-App Link: " + builtLink);
    }

    // Generates QR code for desktop deep linking using Singular Mobile Web-to-App link
    function generateDesktopWebToAppQRCode() {
      var singularWebToAppBaseLink = 'https://seteam.sng.link/D0mvs/y3br?_smtype=3';
      const value = window.singularSdk.buildWebToAppLink(singularWebToAppBaseLink);
      new QRCode(document.getElementById("qrcode"), {
        text: value,
        width: 128,
        height: 128,
        colorDark: "#000",
        colorLight: "#fff",
        correctLevel: QRCode.CorrectLevel.H
      });
    }

    // Simulate user authentication and send login event
    function setUserId() {
      var userId = document.getElementById('userId').value;
      window.singularSdk.login(userId);
      console.log("Singular User ID is Set to: " + userId);
      window.singularSdk.event("sng_login");
    }

    // Simulate user logout and unset Singular user ID
    function unsetUserId() {
      window.singularSdk.logout();
      console.log("Singular User ID is Unset");
    }
  </script>
</body>
</html>

高度なトピック

シングラーバナー

シングラーバナーを有効にする
#

インフォメーションシングラーバナーは企業向け機能です。この機能の詳細については、カスタマーサクセスマネージャーにお問い合わせください。

シンギュラーバナーをモバイルウェブサイトに表示することで、ウェブユーザーをシームレスにアプリに誘導し、最も関連性の高いアプリコンテンツを表示することができます。お客様のウェブサイトでSingular Bannersを有効にすると、Singular Banners UIからバナーのデザイン、デプロイ、メンテナンスを簡単に行うことができます。

関連記事

ステップバイステップ実装ガイド

  1. Singular Native JavaScript WebSDK スクリプトをウェブサイトに追加します。

    バナーの実装を進める前に、上記の統合ガイドに従ってSingular Native JavaScript WebSDK をウェブサイトに追加してください。

  2. Singular にクライアントヒントデータへのアクセス許可を与える。

    2023年2月から3月にかけてChromiumベースのウェブブラウザのユーザーエージェントデータが制限されたため、広告主はクライアントヒントデータを取得し、Singular WebSDKにこのデータを受け取る許可を与える必要があります。詳しくはバナーFAQをご覧ください。

    ウェブサイトが必要とするもの

    • クライアントヒント(accept-ch header )の取得を開始する。
    • ブラウザがフェッチバナーリクエストでクライアントヒントをSingularに送信するように(サードパーティとして)Singularに許可を与える (permissions-policy header)。

    ページ読み込みレスポンスに以下のレスポンスヘッダを追加する:

    accept-ch:
    sec-ch-ua-model,
    sec-ch-ua-platform-version,
    sec-ch-ua-full-version-list
    
    permissions-policy:
    ch-ua-model=("https://sdk-api-v1.singular.net"),
    ch-ua-platform-version=("https://sdk-api-v1.singular.net"),
    ch-ua-full-version-list=("https://sdk-api-v1.singular.net")
  3. WebSDK の初期化に Banner config オプションを追加する。

    Singularバナーは、元のマーケティングソースからの帰属を保持しながら、スマートアプリのダウンロードプロンプトをモバイルウェブサイトに表示します。Web-to-App サポートを有効にすると、SDK はどの広告ネットワークまたはキャンペーンがユーザーをサイトに導いたかを追跡し、その後のアプリのインストールを元のソースに帰着させます。

    /**
     * Initialize Singular SDK with Banner support and web-to-app attribution.
     * This enables smart banners that direct mobile web users to download your app
     * while preserving the original marketing source (UTM parameters, ad network, etc.)
     * for proper attribution when the app is installed.
     * @param {string} sdkKey - Your Singular SDK Key
     * @param {string} sdkSecret - Your Singular SDK Secret
     * @param {string} productId - Your Product ID (e.g., com.your-website)
     */
    function initSingularSDK() {
      // Enable web-to-app tracking to preserve attribution data
      var bannersOptions = new BannersOptions().withWebToAppSupport();
      
      // Configure SDK with banner support
      var config = new SingularConfig(sdkKey, sdkSecret, productId)
        .withBannersSupport(bannersOptions);
      
      // Initialize the SDK
      window.singularSdk.init(config);
      
      // Display the smart banner
      window.singularSdk.showBanner();
    }
    
    // Call on DOM ready
    document.addEventListener('DOMContentLoaded', function() {
      initSingularSDK();
    });
  4. ページルートでのバナー再表示(シングルページアプリケーションのみ)

    アプリケーションがシングルページアプリケーションの場合、各ページルートでバナーを非表示にし、再表示する必要があります。これにより、Singularがウェブ体験に適したバナーを配信するようになります。

    バナーの非表示と再表示を行うには、以下のコードを使用してください:

    window.singularSdk.hideBanner();
    window.singularSdk.showBanner();
  5. [詳細オプション] リンク設定のカスタマイズ

    Singularはコードを通してバナーのリンクをパーソナライズする方法を提供します。

    リンクをパーソナライズするには

    • LinkParams オブジェクトを作成し、以下の関数を1つ以上使用してください。これはwindow.singularSdk.showBanner() を呼び出す前に行ってください。
    • そしてwindow.singularSdk.showBanner() を呼び出すときにLinkParams オブジェクトを渡します。

    // Define a LinkParams object
    let bannerParams = new LinkParams();
    
    // Configure link options (see details on each option below)
    bannerParams.withAndroidRedirect("androidRedirectValue");
    bannerParams.withAndroidDL("androidDLParamValue");
    bannerParams.withAndroidDDL("androidDDLparamValue");
    bannerParams.withIosRedirect("iosRedirectValue");
    bannerParams.withIosDL("iosDLValue");
    bannerParams.withIosDDL("iosDDLValue");
    
    // Show the banner with the defined options
    window.singularSdk.showBanner(bannerParams);

    オプションのリスト

    メソッド 説明
    withAndroidRedirect Androidアプリのダウンロードページ(通常はPlayストアのページ)へのリダイレクトリンクを渡します。
    withAndroidDL Androidアプリ内のページへのディープリンクを渡します。
    withAndroidDDL 延期されたディープリンク、つまり、ユーザーがまだインストールしていないAndroidアプリのページへのリンクを渡します。
    withIosRedirect iOSアプリのダウンロードページ(通常はApp Storeページ)へのリダイレクトリンクを渡します。
    withIosDL iOSアプリ内のページへのディープリンクを渡します。
    withIosDDL 延期されたディープリンク、つまりユーザーがまだインストールしていないiOSアプリ内のページへのリンクを渡します。
  6. [詳細オプション] コードを使ってバナーを強制的に隠す/表示する

    ステップ3で述べたように、単一ページのアプリケーションの場合、適切なバナーが配信されるように、各ページのルートでhideBanner()showBanner() メソッドを使用する必要があります(上記参照)。

    hideBanner() また、表示されているバナーを非表示にしたい場合や、非表示にしたバナーを再度表示したい場合にも、showBanner()

    メソッド 説明
    singularSdk.hideBanner() 表示されているバナーをページから非表示にします。
    singularSdk.showBanner() 設定済みのバナーを表示する。
    singularSdk.showBanner(params) 設定済みのバナーを表示するが、linkParamsオブジェクトで定義されたオプションでリンクを上書きする(ステップ4参照)。

グローバル・プロパティ

グローバル・プロパティ
#

Singular SDKでは、アプリから送信されるすべてのセッションやイベントと一緒にSingularサーバーに送信されるカスタムプロパティを定義できます。これらのプロパティは、ユーザーやアプリのモード/ステータスなど、どんな情報でも表すことができます。

  • 有効なJSONオブジェクトとして、最大5つのグローバルプロパティを定義できます。グローバルプロパティは、クリアされるかブラウザのコンテキストが変更されるまで、ブラウザlocalstorage に保持されます。

  • 各プロパティ名と値の長さは200文字までです。これより長いプロパティ名や値を渡すと、200文字に切り詰められます。

  • グローバルプロパティは現在Singularのユーザーレベルのイベントログ(属性ログのエクスポートを参照)とポストバックに反映されます。

  • グローバルプロパティはSingularからサードパーティへのポストバックで送信することができます。

WebSDKが初期化される前にグローバルプロパティの設定をサポートするには、カスタムsetGlobalPropertyBeforeInit関数を実装し、SDK初期化の前にこれを呼び出す必要があります。初期化前にグローバルプロパティを設定する必要がない場合は、このカスタムコードを省略できます。

初期化後にGlobal Properitesを処理するには、SDK関数を使用する必要があります:setGlobalProperties() getGlobalProperties() clearGlobalProperties()

setGlobalPropertyBeforeInit()setGlobalProperties()getGlobalProperties()clearGlobalProperties()
/**
 * Set a Singular global property before SDK initialization.
 * Allows up to 5 key/value pairs. Optionally overwrites existing value for a key.
 * 
 * @param {string} sdkKey - The Singular SDK key.
 * @param {string} productId - The Singular product ID.
 * @param {string} propertyKey - The property key to set.
 * @param {string} propertyValue - The property value to set.
 * @param {boolean} overrideExisting - Whether to overwrite the property if it already exists.
 */
function setGlobalPropertyBeforeInit(sdkKey, productId, propertyKey, propertyValue, overrideExisting) {
  const storageKey = `${sdkKey}_${productId}_global_properties`;
  let properties = {};

  // Try to load any existing properties
  const existing = localStorage.getItem(storageKey);
  if (existing) {
    try {
      properties = JSON.parse(existing) || {};
    } catch (e) {
      // If parsing fails, reset properties
      properties = {};
    }
  }

  // Only allow up to 5 keys
  const propertyExists = Object.prototype.hasOwnProperty.call(properties, propertyKey);
  const numKeys = Object.keys(properties).length;
  if (!propertyExists && numKeys >= 5) {
    console.warn("You can only set up to 5 Singular global properties.");
    return;
  }

  // Apply logic for overwrite or skip
  if (propertyExists && !overrideExisting) {
    console.log(`Global property '${propertyKey}' exists and overrideExisting is false; property not changed.`);
    return;
  }

  properties[propertyKey] = propertyValue;
  localStorage.setItem(storageKey, JSON.stringify(properties));
  console.log("Singular Global Properties set:", storageKey, properties);
}

オーガニック検索トラッキング

オーガニック検索トラッキングの例
#

重要この例は、Organic Searchトラッキングを有効にするための回避策として提供されています。このコードはあくまでも例として使用し、マーケティング部門のニーズに基づいてウェブ開発者が更新および維持する必要があります。 Organic Searchトラッキングは広告主ごとに異なる意味を持つ場合があります。 サンプルを確認し、ニーズに合わせて調整してください。

これを使用する理由

  • キャンペーンパラメータが存在しない場合でも、オーガニック検索の訪問が適切にトラッキングされるようにします。

  • Singular「Source」パラメータwpsrc(リファラー)の値と「Campaign Name」パラメータwpcn 「OrganicSearch」としてURLに追加し、明確なアトリビューションを実現します。

  • 後で使用できるように、現在のURLとリファラーをlocalStorage

  • 純粋なJavaScriptで、依存性はゼロ。

仕組み

  1. ページのURLにGoogle、Facebook、TikTok、UTMなどのキャンペーンパラメータがあるかチェックします。

  2. キャンペーンパラメータが存在せず、リファラーが検索エンジンの場合、:

    • wpsrc (リファラーを値として)
    • wpcn (OrganicSearchを値として)
  3. ページをリロードすることなく、ブラウザのURLを更新します。

  4. 現在のURLとリファラーをsng_urlsng_ref としてlocalStorageに保存します。

使用方法

  1. setupOrganicSearchTracking.js をライブラリとしてサイトに追加します。
  2. WebSDK を初期化する前に setupOrganicSearchTracking() 関数を呼び出します。
setupOrganicSearchTracking.js
// singular-web-organic-search-tracking: setupOrganicSearchTracking.js
// Tracks organic search referrals by appending wpsrc and wpcn to the URL if no campaign parameters exist and the referrer is a search engine.

// Configuration for debugging (set to true to enable logs)
const debug = true;

// List of campaign parameters to check for exclusion
const campaignParams = [
    'gclid', 'fbclid', 'ttclid', 'msclkid', 'twclid', 'li_fat_id',
    'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'wpsrc'
];

// Whitelist of legitimate search engine domains (prevents false positives)
const legitimateSearchEngines = new Set([
    // Google domains
    'google.com', 'google.co.uk', 'google.ca', 'google.com.au', 'google.de', 
    'google.fr', 'google.it', 'google.es', 'google.co.jp', 'google.co.kr',
    'google.com.br', 'google.com.mx', 'google.co.in', 'google.ru', 'google.com.sg',
    
    // Bing domains  
    'bing.com', 'bing.co.uk', 'bing.ca', 'bing.com.au', 'bing.de',
    
    // Yahoo domains
    'yahoo.com', 'yahoo.co.uk', 'yahoo.ca', 'yahoo.com.au', 'yahoo.de',
    'yahoo.fr', 'yahoo.it', 'yahoo.es', 'yahoo.co.jp',
    
    // Other search engines
    'baidu.com', 'duckduckgo.com', 'yandex.com', 'yandex.ru',
    'ask.com', 'aol.com', 'ecosia.org', 'startpage.com', 
    'qwant.com', 'seznam.cz', 'naver.com', 'daum.net'
]);

// Extract main domain from hostname (removes subdomains)
function getMainDomain(hostname) {
    if (!hostname) return '';
    
    const lowerHost = hostname.toLowerCase();
    
    // Handle special cases for known search engines with country codes
    const searchEnginePatterns = {
        'google': (host) => {
            // Match google.TLD patterns more precisely
            if (host.includes('google.co.') || host.includes('google.com')) {
                const parts = host.split('.');
                const googleIndex = parts.findIndex(part => part === 'google');
                if (googleIndex !== -1 && googleIndex < parts.length - 1) {
                    return parts.slice(googleIndex).join('.');
                }
            }
            return null;
        },
        'bing': (host) => {
            if (host.includes('bing.co') || host.includes('bing.com')) {
                const parts = host.split('.');
                const bingIndex = parts.findIndex(part => part === 'bing');
                if (bingIndex !== -1 && bingIndex < parts.length - 1) {
                    return parts.slice(bingIndex).join('.');
                }
            }
            return null;
        },
        'yahoo': (host) => {
            if (host.includes('yahoo.co') || host.includes('yahoo.com')) {
                const parts = host.split('.');
                const yahooIndex = parts.findIndex(part => part === 'yahoo');
                if (yahooIndex !== -1 && yahooIndex < parts.length - 1) {
                    return parts.slice(yahooIndex).join('.');
                }
            }
            return null;
        }
    };
    
    // Try specific patterns for major search engines
    for (const [engine, patternFn] of Object.entries(searchEnginePatterns)) {
        if (lowerHost.includes(engine)) {
            const result = patternFn(lowerHost);
            if (result) return result;
        }
    }
    
    // Handle other known engines with simple mapping
    const otherEngines = {
        'baidu.com': 'baidu.com',
        'duckduckgo.com': 'duckduckgo.com', 
        'yandex.ru': 'yandex.ru',
        'yandex.com': 'yandex.com',
        'ask.com': 'ask.com',
        'aol.com': 'aol.com',
        'ecosia.org': 'ecosia.org',
        'startpage.com': 'startpage.com',
        'qwant.com': 'qwant.com',
        'seznam.cz': 'seznam.cz',
        'naver.com': 'naver.com',
        'daum.net': 'daum.net'
    };
    
    for (const [domain, result] of Object.entries(otherEngines)) {
        if (lowerHost.includes(domain)) {
            return result;
        }
    }
    
    // Fallback: Extract main domain by taking last 2 parts (for unknown domains)
    const parts = hostname.split('.');
    if (parts.length >= 2) {
        return parts[parts.length - 2] + '.' + parts[parts.length - 1];
    }
    
    return hostname;
}

// Get query parameter by name, using URL.searchParams with regex fallback for IE11
function getParameterByName(name, url) {
    if (!url) url = window.location.href;
    try {
        return new URL(url).searchParams.get(name) || null;
    } catch (e) {
        if (debug) console.warn('URL API not supported, falling back to regex:', e);
        name = name.replace(/[\[\]]/g, '\\$&');
        const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
        const results = regex.exec(url);
        if (!results) return null;
        if (!results[2]) return '';
        return decodeURIComponent(results[2].replace(/\+/g, ' '));
    }
}

// Check if any campaign parameters exist in the URL
function hasAnyParameter(url, params) {
    return params.some(param => getParameterByName(param, url) !== null);
}

// Improved search engine detection - only checks hostname, uses whitelist
function isSearchEngineReferrer(referrer) {
    if (!referrer) return false;
    
    let hostname = '';
    try {
        hostname = new URL(referrer).hostname.toLowerCase();
    } catch (e) {
        // Fallback regex for hostname extraction
        const match = referrer.match(/^(?:https?:\/\/)?([^\/\?#]+)/i);
        hostname = match ? match[1].toLowerCase() : '';
    }
    
    if (!hostname) return false;
    
    // First check: exact match against whitelist
    if (legitimateSearchEngines.has(hostname)) {
        if (debug) console.log('Exact match found for:', hostname);
        return true;
    }
    
    // Second check: subdomain of legitimate search engine
    const hostParts = hostname.split('.');
    if (hostParts.length >= 3) {
        // Try domain.tld combination (e.g., google.com from www.google.com)
        const mainDomain = hostParts[hostParts.length - 2] + '.' + hostParts[hostParts.length - 1];
        if (legitimateSearchEngines.has(mainDomain)) {
            if (debug) console.log('Subdomain match found for:', hostname, '-> main domain:', mainDomain);
            return true;
        }
        
        // Try last 3 parts for country codes (e.g., google.co.uk from www.google.co.uk)
        if (hostParts.length >= 3) {
            const ccDomain = hostParts[hostParts.length - 3] + '.' + hostParts[hostParts.length - 2] + '.' + hostParts[hostParts.length - 1];
            if (legitimateSearchEngines.has(ccDomain)) {
                if (debug) console.log('Country code domain match found for:', hostname, '-> cc domain:', ccDomain);
                return true;
            }
        }
    }
    
    if (debug) {
        console.log('Hostname not recognized as legitimate search engine:', hostname);
    }
    
    return false;
}

// Main function to update URL with organic search tracking parameters
function setupOrganicSearchTracking() {
    const url = window.location.href;
    const referrer = document.referrer || '';

    // Store URL and referrer in localStorage
    try {
        localStorage.setItem('sng_url', url);
        localStorage.setItem('sng_ref', referrer);
    } catch (e) {
        if (debug) console.warn('localStorage not available:', e);
    }

    if (debug) {
        console.log('Current URL:', url);
        console.log('Referrer:', referrer);
    }

    // Skip if campaign parameters exist or referrer is not a search engine
    const hasCampaignParams = hasAnyParameter(url, campaignParams);
    if (hasCampaignParams || !isSearchEngineReferrer(referrer)) {
        if (debug) console.log('Skipping URL update: Campaign params exist or referrer is not a legitimate search engine');
        return;
    }

    // Extract and validate referrer hostname
    let referrerHostname = '';
    try {
        referrerHostname = new URL(referrer).hostname;
    } catch (e) {
        if (debug) console.warn('Invalid referrer URL, falling back to regex:', e);
        referrerHostname = referrer.match(/^(?:https?:\/\/)?([^\/]+)/i)?.[1] || '';
    }

    // Extract main domain from hostname
    const mainDomain = getMainDomain(referrerHostname);
    
    if (debug) {
        console.log('Full hostname:', referrerHostname);
        console.log('Main domain:', mainDomain);
    }

    // Only proceed if main domain is valid and contains safe characters
    if (!mainDomain || !/^[a-zA-Z0-9.-]+$/.test(mainDomain)) {
        if (debug) console.log('Skipping URL update: Invalid or unsafe main domain');
        return;
    }

    // Update URL with wpsrc and wpcn parameters
    const urlObj = new URL(url);
    
    // Set wpsrc to the main domain (e.g., google.com instead of tagassistant.google.com)
    urlObj.searchParams.set('wpsrc', mainDomain);
    
    // Set wpcn to 'Organic Search' to identify the campaign type
    urlObj.searchParams.set('wpcn', 'Organic Search');

    // Update the URL without reloading (check if history API is available)
    if (window.history && window.history.replaceState) {
        try {
            window.history.replaceState({}, '', urlObj.toString());
            if (debug) console.log('Updated URL with organic search tracking:', urlObj.toString());
        } catch (e) {
            if (debug) console.warn('Failed to update URL:', e);
        }
    } else {
        if (debug) console.warn('History API not supported, cannot update URL');
    }
}

クロスサブドメイントラッキング

デフォルトでは、Singular WebSDK は Singular Device ID を生成し、ブラウザのストレージを使用して永続化します。このストレージはサブドメイン間で共有できないため、SDKはサブドメインごとに新しいIDを生成することになります。

サブドメイン間でSingular Device IDを永続化したい場合は、以下のオプションのいずれかを使用できます:

方法B(詳細):単一デバイスIDを手動で設定する
#

方法B(詳細):手動でSingularデバイスIDを設定する

Singular SDKにデバイスIDを自動的に保持させたくない場合は、例えばトップレベルドメインCookieやサーバーサイドCookieを使って、ドメイン間でIDを手動で保持することができます。値はSingularが以前に生成したIDで、有効なuuid4形式でなければなりません。

注:Singular Device IDは、initメソッドを呼び出した後、またはInitFinishedCallbackを使用して、singularSdk.getSingularDeviceId()を使用して読み取ることができます。

withPersistentSingularDeviceId メソッド

説明

永続化したいシンギュラー・デバイス ID を含む構成オプションで SDK を初期化します。

シグネチャ withPersistentSingularDeviceId(singularDeviceId)
使用例
function initSingularSDK() {
  const config = new SingularConfig(sdkKey, sdkSecret, productId)
    .withPersistentSingularDeviceId(singularDeviceId);
  window.singularSdk.init(config);
}

次のステップ

関連記事