Repository: getsentry/sentry-capacitor
Pull request #576 (2024-02-16 11:52:43)

Fix: Implement userfeedback layer for Capacitor Client Layer
This PR Implements the missing layer to capture user feedbacks from the Capacitor Client.

#skip-changelog
Sub-Task of #573


Patch:
diff --git a/src/client.ts b/src/client.ts
index 6b39b71..5a7ba1e 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -1,4 +1,4 @@
-import { eventFromException, eventFromMessage } from '@sentry/browser';
+import { createUserFeedbackEnvelope, eventFromException, eventFromMessage } from '@sentry/browser';
 import { BaseClient } from '@sentry/core';
 import type {
   Envelope,
@@ -12,6 +12,7 @@ import type {
 } from '@sentry/types';
 import { logger, SentryError } from '@sentry/utils';
 
+import { defaultSdkInfo } from './integrations/sdkinfo';
 import type { CapacitorClientOptions } from './options';
 import { mergeOutcomes } from './utils/outcome';
 import { NATIVE } from './wrapper';
@@ -31,8 +32,7 @@ export class CapacitorClient extends BaseClient<CapacitorClientOptions> {
    */
   public constructor(options: CapacitorClientOptions) {
     options._metadata = options._metadata || {};
-    // TODO: Implement defaultSdkInfo.
-    // options._metadata.sdk = options._metadata.sdk; || defaultSdkInfo;
+     options._metadata.sdk = options._metadata.sdk  || defaultSdkInfo;
     super(options);
 
     this._outcomesBuffer = [];
@@ -96,8 +96,12 @@ export class CapacitorClient extends BaseClient<CapacitorClientOptions> {
    */
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
   public captureUserFeedback(feedback: UserFeedback): void {
-    // TODO: Implement capture user feedback
-    throw new Error(`${feedback} captureUserFeedback not implemented.`);
+    const envelope = createUserFeedbackEnvelope(feedback, {
+      metadata: this._options._metadata,
+      dsn: this.getDsn(),
+      tunnel: this._options.tunnel,
+    });
+    this._sendEnvelope(envelope);
   }
 
   /**
diff --git a/src/integrations/sdkinfo.ts b/src/integrations/sdkinfo.ts
index 88df351..c4e8e6c 100644
--- a/src/integrations/sdkinfo.ts
+++ b/src/integrations/sdkinfo.ts
@@ -1,9 +1,23 @@
-import type { EventProcessor, Integration, Package } from '@sentry/types';
+import type { EventProcessor, Integration, Package, SdkInfo as SdkInfoType } from '@sentry/types';
 import { logger } from '@sentry/utils';
 
-import { SDK_NAME, SDK_VERSION } from '../version';
+import { SDK_NAME, SDK_PACKAGE_NAME, SDK_VERSION } from '../version';
 import { NATIVE } from '../wrapper';
 
+type DefaultSdkInfo = Pick<Required<SdkInfoType>, 'name' | 'packages' | 'version'>;
+
+export const defaultSdkInfo: DefaultSdkInfo = {
+  name: SDK_NAME,
+  packages: [
+    {
+      name: SDK_PACKAGE_NAME,
+      version: SDK_VERSION,
+    },
+  ],
+  version: SDK_VERSION,
+};
+
+
 /** Default SdkInfo instrumentation */
 export class SdkInfo implements Integration {
   /**
@@ -38,14 +52,14 @@ export class SdkInfo implements Integration {
 
       event.platform = event.platform || 'javascript';
       event.sdk = event.sdk || {};
-      event.sdk.name = event.sdk.name || SDK_NAME;
-      event.sdk.version = event.sdk.version || SDK_VERSION;
+      event.sdk.name = event.sdk.name || defaultSdkInfo.name;;
+      event.sdk.version = event.sdk.version || defaultSdkInfo.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',
+          name: SDK_PACKAGE_NAME,
           version: SDK_VERSION,
         },
       ];


Test patch:
diff --git a/test/client.test.ts b/test/client.test.ts
index cf3af43..b85a403 100644
--- a/test/client.test.ts
+++ b/test/client.test.ts
@@ -3,6 +3,7 @@ import type { Envelope, Transport } from '@sentry/types';
 import { CapacitorClient } from '../src/client';
 import type { CapacitorClientOptions } from '../src/options';
 import { NativeTransport } from '../src/transports/native';
+import { SDK_NAME, SDK_PACKAGE_NAME, SDK_VERSION } from '../src/version';
 import { NATIVE } from '../src/wrapper';
 
 interface MockedCapacitor {
@@ -69,7 +70,7 @@ jest.mock('../src/plugin', () => {
 });
 
 import * as Plugin from '../src/plugin';
-
+import { envelopeHeader, envelopeItemHeader, envelopeItemPayload, envelopeItems, firstArg, getMockSession, getMockUserFeedback } from './testutils';
 
 const EXAMPLE_DSN = 'https://6890c2f6677340daa4804f8194804ea2@o19635.ingest.sentry.io/148053';
 
@@ -209,7 +210,6 @@ describe('Tests CapacitorClient', () => {
     });
   });
 
-  /* TODO: To be implemented
   describe('UserFeedback', () => {
     test('sends UserFeedback to native Layer', () => {
       const mockTransportSend: jest.Mock = jest.fn(() => Promise.resolve());
@@ -241,10 +241,7 @@ describe('Tests CapacitorClient', () => {
       });
     });
   });
-*/
 
-  /*
-  TODO: FIX SdkInfo
   describe('envelopeHeader SdkInfo', () => {
     let mockTransportSend: jest.Mock;
     let client: CapacitorClient;
@@ -293,8 +290,7 @@ describe('Tests CapacitorClient', () => {
       expect(getSdkInfoFrom(mockTransportSend)).toStrictEqual(expectedSdkInfo);
     });
   });
-*/
-    /* TODO: Fix SDKInfo
+
   describe('event data enhancement', () => {
     test('event contains sdk default information', async () => {
       const mockedSend = jest.fn<PromiseLike<void>, [Envelope]>().mockResolvedValue(undefined);
@@ -314,6 +310,7 @@ describe('Tests CapacitorClient', () => {
       const actualEvent: Event | undefined = <Event>(
         mockedSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload]
       );
+      // @ts-ignore SDK is not inside the event by default.
       expect(actualEvent?.sdk?.packages).toEqual([
         {
           name: SDK_PACKAGE_NAME,
@@ -323,7 +320,7 @@ describe('Tests CapacitorClient', () => {
     });
 
   });
-*/
+
   describe('normalizes events', () => {
     /* TODO: Fix later
     test('handles circular input', async () => {