mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-19 11:11:05 -05:00
fixes ios sdk issues and removes callbacks
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,5 @@ struct EnvironmentData: Codable {
|
||||
let surveys: [Survey]?
|
||||
let actionClasses: [ActionClass]?
|
||||
let project: Project
|
||||
let recaptchaSiteKey: String?
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user