Repository: formbricks/formbricks
Pull request #5416: fix: Address SonarQube code smells
Author: login: vaxi87
Created: 2025-04-18T12:07:50Z | Merged: 2025-04-21T09:30:45Z

<!-- We require pull request titles to follow the Conventional Commits specification ( https://www.conventionalcommits.org/en/v1.0.0/#summary ). Please make sure your title follow these conventions -->

## What does this PR do?

Address SonarQube issue reports

<!-- Please provide a screenshots or a loom video for visual changes to speed up reviews
 Loom Video: https://www.loom.com/
-->

## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration -->

- Smoke test is needed, because the APIClient class was refactored.

## Checklist

<!-- We're starting to get more and more contributions. Please help us making this efficient for all of us and go through this checklist. Please tick off what you did  -->

### Required

- [x] Filled out the "How to test" section in this PR
- [ ] Read [How we Code at Formbricks](<[https://github.com/formbricks/formbricks/blob/main/CONTRIBUTING.md](https://formbricks.com/docs/contributing/how-we-code)>)
- [x] Self-reviewed my own code
- [x] Commented on my code in hard-to-understand bits
- [ ] Ran `pnpm build`
- [ ] Checked for warnings, there are none
- [ ] Removed all `console.logs`
- [x] Merged the latest changes from main onto my branch with `git pull origin main`
- [ ] My changes don't cause any responsiveness issues
- [ ] First PR at Formbricks? [Please sign the CLA!](https://cla-assistant.io/formbricks/formbricks) Without it we wont be able to merge it 🙏

### Appreciated

- [ ] If a UI change was made: Added a screen recording or screenshots to this PR
- [ ] Updated the Formbricks Docs if changes were necessary


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Integrated the AnyCodable Swift package as a dependency for iOS, ensuring compatibility and maintainability of type-erased encoding/decoding.
  - Improved Android app logging for survey events and errors, providing clearer feedback during survey interactions.
  - Added user language synchronization with server state and reset on logout for a consistent multilingual experience.
  - Prevented overwriting of existing user ID without logout to avoid user conflicts.

- **Refactor**
  - Streamlined network request and response handling for iOS, improving code structure and maintainability without changing user-facing behavior.
  - Simplified website data removal logic in survey web views for iOS.

- **Chores**
  - Updated ignored files and directories to exclude user-specific workspace data from version control.
  - Removed local AnyCodable helper files in favor of the new package dependency.
  - Added a resolved package file to track Swift package dependencies.
  - Expanded CORS header configuration to cover multiple API versions for broader client API support.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/.gitignore b/.gitignore
index aa874edc93f4..8c8df6695892 100644
--- a/.gitignore
+++ b/.gitignore
@@ -72,3 +72,4 @@ infra/terraform/.terraform/
 # IntelliJ IDEA
 /.idea/
 /*.iml
+packages/ios/FormbricksSDK/FormbricksSDK.xcodeproj/project.xcworkspace/xcuserdata
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index 1840f070d176..d5e1abf85eef 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -127,7 +127,7 @@ const nextConfig = {
       },
       {
         // matching all API routes
-        source: "/api/v1/client/:path*",
+        source: "/api/(v1|v2)/client/:path*",
         headers: [
           { key: "Access-Control-Allow-Credentials", value: "true" },
           { key: "Access-Control-Allow-Origin", value: "*" },
diff --git a/packages/android/app/src/main/java/com/formbricks/demo/MainActivity.kt b/packages/android/app/src/main/java/com/formbricks/demo/MainActivity.kt
index 0ef212b66387..5679e0f70078 100644
--- a/packages/android/app/src/main/java/com/formbricks/demo/MainActivity.kt
+++ b/packages/android/app/src/main/java/com/formbricks/demo/MainActivity.kt
@@ -1,12 +1,14 @@
 package com.formbricks.demo
 
 import android.os.Bundle
+import android.util.Log
 import android.widget.Button
 import androidx.activity.enableEdgeToEdge
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowInsetsCompat
 import com.formbricks.formbrickssdk.Formbricks
+import com.formbricks.formbrickssdk.FormbricksCallback
 import com.formbricks.formbrickssdk.helper.FormbricksConfig
 import java.util.UUID
 
@@ -15,6 +17,25 @@ class MainActivity : AppCompatActivity() {
         super.onCreate(savedInstanceState)
         enableEdgeToEdge()
 
+        Formbricks.callback = object: FormbricksCallback {
+            override fun onSurveyStarted() {
+                Log.d("FormbricksCallback", "onSurveyStarted")
+            }
+
+            override fun onSurveyFinished() {
+                Log.d("FormbricksCallback", "onSurveyFinished")
+            }
+
+            override fun onSurveyClosed() {
+                Log.d("FormbricksCallback", "onSurveyClosed")
+            }
+
+            override fun onError(error: Exception) {
+                Log.d("FormbricksCallback", "onError: ${error.localizedMessage}")
+            }
+
+        }
+
         val config = FormbricksConfig.Builder("[appUrl]","[environmentId]")
             .setLoggingEnabled(true)
             .setFragmentManager(supportFragmentManager)
@@ -35,4 +56,4 @@ class MainActivity : AppCompatActivity() {
             Formbricks.track("click_demo_button")
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/ios/Formbricks.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/ios/Formbricks.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 000000000000..e341bff87ded
--- /dev/null
+++ b/packages/ios/Formbricks.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,15 @@
+{
+  "originHash" : "92c0230fb0adc404299bb05aba6c51a76f86c388fdfb9f4e9bed3a757f80fc07",
+  "pins" : [
+    {
+      "identity" : "anycodable",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/Flight-School/AnyCodable",
+      "state" : {
+        "revision" : "862808b2070cd908cb04f9aafe7de83d35f81b05",
+        "version" : "0.6.7"
+      }
+    }
+  ],
+  "version" : 3
+}
diff --git a/packages/ios/FormbricksSDK/FormbricksSDK.xcodeproj/project.pbxproj b/packages/ios/FormbricksSDK/FormbricksSDK.xcodeproj/project.pbxproj
index 1004b13303e6..e64e6bfc180a 100644
--- a/packages/ios/FormbricksSDK/FormbricksSDK.xcodeproj/project.pbxproj
+++ b/packages/ios/FormbricksSDK/FormbricksSDK.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		4D7D8DD62DB14F18002C453E /* AnyCodable in Frameworks */ = {isa = PBXBuildFile; productRef = 4D7D8DD52DB14F18002C453E /* AnyCodable */; };
 		4DDAED692D50D49B00A19B1F /* FormbricksSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DDAED602D50D49A00A19B1F /* FormbricksSDK.framework */; };
 /* End PBXBuildFile section */
 
@@ -108,6 +109,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				4D7D8DD62DB14F18002C453E /* AnyCodable in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -122,11 +124,19 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		4D7D8DD42DB14F18002C453E /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
 		4DDAED562D50D49A00A19B1F = {
 			isa = PBXGroup;
 			children = (
 				4DDAED622D50D49A00A19B1F /* FormbricksSDK */,
 				4DDAED6C2D50D49B00A19B1F /* FormbricksSDKTests */,
+				4D7D8DD42DB14F18002C453E /* Frameworks */,
 				4DDAED612D50D49A00A19B1F /* Products */,
 			);
 			sourceTree = "<group>";
@@ -171,6 +181,7 @@
 			);
 			name = FormbricksSDK;
 			packageProductDependencies = (
+				4D7D8DD52DB14F18002C453E /* AnyCodable */,
 			);
 			productName = FormbricksSDK;
 			productReference = 4DDAED602D50D49A00A19B1F /* FormbricksSDK.framework */;
@@ -227,6 +238,9 @@
 			);
 			mainGroup = 4DDAED562D50D49A00A19B1F;
 			minimizedProjectReferenceProxies = 1;
+			packageReferences = (
+				4DA4A0952DB14E67007299C0 /* XCRemoteSwiftPackageReference "AnyCodable" */,
+			);
 			preferredProjectObjectVersion = 77;
 			productRefGroup = 4DDAED612D50D49A00A19B1F /* Products */;
 			projectDirPath = "";
@@ -536,6 +550,25 @@
 			defaultConfigurationName = Release;
 		};
 /* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+		4DA4A0952DB14E67007299C0 /* XCRemoteSwiftPackageReference "AnyCodable" */ = {
+			isa = XCRemoteSwiftPackageReference;
+			repositoryURL = "https://github.com/Flight-School/AnyCodable";
+			requirement = {
+				kind = exactVersion;
+				version = 0.6.7;
+			};
+		};
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+		4D7D8DD52DB14F18002C453E /* AnyCodable */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = 4DA4A0952DB14E67007299C0 /* XCRemoteSwiftPackageReference "AnyCodable" */;
+			productName = AnyCodable;
+		};
+/* End XCSwiftPackageProductDependency section */
 	};
 	rootObject = 4DDAED572D50D49A00A19B1F /* Project object */;
 }
diff --git a/packages/ios/FormbricksSDK/FormbricksSDK/Formbricks.swift b/packages/ios/FormbricksSDK/FormbricksSDK/Formbricks.swift
index 26ab43931fae..101f89875d2f 100644
--- a/packages/ios/FormbricksSDK/FormbricksSDK/Formbricks.swift
+++ b/packages/ios/FormbricksSDK/FormbricksSDK/Formbricks.swift
@@ -91,6 +91,11 @@ import Network
             return
         }
         
