Repository: getsentry/sentry-capacitor (ts)

Issue:
Angular - Uncaught SentryError: Native Client is not available, can't start on native.
### Environment

"@sentry/angular": "7.37.1", 
Capacitor 0.11.2

### Steps to Reproduce

I install this in iOS with the versions mentioned here:
https://github.com/getsentry/sentry-capacitor/issues/193

In app component:


```
Sentry.init(
      {
        dsn: 'mydsn',
        // To set your release and dist versions
        release: 'uscm@4.2.2',
        dist: '1',
        // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
        // We recommend adjusting this value in production.
        tracesSampleRate: 1.0,
        integrations: [
          new BrowserTracing({
            tracingOrigins: ['localhost', 'https://myapi'],
          }),
        ]
      },
      // Forward the init method to the sibling Framework.
      SentrySibling.init
    );


```

In app.module:


```
{
      provide: ErrorHandler,
      // Attach the Sentry ErrorHandler
      useValue: SentrySibling.createErrorHandler(),
    },

```

### Expected Result

Launch successfully

### Actual Result

The app fails to launch with the error in the title

"Called fetchSdkInfo without initializing cocoa SDK."  error on iOS,  in React app
### Environment
Ionic React app with Capacitor.

package.json:
```
    "@capacitor/cli": "4.6.3",
    "@capacitor/android": "4.6.3",
    "@capacitor/app": "4.1.1",
    "@capacitor/core": "4.6.3",
    "@capacitor/device": "^4.1.0",
    "@capacitor/ios": "4.6.3",
    "@ionic/react": "^6.5.3",
    "@ionic/react-router": "^6.5.3",
    "@sentry/capacitor": "^0.11.2",
    "@sentry/react": "7.37.1",
    "@sentry/tracing": "7.37.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router": "^5.3.4",
    "react-router-dom": "^5.3.4",
    "react-scripts": "^5.0.1",
```

### Steps to Reproduce

1. config sentry:
```
import * as Sentry from '@sentry/capacitor';
import * as SentryReact from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
import { ExtraErrorData as ExtraErrorDataIntegration } from '@sentry/integrations';
import { ERROR_DEPTH } from './const';
import { MOBILE_API_URL, SENTRY_DSN } from 'constants/env';

const integrations = [
  new BrowserTracing({
    tracePropagationTargets: [MOBILE_API_URL],
  }),
  new ExtraErrorDataIntegration({
    depth: ERROR_DEPTH, // Defaults to 3.
    /* /!\ When changing this value, make sure to update `normalizeDepth` of the whole SDK
     * to `depth + 1` in order to get it serialized properly - https://docs.sentry.io/platforms/javascript/configuration/options/#normalize-depth
     */
  }),
];

// Attempt to solve the issue inspired by https://github.com/getsentry/sentry-capacitor/issues/91
// type ReactOptions = NonNullable<Parameters<typeof SentryReact.init>[0]>;
// type ReactOptionsWithoutReact = Omit<ReactOptions, 'React'>;
// const initSentryReact = (options?: ReactOptions): void =>
//   SentryReact.init({ ...options, React });

Sentry.init(
  {
    dsn: SENTRY_DSN,
    normalizeDepth: ERROR_DEPTH + 1,
    tracesSampler: (samplingContext) => {
      if (samplingContext?.transactionContext?.name === 'GET /api/status') {
        return 0;
      }
      if (process.env.REACT_APP_ENV === 'production') {
        return 0.2;
      }
      if (process.env.REACT_APP_ENV === 'preprod') {
        return 1;
      }
      if (process.env.REACT_APP_ENV === 'local') {
        return 1;
      }
      return 0;
    },
    integrations,
    environment: process.env.REACT_APP_ENV,
  },
  SentryReact.init
);
```

2. in Homepage.tsx:
```
  useEffect(() => {
    console.log('capture Exception Sentry');
    Sentry.captureException('Test Captured Exception');
  }, []);
```

3. ionic cap sync ios
4. xcode build on local iphone



### Expected Result
Error is sent to sentry, from android and ios real devices

### Actual Result
Error is sent to sentry from android device, but not from ios device.
iOS shows the following errors:
Logs:

``{message: "Called fetchSdkInfo without initializing cocoa SDK.", errorMessage: "Called fetchSdkInfo without initializing cocoa SDK."}``

Maybe also related log: 
``Unhandled Promise Rejection: DataCloneError: The object can not be cloned.``

