fixes ios sdk issues and removes callbacks

This commit is contained in:
pandeymangg
2025-05-13 14:48:23 +05:30
parent dde5a55446
commit c2af0c3fb6
12 changed files with 225 additions and 64 deletions

View File

@@ -1,29 +1,10 @@
import UIKit
import FormbricksSDK
class AppDelegate: NSObject, UIApplicationDelegate, FormbricksDelegate {
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
Formbricks.delegate = self
return true
}
// MARK: - FormbricksDelegate
func onSurveyStarted() {
print("from the delegate: survey started")
}
func onSurveyFinished() {
print("survey finished")
}
func onSurveyClosed() {
print("survey closed")
}
func onError(_ error: Error) {
print("survey error:", error.localizedDescription)
}
}

View File

@@ -1,14 +1,6 @@
import Foundation
import Network
/// Formbricks SDK delegate protocol. It contains the main methods to interact with the SDK.
public protocol FormbricksDelegate: AnyObject {
func onSurveyStarted()
func onSurveyFinished()
func onSurveyClosed()
func onError(_ error: Error)
}
/// The main class of the Formbricks SDK. It contains the main methods to interact with the SDK.
@objc(Formbricks) public class Formbricks: NSObject {
@@ -23,7 +15,6 @@ public protocol FormbricksDelegate: AnyObject {
static internal var apiQueue: OperationQueue? = OperationQueue()
static internal var logger: Logger?
static internal var service = FormbricksService()
public static weak var delegate: FormbricksDelegate?
// make this class not instantiatable outside of the SDK
internal override init() {
@@ -58,7 +49,6 @@ public protocol FormbricksDelegate: AnyObject {
guard !isInitialized else {
let error = FormbricksSDKError(type: .sdkIsAlreadyInitialized)
delegate?.onError(error)
Formbricks.logger?.error(error.message)
return
}
@@ -101,7 +91,6 @@ public protocol FormbricksDelegate: AnyObject {
@objc public static func setUserId(_ userId: String) {
guard Formbricks.isInitialized else {
let error = FormbricksSDKError(type: .sdkIsNotInitialized)
delegate?.onError(error)
Formbricks.logger?.error(error.message)
return
}
@@ -126,7 +115,6 @@ public protocol FormbricksDelegate: AnyObject {
@objc public static func setAttribute(_ attribute: String, forKey key: String) {
guard Formbricks.isInitialized else {
let error = FormbricksSDKError(type: .sdkIsNotInitialized)
delegate?.onError(error)
Formbricks.logger?.error(error.message)
return
}
@@ -146,7 +134,6 @@ public protocol FormbricksDelegate: AnyObject {
@objc public static func setAttributes(_ attributes: [String : String]) {
guard Formbricks.isInitialized else {
let error = FormbricksSDKError(type: .sdkIsNotInitialized)
delegate?.onError(error)
Formbricks.logger?.error(error.message)
return
}
@@ -166,7 +153,6 @@ public protocol FormbricksDelegate: AnyObject {
@objc public static func setLanguage(_ language: String) {
guard Formbricks.isInitialized else {
let error = FormbricksSDKError(type: .sdkIsNotInitialized)
delegate?.onError(error)
Formbricks.logger?.error(error.message)
return
}
@@ -191,7 +177,6 @@ public protocol FormbricksDelegate: AnyObject {
@objc public static func track(_ action: String) {
guard Formbricks.isInitialized else {
let error = FormbricksSDKError(type: .sdkIsNotInitialized)
delegate?.onError(error)
Formbricks.logger?.error(error.message)
return
}
@@ -218,7 +203,6 @@ public protocol FormbricksDelegate: AnyObject {
@objc public static func logout() {
guard Formbricks.isInitialized else {
let error = FormbricksSDKError(type: .sdkIsNotInitialized)
delegate?.onError(error)
Formbricks.logger?.error(error.message)
return
}

View File

@@ -106,7 +106,6 @@ extension SurveyManager {
case .failure:
self?.hasApiError = true
let error = FormbricksSDKError(type: .unableToRefreshEnvironment)
Formbricks.delegate?.onError(error)
Formbricks.logger?.error(error.message)
self?.startErrorTimer()
}
@@ -186,7 +185,6 @@ extension SurveyManager {
return try? JSONDecoder().decode(EnvironmentResponse.self, from: data)
} else {
let error = FormbricksSDKError(type: .unableToRetrieveEnvironment)
Formbricks.delegate?.onError(error)
Formbricks.logger?.error(error.message)
return nil
}
@@ -197,7 +195,6 @@ extension SurveyManager {
backingEnvironmentResponse = newValue
} else {
let error = FormbricksSDKError(type: .unableToPersistEnvironment)
Formbricks.delegate?.onError(error)
Formbricks.logger?.error(error.message)
}
}
@@ -231,7 +228,6 @@ private extension SurveyManager {
default:
let error = FormbricksSDKError(type: .invalidDisplayOption)
Formbricks.delegate?.onError(error)
Formbricks.logger?.error(error.message)
return false
}

View File

@@ -102,7 +102,6 @@ final class UserManager: UserManagerSyncable {
self?.surveyManager?.filterSurveys()
self?.startSyncTimer()
case .failure(let error):
Formbricks.delegate?.onError(error)
Formbricks.logger?.error(error)
}
}

View File

@@ -2,4 +2,5 @@ struct EnvironmentData: Codable {
let surveys: [Survey]?
let actionClasses: [ActionClass]?
let project: Project
let recaptchaSiteKey: String?
}

View File

@@ -17,6 +17,31 @@ struct SurveyLanguage: Codable {
}
}
struct SurveyRecaptcha: Codable {
let enabled: Bool
let threshold: Double
private enum CodingKeys: String, CodingKey {
case enabled
case threshold
}
// Optional: enforce range at decode time
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.enabled = try container.decode(Bool.self, forKey: .enabled)
let value = try container.decode(Double.self, forKey: .threshold)
guard (0.1...0.9).contains(value) else {
throw DecodingError.dataCorruptedError(
forKey: .threshold,
in: container,
debugDescription: "threshold must be between 0.1 and 0.9"
)
}
self.threshold = value
}
}
struct LanguageDetail: Codable {
let id: String
let code: String
@@ -36,4 +61,5 @@ struct Survey: Codable {
let segment: Segment?
let styling: Styling?
let languages: [SurveyLanguage]?
let recaptcha: SurveyRecaptcha?
}

View File

@@ -1,11 +1,198 @@
struct Segment: Codable {
let id: String?
let createdAt: String?
let updatedAt: String?
let title: String?
let description: String?
let isPrivate: Bool?
let filters: [String]?
let environmentId: String?
let surveys: [String]?
import Foundation
// MARK: - Connector
enum SegmentConnector: String, Codable {
case and
case or
}
// MARK: - Filter Operators
/// Combined operator set for all filter types
enum FilterOperator: String, Codable {
// Base / Arithmetic
case lessThan
case lessEqual
case greaterThan
case greaterEqual
case equals
case notEquals
// Attribute / String
case contains
case doesNotContain
case startsWith
case endsWith
// Existence
case isSet
case isNotSet
// Segment membership
case userIsIn
case userIsNotIn
}
// MARK: - Filter Value
enum SegmentFilterValue: Codable {
case string(String)
case number(Double)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let num = try? container.decode(Double.self) {
self = .number(num)
} else if let str = try? container.decode(String.self) {
self = .string(str)
} else {
throw DecodingError.typeMismatch(
SegmentFilterValue.self,
DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Value is neither Double nor String"
)
)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .number(let num):
try container.encode(num)
case .string(let str):
try container.encode(str)
}
}
}
// MARK: - Root
enum SegmentFilterRoot: Codable {
case attribute(contactAttributeKey: String)
case person(personIdentifier: String)
case segment(segmentId: String)
case device(deviceType: String)
private enum CodingKeys: String, CodingKey {
case type
case contactAttributeKey
case personIdentifier
case segmentId
case deviceType
}
private enum RootType: String, Codable {
case attribute
case person
case segment
case device
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(RootType.self, forKey: .type)
switch type {
case .attribute:
let key = try container.decode(String.self, forKey: .contactAttributeKey)
self = .attribute(contactAttributeKey: key)
case .person:
let id = try container.decode(String.self, forKey: .personIdentifier)
self = .person(personIdentifier: id)
case .segment:
let id = try container.decode(String.self, forKey: .segmentId)
self = .segment(segmentId: id)
case .device:
let type = try container.decode(String.self, forKey: .deviceType)
self = .device(deviceType: type)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .attribute(let key):
try container.encode(RootType.attribute, forKey: .type)
try container.encode(key, forKey: .contactAttributeKey)
case .person(let id):
try container.encode(RootType.person, forKey: .type)
try container.encode(id, forKey: .personIdentifier)
case .segment(let id):
try container.encode(RootType.segment, forKey: .type)
try container.encode(id, forKey: .segmentId)
case .device(let type):
try container.encode(RootType.device, forKey: .type)
try container.encode(type, forKey: .deviceType)
}
}
}
// MARK: - Qualifier
struct SegmentFilterQualifier: Codable {
let `operator`: FilterOperator
}
// MARK: - Primitive Filter
struct SegmentPrimitiveFilter: Codable {
let id: String
let root: SegmentFilterRoot
let value: SegmentFilterValue
let qualifier: SegmentFilterQualifier
// Add run-time refinements if needed
}
// MARK: - Recursive Filter Resource
enum SegmentFilterResource: Codable {
case primitive(SegmentPrimitiveFilter)
case group([SegmentFilter])
init(from decoder: Decoder) throws {
// Try primitive first
if let prim = try? SegmentPrimitiveFilter(from: decoder) {
self = .primitive(prim)
} else {
let nested = try [SegmentFilter](from: decoder)
self = .group(nested)
}
}
func encode(to encoder: Encoder) throws {
switch self {
case .primitive(let prim):
try prim.encode(to: encoder)
case .group(let arr):
try arr.encode(to: encoder)
}
}
}
// MARK: - Base Filter (node)
struct SegmentFilter: Codable {
let id: String
let connector: SegmentConnector?
let resource: SegmentFilterResource
}
// MARK: - Segment Model
struct Segment: Codable {
let id: String
let title: String
let description: String?
let isPrivate: Bool
let filters: [SegmentFilter]
let environmentId: String
let createdAt: Date
let updatedAt: Date
let surveys: [String]
private enum CodingKeys: String, CodingKey {
case id, title, description, filters, surveys
case isPrivate = "isPrivate"
case environmentId, createdAt, updatedAt
}
}

View File

@@ -39,7 +39,6 @@ class APIClient<Request: CodableRequest>: Operation, @unchecked Sendable {
private func processResponse(data: Data?, response: URLResponse?, error: Error?) {
guard let httpStatus = (response as? HTTPURLResponse)?.status else {
let error = FormbricksAPIClientError(type: .invalidResponse)
Formbricks.delegate?.onError(error)
Formbricks.logger?.error("ERROR \(error.message)")
completion?(.failure(error))
return
@@ -57,7 +56,6 @@ class APIClient<Request: CodableRequest>: Operation, @unchecked Sendable {
private func handleSuccessResponse(data: Data?, statusCode: Int, message: inout String) {
guard let data = data else {
let error = FormbricksAPIClientError(type: .invalidResponse, statusCode: statusCode)
Formbricks.delegate?.onError(error)
completion?(.failure(error))
return
}
@@ -89,16 +87,13 @@ class APIClient<Request: CodableRequest>: Operation, @unchecked Sendable {
if let error = error {
log.append("\nError: \(error.localizedDescription)")
Formbricks.delegate?.onError(error)
Formbricks.logger?.error(log)
completion?(.failure(error))
} else if let data = data, let apiError = try? request.decoder.decode(FormbricksAPIError.self, from: data) {
Formbricks.delegate?.onError(apiError)
Formbricks.logger?.error("\(log)\n\(apiError.getDetailedErrorMessage())")
completion?(.failure(apiError))
} else {
let error = FormbricksAPIClientError(type: .responseError, statusCode: statusCode)
Formbricks.delegate?.onError(error)
Formbricks.logger?.error("\(log)\n\(error.message)")
completion?(.failure(error))
}
@@ -119,10 +114,8 @@ class APIClient<Request: CodableRequest>: Operation, @unchecked Sendable {
}
let error = FormbricksAPIClientError(type: .invalidResponse, statusCode: statusCode)
Formbricks.delegate?.onError(error)
Formbricks.logger?.error(error.message)
completion?(.failure(error))
}
private func logRequest(_ request: URLRequest) {

View File

@@ -101,7 +101,6 @@ private extension UpdateQueue {
guard let userId = effectiveUserId else {
let error = FormbricksSDKError(type: .userIdIsNotSetYet)
Formbricks.delegate?.onError(error)
Formbricks.logger?.error(error.message)
return
}

View File

@@ -110,7 +110,6 @@ private class WebViewData {
let jsonData = try JSONSerialization.data(withJSONObject: data, options: [])
return String(data: jsonData, encoding: .utf8)?.replacingOccurrences(of: "\\\"", with: "'")
} catch {
Formbricks.delegate?.onError(error)
Formbricks.logger?.error(error.message)
return nil
}

View File

@@ -119,12 +119,10 @@ final class JsMessageHandler: NSObject, WKScriptMessageHandler {
/// Happens when a survey is shown.
case .onDisplayCreated:
Formbricks.delegate?.onSurveyStarted()
Formbricks.surveyManager?.onNewDisplay(surveyId: surveyId)
/// Happens when the user closes the survey view with the close button.
case .onClose:
Formbricks.delegate?.onSurveyClosed()
Formbricks.surveyManager?.dismissSurveyWebView()
/// Happens when the survey wants to open an external link in the default browser.
@@ -140,7 +138,6 @@ final class JsMessageHandler: NSObject, WKScriptMessageHandler {
} else {
let error = FormbricksSDKError(type: .invalidJavascriptMessage)
Formbricks.delegate?.onError(error)
Formbricks.logger?.error("\(error.message): \(message.body)")
}
}

View File

@@ -210,7 +210,6 @@ const refineFilters = (filters: TBaseFilters): boolean => {
// The filters can be nested, so we need to use z.lazy to define the type
// more on recusrsive types -> https://zod.dev/?id=recursive-types
// TODO: Figure out why this is not working, and then remove the ts-ignore
export const ZSegmentFilters: z.ZodType<TBaseFilters> = z
.array(
z.object({