package com.darkmatter.logrocket; import android.app.Activity; import android.app.Application; import android.util.Log; import com.logrocket.core.CustomEventBuilder; import com.logrocket.core.LogRocket; import org.json.JSONException; import org.json.JSONObject; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * Unity to LogRocket Android bridge. * * This is a loose .java source file: Unity compiles it into the unityLibrary * Gradle module, which carries the LogRocket Maven dependency added by the * External Dependency Manager (see LogRocketDependencies.xml). That lets us call * the SDK directly - including the init/getSessionURL lambdas - instead of * reflecting, so there is no functional-interface name to guess. * * Because the calls are direct, Android builds REQUIRE the LogRocket AAR: keep * the androidPackage entry in LogRocketDependencies.xml enabled, or this file * will fail to compile. * * API verified against the LogRocket Android SDK 3.x * (docs.logrocket.com/reference/android): com.logrocket.core.LogRocket, * com.logrocket.core.CustomEventBuilder. */ public final class LogRocketUnityBridge { private static final String TAG = "LogRocketBridge"; private static volatile String sessionUrl; private LogRocketUnityBridge() { } public static void init(Activity activity, String appId) { try { Application app = activity.getApplication(); // Docs recommend init from a custom Application.attachBaseContext for // earliest-startup capture. Runtime init from the current activity // (as the React Native SDK does) works but misses the launch window. LogRocket.init(app, app.getBaseContext(), options -> options.setAppID(appId)); LogRocket.getSessionURL(url -> sessionUrl = url); Log.i(TAG, "LogRocket initialized: " + appId); } catch (Throwable t) { Log.e(TAG, "init failed", t); } } public static void identify(String userId, String traitsJson) { try { Map traits = parse(traitsJson); if (traits.isEmpty()) { LogRocket.identify(userId); } else { LogRocket.identify(userId, traits); } } catch (Throwable t) { Log.e(TAG, "identify failed", t); } } public static void track(String name, String propsJson) { try { CustomEventBuilder builder = new CustomEventBuilder(name); for (Map.Entry e : parse(propsJson).entrySet()) { builder.put(e.getKey(), e.getValue()); } LogRocket.track(builder); } catch (Throwable t) { Log.e(TAG, "track failed", t); } } public static void log(String severity, String message) { // LogRocket auto-captures logcat, so emitting here is enough to record it. Log.println(level(severity), TAG, message == null ? "" : message); } public static void logException(String message, String stack) { Log.e(TAG, (message == null ? "" : message) + "\n" + (stack == null ? "" : stack)); } public static String getSessionUrl() { return sessionUrl; } // Recording starts automatically at init. These provide manual control, // verified against the 3.1.0 AAR: endSession() halts the current recording // (SDK stays alive), startNewSession() begins a fresh one. public static void startReplay() { try { LogRocket.startNewSession(); } catch (Throwable t) { Log.e(TAG, "startReplay failed", t); } } public static void stopReplay() { try { LogRocket.endSession(); } catch (Throwable t) { Log.e(TAG, "stopReplay failed", t); } } private static int level(String severity) { if ("error".equals(severity)) return Log.ERROR; if ("warn".equals(severity)) return Log.WARN; if ("debug".equals(severity)) return Log.DEBUG; return Log.INFO; } private static Map parse(String json) { Map out = new HashMap<>(); if (json == null || json.isEmpty()) return out; try { JSONObject obj = new JSONObject(json); Iterator it = obj.keys(); while (it.hasNext()) { String key = it.next(); out.put(key, String.valueOf(obj.get(key))); } } catch (JSONException e) { Log.w(TAG, "bad json: " + json); } return out; } }