Capture of logs: 
<img width="1142" alt="Screenshot 2023-03-24 at 10 19 51" src="https://user-images.githubusercontent.com/21168856/227477614-18797f04-a150-4c31-b185-9f2dd2c13f25.png">




Pull request description:
Fix iOS not Sending events.
Reworked the Capacitor Options sent to native in order to only allow parameters that are supported, the previous filter was weak since new types and custom ones added by the users could have the risk of breaking the native initialisation on iOS.

Fixes #338, may also fix #337

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a87089..a860a1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## Unreleased
+
+### Fixes
+
+- Fix iOS not Sending events ([#370](https://github.com/getsentry/sentry-capacitor/pull/370))
+
 ## 0.11.3
 
 ### Fixes
diff --git a/src/nativeOptions.ts b/src/nativeOptions.ts
new file mode 100644
index 0000000..473b11b
--- /dev/null
+++ b/src/nativeOptions.ts
@@ -0,0 +1,36 @@
+import type { CapacitorOptions } from './options';
+
+/**
+ * Create a new CapacitorOption without any parameter that could crash the bridge (in short, not being a string, number or boolean).
+ * some of the excluded parameters are: Integrations, app, vue, beforeSend, beforeBreadcrumb, integrations, defaultIntegrations, transport, tracesSampler.
+ * @param options CapacitorOptions
+ */
+export function FilterNativeOptions(options: CapacitorOptions): CapacitorOptions {
+  return {
+    // allowUrls: Only available on the JavaScript Layer.
+    attachStacktrace: options.attachStacktrace,
+    attachThreads: options.attachThreads,
+    debug: options.debug,
+    // denyUrls Only available on the JavaScript Layer.
+    dist: options.dist,
+    dsn: options.dsn,
+    enabled: options.enabled,
+    enableNdkScopeSync: options.enableNdkScopeSync,
+    enableOutOfMemoryTracking: options.enableOutOfMemoryTracking,
+    enableTracing: options.enableTracing,
+    environment: options.environment,
+    // ignoreErrors: Only available on the JavaScript Layer.
+    // ignoreTransactions: Only available on the JavaScript Layer.
+    maxBreadcrumbs: options.maxBreadcrumbs,
+    // maxValueLength: Only available on the JavaScript Layer.
+    release: options.release,
+    // replaysOnErrorSampleRate: Only handled on the JavaScript Layer.
+    // replaysSessionSampleRate: Only handled on the JavaScript Layer.
+    sampleRate: options.sampleRate,
+    sendClientReports: options.sendClientReports,
+    sendDefaultPii: options.sendDefaultPii,
+    sessionTrackingIntervalMillis: options.sessionTrackingIntervalMillis,
+    tracesSampleRate: options.tracesSampleRate,
+    // tunnel: options.tunnel: Only handled on the JavaScript Layer.
+  };
+}
diff --git a/src/wrapper.ts b/src/wrapper.ts
index aeba79a..b6055e7 100644
--- a/src/wrapper.ts
+++ b/src/wrapper.ts
@@ -3,6 +3,7 @@ import type { BaseEnvelopeItemHeaders, Breadcrumb, Envelope, EnvelopeItem, Event
 import { dropUndefinedKeys, logger, SentryError } from '@sentry/utils';
 
 import type { NativeDeviceContextsResponse } from './definitions';
+import { FilterNativeOptions } from './nativeOptions';
 import type { CapacitorOptions } from './options';
 import { SentryCapacitor } from './plugin';
 import { utf8ToBytes } from './vendor';
@@ -96,21 +97,7 @@ export const NATIVE = {
     }
 
     // filter out all options that would crash native
-    /* eslint-disable @typescript-eslint/unbound-method, @typescript-eslint/no-unused-vars */
-    const {
-      // @ts-ignore Vue specific option.
-      app,
-      // @ts-ignore Vue specific option.
-      vue,
-      beforeSend,
-      beforeBreadcrumb,
-      integrations,
-      defaultIntegrations,
-      transport,
-      tracesSampler,
-      ...filteredOptions
-    } = options;
-
+    const filteredOptions = FilterNativeOptions(options);
     return SentryCapacitor.initNativeSdk({ options: filteredOptions });
   },
 


Test patch:
diff --git a/test/nativeOptions.test.ts b/test/nativeOptions.test.ts
new file mode 100644
index 0000000..cd855f3
--- /dev/null
+++ b/test/nativeOptions.test.ts
@@ -0,0 +1,68 @@
+import type { Instrumenter, StackParser } from '@sentry/types';
+
+import { FilterNativeOptions } from '../src/nativeOptions';
+
+describe('nativeOptions', () => {
+  test('invalid types not included', async () => {
+    const nativeOptions = FilterNativeOptions(
+      {
+        _experiments: [true],
+        _metadata: {},
+        allowUrls: ['test'],
+        attachStacktrace: true,
+        attachThreads: true,
+        beforeBreadcrumb: ((bread) => bread),
+        beforeSend: ((evt) => evt),
+        beforeSendTransaction: ((transaction) => transaction),
+        debug: true,
+        defaultIntegrations: [],
+        denyUrls: [],
+        dist: '1',
+        dsn: 'dns',
+        enableAutoSessionTracking: true,
+        enabled: true,
+        enableNative: true,
+        enableNativeCrashHandling: true,
+        enableNativeNagger: true,
+        enableNdkScopeSync: true,
+        enableOutOfMemoryTracking: true,
+        enableTracing: true,
+        environment: 'Prod',
+        ignoreErrors: ['test'],
+        ignoreTransactions: ['test'],
+        initialScope: {},
+        instrumenter: {} as Instrumenter,
+        integrations: [],
+        maxBreadcrumbs: 100,
+        maxValueLength: 100,
+        normalizeDepth: 100,
+        normalizeMaxBreadth: 100,
+        release: '1',
+        replaysOnErrorSampleRate: 1,
+        replaysSessionSampleRate: 1,
+        sampleRate: 1,
+        sendClientReports: true,
+        sendDefaultPii: true,
+        sessionTrackingIntervalMillis: 10,
+        shutdownTimeout: 10,
+        stackParser: {} as StackParser,
+        tracesSampler: ((_) => 1),
+        tracesSampleRate: 1,
+        transportOptions: {},
+        tunnel: 'test',
+      });
+
+    const keys = Object.keys(nativeOptions);
+    const keysFilter = keys.filter(key =>
+      // @ts-ignore allowed for testing.
+      (typeof nativeOptions[key]) !== 'string' &&
+      // @ts-ignore allowed for testing.
+      (typeof nativeOptions[key]) !== 'number' &&
+      // @ts-ignore allowed for testing.
+      (typeof nativeOptions[key]) !== 'boolean' &&
+      // @ts-ignore allowed for testing.
+      (typeof nativeOptions[key]) !== undefined)
+
+    expect(keysFilter.toString()).toBe('');
+  });
+});
diff --git a/test/wrapper.test.ts b/test/wrapper.test.ts
index db68f6c..a056df7 100644
--- a/test/wrapper.test.ts
+++ b/test/wrapper.test.ts
@@ -87,7 +87,7 @@ describe('Tests Native Wrapper', () => {
       expect(SentryCapacitor.initNativeSdk).toBeCalled();
     });
 
-    test('Vue options to be removed', async () => {
+    test('Vue and App options to be removed', async () => {
 
       const initNativeSdk = jest.spyOn(SentryCapacitor, 'initNativeSdk');
 
@@ -104,6 +104,36 @@ describe('Tests Native Wrapper', () => {
       expect(initNativeSdk).toBeCalled();
     });
 
+
+    test('default options to be removed', async () => {
+      const initNativeSdk = jest.spyOn(SentryCapacitor, 'initNativeSdk');
+      await NATIVE.initNativeSdk(
+        {
+          dsn: 'test',
+          enableNative: true,
+          integrations: [],
+          defaultIntegrations: [],
+          beforeSend: ((event) => event),
+          beforeBreadcrumb: ((breadcrumb) => breadcrumb),
+          beforeSendTransaction: (transaction) => transaction,
+          transport: jest.fn(),
+          tracesSampler: jest.fn(),
+        });
+
+      const nativeOption = initNativeSdk.mock.calls[0][0].options;
+      expect(SentryCapacitor.initNativeSdk).toBeCalledTimes(1);
+      // @ts-ignore Not part of Capacitor Options but it is extended by Vue Options.
+      expect(nativeOption.integrations).toBeUndefined();
+      expect(nativeOption.defaultIntegrations).toBeUndefined();
+      expect(nativeOption.beforeSend).toBeUndefined();
+      expect(nativeOption.beforeSendTransaction).toBeUndefined();
+      expect(nativeOption.beforeBreadcrumb).toBeUndefined();
+      expect(nativeOption.transport).toBeUndefined();
+      expect(nativeOption.tracesSampler).toBeUndefined();
+
+      expect(initNativeSdk).toBeCalled();
+    });
+
     test('warns if there is no dsn', async () => {
       SentryCapacitor.initNativeSdk = jest.fn();
       logger.warn = jest.fn();