Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>mParticle WebView Bridge Test</title>
<style>
body { font-family: sans-serif; background: #111; color: #fff; padding: 16px; }
button { padding: 12px 14px; margin-right: 10px; border-radius: 10px; border: 0; background: #4079fe; color: #fff; font-weight: 600; }
.muted { opacity: 0.7; }
</style>
<script type="text/javascript">
// WebView-only snippet (NO API KEY) + required bridge name.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this (NO API KEY) + required bridge name?

const mySdkConfig = {
requiredWebviewBridgeName: "higgsWebviewBridge",
isDevelopmentMode: true
};
window.mParticle = { config: mySdkConfig };

(function () {
window.mParticle = window.mParticle || {
EventType: { Unknown: 0, Navigation: 1, Location: 2, Search: 3, Transaction: 4, UserContent: 5, UserPreference: 6, Social: 7, Other: 8 }
};
window.mParticle.eCommerce = { Cart: {} };
window.mParticle.Identity = {};
window.mParticle.config = window.mParticle.config || {};
window.mParticle.config.rq = [];
window.mParticle.ready = function (t) { window.mParticle.config.rq.push(t); };
function e(e, o) {
return function () {
if (o) { e = o + "." + e; }
var t = Array.prototype.slice.call(arguments);
t.unshift(e);
window.mParticle.config.rq.push(t);
};
}
var o = ["endSession", "logError", "logEvent", "logForm", "logLink", "logPageView", "setSessionAttribute", "setAppName", "setAppVersion", "setOptOut", "setPosition", "startNewSession", "startTrackingLocation", "stopTrackingLocation"];
var n = ["setCurrencyCode", "logCheckout"];
var i = ["identify", "login", "logout", "modify"];
o.forEach(function (t) { window.mParticle[t] = e(t); });
n.forEach(function (t) { window.mParticle.eCommerce[t] = e(t, "eCommerce"); });
i.forEach(function (t) { window.mParticle.Identity[t] = e(t, "Identity"); });
var r = document.createElement("script");
r.type = "text/javascript";
r.async = true;
r.src = "https://jssdkcdns.mparticle.com/js/v2/mparticle.js";
var c = document.getElementsByTagName("script")[0];
c.parentNode.insertBefore(r, c);
})();
</script>
</head>
<body>
<h2>mParticle WebView Bridge</h2>
<p class="muted">
This page is loaded from <code>file:///android_asset</code> and uses the Web SDK in bridged mode.
Click the button to log a JS event that should be forwarded through the Android SDK.
</p>

<button id="btnEvent">Log JS Event</button>
<button id="btnPage">Log Page View</button>

<hr style="border:0;border-top:1px solid rgba(255,255,255,0.15);margin:18px 0;" />

<h3 style="margin:0 0 10px 0;">Identity Alias (JS → Native)</h3>
<p class="muted" style="margin-top:0;">
This calls <code>mParticle.Identity.aliasUsers</code>. In WebView bridge mode this is queued as a JS request and
forwarded to the Android SDK. We auto-populate a valid aliasRequest from the native SDK (MPIDs + time window),
similar to the docs reference.
</p>

<div style="display:flex;flex-direction:column;gap:10px;max-width:520px;">
<div class="muted" id="aliasInfo">Waiting for native MPIDs…</div>
<div>
<button id="btnAlias" disabled style="opacity:0.6;">Send Alias Users</button>
</div>
</div>

<script>
function log(msg) {
// Keep logs out of the UI; use console instead.
console.log((new Date().toISOString()) + " " + msg);
}

document.getElementById("btnEvent").addEventListener("click", function () {
if (!window.mParticle) return log("mParticle not available");
window.mParticle.logEvent(
"WebView JS Button Click",
window.mParticle.EventType.Other,
{ source: "webview", bridgeName: "higgsWebviewBridge" }
);
log("Called mParticle.logEvent('WebView JS Button Click')");
});

document.getElementById("btnPage").addEventListener("click", function () {
if (!window.mParticle) return log("mParticle not available");
window.mParticle.logPageView("WebView Bridge Test", { source: "webview" });
log("Called mParticle.logPageView('WebView Bridge Test')");
});

window.__mp = window.__mp || {};
window.__mp.aliasRequest = window.__mp.aliasRequest || null;

// Called by native via evaluateJavascript after page load
window.setNativeAliasRequest = function (aliasRequest) {
window.__mp.aliasRequest = aliasRequest || null;
const info = document.getElementById("aliasInfo");
const btn = document.getElementById("btnAlias");
if (!window.__mp.aliasRequest) {
info.textContent = "Missing aliasRequest from native SDK";
btn.disabled = true;
btn.style.opacity = "0.6";
return;
}
const ar = window.__mp.aliasRequest;
const valid = !!ar.sourceMpid && !!ar.destinationMpid && String(ar.sourceMpid) !== String(ar.destinationMpid);
info.textContent =
`Using sourceMpid=${ar.sourceMpid} → destinationMpid=${ar.destinationMpid} ` +
`(scope=${ar.scope || "device"})`;
btn.disabled = !valid;
btn.style.opacity = valid ? "1" : "0.6";
if (!valid) {
info.textContent = "Invalid MPIDs from native SDK (need 2 unique users)";
}
};

// If native injected a pending request before this function was defined, apply it now.
if (window.__mp && window.__mp._pendingAliasRequest) {
window.setNativeAliasRequest(window.__mp._pendingAliasRequest);
}

// Log screen view from JS (not native) when page loads
window.mParticle.ready(function() {
if (window.mParticle && typeof window.mParticle.logScreen === "function") {
window.mParticle.logScreen("WebView Bridge");
log("Called mParticle.logScreen('WebView Bridge') from JS");
}
});

document.getElementById("btnAlias").addEventListener("click", function () {
// Send alias request from WebView (JS) per Web SDK docs:
// https://docs.mparticle.com/developers/client-sdks/web/idsync/#user-aliasing
if (!window.mParticle || !window.mParticle.Identity) return log("mParticle.Identity not available");
if (typeof window.mParticle.Identity.aliasUsers !== "function") return log("mParticle.Identity.aliasUsers not available yet");
if (!window.__mp.aliasRequest) return log("Alias blocked: missing aliasRequest (native injection not ready)");

const sourceMpidStr = String(window.__mp.aliasRequest.sourceMpid || "").trim();
const destinationMpidStr = String(window.__mp.aliasRequest.destinationMpid || "").trim();

if (!sourceMpidStr || !destinationMpidStr) {
return log("Alias blocked: missing MPIDs");
}
if (sourceMpidStr === destinationMpidStr) {
return log("Alias blocked: sourceMpid and destinationMpid must be unique");
}

// Parse MPIDs as numbers for the bridge (may lose precision for very large MPIDs,
// but the bridge might require numeric values to convert to native long).
const sourceMpidNum = Number(sourceMpidStr);
const destinationMpidNum = Number(destinationMpidStr);

if (!Number.isFinite(sourceMpidNum) || !Number.isFinite(destinationMpidNum)) {
return log(`Alias blocked: MPIDs not numeric (source=${sourceMpidStr}, dest=${destinationMpidStr})`);
}
if (sourceMpidNum <= 0 || destinationMpidNum <= 0) {
return log(`Alias blocked: MPIDs must be positive (source=${sourceMpidNum}, dest=${destinationMpidNum})`);
}

const now = Date.now();
// Per Web SDK docs format: MPIDs as numbers, times as numbers (ms since epoch)
const aliasRequest = {
sourceMpid: sourceMpidNum,
destinationMpid: destinationMpidNum,
startTime: now - (30 * 60 * 1000), // last 30 minutes
endTime: now,
scope: "device"
};

log(`Sending aliasRequest: sourceMpid=${sourceMpidNum} (from "${sourceMpidStr}"), destinationMpid=${destinationMpidNum} (from "${destinationMpidStr}")`);

window.mParticle.Identity.aliasUsers(aliasRequest, function (code, body) {
log(`aliasUsers callback: code=${code} body=${JSON.stringify(body)}`);
});
log(`Called mParticle.Identity.aliasUsers(${JSON.stringify(aliasRequest)})`);
});

log("Loaded. Waiting for user action…");
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package com.mparticle.example.higgsshopsampleapp.fragments

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.fragment.app.Fragment
import com.mparticle.MParticle
import com.mparticle.identity.AliasRequest
import com.mparticle.example.higgsshopsampleapp.R
import com.mparticle.example.higgsshopsampleapp.activities.MainActivity
import com.mparticle.example.higgsshopsampleapp.databinding.FragmentWebviewBridgeBinding

class WebViewBridgeFragment : Fragment() {

private lateinit var binding: FragmentWebviewBridgeBinding

private val tag = "WebViewBridgeFragment"
private val bridgeName = "higgsWebviewBridge"
private val assetUrl = "file:///android_asset/webview_bridge_test.html"

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
(activity as MainActivity).setActionBarTitle("")
inflater.context.setTheme(R.style.Theme_mParticle_SampleApp)
binding = FragmentWebviewBridgeBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// All events should come from JS, not native - screen view will be logged from WebView

binding.btnAliasNative.setOnClickListener {
aliasUsersNative()
}

configureWebView(binding.webview)
// Per docs, the WebView must be registered before loading content.
MParticle.getInstance()?.registerWebView(binding.webview, bridgeName)
binding.webview.loadUrl(assetUrl)
}

private fun configureWebView(webView: WebView) {
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.allowFileAccess = true
webView.settings.allowContentAccess = true

webView.webChromeClient = WebChromeClient()
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url)
injectAliasRequestIntoPage(view)
}
}
}

