diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bbcf82f..2a9d07c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ ### Dependencies +- Bump Sentry Android SDK to `6.19.0` ([#406](https://github.com/getsentry/sentry-capacitor/pull/406)) + - [changelog](https://github.com/getsentry/sentry-java/releases/tag/6.19.0) + - [diff](https://github.com/getsentry/sentry-java/compare/6.17.0...6.19.0) - Bump sentry-cocoa SDK to `8.8.0` ([#397](https://github.com/getsentry/sentry-capacitor/pull/397)) - [changelog](https://github.com/getsentry/sentry-cocoa/releases/tag/8.8.0) - [diff](https://github.com/getsentry/sentry-cocoa/compare/7.27.1...8.8.0) diff --git a/android/build.gradle b/android/build.gradle index 9c08a19c..efa8857a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -80,7 +80,7 @@ repositories { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':capacitor-android') - implementation 'io.sentry:sentry-android:6.17.0' + implementation 'io.sentry:sentry-android:6.19.0' testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" diff --git a/android/src/main/java/io/sentry/capacitor/SentryCapacitor.java b/android/src/main/java/io/sentry/capacitor/SentryCapacitor.java index dfb833b0..f5b9f5bd 100644 --- a/android/src/main/java/io/sentry/capacitor/SentryCapacitor.java +++ b/android/src/main/java/io/sentry/capacitor/SentryCapacitor.java @@ -2,7 +2,6 @@ import android.content.Context; import android.content.pm.PackageInfo; -import android.util.Log; import com.getcapacitor.JSArray; import com.getcapacitor.JSObject; import com.getcapacitor.NativePlugin; @@ -16,6 +15,7 @@ import io.sentry.SentryEvent; import io.sentry.SentryLevel; import io.sentry.UncaughtExceptionHandlerIntegration; +import io.sentry.android.core.BuildConfig; import io.sentry.android.core.AnrIntegration; import io.sentry.android.core.NdkIntegration; import io.sentry.android.core.SentryAndroid; @@ -25,7 +25,6 @@ import java.io.File; import java.io.FileOutputStream; import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -37,6 +36,9 @@ @NativePlugin public class SentryCapacitor extends Plugin { + private static final String NATIVE_SDK_NAME = "sentry.native.android.capacitor"; + private static final String ANDROID_SDK_NAME = "sentry.java.android.capacitor"; + static final Logger logger = Logger.getLogger("capacitor-sentry"); private Context context; private static PackageInfo packageInfo; @@ -63,11 +65,22 @@ public void initNativeSdk(final PluginCall call) { SentryAndroid.init( this.getContext(), options -> { + SdkVersion sdkVersion = options.getSdkVersion(); + if (sdkVersion == null) { + sdkVersion = new SdkVersion(ANDROID_SDK_NAME, BuildConfig.VERSION_NAME); + } else { + sdkVersion.setName(ANDROID_SDK_NAME); + } + if (capOptions.has("debug") && capOptions.getBool("debug")) { options.setDebug(true); logger.setLevel(Level.INFO); } + options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion()); + options.setNativeSdkName(NATIVE_SDK_NAME); + options.setSdkVersion(sdkVersion); + String dsn = capOptions.getString("dsn") != null ? capOptions.getString("dsn") : ""; logger.info(String.format("Starting with DSN: '%s'", dsn)); options.setDsn(dsn); @@ -343,11 +356,11 @@ public void setEventOriginTag(SentryEvent event) { if (sdk != null) { switch (sdk.getName()) { // If the event is from capacitor js, it gets set there and we do not handle it here. - case "sentry.native": - setEventEnvironmentTag(event, "android", "native"); + case NATIVE_SDK_NAME: + setEventEnvironmentTag(event, "native"); break; - case "sentry.java.android": - setEventEnvironmentTag(event, "android", "java"); + case ANDROID_SDK_NAME: + setEventEnvironmentTag(event, "java"); break; default: break; @@ -355,8 +368,8 @@ public void setEventOriginTag(SentryEvent event) { } } - private void setEventEnvironmentTag(SentryEvent event, String origin, String environment) { - event.setTag("event.origin", origin); + private void setEventEnvironmentTag(SentryEvent event, String environment) { + event.setTag("event.origin", "android"); event.setTag("event.environment", environment); } diff --git a/ios/Plugin/Plugin.swift b/ios/Plugin/Plugin.swift index 351cf69a..c160e0f9 100644 --- a/ios/Plugin/Plugin.swift +++ b/ios/Plugin/Plugin.swift @@ -9,6 +9,8 @@ import Sentry @objc(SentryCapacitor) public class SentryCapacitor: CAPPlugin { + private let nativeSdkName = "sentry.cocoa.capacitor"; + private var sentryOptions: Options? // The Cocoa SDK is init. after the notification didBecomeActiveNotification is registered. @@ -45,6 +47,9 @@ public class SentryCapacitor: CAPPlugin { do { let options = try Options.init(dict: optionsDict) + let sdkVersion = PrivateSentrySDKOnly.getSdkVersionString() + PrivateSentrySDKOnly.setSdkName(nativeSdkName, andVersionString: sdkVersion) + // Note: For now, in sentry-cocoa, beforeSend is not called before captureEnvelope options.beforeSend = { [weak self] event in self?.setEventOriginTag(event: event) @@ -290,15 +295,26 @@ public class SentryCapacitor: CAPPlugin { } private func setEventOriginTag(event: Event) { - guard let sdk = event.sdk, isValidSdk(sdk: sdk), let name = sdk["name"] as? String, name == "sentry.cocoa" else { + guard let sdk = event.sdk, isValidSdk(sdk: sdk), let name = sdk["name"] as? String, name == nativeSdkName else { return } - setEventEnvironmentTag(event: event, origin: "ios", environment: "native") + setEventEnvironmentTag(event: event, environment: "native") } - private func setEventEnvironmentTag(event: Event, origin: String, environment: String) { - event.tags?["event.origin"] = origin - event.tags?["event.environment"] = environment + private func setEventEnvironmentTag(event: Event, environment: String) { + var newTags = [String: String]() + + if let tags = event.tags, !tags.isEmpty { + newTags.merge(tags) { (_, new) in new } + } + + newTags["event.origin"] = "ios" + + if !environment.isEmpty { + newTags["event.environment"] = environment + } + + event.tags = newTags } private func isValidSdk(sdk: [String: Any]) -> Bool { diff --git a/src/integrations/sdkinfo.ts b/src/integrations/sdkinfo.ts index 1e1517ea..88df3515 100644 --- a/src/integrations/sdkinfo.ts +++ b/src/integrations/sdkinfo.ts @@ -16,7 +16,7 @@ export class SdkInfo implements Integration { */ public name: string = SdkInfo.id; - private _nativeSdkInfo: Package | null = null; + private _nativeSdkPackage: Package | null = null; /** * @inheritDoc @@ -25,9 +25,9 @@ export class SdkInfo implements Integration { addGlobalEventProcessor(async event => { // The native SDK info package here is only used on iOS as `beforeSend` is not called on `captureEnvelope`. // this._nativeSdkInfo should be defined a following time so this call won't always be awaited. - if (NATIVE.platform === 'ios' && this._nativeSdkInfo === null) { + if (NATIVE.platform === 'ios' && this._nativeSdkPackage === null) { try { - this._nativeSdkInfo = await NATIVE.fetchNativeSdkInfo(); + this._nativeSdkPackage = await NATIVE.fetchNativeSdkInfo(); } catch (_) { // If this fails, go ahead as usual as we would rather have the event be sent with a package missing. logger.warn( @@ -37,19 +37,18 @@ export class SdkInfo implements Integration { } event.platform = event.platform || 'javascript'; - event.sdk = { - ...event.sdk, - name: SDK_NAME, - packages: [ - ...((event.sdk && event.sdk.packages) || []), - ...((this._nativeSdkInfo && [this._nativeSdkInfo]) || []), - { - name: 'npm:@sentry/capacitor', - version: SDK_VERSION, - }, - ], - version: SDK_VERSION, - }; + event.sdk = event.sdk || {}; + event.sdk.name = event.sdk.name || SDK_NAME; + event.sdk.version = event.sdk.version || SDK_VERSION; + event.sdk.packages = [ + // default packages are added by baseclient and should not be added here + ...(event.sdk.packages || []), + ...((this._nativeSdkPackage && [this._nativeSdkPackage]) || []), + { + name: 'npm:@sentry/capacitor', + version: SDK_VERSION, + }, + ]; return event; }); diff --git a/src/version.ts b/src/version.ts index c1d64aa1..97eebde1 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,3 @@ +export const SDK_PACKAGE_NAME = 'npm:@sentry/capacitor'; export const SDK_NAME = 'sentry.javascript.capacitor'; export const SDK_VERSION = '0.12.1'; diff --git a/test/integrations/sdkinfo.test.ts b/test/integrations/sdkinfo.test.ts new file mode 100644 index 00000000..7224f4f5 --- /dev/null +++ b/test/integrations/sdkinfo.test.ts @@ -0,0 +1,88 @@ +import type { Event, EventHint, Package } from '@sentry/types'; + +import { SDK_NAME, SDK_VERSION } from '../../src/'; +import { SdkInfo } from '../../src/integrations'; +import { NATIVE } from '../../src/wrapper'; + +let mockedFetchNativeSdkInfo: jest.Mock, []>; + +const mockPackage = { + name: 'sentry-cocoa', + version: '0.0.1', +}; + +jest.mock('../../src/wrapper', () => { + const actual = jest.requireActual('../../src/wrapper'); + + return { + NATIVE: { + ...actual.NATIVE, + platform: 'ios', + fetchNativeSdkInfo: () => mockedFetchNativeSdkInfo(), + }, + }; +}); + +afterEach(() => { + NATIVE.platform = 'ios'; +}); + +describe('Sdk Info', () => { + it('Adds native package and javascript platform to event on iOS', async () => { + mockedFetchNativeSdkInfo = jest.fn().mockResolvedValue(mockPackage); + const mockEvent: Event = {}; + const processedEvent = await executeIntegrationFor(mockEvent); + + expect(processedEvent?.sdk?.packages).toEqual(expect.arrayContaining([mockPackage])); + expect(processedEvent?.platform === 'javascript'); + expect(mockedFetchNativeSdkInfo).toBeCalledTimes(1); + }); + + it('Adds javascript platform but not native package on Android', async () => { + NATIVE.platform = 'android'; + mockedFetchNativeSdkInfo = jest.fn().mockResolvedValue(mockPackage); + const mockEvent: Event = {}; + const processedEvent = await executeIntegrationFor(mockEvent); + + expect(processedEvent?.sdk?.packages).toEqual(expect.not.arrayContaining([mockPackage])); + expect(processedEvent?.platform === 'javascript'); + expect(mockedFetchNativeSdkInfo).not.toBeCalled(); + }); + + it('Does not overwrite existing sdk name and version', async () => { + mockedFetchNativeSdkInfo = jest.fn().mockResolvedValue(null); + const mockEvent: Event = { + sdk: { + name: 'test-sdk', + version: '1.0.0', + }, + }; + const processedEvent = await executeIntegrationFor(mockEvent); + + expect(processedEvent?.sdk?.name).toEqual('test-sdk'); + expect(processedEvent?.sdk?.version).toEqual('1.0.0'); + }); + + it('Does use default sdk name and version', async () => { + mockedFetchNativeSdkInfo = jest.fn().mockResolvedValue(null); + const mockEvent: Event = {}; + const processedEvent = await executeIntegrationFor(mockEvent); + + expect(processedEvent?.sdk?.name).toEqual(SDK_NAME); + expect(processedEvent?.sdk?.version).toEqual(SDK_VERSION); + }); +}); + +function executeIntegrationFor(mockedEvent: Event, mockedHint: EventHint = {}): Promise { + const integration = new SdkInfo(); + return new Promise((resolve, reject) => { + integration.setupOnce(async eventProcessor => { + try { + const processedEvent = await eventProcessor(mockedEvent, mockedHint); + resolve(processedEvent); + } catch (e) { + reject(e); + } + }); + }); +}