114 lines
4.4 KiB
Objective-C
114 lines
4.4 KiB
Objective-C
// 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 <Foundation/Foundation.h>
|
|
|
|
#if __has_include(<LogRocket/LogRocket-Swift.h>)
|
|
#import <LogRocket/LogRocket-Swift.h>
|
|
#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<NSString*, NSString*>. 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<NSString*, NSString*>* 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<NSString*, NSString*>* 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;
|
|
}
|