Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ jobs:
with:
fetch-depth: 0 # Shallow clones should be disabled for better SonarQube analysis

- name: Select Xcode
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: latest-stable

- name: Show available simulators
run: xcrun simctl list devices available

- name: Install Dependencies
run: swift package resolve

Expand All @@ -34,7 +42,7 @@ jobs:
-scheme 'FormbricksSDK' \
-sdk iphonesimulator \
-config Debug \
-destination 'platform=iOS Simulator,name=iPhone SE (3rd generation),OS=18.5' \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
-derivedDataPath build \
-enableCodeCoverage YES

Expand Down
40 changes: 25 additions & 15 deletions Sources/FormbricksSDK/Formbricks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import Network
@objc(Formbricks) public class Formbricks: NSObject {

static internal var appUrl: String?
static internal var environmentId: String?
static internal var workspaceId: String?
/// Backward-compatible alias for `workspaceId`.
@available(*, deprecated, renamed: "workspaceId", message: "Use workspaceId instead. environmentId will be removed in a future version.")
static internal var environmentId: String? {
get { workspaceId }
set { workspaceId = newValue }
}
static internal var language: String = "default"
static internal var isInitialized: Bool = false

Expand All @@ -30,31 +36,35 @@ import Network

Example:
```swift
let config = FormbricksConfig.Builder(appUrl: "APP_URL_HERE", environmentId: "TOKEN_HERE")
let config = FormbricksConfig.Builder(appUrl: "APP_URL_HERE", workspaceId: "TOKEN_HERE")
.setUserId("USER_ID_HERE")
.setLogLevel(.debug)
.build()

Formbricks.setup(with: config)
```
*/
@objc public static func setup(with config: FormbricksConfig, force: Bool = false) {
logger = Logger()
apiQueue = OperationQueue()

if force {
isInitialized = false
}

guard !isInitialized else {
let error = FormbricksSDKError(type: .sdkIsAlreadyInitialized)
Formbricks.logger?.error(error.message)
return
}

self.appUrl = config.appUrl
self.environmentId = config.environmentId
self.workspaceId = config.workspaceId
self.logger?.logLevel = config.logLevel

if config.usedDeprecatedEnvironmentId {
Formbricks.logger?.debug("environmentId is deprecated and will be removed in a future version. Please use workspaceId instead.")
}

// Validate appUrl before proceeding with setup
guard let url = URL(string: config.appUrl) else {
Expand All @@ -63,11 +73,11 @@ import Network
}

// Validate that appUrl uses HTTPS (block HTTP for security)
guard url.scheme?.lowercased() == "https" else {
let errorMessage = "HTTP requests are blocked for security. Only HTTPS URLs are allowed. Provided app url: \(config.appUrl). SDK setup aborted."
Formbricks.logger?.error(errorMessage)
return
}
guard url.scheme?.lowercased() == "https" else {
let errorMessage = "HTTP requests are blocked for security. Only HTTPS URLs are allowed. Provided app url: \(config.appUrl). SDK setup aborted."
Formbricks.logger?.error(errorMessage)
return
}

let svc: FormbricksServiceProtocol = config.customService ?? FormbricksService()

Expand All @@ -88,7 +98,7 @@ import Network
surveyManager = SurveyManager.create(userManager: userManager!, presentSurveyManager: presentSurveyManager!, service: svc)
userManager?.surveyManager = surveyManager

surveyManager?.refreshEnvironmentIfNeeded(force: force)
surveyManager?.refreshWorkspaceIfNeeded(force: force)
userManager?.syncUserStateIfNeeded()

self.isInitialized = true
Expand Down Expand Up @@ -289,7 +299,7 @@ import Network
}

/**
Cleans up the SDK. This will clear the user attributes, the user id and the environment state.
Cleans up the SDK. This will clear the user attributes, the user id and the workspace state.
The SDK must be initialized before calling this method.
If `waitForOperations` is set to `true`, it will wait for all operations to finish before cleaning up.
If `waitForOperations` is set to `false`, it will clean up immediately.
Expand Down Expand Up @@ -331,7 +341,7 @@ import Network
apiQueue = nil
isInitialized = false
appUrl = nil
environmentId = nil
workspaceId = nil
logger = nil
language = "default"
}
Expand Down
48 changes: 33 additions & 15 deletions Sources/FormbricksSDK/Helpers/ConfigBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,61 @@ import Foundation
/// The configuration object for the Formbricks SDK.
@objc(FormbricksConfig) public class FormbricksConfig: NSObject {
let appUrl: String
let environmentId: String
@objc public let workspaceId: String
let userId: String?
let attributes: [String: AttributeValue]?
let logLevel: LogLevel
/// Optional custom service, injected via Builder
let customService: FormbricksServiceProtocol?

init(appUrl: String, environmentId: String, userId: String?, attributes: [String: AttributeValue]?, logLevel: LogLevel, customService: FormbricksServiceProtocol?) {
/// True if this config was built using the deprecated `environmentId` parameter.
let usedDeprecatedEnvironmentId: Bool

/// Backward-compatible alias for `workspaceId`.
@available(*, deprecated, renamed: "workspaceId", message: "Use workspaceId instead. environmentId will be removed in a future version.")
@objc public var environmentId: String { workspaceId }
Comment thread
pandeymangg marked this conversation as resolved.

init(appUrl: String, workspaceId: String, userId: String?, attributes: [String: AttributeValue]?, logLevel: LogLevel, customService: FormbricksServiceProtocol?, usedDeprecatedEnvironmentId: Bool = false) {
self.appUrl = appUrl
self.environmentId = environmentId
self.workspaceId = workspaceId
self.userId = userId
self.attributes = attributes
self.logLevel = logLevel
self.customService = customService
self.usedDeprecatedEnvironmentId = usedDeprecatedEnvironmentId
}

/// The builder class for the FormbricksConfig object.
@objc(FormbricksConfigBuilder) public class Builder: NSObject {
var appUrl: String
var environmentId: String
var workspaceId: String
var userId: String?
var attributes: [String: AttributeValue] = [:]
var logLevel: LogLevel = .error
/// Optional custom service, injected via Builder
var customService: FormbricksServiceProtocol?

var usedDeprecatedEnvironmentId: Bool = false

/// Initializes the builder with the workspace ID.
@objc public init(appUrl: String, workspaceId: String) {
self.appUrl = appUrl
self.workspaceId = workspaceId
}

/// Initializes the builder with the environment ID.
/// - Warning: `environmentId` is deprecated — use `init(appUrl:workspaceId:)` instead.
@available(*, deprecated, renamed: "init(appUrl:workspaceId:)", message: "Use init(appUrl:workspaceId:) instead. environmentId will be removed in a future version.")
@objc public init(appUrl: String, environmentId: String) {
self.appUrl = appUrl
self.environmentId = environmentId
self.workspaceId = environmentId
self.usedDeprecatedEnvironmentId = true
}

/// Sets the user id for the Builder object.
@objc public func set(userId: String) -> Builder {
self.userId = userId
return self
}

/// Sets the attributes for the Builder object.
///
/// Thanks to `ExpressibleByStringLiteral`, `ExpressibleByIntegerLiteral`,
Expand All @@ -58,27 +76,27 @@ import Foundation
self.attributes = stringAttributes.mapValues { .string($0) }
return self
}

/// Adds a string attribute to the Builder object (Obj-C compatible).
@objc public func add(attribute: String, forKey key: String) -> Builder {
self.attributes[key] = .string(attribute)
return self
}

/// Sets the log level for the Builder object.
@objc public func setLogLevel(_ logLevel: LogLevel) -> Builder {
self.logLevel = logLevel
return self
}

func service(_ svc: FormbricksServiceProtocol) -> FormbricksConfig.Builder {
self.customService = svc
return self
}

/// Builds the FormbricksConfig object from the Builder object.
@objc public func build() -> FormbricksConfig {
return FormbricksConfig(appUrl: appUrl, environmentId: environmentId, userId: userId, attributes: attributes, logLevel: logLevel, customService: customService)
return FormbricksConfig(appUrl: appUrl, workspaceId: workspaceId, userId: userId, attributes: attributes, logLevel: logLevel, customService: customService, usedDeprecatedEnvironmentId: usedDeprecatedEnvironmentId)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

internal enum FormbricksEnvironment {
internal enum FormbricksWorkspace {

/// Only `appUrl` is user-supplied. Returns nil if it's missing.
internal static var baseApiUrl: String? {
Expand All @@ -18,13 +18,13 @@ internal enum FormbricksEnvironment {
return surveyScriptURL.absoluteString
}

/// Returns the full environment‐fetch URL as a String for the given ID
static var getEnvironmentRequestEndpoint: String {
return ["api", "v2", "client", "{environmentId}", "environment"].joined(separator: "/")
/// Returns the workspace-state fetch URL path with a `{workspaceId}` placeholder.
static var getWorkspaceStateRequestEndpoint: String {
return ["api", "v2", "client", "{workspaceId}", "environment"].joined(separator: "/")
}

/// Returns the full post-user URL as a String for the given ID
/// Returns the post-user URL path with a `{workspaceId}` placeholder.
static var postUserRequestEndpoint: String {
return ["api", "v2", "client", "{environmentId}", "user"].joined(separator: "/")
return ["api", "v2", "client", "{workspaceId}", "user"].joined(separator: "/")
}
}
4 changes: 2 additions & 2 deletions Sources/FormbricksSDK/Manager/PresentSurveyManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ final class PresentSurveyManager {
/// Present the webview
/// The native background is always `.clear` — overlay rendering is handled
/// entirely by the JS survey library inside the WebView to avoid double-overlay artifacts.
func present(environmentResponse: EnvironmentResponse, id: String, completion: ((Bool) -> Void)? = nil) {
func present(workspaceResponse: WorkspaceResponse, id: String, completion: ((Bool) -> Void)? = nil) {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
if let window = UIApplication.safeKeyWindow {
let view = FormbricksView(viewModel: FormbricksViewModel(environmentResponse: environmentResponse, surveyId: id))
let view = FormbricksView(viewModel: FormbricksViewModel(workspaceResponse: workspaceResponse, surveyId: id))
let vc = UIHostingController(rootView: view)
vc.modalPresentationStyle = .overCurrentContext
vc.view.backgroundColor = .clear
Expand Down
Loading
Loading