/**
* Inject MPIDs into the page so the WebView can send alias requests via mParticle.Identity.aliasUsers()
*/
private fun injectAliasRequestIntoPage(webView: WebView) {
val identity = MParticle.getInstance()?.Identity() ?: return
val users = try {
identity.users
} catch (_: Throwable) {
identity.getUsers()
}
if (users.size < 2) return

// Match docs: users are in reverse chronological order:
// source = users[1], destination = users[0]
val sourceUser = users[1]
val destinationUser = users[0]

val sourceMpid = try {
sourceUser.id
} catch (_: Throwable) {
sourceUser.getId()
}.toString()

val destinationMpid = try {
destinationUser.id
} catch (_: Throwable) {
destinationUser.getId()
}.toString()

Log.i(tag, "Mansi sourceMpid=$sourceMpid destinationMpid=$destinationMpid")

if (sourceMpid.isBlank() || destinationMpid.isBlank()) return
if (sourceMpid == destinationMpid) return

val now = System.currentTimeMillis()
val startTime = now - (30 * 60 * 1000)
val endTime = now

val js =
"window.__mp = window.__mp || {};" +
"window.__mp._pendingAliasRequest = {sourceMpid:'$sourceMpid',destinationMpid:'$destinationMpid',startTime:$startTime,endTime:$endTime,scope:'device'};" +
"if (window.setNativeAliasRequest) { window.setNativeAliasRequest(window.__mp._pendingAliasRequest); }"
webView.evaluateJavascript(js, null)
}

/**
* Native alias invoked from the fragment button (and from the WebView bridge).
* Uses the "working" approach: source=users[1], destination=users[0], time window = last 30 minutes.
*/

// Kept native button as-is (optional): it calls the Android SDK alias directly.
private fun aliasUsersNative() {
val identity = MParticle.getInstance()?.Identity() ?: return
val users = try {
identity.users
} catch (_: Throwable) {
identity.getUsers()
}
if (users.size < 2) {
binding.subtitle.text = getString(R.string.webview_bridge_alias_need_two_users)
return
}
val sourceUser = users[1]
val destinationUser = users[0]
val now = System.currentTimeMillis()
val request: AliasRequest = AliasRequest.Builder()
.sourceMpid(sourceUser.id.toString().toLongOrNull() ?: 0L)
.destinationMpid(destinationUser.id.toString().toLongOrNull() ?: 0L)
.startTime(now - (30 * 60 * 1000))
.endTime(now)
.build()
identity.aliasUsers(request)
}

override fun onDestroyView() {
// Avoid leaking the WebView.
binding.webview.apply {
stopLoading()
loadUrl("about:blank")
clearHistory()
removeAllViews()
destroy()
}
super.onDestroyView()
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<!-- Material "public" (globe) -->
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-0.69 0.1,-1.35 0.29,-1.98L8,13.73V14c0,1.1 0.9,2 2,2v4.07C6.06,19.56 4,16.07 4,12zM18.93,17.36c-0.26,-0.81 -1,-1.36 -1.93,-1.36h-1v-3c0,-0.55 -0.45,-1 -1,-1H8v-2h2c0.55,0 1,-0.45 1,-1V6h2c1.1,0 2,-0.9 2,-2v-0.41C18.93,4.78 20,6.33 20,8c0,1.1 -0.9,2 -2,2h-1c-0.55,0 -1,0.45 -1,1v1c0,0.55 0.45,1 1,1h3.63c-0.55,1.79 -1.66,3.33 -3.08,4.36z" />
</vector>
Loading