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
59 changes: 59 additions & 0 deletions Sources/Rownd/Models/RowndConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,64 @@

import Foundation


public struct SuperTokensAppInfo: Encodable {
public var appName: String
public var apiDomain: String
public var apiBasePath: String

internal var normalizedApiDomain: String? {
let domain = apiDomain.trimmingCharacters(in: .whitespacesAndNewlines)
.trimmingCharacters(in: CharacterSet(charactersIn: "/"))

guard let url = URL(string: domain),
let scheme = url.scheme?.lowercased(),
scheme == "http" || scheme == "https",
url.host != nil
else {
return nil
}

return url.absoluteString.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
}

internal var normalizedApiBasePath: String {
let segments = apiBasePath
.trimmingCharacters(in: .whitespacesAndNewlines)
.split(separator: "/")
.map(String.init)

return segments.isEmpty ? "" : "/" + segments.joined(separator: "/")
}

internal var migrationURL: URL? {
guard let normalizedApiDomain else {
return nil
}

guard var components = URLComponents(string: normalizedApiDomain) else {
return nil
}

components.path = normalizedApiBasePath + "/plugin/rownd/migrate"
return components.url
}

public init(appName: String, apiDomain: String, apiBasePath: String = "/auth") {
self.appName = appName
self.apiDomain = apiDomain
self.apiBasePath = apiBasePath
}
}

public struct SuperTokensConfig: Encodable {
public var appInfo: SuperTokensAppInfo

public init(appInfo: SuperTokensAppInfo) {
self.appInfo = appInfo
}
}

public struct RowndConfig: Encodable {
internal init() {}

Expand All @@ -21,6 +79,7 @@ public struct RowndConfig: Encodable {
public var customizations: RowndCustomizations = RowndCustomizations()

// These will not be encoded
public var supertokens: SuperTokensConfig? = nil
public var appGroupPrefix: String?
public var enableSmartLinkPasteBehavior: Bool = true
public var signInLinkPattern: String = ".*\\.rownd\\.link$"
Expand Down
2 changes: 2 additions & 0 deletions Sources/Rownd/Rownd.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class Rownd: NSObject {
config.appKey = _appKey
}

registerSuperTokensSyncEventHandler()
Comment thread
mhamann marked this conversation as resolved.

let state = await inst.inflateStoreCache()

// Skip the rest within app extensions
Expand Down
67 changes: 67 additions & 0 deletions Sources/Rownd/framework/SuperTokensSync.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Foundation
import OSLog

private let log = Logger(subsystem: "io.rownd.sdk", category: "supertokens-sync")

private final class SuperTokensSyncEventHandler: RowndEventHandlerDelegate {
func handleRowndEvent(_ event: RowndEvent) {
let userType = event.data?["user_type"] ?? nil

guard event.event == .signInCompleted,
userType?.value as? String == "new_user",
let appInfo = Rownd.config.supertokens?.appInfo
else {
return
}

Task {
do {
guard let accessToken = try await Rownd.getAccessToken() else {
return
}

await syncUserToSuperTokens(accessToken: accessToken, appInfo: appInfo)
} catch {
log.error("[Rownd->ST] failed to read access token for migration: \(error.localizedDescription)")
}
}
}
}

private let superTokensSyncEventHandler = SuperTokensSyncEventHandler()

func registerSuperTokensSyncEventHandler() {
let alreadyRegistered = Context.currentContext.eventListeners.contains { listener in
listener === superTokensSyncEventHandler
}

if !alreadyRegistered {
Context.currentContext.eventListeners.append(superTokensSyncEventHandler)
}
}

func syncUserToSuperTokens(
accessToken: String,
appInfo: SuperTokensAppInfo
) async {
guard let url = appInfo.migrationURL else {
log.error(
"[Rownd->ST] invalid migration URL constructed from apiDomain=\(appInfo.apiDomain) apiBasePath=\(appInfo.apiBasePath)"
)
return
}

do {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
request.httpShouldHandleCookies = true

let (_, response) = try await URLSession.shared.data(for: request)
if let http = response as? HTTPURLResponse, !(200...299).contains(http.statusCode) {
log.error("[Rownd->ST] migrate failed with status: \(http.statusCode)")
}
} catch {
log.error("[Rownd->ST] migrate failed (non-fatal): \(error.localizedDescription)")
}
}
Loading