Syncing deviceUUID Between iOS mobile SDK and JS SDKs in app Using a WebView

👍

Expected result of the “Syncing deviceUUID Between iOS mobile SDK and JS SDKs in app Using a WebView” step:

A single customer profile is created in Maestra, combining actions from both the mobile SDK and the website using the JS SDK.

📘

If your app uses a WebView that loads a website with the JS tracker, installing the mobile app will result in two separate customer profiles in Maestra, each with a different deviceUUID: one from the JS SDK and one from the mobile SDK.
To prevent this and keep all data in a single customer profile, follow the steps below:

  1. Add code to your app that syncs the deviceUUID with the JS SDK when it is received from the Mobile SDK.
func syncMindboxDeviceUUIDs(with webView: WKWebView) {
    Mindbox.shared.getDeviceUUID { uuid in
        guard !uuid.isEmpty else {
            Mindbox.logger.log(level: .error, message: "[WebView]: Device UUID is empty or invalid")
            return
        }

        let script = """
            document.cookie = "mindboxDeviceUUID=\(uuid); path=/";
            window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)');
        """

        DispatchQueue.main.async {
            webView.evaluateJavaScript(script) { _, error in
                if let error = error {
                    Mindbox.logger.log(level: .error, message: "[WebView]: Error setting cookies and localStorage: \(error)")
                } else {
                    Mindbox.logger.log(level: .default, message: "[WebView]: Cookies and localStorage set successfully.")
                }
            }
        }
    }
}
  1. When implementing WKNavigationDelegate for your WKWebView, call this method in webView(_:didCommit:).
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
    viewModel.syncMindboxDeviceUUIDs(with: webView)

    let message = "[WebView]: \(#function): Content started arriving for: \(webView.url?.absoluteString ?? "Unknown URL")"
    Mindbox.logger.log(level: .debug, message: message)
}

Implementation Example

import Observation
import WebKit
import Mindbox

@Observable final class ViewModel {

    /// Syncing deviceUUID
    func syncMindboxDeviceUUIDs(with webView: WKWebView) {
        Mindbox.shared.getDeviceUUID { uuid in
            guard !uuid.isEmpty else {
                Mindbox.logger.log(level: .error, message: "[WebView]: Device UUID is empty or invalid")
                return
            }

            let script = """
                document.cookie = "mindboxDeviceUUID=\(uuid); path=/";
                window.localStorage.setItem('mindboxDeviceUUID', '\(uuid)');
            """

            DispatchQueue.main.async {
                webView.evaluateJavaScript(script) { _, error in
                    if let error = error {
                        Mindbox.logger.log(level: .error, message: "[WebView]: Error setting cookies and localStorage: \(error)")
                    } else {
                        Mindbox.logger.log(level: .default, message: "[WebView]: Cookies and localStorage set successfully.")
                    }
                }
            }
        }
    }

    /// Use this method to clear WebView data.
    func clearAllWebsiteData() {
        let dataStore = WKWebsiteDataStore.default()
        let dataTypes = WKWebsiteDataStore.allWebsiteDataTypes()

        dataStore.removeData(ofTypes: dataTypes, modifiedSince: Date.distantPast) {
            Mindbox.logger.log(level: .default, message: "[WebView]: All web data cleared")
        }
    }
}

extension ViewModel {

    /// Can be used to view and verify the deviceUUID.
    func viewCookiesAndLocalStorage(with webView: WKWebView) {
        print("\n" + #function)

        Mindbox.shared.getDeviceUUID { uuid in
            let message = "[WebView]: Mobile SDK UUID: \(uuid)"
            print(message)
            Mindbox.logger.log(level: .default, message: message)
        }
        
        let script = """
            JSON.stringify({
                cookies: document.cookie || "No cookies found",
                localStorage: window.localStorage.getItem('mindboxDeviceUUID') || "No value found"
            })
        """

        DispatchQueue.main.async {
            webView.evaluateJavaScript(script) { result, error in
                if let error = error {
                    let message = "[WebView]: Error retrieving cookies and localStorage: \(error)"
                    print(message)
                    Mindbox.logger.log(level: .error, message: message)
                } else {
                    let message = "[WebView]: Cookies and LocalStorage: \(result ?? "nil")"
                    print("Start===============")
                    print("Cookies and LocalStorage: \(result ?? "nil")")
                    print("===============End\n")
                    Mindbox.logger.log(level: .default, message: message)
                }
            }
        }
    }
}
import SwiftUI
import WebKit
import Mindbox

struct WebView: UIViewRepresentable {

    static var currentWebView: WKWebView?

    let url: URL?
    var viewModel: ViewModel

    func makeCoordinator() -> Coordinator {
        Coordinator(viewModel: viewModel)
    }

    func makeUIView(context: Context) -> WKWebView {
        let wkWebView = WKWebView()
        wkWebView.navigationDelegate = context.coordinator

#if DEBUG
        wkWebView.isInspectable = true
#endif

        guard let url else { return wkWebView }

        let request = URLRequest(url: url)
        wkWebView.load(request)

        WebView.currentWebView = wkWebView
        return wkWebView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) { }

    final class Coordinator: NSObject {
        var viewModel: ViewModel

        init(viewModel: ViewModel) {
            self.viewModel = viewModel
        }
    }
}

// MARK: - WKNavigationDelegate

extension WebView.Coordinator: WKNavigationDelegate {
    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
        viewModel.syncMindboxDeviceUUIDs(with: webView)

        let message = "[WebView]: \(#function): Content started arriving for: \(webView.url?.absoluteString ?? "Unknown URL")"
        Mindbox.logger.log(level: .debug, message: message)
    }
}
import SwiftUI
import WebKit

struct ContentView: View {

    let webViewModel: ViewModel

    private let url = URL(string: "https://your-website.com/")

    var body: some View {
        VStack {
            WebView(url: url, viewModel: webViewModel)

            Button("Print in console Cookies and LocalStorage") {
                guard let webView = WebView.currentWebView else {
                    print("WebView is not initialized yet")
                    return
                }
                webViewModel.viewCookiesAndLocalStorage(with: webView)
            }.padding()
        }
    }
}