+        if let existing = userManager?.userId, !existing.isEmpty {
+            logger?.error("A userId is already set (\"\(existing)\") – please call Formbricks.logout() before setting a new one.")
+            return
+        }
+        
         userManager?.set(userId: userId)
     }
     
diff --git a/packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyCodable.swift b/packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyCodable.swift
deleted file mode 100644
index 28e949ccf90b..000000000000
--- a/packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyCodable.swift
+++ /dev/null
@@ -1,147 +0,0 @@
-// https://github.com/Flight-School/AnyCodable/blob/master/Sources/AnyCodable/AnyCodable.swift
-
-import Foundation
-/**
- A type-erased `Codable` value.
-
- The `AnyCodable` type forwards encoding and decoding responsibilities
- to an underlying value, hiding its specific underlying type.
-
- You can encode or decode mixed-type values in dictionaries
- and other collections that require `Encodable` or `Decodable` conformance
- by declaring their contained type to be `AnyCodable`.
-
- - SeeAlso: `AnyEncodable`
- - SeeAlso: `AnyDecodable`
- */
-struct AnyCodable: Codable {
-    public let value: Any
-
-    public init<T>(_ value: T?) {
-        self.value = value ?? ()
-    }
-}
-
-extension AnyCodable: AnyEncodableProtocol, AnyDecodableProtocol {}
-
-extension AnyCodable: Equatable {
-    public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool {
-        switch (lhs.value, rhs.value) {
-        case is (Void, Void):
-            return true
-        case let (lhs as Bool, rhs as Bool):
-            return lhs == rhs
-        case let (lhs as Int, rhs as Int):
-            return lhs == rhs
-        case let (lhs as Int8, rhs as Int8):
-            return lhs == rhs
-        case let (lhs as Int16, rhs as Int16):
-            return lhs == rhs
-        case let (lhs as Int32, rhs as Int32):
-            return lhs == rhs
-        case let (lhs as Int64, rhs as Int64):
-            return lhs == rhs
-        case let (lhs as UInt, rhs as UInt):
-            return lhs == rhs
-        case let (lhs as UInt8, rhs as UInt8):
-            return lhs == rhs
-        case let (lhs as UInt16, rhs as UInt16):
-            return lhs == rhs
-        case let (lhs as UInt32, rhs as UInt32):
-            return lhs == rhs
-        case let (lhs as UInt64, rhs as UInt64):
-            return lhs == rhs
-        case let (lhs as Float, rhs as Float):
-            return lhs == rhs
-        case let (lhs as Double, rhs as Double):
-            return lhs == rhs
-        case let (lhs as String, rhs as String):
-            return lhs == rhs
-        case let (lhs as [String: AnyCodable], rhs as [String: AnyCodable]):
-            return lhs == rhs
-        case let (lhs as [AnyCodable], rhs as [AnyCodable]):
-            return lhs == rhs
-        case let (lhs as [String: Any], rhs as [String: Any]):
-            return NSDictionary(dictionary: lhs) == NSDictionary(dictionary: rhs)
-        case let (lhs as [Any], rhs as [Any]):
-            return NSArray(array: lhs) == NSArray(array: rhs)
-        case is (NSNull, NSNull):
-            return true
-        default:
-            return false
-        }
-    }
-}
-
-extension AnyCodable: CustomStringConvertible {
-    public var description: String {
-        switch value {
-        case is Void:
-            return String(describing: nil as Any?)
-        case let value as CustomStringConvertible:
-            return value.description
-        default:
-            return String(describing: value)
-        }
-    }
-}
-
-extension AnyCodable: CustomDebugStringConvertible {
-    public var debugDescription: String {
-        if let value = value as? CustomDebugStringConvertible {
-            return "AnyCodable(\(value.debugDescription))"
-        }
-        return "AnyCodable(\(description))"
-    }
-}
-
-extension AnyCodable: ExpressibleByNilLiteral {}
-extension AnyCodable: ExpressibleByBooleanLiteral {}
-extension AnyCodable: ExpressibleByIntegerLiteral {}
-extension AnyCodable: ExpressibleByFloatLiteral {}
-extension AnyCodable: ExpressibleByStringLiteral {}
-extension AnyCodable: ExpressibleByStringInterpolation {}
-extension AnyCodable: ExpressibleByArrayLiteral {}
-extension AnyCodable: ExpressibleByDictionaryLiteral {}
-
-
-extension AnyCodable: Hashable {
-    public func hash(into hasher: inout Hasher) {
-        switch value {
-        case let value as Bool:
-            hasher.combine(value)
-        case let value as Int:
-            hasher.combine(value)
-        case let value as Int8:
-            hasher.combine(value)
-        case let value as Int16:
-            hasher.combine(value)
-        case let value as Int32:
-            hasher.combine(value)
-        case let value as Int64:
-            hasher.combine(value)
-        case let value as UInt:
-            hasher.combine(value)
-        case let value as UInt8:
-            hasher.combine(value)
-        case let value as UInt16:
-            hasher.combine(value)
-        case let value as UInt32:
-            hasher.combine(value)
-        case let value as UInt64:
-            hasher.combine(value)
-        case let value as Float:
-            hasher.combine(value)
-        case let value as Double:
-            hasher.combine(value)
-        case let value as String:
-            hasher.combine(value)
-        case let value as [String: AnyCodable]:
-            hasher.combine(value)
-        case let value as [AnyCodable]:
-            hasher.combine(value)
-        default:
-            break
-        }
-    }
-}
diff --git a/packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyDecodable.swift b/packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyDecodable.swift
deleted file mode 100644
index 9941c869f424..000000000000
--- a/packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyDecodable.swift
+++ /dev/null
@@ -1,189 +0,0 @@
-// https://github.com/Flight-School/AnyCodable/blob/master/Sources/AnyCodable/AnyCodable.swift
-
-#if canImport(Foundation)
-import Foundation
-#endif
-
-/**
- A type-erased `Decodable` value.
-
- The `AnyDecodable` type forwards decoding responsibilities
- to an underlying value, hiding its specific underlying type.
-
- You can decode mixed-type values in dictionaries
- and other collections that require `Decodable` conformance
- by declaring their contained type to be `AnyDecodable`:
-
-     let json = """
-     {
-         "boolean": true,
-         "integer": 42,
-         "double": 3.141592653589793,
-         "string": "string",
-         "array": [1, 2, 3],
-         "nested": {
-             "a": "alpha",
-             "b": "bravo",
-             "c": "charlie"
-         },
-         "null": null
-     }
-     """.data(using: .utf8)!
-
-     let decoder = JSONDecoder()
-     let dictionary = try! decoder.decode([String: AnyDecodable].self, from: json)
- */
-struct AnyDecodable: Decodable {
-    public let value: Any
-
-    public init<T>(_ value: T?) {
-        self.value = value ?? ()
-    }
-}
-
-@usableFromInline
-protocol AnyDecodableProtocol {
-    var value: Any { get }
-    init<T>(_ value: T?)
-}
-
-extension AnyDecodable: AnyDecodableProtocol {}
-
-extension AnyDecodableProtocol {
-    public init(from decoder: Decoder) throws {
-        let container = try decoder.singleValueContainer()
-
-        if container.decodeNil() {
-            #if canImport(Foundation)
-                self.init(NSNull())
-            #else
-                self.init(Optional<Self>.none)
-            #endif
-        } else if let bool = try? container.decode(Bool.self) {
-            self.init(bool)
-        } else if let int = try? container.decode(Int.self) {
-            self.init(int)
-        } else if let uint = try? container.decode(UInt.self) {
-            self.init(uint)
-        } else if let double = try? container.decode(Double.self) {
-            self.init(double)
-        } else if let string = try? container.decode(String.self) {
-            self.init(string)
-        } else if let array = try? container.decode([AnyDecodable].self) {
-            self.init(array.map { $0.value })
-        } else if let dictionary = try? container.decode([String: AnyDecodable].self) {
-            self.init(dictionary.mapValues { $0.value })
-        } else {
-            throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyDecodable value cannot be decoded")
-        }
-    }
-}
-
-extension AnyDecodable: Equatable {
-    public static func == (lhs: AnyDecodable, rhs: AnyDecodable) -> Bool {
-        switch (lhs.value, rhs.value) {
-#if canImport(Foundation)
-        case is (NSNull, NSNull), is (Void, Void):
-            return true
-#endif
-        case let (lhs as Bool, rhs as Bool):
-            return lhs == rhs
-        case let (lhs as Int, rhs as Int):
-            return lhs == rhs
-        case let (lhs as Int8, rhs as Int8):
-            return lhs == rhs
-        case let (lhs as Int16, rhs as Int16):
-            return lhs == rhs
-        case let (lhs as Int32, rhs as Int32):
-            return lhs == rhs
-        case let (lhs as Int64, rhs as Int64):
-            return lhs == rhs
-        case let (lhs as UInt, rhs as UInt):
-            return lhs == rhs
-        case let (lhs as UInt8, rhs as UInt8):
-            return lhs == rhs
-        case let (lhs as UInt16, rhs as UInt16):
-            return lhs == rhs
-        case let (lhs as UInt32, rhs as UInt32):
-            return lhs == rhs
-        case let (lhs as UInt64, rhs as UInt64):
-            return lhs == rhs
-        case let (lhs as Float, rhs as Float):
-            return lhs == rhs
-        case let (lhs as Double, rhs as Double):
-            return lhs == rhs
-        case let (lhs as String, rhs as String):
-            return lhs == rhs
-        case let (lhs as [String: AnyDecodable], rhs as [String: AnyDecodable]):
-            return lhs == rhs
-        case let (lhs as [AnyDecodable], rhs as [AnyDecodable]):
-            return lhs == rhs
-        default:
-            return false
-        }
-    }
-}
-
-extension AnyDecodable: CustomStringConvertible {
-    public var description: String {
-        switch value {
-        case is Void:
-            return String(describing: nil as Any?)
-        case let value as CustomStringConvertible:
-            return value.description
-        default:
-            return String(describing: value)
-        }
-    }
-}
-
-extension AnyDecodable: CustomDebugStringConvertible {
-    public var debugDescription: String {
-        if let value = value as? CustomDebugStringConvertible {
-            return "AnyDecodable(\(value.debugDescription))"
-        } else {
-            return "AnyDecodable(\(description))"
-        }
-    }
-}
-
-extension AnyDecodable: Hashable {
-    public func hash(into hasher: inout Hasher) {
-        switch value {
-        case let value as Bool:
-            hasher.combine(value)
-        case let value as Int:
-            hasher.combine(value)
-        case let value as Int8:
-            hasher.combine(value)
-        case let value as Int16:
-            hasher.combine(value)
-        case let value as Int32:
-            hasher.combine(value)
-        case let value as Int64:
-            hasher.combine(value)
-        case let value as UInt:
-            hasher.combine(value)
-        case let value as UInt8:
-            hasher.combine(value)
-        case let value as UInt16:
-            hasher.combine(value)
-        case let value as UInt32:
-            hasher.combine(value)
-        case let value as UInt64:
-            hasher.combine(value)
-        case let value as Float:
-            hasher.combine(value)
-        case let value as Double:
-            hasher.combine(value)
-        case let value as String:
-            hasher.combine(value)
-        case let value as [String: AnyDecodable]:
-            hasher.combine(value)
-        case let value as [AnyDecodable]:
-            hasher.combine(value)
-        default:
-            break
-        }
-    }
-}
diff --git a/packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyEncodable.swift b/packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyEncodable.swift
deleted file mode 100644
index f891fec484a4..000000000000
--- a/packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyEncodable.swift
+++ /dev/null
@@ -1,292 +0,0 @@
-// https://github.com/Flight-School/AnyCodable/blob/master/Sources/AnyCodable/AnyCodable.swift
-
-#if canImport(Foundation)
-import Foundation
-#endif
-
-/**
- A type-erased `Encodable` value.
-
- The `AnyEncodable` type forwards encoding responsibilities
- to an underlying value, hiding its specific underlying type.
-
- You can encode mixed-type values in dictionaries
- and other collections that require `Encodable` conformance
- by declaring their contained type to be `AnyEncodable`:
-
-     let dictionary: [String: AnyEncodable] = [
-         "boolean": true,
-         "integer": 42,
-         "double": 3.141592653589793,
-         "string": "string",
-         "array": [1, 2, 3],
-         "nested": [
-             "a": "alpha",
-             "b": "bravo",
-             "c": "charlie"
-         ],
-         "null": nil
-     ]
-
-     let encoder = JSONEncoder()
-     let json = try! encoder.encode(dictionary)
- */
-struct AnyEncodable: Encodable {
-    public let value: Any
-
-    public init<T>(_ value: T?) {
-        self.value = value ?? ()
-    }
-}
-
-@usableFromInline
-protocol AnyEncodableProtocol {
-    var value: Any { get }
-    init<T>(_ value: T?)
-}
-
-extension AnyEncodable: AnyEncodableProtocol {}
-
-// MARK: - Encodable
-
-extension AnyEncodableProtocol {
-    public func encode(to encoder: Encoder) throws {
-        var container = encoder.singleValueContainer()
-
-        switch value {
-        #if canImport(Foundation)
-        case is NSNull:
-            try container.encodeNil()
-        #endif
-        case is Void:
-            try container.encodeNil()
-        case let bool as Bool:
-            try container.encode(bool)
-        case let int as Int:
-            try container.encode(int)
-        case let int8 as Int8:
-            try container.encode(int8)
-        case let int16 as Int16:
-            try container.encode(int16)
-        case let int32 as Int32:
-            try container.encode(int32)
-        case let int64 as Int64:
-            try container.encode(int64)
-        case let uint as UInt:
-            try container.encode(uint)
-        case let uint8 as UInt8:
-            try container.encode(uint8)
-        case let uint16 as UInt16:
-            try container.encode(uint16)
-        case let uint32 as UInt32:
-            try container.encode(uint32)
-        case let uint64 as UInt64:
-            try container.encode(uint64)
-        case let float as Float:
-            try container.encode(float)
-        case let double as Double:
-            try container.encode(double)
-        case let string as String:
-            try container.encode(string)
-        #if canImport(Foundation)
-        case let number as NSNumber:
-            try encode(nsnumber: number, into: &container)
-        case let date as Date:
-            try container.encode(date)
-        case let url as URL:
-            try container.encode(url)
-        #endif
-        case let array as [Any?]:
-            try container.encode(array.map { AnyEncodable($0) })
-        case let dictionary as [String: Any?]:
-            try container.encode(dictionary.mapValues { AnyEncodable($0) })
-        case let encodable as Encodable:
-            try encodable.encode(to: encoder)
-        default:
-            let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyEncodable value cannot be encoded")
-            throw EncodingError.invalidValue(value, context)
-        }
-    }
-
-    #if canImport(Foundation)
-    private func encode(nsnumber: NSNumber, into container: inout SingleValueEncodingContainer) throws {
-        switch Character(Unicode.Scalar(UInt8(nsnumber.objCType.pointee)))  {
-        case "B":
-            try container.encode(nsnumber.boolValue)
-        case "c":
-            try container.encode(nsnumber.int8Value)
-        case "s":
-            try container.encode(nsnumber.int16Value)
-        case "i", "l":
-            try container.encode(nsnumber.int32Value)
-        case "q":
-            try container.encode(nsnumber.int64Value)
-        case "C":
-            try container.encode(nsnumber.uint8Value)
-        case "S":
-            try container.encode(nsnumber.uint16Value)
-        case "I", "L":
-            try container.encode(nsnumber.uint32Value)
-        case "Q":
-            try container.encode(nsnumber.uint64Value)
-        case "f":
-            try container.encode(nsnumber.floatValue)
-        case "d":
-            try container.encode(nsnumber.doubleValue)
-        default:
-            let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "NSNumber cannot be encoded because its type is not handled")
-            throw EncodingError.invalidValue(nsnumber, context)
-        }
-    }
-    #endif
-}
-
-extension AnyEncodable: Equatable {
-    public static func == (lhs: AnyEncodable, rhs: AnyEncodable) -> Bool {
-        switch (lhs.value, rhs.value) {
-        case is (Void, Void):
-            return true
-        case let (lhs as Bool, rhs as Bool):
-            return lhs == rhs
-        case let (lhs as Int, rhs as Int):
-            return lhs == rhs
-        case let (lhs as Int8, rhs as Int8):
-            return lhs == rhs
-        case let (lhs as Int16, rhs as Int16):
-            return lhs == rhs
-        case let (lhs as Int32, rhs as Int32):
-            return lhs == rhs
-        case let (lhs as Int64, rhs as Int64):
-            return lhs == rhs
-        case let (lhs as UInt, rhs as UInt):
-            return lhs == rhs
-        case let (lhs as UInt8, rhs as UInt8):
-            return lhs == rhs
-        case let (lhs as UInt16, rhs as UInt16):
-            return lhs == rhs
-        case let (lhs as UInt32, rhs as UInt32):
-            return lhs == rhs
-        case let (lhs as UInt64, rhs as UInt64):
-            return lhs == rhs
-        case let (lhs as Float, rhs as Float):
-            return lhs == rhs
-        case let (lhs as Double, rhs as Double):
-            return lhs == rhs
-        case let (lhs as String, rhs as String):
-            return lhs == rhs
-        case let (lhs as [String: AnyEncodable], rhs as [String: AnyEncodable]):
-            return lhs == rhs
-        case let (lhs as [AnyEncodable], rhs as [AnyEncodable]):
-            return lhs == rhs
-        default:
-            return false
-        }
-    }
-}
-
-extension AnyEncodable: CustomStringConvertible {
-    public var description: String {
-        switch value {
-        case is Void:
-            return String(describing: nil as Any?)
-        case let value as CustomStringConvertible:
-            return value.description
-        default:
-            return String(describing: value)
-        }
-    }
-}
-
-extension AnyEncodable: CustomDebugStringConvertible {
-    public var debugDescription: String {
-        if let value = value as? CustomDebugStringConvertible {
-            return "AnyEncodable(\(value.debugDescription))"
-        } else {
-            return "AnyEncodable(\(description))"
-        }
-    }
-}
-
-extension AnyEncodable: ExpressibleByNilLiteral {}
-extension AnyEncodable: ExpressibleByBooleanLiteral {}
-extension AnyEncodable: ExpressibleByIntegerLiteral {}
-extension AnyEncodable: ExpressibleByFloatLiteral {}
-extension AnyEncodable: ExpressibleByStringLiteral {}
-extension AnyEncodable: ExpressibleByStringInterpolation {}
-extension AnyEncodable: ExpressibleByArrayLiteral {}
-extension AnyEncodable: ExpressibleByDictionaryLiteral {}
-
-extension AnyEncodableProtocol {
-    public init(nilLiteral _: ()) {
-        self.init(nil as Any?)
-    }
-
-    public init(booleanLiteral value: Bool) {
-        self.init(value)
-    }
-
-    public init(integerLiteral value: Int) {
-        self.init(value)
-    }
-
-    public init(floatLiteral value: Double) {
-        self.init(value)
-    }
-
-    public init(extendedGraphemeClusterLiteral value: String) {
-        self.init(value)
-    }
-
-    public init(stringLiteral value: String) {
-        self.init(value)
-    }
-
-    public init(arrayLiteral elements: Any...) {
-        self.init(elements)
-    }
-
-    public init(dictionaryLiteral elements: (AnyHashable, Any)...) {
-        self.init([AnyHashable: Any](elements, uniquingKeysWith: { first, _ in first }))
-    }
-}
-
-extension AnyEncodable: Hashable {
-    public func hash(into hasher: inout Hasher) {
-        switch value {
-        case let value as Bool:
-            hasher.combine(value)
-        case let value as Int:
-            hasher.combine(value)
-        case let value as Int8:
-            hasher.combine(value)
-        case let value as Int16:
-            hasher.combine(value)
-        case let value as Int32:
-            hasher.combine(value)
-        case let value as Int64:
-            hasher.combine(value)
-        case let value as UInt:
-            hasher.combine(value)
-        case let value as UInt8:
-            hasher.combine(value)
-        case let value as UInt16:
-            hasher.combine(value)
-        case let value as UInt32:
-            hasher.combine(value)
-        case let value as UInt64:
-            hasher.combine(value)
-        case let value as Float:
-            hasher.combine(value)
-        case let value as Double:
-            hasher.combine(value)
-        case let value as String:
-            hasher.combine(value)
-        case let value as [String: AnyEncodable]:
-            hasher.combine(value)
-        case let value as [AnyEncodable]:
-            hasher.combine(value)
-        default:
-            break
-        }
-    }
-}
diff --git a/packages/ios/FormbricksSDK/FormbricksSDK/Manager/UserManager.swift b/packages/ios/FormbricksSDK/FormbricksSDK/Manager/UserManager.swift
index dccea0ac59b5..d679aa7716e4 100644
--- a/packages/ios/FormbricksSDK/FormbricksSDK/Manager/UserManager.swift
+++ b/packages/ios/FormbricksSDK/FormbricksSDK/Manager/UserManager.swift
@@ -95,6 +95,9 @@ final class UserManager: UserManagerSyncable {
                 self?.lastDisplayedAt = userResponse.data.state?.data?.lastDisplayAt
                 self?.expiresAt = userResponse.data.state?.expiresAt
                 
+                let serverLanguage = userResponse.data.state?.data?.language
+                Formbricks.language = serverLanguage ?? "default"
+                
                 self?.updateQueue?.reset()
                 self?.surveyManager?.filterSurveys()
                 self?.startSyncTimer()
@@ -128,6 +131,7 @@ final class UserManager: UserManagerSyncable {
         backingResponses = nil
         backingLastDisplayedAt = nil
         backingExpiresAt = nil
+        Formbricks.language = "default"
         updateQueue?.reset()
         
         if isUserIdDefined {
diff --git a/packages/ios/FormbricksSDK/FormbricksSDK/Model/User/UserStateDetails.swift b/packages/ios/FormbricksSDK/FormbricksSDK/Model/User/UserStateDetails.swift
index d45f0cb791c4..52808141d37c 100644
--- a/packages/ios/FormbricksSDK/FormbricksSDK/Model/User/UserStateDetails.swift
+++ b/packages/ios/FormbricksSDK/FormbricksSDK/Model/User/UserStateDetails.swift
@@ -7,4 +7,5 @@ struct UserStateDetails: Codable {
     let displays: [Display]?
     let responses: [String]?
     let lastDisplayAt: Date?
+    let language: String?
 }
diff --git a/packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift b/packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift
index e6b35dc29437..b2f384f9e0a4 100644
--- a/packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift
+++ b/packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift
@@ -12,131 +12,124 @@ class APIClient<Request: CodableRequest>: Operation, @unchecked Sendable {
     }
     
     override func main() {
-        guard let apiURL = request.baseURL, var baseUrlComponents = URLComponents(string: apiURL) else {
+        guard let finalURL = buildFinalURL() else {
             completion?(.failure(FormbricksSDKError(type: .sdkIsNotInitialized)))
             return
         }
         
-        baseUrlComponents.queryItems = request.queryParams?.map { URLQueryItem(name: $0.key, value: $0.value) }
+        let urlRequest = createURLRequest(forURL: finalURL)
+        logRequest(urlRequest)
+        
+        session.dataTask(with: urlRequest) { data, response, error in
+            self.processResponse(data: data, response: response, error: error)
+        }.resume()
+    }
+    
+    private func buildFinalURL() -> URL? {
+        guard let apiURL = request.baseURL, var components = URLComponents(string: apiURL) else { return nil }
         
-        guard var finalURL = baseUrlComponents.url else {
-            completion?(.failure(FormbricksSDKError(type: .invalidAppUrl)))
+        components.queryItems = request.queryParams?.map { URLQueryItem(name: $0.key, value: $0.value) }
+        
+        guard var url = components.url, let path = setPathParams(request.requestEndPoint) else { return nil }
+
+        url.appendPathComponent(path)
+        return url
+    }
+    
+    private func processResponse(data: Data?, response: URLResponse?, error: Error?) {
+        guard let httpStatus = (response as? HTTPURLResponse)?.status else {
+            let error = FormbricksAPIClientError(type: .invalidResponse)
+            Formbricks.logger?.error("ERROR \(error.message)")
+            completion?(.failure(error))
             return
         }
+
+        var message = "\(httpStatus.rawValue) <<< \(response?.url?.absoluteString ?? "")"
         
-        guard let requestEndPoint = setPathParams(request.requestEndPoint) else {
-            completion?(.failure(FormbricksSDKError(type: .sdkIsNotInitialized)))
+        if httpStatus.responseType == .success {
+            handleSuccessResponse(data: data, statusCode: httpStatus.rawValue, message: &message)
+        } else {
+            handleFailureResponse(data: data, error: error, statusCode: httpStatus.rawValue, message: message)
+        }
+    }
+    
+    private func handleSuccessResponse(data: Data?, statusCode: Int, message: inout String) {
+        guard let data = data else {
+            completion?(.failure(FormbricksAPIClientError(type: .invalidResponse, statusCode: statusCode)))
             return
         }
-        
-        finalURL.appendPathComponent(requestEndPoint)
-        
-        let urlRequest = createURLRequest(forURL: finalURL)
-        
-        // LOG
-        var requestLogMessage = "\(request.requestType.rawValue) >>> "
-        if let urlString = urlRequest.url?.absoluteString {
-            requestLogMessage.append(urlString)
+
+        if let responseString = String(data: data, encoding: .utf8) {
+            message.append("\n\(responseString)\n")
         }
-        if let headers = urlRequest.allHTTPHeaderFields {
-            requestLogMessage.append("\nHeaders: \(headers)")
+
+        do {
+            if Request.Response.self == VoidResponse.self {
+                Formbricks.logger?.info(message)
+                completion?(.success(VoidResponse() as! Request.Response))
+            } else {
+                var body = try request.decoder.decode(Request.Response.self, from: data)
+                if var env = body as? EnvironmentResponse, let jsonString = String(data: data, encoding: .utf8) {
+                    env.responseString = jsonString
+                    body = env as! Request.Response
+                }
+                Formbricks.logger?.info(message)
+                completion?(.success(body))
+            }
+        } catch {
+            handleDecodingError(error, message: &message, statusCode: statusCode)
+        }
+    }
+    
+    private func handleFailureResponse(data: Data?, error: Error?, statusCode: Int, message: String) {
+        var log = message
+
+        if let error = error {
+            log.append("\nError: \(error.localizedDescription)")
+            Formbricks.logger?.error(log)
+            completion?(.failure(error))
+        } else if let data = data, let apiError = try? request.decoder.decode(FormbricksAPIError.self, from: data) {
+            Formbricks.logger?.error("\(log)\n\(apiError.getDetailedErrorMessage())")
+            completion?(.failure(apiError))
+        } else {
+            let error = FormbricksAPIClientError(type: .responseError, statusCode: statusCode)
+            Formbricks.logger?.error("\(log)\n\(error.message)")
+            completion?(.failure(error))
         }
-        if let body = urlRequest.httpBody {
-            requestLogMessage.append("\nBody: \(String(data: body, encoding: .utf8) ?? "")")
+    }
+    
+    private func handleDecodingError(_ error: Error, message: inout String, statusCode: Int) {
+        switch error {
+        case let DecodingError.dataCorrupted(context):
+            message.append("Data corrupted: \(context)")
+        case let DecodingError.keyNotFound(key, context):
+            message.append("Key '\(key)' not found: \(context.debugDescription)\ncodingPath: \(context.codingPath)")
+        case let DecodingError.valueNotFound(value, context):
+            message.append("Value '\(value)' not found: \(context.debugDescription)\ncodingPath: \(context.codingPath)")
+        case let DecodingError.typeMismatch(type, context):
+            message.append("Type '\(type)' mismatch: \(context.debugDescription)\ncodingPath: \(context.codingPath)")
+        default:
+            message.append("Error: \(error.localizedDescription)")
         }
+
+        Formbricks.logger?.error(message)
+        completion?(.failure(FormbricksAPIClientError(type: .invalidResponse, statusCode: statusCode)))
+    }
+    
+    private func logRequest(_ request: URLRequest) {
+        var message = "\(request.httpMethod ?? "") >>> \(request.url?.absoluteString ?? "")"
         
-        Formbricks.logger?.info(requestLogMessage)
+        if let headers = request.allHTTPHeaderFields {
+            message.append("\nHeaders: \(headers)")
+        }
         
-        session.dataTask(with: urlRequest) { (data, response, error) in
-            if let httpStatus = (response as? HTTPURLResponse)?.status {
-                var responseLogMessage = "\(httpStatus.rawValue) <<< "
-                if let urlString = response?.url?.absoluteString {
-                    responseLogMessage.append(urlString)
-                }
-                
-                if httpStatus.responseType == .success {
-                    guard let data = data else {
-                        self.completion?(.failure(FormbricksAPIClientError(type: .invalidResponse, statusCode: httpStatus.rawValue)))
-                        return
-                    }
-                    if let responseString = String(data: data, encoding: .utf8) {
-                        responseLogMessage.append("\n\(responseString)\n")
-                    }
-                    
-                    do {
-                        if Request.Response.self == VoidResponse.self {
-                            Formbricks.logger?.info(responseLogMessage)
-                            
-                            if let response = VoidResponse() as? Request.Response {
-                                self.completion?(.success(response))
-                            } else {
-                                self.completion?(.failure(FormbricksAPIClientError(type: .invalidResponse)))
-                            }
-                        } else {
-                            var body = try self.request.decoder.decode(Request.Response.self, from: data)
-                            Formbricks.logger?.info(responseLogMessage)
-                            
-                            // We want to save the entire response dictionary for the environment response
-                            if var environmentResponse = body as? EnvironmentResponse,
-                               let jsonString = String(data: data, encoding: .utf8) {
-                                environmentResponse.responseString = jsonString
-                                body = environmentResponse as! Request.Response
-                            }
+        if let body = request.httpBody, let bodyString = String(data: body, encoding: .utf8) {
+            message.append("\nBody: \(bodyString)")
+        }
 
-                            
-                            self.completion?(.success(body))
-                        }
-                    }
-                    catch let DecodingError.dataCorrupted(context) {
-                        responseLogMessage.append("Data corrupted \(context)\n")
-                        Formbricks.logger?.error(responseLogMessage)
-                        self.completion?(.failure(FormbricksAPIClientError(type: .invalidResponse, statusCode: httpStatus.rawValue)))
-                    }
-                    catch let DecodingError.keyNotFound(key, context) {
-                        responseLogMessage.append("Key '\(key)' not found: \(context.debugDescription)\n")
-                        responseLogMessage.append("codingPath: \(context.codingPath)")
-                        Formbricks.logger?.error(responseLogMessage)
-                        self.completion?(.failure(FormbricksAPIClientError(type: .invalidResponse, statusCode: httpStatus.rawValue)))
-                    }
-                    catch let DecodingError.valueNotFound(value, context) {
-                        responseLogMessage.append("Value '\(value)' not found: \(context.debugDescription)\n")
-                        responseLogMessage.append("codingPath: \(context.codingPath)")
-                        Formbricks.logger?.error(responseLogMessage)
-                        self.completion?(.failure(FormbricksAPIClientError(type: .invalidResponse, statusCode: httpStatus.rawValue)))
-                    }
-                    catch let DecodingError.typeMismatch(type, context)  {
-                        responseLogMessage.append("Type '\(type)' mismatch: \(context.debugDescription)\n")
-                        responseLogMessage.append("codingPath: \(context.codingPath)")
-                        Formbricks.logger?.error(responseLogMessage)
-                        self.completion?(.failure(FormbricksAPIClientError(type: .invalidResponse, statusCode: httpStatus.rawValue)))
-                    }
-                    catch {
-                        responseLogMessage.append("error: \(error.message)")
-                        Formbricks.logger?.error(responseLogMessage)
-                        self.completion?(.failure(FormbricksAPIClientError(type: .invalidResponse, statusCode: httpStatus.rawValue)))
-                    }
-                } else {
-                    if let error = error {
-                        responseLogMessage.append("\nError: \(error.localizedDescription)")
-                        Formbricks.logger?.error(responseLogMessage)
-                        self.completion?(.failure(error))
-                    } else if let data = data, let apiError = try? self.request.decoder.decode(FormbricksAPIError.self, from: data) {
-                        Formbricks.logger?.error("\(responseLogMessage)\n\(apiError.getDetailedErrorMessage())")
-                        self.completion?(.failure(apiError))
-                    } else {
-                        let error = FormbricksAPIClientError(type: .responseError, statusCode: httpStatus.rawValue)
-                        Formbricks.logger?.error("\(responseLogMessage)\n\(error.message)")
-                        self.completion?(.failure(error))
-                    }
-                }
-            }
-            else {
-                let error = FormbricksAPIClientError(type: .invalidResponse)
-                Formbricks.logger?.error("ERROR \(error.message)")
-                self.completion?(.failure(error))
-            }
-        }.resume()
+        Formbricks.logger?.info(message)
     }
+
 }
 
 private extension APIClient {
diff --git a/packages/ios/FormbricksSDK/FormbricksSDK/Networking/Queue/UpdateQueue.swift b/packages/ios/FormbricksSDK/FormbricksSDK/Networking/Queue/UpdateQueue.swift
index 5d1ea197cb42..bb847037dd59 100644
--- a/packages/ios/FormbricksSDK/FormbricksSDK/Networking/Queue/UpdateQueue.swift
+++ b/packages/ios/FormbricksSDK/FormbricksSDK/Networking/Queue/UpdateQueue.swift
@@ -61,6 +61,7 @@ final class UpdateQueue {
             } else {
                 // If no userId, just update locally without API call
                 Formbricks.logger?.debug("UpdateQueue - updating language locally: \(language)")
+                return
             }
             
             startDebounceTimer()
diff --git a/packages/ios/FormbricksSDK/FormbricksSDK/WebView/SurveyWebView.swift b/packages/ios/FormbricksSDK/FormbricksSDK/WebView/SurveyWebView.swift
index e7e02711b56d..7865da02c877 100644
--- a/packages/ios/FormbricksSDK/FormbricksSDK/WebView/SurveyWebView.swift
+++ b/packages/ios/FormbricksSDK/FormbricksSDK/WebView/SurveyWebView.swift
@@ -54,15 +54,19 @@ struct SurveyWebView: UIViewRepresentable {
     func clean() {
         HTTPCookieStorage.shared.removeCookies(since: Date.distantPast)
         WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in
-            records.forEach { record in
-                WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {
-                    /*
-                     This completion handler is intentionally empty since we only need to 
-                     ensure the data is removed. No additional actions are required after
-                     the website data has been cleared.
-                    */
-                })
-            }
+            self.remove(records)
+        }
+    }
+    
+    private func remove(_ records: [WKWebsiteDataRecord]) {
+        records.forEach { record in
+            WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {
+                /*
+                 This completion handler is intentionally empty since we only need to
+                 ensure the data is removed. No additional actions are required after
+                 the website data has been cleared.
+                */
+            })
         }
     }
 }


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>packages/ios/Demo/Demo/AppDelegate.swift (1)</summary><blockquote>

`13-13`: **Unnecessary empty line.**

There's an empty line here that could be removed for better code consistency.

```diff
-        
```

</blockquote></details>
<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK.xcodeproj/project.pbxproj (1)</summary><blockquote>

`555-561`: **Pinning an exact SPM version limits patch updates**

The new Swift‑Package reference locks `AnyCodable` to `0.6.7` exactly.  
While this guarantees reproducibility, it also prevents automatic adoption of backward‑compatible patch versions that may include security fixes.

```diff
- requirement = { kind = exactVersion; version = 0.6.7; };
+ requirement = { kind = upToNextMinorVersion; minimumVersion = 0.6.7; };
```

Consider `upToNextMinorVersion` (or at least `upToNextPatchVersion`) unless you have a strong reason to freeze the dependency.

</blockquote></details>
<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift (1)</summary><blockquote>

`119-131`: **Logging request bodies may leak PII in production**

`logRequest` indiscriminately prints headers and bodies.  
For POST/PUT endpoints this can expose tokens or user answers.  
Gate verbose logging behind a debug flag or omit sensitive headers (`Authorization`, etc.).

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 81d717ccffa4e815a1d241e904e413228c589443 and 74d24f80d44f281aec7e747e823c1aed63753d50.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `.gitignore` (1 hunks)
* `packages/android/app/src/main/java/com/formbricks/demo/MainActivity.kt` (2 hunks)
* `packages/ios/Demo/Demo/AppDelegate.swift` (1 hunks)
* `packages/ios/Formbricks.xcworkspace/xcshareddata/swiftpm/Package.resolved` (1 hunks)
* `packages/ios/FormbricksSDK/FormbricksSDK.xcodeproj/project.pbxproj` (6 hunks)
* `packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyCodable.swift` (0 hunks)
* `packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyDecodable.swift` (0 hunks)
* `packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyEncodable.swift` (0 hunks)
* `packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift` (1 hunks)
* `packages/ios/FormbricksSDK/FormbricksSDK/WebView/SurveyWebView.swift` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (3)</summary>

* packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyDecodable.swift
* packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyEncodable.swift
* packages/ios/FormbricksSDK/FormbricksSDK/Helpers/AnyCodable/AnyCodable.swift

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift (2)</summary><blockquote>

<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Logger/Logger.swift (2)</summary>

* `info` (29-31)
* `log` (44-60)

</details>
<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Model/Error/FormbricksAPIError.swift (1)</summary>

* `getDetailedErrorMessage` (10-16)

</details>

</blockquote></details>

</details><details>
<summary>🪛 SwiftLint (0.57.0)</summary>

<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift</summary>

[Error] 69-69: Force casts should be avoided

(force_cast)

---

[Error] 74-74: Force casts should be avoided

(force_cast)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>.gitignore (1)</summary>

`75-75`: **Good addition to ignore user-specific Xcode files.**

Adding Xcode's user data directory to the gitignore is a best practice. This prevents personal IDE configurations from being committed to the repository and avoids potential conflicts between developers.

</details>
<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/WebView/SurveyWebView.swift (1)</summary>

`44-60`: **Good refactoring to improve separation of concerns.**

Extracting the website data removal logic into a dedicated private method follows the Single Responsibility Principle. The added documentation for the intentionally empty completion handler is excellent - it clearly explains why the empty block exists, addressing a common code smell flagged by static analyzers.

</details>
<details>
<summary>packages/ios/Demo/Demo/AppDelegate.swift (1)</summary>

`7-7`: **Replaced placeholder values with concrete configuration.**

Using specific values for the local development environment is appropriate for this demo application. The localhost URL indicates this is intended for testing with a local Formbricks server.

</details>
<details>
<summary>packages/ios/Formbricks.xcworkspace/xcshareddata/swiftpm/Package.resolved (1)</summary>

`1-15`: **Good practice using the AnyCodable package.**

Adding this dependency on a well-maintained open-source library is preferable to custom implementations. Using the Swift Package Manager with pinned versions (0.6.7) ensures reproducible builds.

This change aligns with the PR objective of addressing code smells, as it replaces custom code with a standardized solution.

</details>
<details>
<summary>packages/android/app/src/main/java/com/formbricks/demo/MainActivity.kt (1)</summary>

`20-37`: **Clear callback needs explicit lifecycle clean‑up**

Great to see the SDK callback wired with helpful logs, however the anonymous object is assigned to a static (`Formbricks.callback`).  
Because it outlives the Activity, the inner class may hold references longer than necessary and leak memory once the Activity is destroyed.

```diff
override fun onDestroy() {
    super.onDestroy()
+   Formbricks.callback = null   // prevent potential leak
}
```

Consider resetting the callback (or assigning it in `Application`) to avoid retaining the Activity context.

</details>
<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift (1)</summary>

`28-37`: **`buildFinalURL` may double‑slash or lose trailing slash**

When `request.requestEndPoint` starts with “/”, `appendPathComponent` inserts an extra slash.
Additionally, original query items on the endpoint (if any) are lost.

Consider `URL(string:relativeTo:)` or `URLComponents`’ `path` instead:

```swift
components.path = (components.path as NSString).appendingPathComponent(path)
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T12:10:10Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (4)</summary><blockquote>

<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift (4)</summary><blockquote>

`23-25`: _🛠️ Refactor suggestion_

**Use a weak reference to `self` in the closure to prevent potential memory leaks**

The data task closure strongly captures `self`. Since URLSession retains this closure, this can lead to a retain cycle if the operation is cancelled while the network request is still in progress.


```diff
- session.dataTask(with: urlRequest) { data, response, error in
-     self.processResponse(data: data, response: response, error: error)
- }.resume()
+ session.dataTask(with: urlRequest) { [weak self] data, response, error in
+     self?.processResponse(data: data, response: response, error: error)
+ }.resume()
```

---

`69-69`: _🛠️ Refactor suggestion_

**Replace force cast with conditional cast to improve type safety**

Force casts can lead to runtime crashes if the type assumptions change. This was flagged by SwiftLint as well.


```diff
- completion?(.success(VoidResponse() as! Request.Response))
+ if let voidResponse = VoidResponse() as? Request.Response {
+     completion?(.success(voidResponse))
+ } else {
+     completion?(.failure(FormbricksAPIClientError(type: .invalidResponse)))
+ }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 SwiftLint (0.57.0)</summary>

[Error] 69-69: Force casts should be avoided

(force_cast)

</details>

</details>

---

`74-74`: _🛠️ Refactor suggestion_

**Replace force cast with conditional cast to improve type safety**

Similar to the previous issue, this force cast can lead to runtime crashes if the type assumptions change.


```diff
- body = env as! Request.Response
+ if let typedEnv = env as? Request.Response {
+     body = typedEnv
+ } else {
+     Formbricks.logger?.error("Failed to cast EnvironmentResponse to Request.Response")
+     completion?(.failure(FormbricksAPIClientError(type: .invalidResponse, statusCode: statusCode)))
+     return
+ }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 SwiftLint (0.57.0)</summary>

[Error] 74-74: Force casts should be avoided

(force_cast)

</details>

</details>

---

`14-26`: _⚠️ Potential issue_

**The operation still completes before the network request finishes**

This refactoring doesn't address the underlying issue where the operation is marked as completed as soon as `main()` exits, even though the network request is still in progress. This breaks dependencies, `maxConcurrentOperationCount`, and cancellation semantics.


Consider implementing a proper asynchronous Operation by overriding `isAsynchronous`, `isExecuting`, and `isFinished` properties, and implementing state management for the operation.

```swift
class AsyncAPIClient<Request: CodableRequest>: Operation, @unchecked Sendable {
    private var _executing = false
    private var _finished = false
    
    override var isAsynchronous: Bool { return true }
    
    override var isExecuting: Bool {
        get { return _executing }
        set {
            willChangeValue(forKey: "isExecuting")
            _executing = newValue
            didChangeValue(forKey: "isExecuting")
        }
    }
    
    override var isFinished: Bool {
        get { return _finished }
        set {
            willChangeValue(forKey: "isFinished")
            _finished = newValue
            didChangeValue(forKey: "isFinished")
        }
    }
    
    override func start() {
        if isCancelled {
            isFinished = true
            return
        }
        
        isExecuting = true
        // Your existing main() logic here
    }
    
    // Complete the operation when the network request finishes
    private func finish() {
        isExecuting = false
        isFinished = true
    }
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift (3)</summary><blockquote>

`15-18`: **Consider more robustness in URL construction**

The current implementation returns nil if any part of the URL construction fails. However, there are multiple potential failure points:

1. If `request.baseURL` is nil
2. If `URLComponents(string:)` fails
3. If `components.url` is nil
4. If `setPathParams()` returns nil


Consider providing more specific error handling for each failure case to make debugging easier:

```swift
private func buildFinalURL() -> URL? {
    guard let apiURL = request.baseURL else {
        Formbricks.logger?.error("Base URL is nil")
        return nil
    }
    
    guard var components = URLComponents(string: apiURL) else {
        Formbricks.logger?.error("Invalid base URL: \(apiURL)")
        return nil
    }
    
    components.queryItems = request.queryParams?.map { URLQueryItem(name: $0.key, value: $0.value) }
    
    guard var url = components.url else {
        Formbricks.logger?.error("Failed to create URL from components")
        return nil
    }
    
    guard let path = setPathParams(request.requestEndPoint) else {
        Formbricks.logger?.error("Failed to set path parameters")
        return nil
    }
    
    url.appendPathComponent(path)
    return url
}
```


Also applies to: 28-37

---

`90-91`: **Consider handling HTTP error status codes with more specific errors**

When a request fails with an HTTP error status but no error object or API error is present, a generic `responseError` is used. It would be helpful to include more specific error information based on the status code.


```diff
- let error = FormbricksAPIClientError(type: .responseError, statusCode: statusCode)
- Formbricks.logger?.error("\(log)\n\(error.message)")
- completion?(.failure(error))
+ let errorType: FormbricksAPIClientErrorType
+ switch statusCode {
+     case 400...499:
+         errorType = .clientError
+     case 500...599:
+         errorType = .serverError
+     default:
+         errorType = .responseError
+ }
+ let error = FormbricksAPIClientError(type: errorType, statusCode: statusCode)
+ Formbricks.logger?.error("\(log)\n\(error.message)")
+ completion?(.failure(error))
```

Note: This suggestion assumes the existence of `.clientError` and `.serverError` error types, which you may need to add to the `FormbricksAPIClientErrorType` enum.


Also applies to: 95-98

---

`56-82`: **Too much responsibility in handleSuccessResponse method**

The method `handleSuccessResponse` is doing multiple things: checking for null data, logging the response, handling special case for VoidResponse, JSON decoding, and handling special case for EnvironmentResponse.


Consider breaking this down further for better maintainability:

```swift
private func handleSuccessResponse(data: Data?, statusCode: Int, message: inout String) {
    guard let data = data else {
        completion?(.failure(FormbricksAPIClientError(type: .invalidResponse, statusCode: statusCode)))
        return
    }

    logResponseBody(data, inMessage: &message)
    
    if Request.Response.self == VoidResponse.self {
        handleVoidResponse(message: message)
    } else {
        decodeAndDeliverResponse(data: data, statusCode: statusCode, message: message)
    }
}

private func logResponseBody(_ data: Data, inMessage message: inout String) {
    if let responseString = String(data: data, encoding: .utf8) {
        message.append("\n\(responseString)\n")
    }
}

private func handleVoidResponse(message: String) {
    Formbricks.logger?.info(message)
    if let voidResponse = VoidResponse() as? Request.Response {
        completion?(.success(voidResponse))
    } else {
        completion?(.failure(FormbricksAPIClientError(type: .invalidResponse)))
    }
}

private func decodeAndDeliverResponse(data: Data, statusCode: Int, message: String) {
    do {
        var body = try request.decoder.decode(Request.Response.self, from: data)
        if var env = body as? EnvironmentResponse, let jsonString = String(data: data, encoding: .utf8) {
            env.responseString = jsonString
            if let typedEnv = env as? Request.Response {
                body = typedEnv
            }
        }
        Formbricks.logger?.info(message)
        completion?(.success(body))
    } catch {
        var logMessage = message
        handleDecodingError(error, message: &logMessage, statusCode: statusCode)
    }
}
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 SwiftLint (0.57.0)</summary>

[Error] 69-69: Force casts should be avoided

(force_cast)

---

[Error] 74-74: Force casts should be avoided

(force_cast)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a8a299e4b84fbc7408f6761269af83b796b06674 and 0982813fa7a469aabf10e2cadb7d69356aecea96.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 SwiftLint (0.57.0)</summary>

<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift</summary>

[Error] 69-69: Force casts should be avoided

(force_cast)

---

[Error] 74-74: Force casts should be avoided

(force_cast)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: Run E2E Tests / Run E2E Tests
* GitHub Check: Run Linters / Linters
* GitHub Check: Run Unit Tests / Unit Tests
* GitHub Check: Validate Docker Build
* GitHub Check: SonarQube

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T05:30:33Z
user: login: pandeymangg
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T08:46:07Z

Review comments:
diff_hunk: @@ -15,7 +17,27 @@ class MainActivity : AppCompatActivity() {
         super.onCreate(savedInstanceState)
         enableEdgeToEdge()
 
-        val config = FormbricksConfig.Builder("[appUrl]","[environmentId]")
+        Formbricks.callback = object: FormbricksCallback {
+            override fun onSurveyStarted() {
+                Log.d("FormbricksCallback", "onSurveyStarted")
+            }
+
+            override fun onSurveyFinished() {
+                Log.d("FormbricksCallback", "onSurveyFinished")
+            }
+
+            override fun onSurveyClosed() {
+                Log.d("FormbricksCallback", "onSurveyClosed")
+            }
+
+            override fun onError(error: Exception) {
+                Log.d("FormbricksCallback", "onError: ${error.localizedMessage}")
+            }
+
+        }
+
+//        val config = FormbricksConfig.Builder("[appUrl]","[environmentId]")
+        val config = FormbricksConfig.Builder("http://192.168.0.12:3000","cm8ebrmrx0009sfrno2kebu62")
             .setLoggingEnabled(true)
path: packages/android/app/src/main/java/com/formbricks/demo/MainActivity.kt
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Avoid committing environment secrets & IP‑bound URLs**

The demo now contains a hard‑coded local URL and environment ID:

```kotlin
FormbricksConfig.Builder("http://192.168.0.12:3000","cm8ebrmrx0009sfrno2kebu62")
```

1. Hard‑coding an internal IP will break builds for other developers / CI.  
2. The environment ID may be confidential.

Prefer injecting these values via `BuildConfig` or a local gradle property that’s ignored by VCS.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-18T12:10:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -12,126 +12,124 @@ class APIClient<Request: CodableRequest>: Operation, @unchecked Sendable {
     }
     
     override func main() {
-        guard let apiURL = request.baseURL, var baseUrlComponents = URLComponents(string: apiURL) else {
+        guard let finalURL = buildFinalURL() else {
             completion?(.failure(FormbricksSDKError(type: .sdkIsNotInitialized)))
             return
         }
         
-        baseUrlComponents.queryItems = request.queryParams?.map { URLQueryItem(name: $0.key, value: $0.value) }
+        let urlRequest = createURLRequest(forURL: finalURL)
+        logRequest(urlRequest)
+        
+        session.dataTask(with: urlRequest) { data, response, error in
+            self.processResponse(data: data, response: response, error: error)
+        }.resume()
+    }
path: packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Operation finishes before the network request – makes queue/dep‑handling unreliable**

`main()` schedules an asynchronous `URLSession.dataTask` but immediately returns.  
Since `APIClient` does not override `isAsynchronous`, `isExecuting`, and `isFinished`, `OperationQueue` marks the operation completed as soon as `main()` exits, breaking:

* dependencies (`addDependency`)
* `maxConcurrentOperationCount`
* cancellation semantics

Convert to a proper asynchronous `Operation` or drop `Operation` inheritance.

```swift
// sketch
class AsyncAPIClient<…>: Operation {
  override var isAsynchronous: Bool { true }
  private var _executing = false, _finished = false
  override func start() { … set executing true … }
  func complete(_ result: …) { … set executing false; finished true }
}
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-18T12:10:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -12,126 +12,124 @@
     }
     
     override func main() {
-        guard let apiURL = request.baseURL, var baseUrlComponents = URLComponents(string: apiURL) else {
+        guard let finalURL = buildFinalURL() else {
             completion?(.failure(FormbricksSDKError(type: .sdkIsNotInitialized)))
             return
         }
         
-        baseUrlComponents.queryItems = request.queryParams?.map { URLQueryItem(name: $0.key, value: $0.value) }
+        let urlRequest = createURLRequest(forURL: finalURL)
+        logRequest(urlRequest)
+        
+        session.dataTask(with: urlRequest) { data, response, error in
+            self.processResponse(data: data, response: response, error: error)
+        }.resume()
+    }
+    
+    private func buildFinalURL() -> URL? {
+        guard let apiURL = request.baseURL, var components = URLComponents(string: apiURL) else { return nil }
         
-        guard var finalURL = baseUrlComponents.url else {
-            completion?(.failure(FormbricksSDKError(type: .invalidAppUrl)))
+        components.queryItems = request.queryParams?.map { URLQueryItem(name: $0.key, value: $0.value) }
+        
+        guard var url = components.url, let path = setPathParams(request.requestEndPoint) else { return nil }
+
+        url.appendPathComponent(path)
+        return url
+    }
+    
+    private func processResponse(data: Data?, response: URLResponse?, error: Error?) {
+        guard let httpStatus = (response as? HTTPURLResponse)?.status else {
+            let error = FormbricksAPIClientError(type: .invalidResponse)
+            Formbricks.logger.error("ERROR \(error.message)")
+            completion?(.failure(error))
             return
         }
+
+        var message = "\(httpStatus.rawValue) <<< \(response?.url?.absoluteString ?? "")"
         
-        guard let requestEndPoint = setPathParams(request.requestEndPoint) else {
-            completion?(.failure(FormbricksSDKError(type: .sdkIsNotInitialized)))
+        if httpStatus.responseType == .success {
+            handleSuccessResponse(data: data, statusCode: httpStatus.rawValue, message: &message)
+        } else {
+            handleFailureResponse(data: data, error: error, statusCode: httpStatus.rawValue, message: message)
+        }
+    }
+    
+    private func handleSuccessResponse(data: Data?, statusCode: Int, message: inout String) {
+        guard let data = data else {
+            completion?(.failure(FormbricksAPIClientError(type: .invalidResponse, statusCode: statusCode)))
             return
         }
-        
-        finalURL.appendPathComponent(requestEndPoint)
-        
-        let urlRequest = createURLRequest(forURL: finalURL)
-        
-        // LOG
-        var requestLogMessage = "\(request.requestType.rawValue) >>> "
-        if let urlString = urlRequest.url?.absoluteString {
-            requestLogMessage.append(urlString)
+
+        if let responseString = String(data: data, encoding: .utf8) {
+            message.append("\n\(responseString)\n")
         }
-        if let headers = urlRequest.allHTTPHeaderFields {
-            requestLogMessage.append("\nHeaders: \(headers)")
+
+        do {
+            if Request.Response.self == VoidResponse.self {
+                Formbricks.logger.info(message)
+                completion?(.success(VoidResponse() as! Request.Response))
+            } else {
+                var body = try request.decoder.decode(Request.Response.self, from: data)
+                if var env = body as? EnvironmentResponse, let jsonString = String(data: data, encoding: .utf8) {
+                    env.responseString = jsonString
+                    body = env as! Request.Response
+                }
+                Formbricks.logger.info(message)
+                completion?(.success(body))
+            }
path: packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Force‑casting circumvents type safety**

Both casts flagged by SwiftLint can crash if the type assumptions change:

```swift
completion?(.success(VoidResponse() as! Request.Response))
…
body = env as! Request.Response
```

Safer alternatives:

```diff
if Request.Response.self == VoidResponse.self,
   let voidResp = VoidResponse() as? Request.Response {
    completion?(.success(voidResp))
    return
}

if let env = body as? EnvironmentResponse {
    env.responseString = jsonString
    if let typed = env as? Request.Response {
        body = typed
    }
}
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 SwiftLint (0.57.0)</summary>

[Error] 69-69: Force casts should be avoided

(force_cast)

---

[Error] 74-74: Force casts should be avoided

(force_cast)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-18T12:10:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -12,126 +12,124 @@
     }
     
     override func main() {
-        guard let apiURL = request.baseURL, var baseUrlComponents = URLComponents(string: apiURL) else {
+        guard let finalURL = buildFinalURL() else {
             completion?(.failure(FormbricksSDKError(type: .sdkIsNotInitialized)))
             return
         }
         
-        baseUrlComponents.queryItems = request.queryParams?.map { URLQueryItem(name: $0.key, value: $0.value) }
+        let urlRequest = createURLRequest(forURL: finalURL)
+        logRequest(urlRequest)
+        
+        session.dataTask(with: urlRequest) { data, response, error in
+            self.processResponse(data: data, response: response, error: error)
+        }.resume()
+    }
path: packages/ios/FormbricksSDK/FormbricksSDK/Networking/Base/APIClient.swift
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Capture `self` weakly inside the data‑task closure**

`URLSession` retains the closure; the closure strongly retains `self`, so `APIClient`
is kept alive even after the `Operation` is cancelled.  
Prevent leaks by using `[weak self]`.

```diff
- session.dataTask(with: urlRequest) { data, response, error in
-     self.processResponse(data: data, response: response, error: error)
- }.resume()
+ session.dataTask(with: urlRequest) { [weak self] data, response, error in
+     self?.processResponse(data: data, response: response, error: error)
+ }.resume()
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-18T12:10:09Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-04-18T12:07:54Z
body: @vaxi87 is attempting to deploy a commit to the **formbricks** Team on [Vercel](https://vercel.com).

A member of the Team first needs to [authorize it](https://vercel.com/git/authorize?team=formbricks&type=github&job=%7B%22headInfo%22%3A%7B%22sha%22%3A%2274d24f80d44f281aec7e747e823c1aed63753d50%22%7D%2C%22id%22%3A%22QmWWtBYqYoaN5oQC1R8kjY4Bz7WNyyw9MuJn3CXXoifjNs%22%2C%22org%22%3A%22formbricks%22%2C%22prId%22%3A5416%2C%22repo%22%3A%22formbricks%22%7D).


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-18T12:08:06Z
body: Thank you for following the naming conventions for pull request titles! 🙏
<!-- Sticky Pull Request Commentpr-title-lint-error -->
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-04-18T12:10:06Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

"""
## Walkthrough

This set of changes introduces several significant updates across both the iOS and Android components of the project. On iOS, the custom in-house implementations of `AnyCodable`, `AnyDecodable`, and `AnyEncodable` have been removed from the SDK's Helpers directory. Instead, the Xcode project and workspace have been updated to add the "AnyCodable" Swift package (version 0.6.7) as a dependency, with corresponding updates to the project configuration files and the addition of a `Package.resolved` file to pin the dependency. The `.gitignore` file has been updated to exclude user-specific Xcode workspace data. In the SDK's networking layer, the `APIClient` class has been refactored to modularize URL construction, request logging, and response handling into private helper methods. The `SurveyWebView` struct now uses a dedicated private method for removing website data records. The `Formbricks` class now prevents overwriting an existing user ID without logout, and the user language state is synchronized with the global language property. On Android, the `MainActivity` now implements the `FormbricksCallback` interface, providing logging for survey lifecycle events and errors. Additionally, the Next.js configuration was updated to extend CORS headers to version 2 API routes.

## Possibly related PRs

- formbricks/formbricks#5200: Adds the same `.gitignore` entry for workspace data and modifies Android's `MainActivity.kt` to implement the `FormbricksCallback` interface, indicating direct overlap in changes for Android callback handling and iOS workspace ignoring.
- formbricks/formbricks#5249: Removes and modifies the same `AnyCodable`, `AnyDecodable`, and `AnyEncodable` implementations in the iOS FormbricksSDK Helpers folder, directly affecting the same code entities.
- formbricks/formbricks#4984: Modifies Android `MainActivity.kt` by changing its base class and UI setup, related to the same file where the callback implementation was added in this PR.

## Suggested reviewers

- mattinannt
"""

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡💬 Agentic Chat (Pro Plan, General Availability)</summary>
> 
> - We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0982813fa7a469aabf10e2cadb7d69356aecea96 and 712c3a0aaf7a1bb20e4717b5dffd233d560f7951.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `apps/web/next.config.mjs` (1 hunks)
* `packages/ios/FormbricksSDK/FormbricksSDK/Formbricks.swift` (1 hunks)
* `packages/ios/FormbricksSDK/FormbricksSDK/Manager/UserManager.swift` (2 hunks)
* `packages/ios/FormbricksSDK/FormbricksSDK/Model/User/UserStateDetails.swift` (1 hunks)
* `packages/ios/FormbricksSDK/FormbricksSDK/Networking/Queue/UpdateQueue.swift` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* packages/ios/FormbricksSDK/FormbricksSDK/Model/User/UserStateDetails.swift

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Manager/UserManager.swift (1)</summary><blockquote>

<details>
<summary>packages/android/formbricksSDK/src/main/java/com/formbricks/formbrickssdk/model/user/UserResponseData.kt (1)</summary>

* `state` (5-7)

</details>

</blockquote></details>
<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Formbricks.swift (2)</summary><blockquote>

<details>
<summary>packages/android/formbricksSDK/src/main/java/com/formbricks/formbrickssdk/model/user/UserStateData.kt (1)</summary>

* `userId` (5-12)

</details>
<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Logger/Logger.swift (1)</summary>

* `error` (37-39)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: Run E2E Tests / Run E2E Tests
* GitHub Check: Run Unit Tests / Unit Tests
* GitHub Check: Run Linters / Linters
* GitHub Check: Validate Docker Build
* GitHub Check: SonarQube

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Networking/Queue/UpdateQueue.swift (1)</summary>

`51-68`: **Approve early return in language update without userId**

The added return statement properly prevents unnecessary timer initialization when no user ID is available, as the language update would only be stored locally in this case without any server synchronization.

</details>
<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Formbricks.swift (1)</summary>

`94-97`: **Prevent unintentional user ID overwriting**

This guard clause is a good addition to enforce proper session management by preventing accidental overwriting of an existing user ID. It requires explicitly logging out before setting a new user ID, which helps maintain data consistency.

</details>
<details>
<summary>packages/ios/FormbricksSDK/FormbricksSDK/Manager/UserManager.swift (2)</summary>

`98-100`: **Approve synchronized language handling from server**

The code now properly synchronizes the global language setting with the value received from the server, ensuring consistency between server and client state.

---

`134-134`: **Reset language on logout**

This change appropriately resets the global language setting to the default value when a user logs out, ensuring a clean slate for the next user session.

</details>
<details>
<summary>apps/web/next.config.mjs (1)</summary>

`130-130`: <details>
<summary>❓ Verification inconclusive</summary>

**CORS headers now properly configured for both v1 and v2 API client routes.**

The updated regex pattern `(v1|v2)` appropriately extends CORS headers to both API versions, ensuring consistent cross-origin access behavior across your versioned API endpoints. This is a good improvement that aligns with standard versioning practices.



The CORS configuration uses `Access-Control-Allow-Origin: "*"`, which is quite permissive. While this may be intentional for public API endpoints, please verify this is the desired behavior for v2 routes as well. If more restrictive CORS is needed, consider using environment-specific configurations.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there are any v2 client API routes implemented
rg -l "api/v2/client" --type js --type ts
```

Length of output: 469

---



</details>

**CORS configuration applies to both v1 and v2 client routes**  
I’ve verified v2 client endpoints exist in the following locations:  
- packages/js-core/src/lib/common/api.ts  
- packages/js-core/src/lib/common/tests/api.test.ts  
- packages/react-native/src/lib/common/api.ts  
- packages/react-native/src/lib/common/tests/api.test.ts  
- apps/web/app/api/v2/client/[environmentId]/responses/lib/response.ts  
- apps/web/app/api/v2/client/[environmentId]/displays/route.ts  
- apps/web/app/api/v2/client/[environmentId]/displays/lib/display.ts  

The updated pattern `(v1|v2)` correctly extends CORS headers to both versions. However, since `Access-Control-Allow-Origin: "*"` is extremely permissive, please confirm that a wildcard origin is acceptable for your v2 routes in all environments or consider tightening this (for example, via environment‑specific configs).

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=formbricks/formbricks&utm_content=5416):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR