// Unity to LogRocket iOS bridge. // // Uses __has_include so this file compiles whether or not the LogRocket pod is // integrated. When the pod is present the real, type-checked SDK calls compile // in; when absent every entry point degrades to a no-op so non-LogRocket builds // still link. // // IMPORTANT: this is a .m (Objective-C), NOT .mm (Objective-C++). LogRocket's // umbrella header pulls in C headers (LROFileUtils.h, LROJSONCodec.h) that use // the C `restrict` keyword, which is invalid in Objective-C++ and fails to // compile there. As plain Objective-C the functions already have C linkage, so // the _lr_* symbols resolve from C# DllImport("__Internal") without extern "C". // // API verified against the actual LogRocket.xcframework 3.1.0 ObjC header // (LROSDK / LROConfiguration / LROCustomEventBuilder). The SDK is a Swift // framework with ARC-managed objects - compile with ARC (-fobjc-arc), which // Unity's generated project enables by default. #import #if __has_include() #import #define LR_AVAILABLE 1 #else #define LR_AVAILABLE 0 #endif static NSString* NSStr(const char* s) { return s ? [NSString stringWithUTF8String:s] : @""; } // Parse a JSON object into an NSDictionary. The SDK's // identify userInfo and the event-builder put API are string-typed, so values // are coerced to strings (matching the Android bridge). static NSDictionary* ParseJson(const char* json) { if (!json) return @{}; NSData* data = [NSStr(json) dataUsingEncoding:NSUTF8StringEncoding]; if (!data) return @{}; id obj = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; if (![obj isKindOfClass:[NSDictionary class]]) return @{}; NSMutableDictionary* out = [NSMutableDictionary dictionary]; [(NSDictionary*)obj enumerateKeysAndObjectsUsingBlock:^(id key, id val, BOOL* stop) { if ([key isKindOfClass:[NSString class]]) { out[key] = [val isKindOfClass:[NSString class]] ? val : [val description]; } }]; return out; } // Cached session URL. getSessionURL: is asynchronous and its handler may run on // a background thread, so we cache the latest value and hand the C# side a // snapshot. The first read may be NULL until the callback resolves. static NSString* gSessionUrl = nil; void _lr_init(const char* appId) { #if LR_AVAILABLE LROConfiguration* cfg = [[LROConfiguration alloc] initWithAppID:NSStr(appId)]; [LROSDK initializeWithConfiguration:cfg]; NSLog(@"[LogRocketBridge] initialized appId=%@", NSStr(appId)); #else NSLog(@"[LogRocketBridge] LogRocket pod not integrated - init no-op (appId=%@)", NSStr(appId)); #endif } void _lr_identify(const char* userId, const char* traitsJson) { #if LR_AVAILABLE [LROSDK identifyWithUserID:NSStr(userId) userInfo:ParseJson(traitsJson)]; #endif } void _lr_track(const char* eventName, const char* propsJson) { #if LR_AVAILABLE LROCustomEventBuilder* builder = [[LROCustomEventBuilder alloc] init:NSStr(eventName)]; [ParseJson(propsJson) enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* val, BOOL* stop) { [builder putString:key value:val]; }]; [LROSDK track:builder]; #endif } void _lr_log(const char* severity, const char* message) { // The iOS SDK auto-captures application logs (NSLog/os_log), so emitting via // NSLog is enough for it to be recorded - no manual Logger call required. NSLog(@"[LogRocket:%@] %@", NSStr(severity), NSStr(message)); } void _lr_logException(const char* message, const char* stack) { NSLog(@"[LogRocket:error] %@\n%@", NSStr(message), NSStr(stack)); } void _lr_startReplay(void) { // Recording starts automatically at initialize. This rotates to a fresh // session - useful for explicit restart after a stop. The SDK has no bare // startNewSession; the no-config variant takes a (nullable) BOOL handler. #if LR_AVAILABLE [LROSDK startNewSessionNoConfig:nil]; #endif } void _lr_stopReplay(void) { // endSession halts the current recording but keeps the SDK alive so // startReplay (startNewSession) can resume. shutdown would be more terminal. #if LR_AVAILABLE [LROSDK endSession]; #endif } const char* _lr_sessionUrl(void) { #if LR_AVAILABLE [LROSDK getSessionURL:^(NSString* url) { if (url) gSessionUrl = [url copy]; }]; #endif return gSessionUrl ? [gSessionUrl UTF8String] : NULL; }