diff --git a/android/build.gradle b/android/build.gradle index be57440..7351e7c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -17,4 +17,5 @@ dependencies { implementation 'com.rollbar:rollbar-java:1.4.0' implementation 'com.rollbar:rollbar-android:1.4.0@aar' implementation 'com.facebook.react:react-native:+' + implementation 'com.google.code.gson:gson:+' } diff --git a/android/src/main/java/com/rollbar/RollbarReactNative.java b/android/src/main/java/com/rollbar/RollbarReactNative.java index 9b2c48b..6feded9 100644 --- a/android/src/main/java/com/rollbar/RollbarReactNative.java +++ b/android/src/main/java/com/rollbar/RollbarReactNative.java @@ -2,6 +2,8 @@ import java.util.Map; +import com.google.gson.JsonObject; + import com.facebook.react.ReactPackage; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactApplicationContext; @@ -10,6 +12,7 @@ import com.facebook.react.bridge.ReactMethod; import android.content.Context; +import android.os.Build; import com.rollbar.android.Rollbar; import com.rollbar.android.provider.NotifierProvider; @@ -515,4 +518,22 @@ public void setPerson(ReadableMap personInfo) { public void clearPerson() { Rollbar.instance().clearPersonData(); } + + // Defined as synchronous because the data must be returned in the + // javascript configuration constructor before Rollbar.js is initialized. + @ReactMethod(isBlockingSynchronousMethod = true) + public String deviceAttributes() { + JsonObject attributes = new JsonObject(); + + attributes.addProperty("os", "android"); + attributes.addProperty("phone_model", android.os.Build.MODEL); + attributes.addProperty("android_version", android.os.Build.VERSION.RELEASE); + attributes.addProperty("board", android.os.Build.BOARD); + attributes.addProperty("brand", android.os.Build.BRAND); + attributes.addProperty("device", android.os.Build.DEVICE); + attributes.addProperty("manufacturer", android.os.Build.MANUFACTURER); + attributes.addProperty("product", android.os.Build.PRODUCT); + + return attributes.toString(); + } } diff --git a/ios/RollbarReactNative.m b/ios/RollbarReactNative.m index cf79c26..96ddbd3 100644 --- a/ios/RollbarReactNative.m +++ b/ios/RollbarReactNative.m @@ -4,6 +4,7 @@ #else #import "RCTConvert.h" #endif +#include @implementation RollbarReactNative @@ -254,4 +255,40 @@ + (void)critical:(NSString*)message exception:(NSException*)exception data:(NSDi [[Rollbar currentConfiguration] setPersonId:nil username:nil email:nil]; } +// Defined as synchronous because the data must be returned in the +// javascript configuration constructor before Rollbar.js is initialized. +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(deviceAttributes) +{ + struct utsname systemInfo; + uname(&systemInfo); + NSString *deviceCode = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; + + NSBundle *mainBundle = [NSBundle mainBundle]; + NSString *version = [mainBundle objectForInfoDictionaryKey:(NSString*)kCFBundleVersionKey]; + NSString *shortVersion = [mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + NSString *bundleName = [mainBundle objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey]; + NSString *bundleIdentifier = [mainBundle objectForInfoDictionaryKey:(NSString *)kCFBundleIdentifierKey]; + + NSDictionary *attributes = @{ + @"os": @"ios", + @"os_version": [[UIDevice currentDevice] systemVersion], + @"system_name": [[UIDevice currentDevice] systemName], + @"device_name": [[UIDevice currentDevice] name], + @"device_code" : deviceCode, + @"code_version": version ? version : @"", + @"short_version": shortVersion ? shortVersion : @"", + @"bundle_identifier": bundleIdentifier ? bundleIdentifier : @"", + @"app_name": bundleName ? bundleName : @"" + }; + + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:attributes + options:0 + error:&error]; + + NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + + return jsonString; +} + @end diff --git a/src/Rollbar.js b/src/Rollbar.js index 5dddd1a..cc0e745 100644 --- a/src/Rollbar.js +++ b/src/Rollbar.js @@ -2,6 +2,8 @@ import { Platform, NativeModules } from 'react-native'; import Rollbar from 'rollbar/src/react-native/rollbar'; +import { merge } from '../src/merge'; + const NativeClient = NativeModules.RollbarReactNative; export class Client { @@ -106,13 +108,16 @@ export class Configuration { this.logLevel = options.logLevel || 'debug'; this.reportLevel = options.reportLevel || 'debug'; this.endpoint = options.endpoint || 'https://api.rollbar.com/api/1/item/'; - this.appVersion = options.appVersion || undefined; + this.appVersion = options.appVersion || 'foo'; this.codeBundleId = options.codeBundleId || undefined; this.releaseStage = options.releaseStage || undefined; this.enabledReleaseStages = options.enabledReleaseStages || undefined; this.captureUncaught = options.captureUncaught !== undefined ? options.captureUncaught : true; this.captureUnhandledRejections = options.captureUnhandledRejections !== undefined ? options.captureUnhandledRejections : !__DEV__; - this.payload = options.payload || {}; + + // Ensure captureDeviceInfo is set before calling payloadOptions() below. + this.captureDeviceInfo = options.captureDeviceInfo === undefined ? true : options.captureDeviceInfo; + this.payload = merge(options.payload, this.payloadOptions()); this.enabled = options.enabled === undefined ? true : options.enabled; this.verbose = options.verbose || false; this.transform = options.transform; @@ -135,6 +140,22 @@ export class Configuration { } } + payloadOptions = () => { + if (!this.captureDeviceInfo) { + return {}; + } + + return { + client: { + os: this.deviceAttributes() + } + } + } + + deviceAttributes = () => { + return JSON.parse(NativeClient.deviceAttributes()); + } + toJSON = () => { var result = { accessToken: this.accessToken, @@ -144,6 +165,7 @@ export class Configuration { reportLevel: this.reportLevel, enabled: this.enabled, verbose: this.verbose, + captureDeviceInfo: this.captureDeviceInfo, transform: this.transform, rewriteFilenamePatterns: this.rewriteFilenamePatterns, payload: { diff --git a/src/merge.js b/src/merge.js new file mode 100644 index 0000000..d352fc0 --- /dev/null +++ b/src/merge.js @@ -0,0 +1,52 @@ +'use strict'; + +var hasOwn = Object.prototype.hasOwnProperty; +var toStr = Object.prototype.toString; + +var isPlainObject = function isPlainObject(obj) { + if (!obj || toStr.call(obj) !== '[object Object]') { + return false; + } + + var hasOwnConstructor = hasOwn.call(obj, 'constructor'); + var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf'); + // Not own constructor property must be Object + if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + var key; + for (key in obj) {/**/} + + return typeof key === 'undefined' || hasOwn.call(obj, key); +}; + +export function merge() { + var i, src, copy, clone, name, + result = {}, + current = null, + length = arguments.length; + + for (i=0; i < length; i++) { + current = arguments[i]; + if (current == null) { + continue; + } + + for (name in current) { + src = result[name]; + copy = current[name]; + if (result !== copy) { + if (copy && isPlainObject(copy)) { + clone = src && isPlainObject(src) ? src : {}; + result[name] = merge(clone, copy); + } else if (typeof copy !== 'undefined') { + result[name] = copy; + } + } + } + } + return result; +}