Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- We improved `NativeCookieModule` to persist and restore session cookies on iOS.

## [v0.3.0] - 2025-12-09

- We fixed an issue that caused a FileNotFoundException during file deletion operations.
Expand Down
9 changes: 9 additions & 0 deletions example/ios/MendixNativeExample/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class AppDelegate: RCTAppDelegate {
super.application(application, didFinishLaunchingWithOptions: launchOptions)

//Start - For MendixApplication compatibility only, not part of React Native template
NativeCookieModule.restoreSessionCookies()
MxConfiguration.update(from:
MendixApp.init(
identifier: nil,
Expand All @@ -32,6 +33,14 @@ class AppDelegate: RCTAppDelegate {
return true
}

override func applicationDidEnterBackground(_ application: UIApplication) {
NativeCookieModule.persistSessionCookies()
}

override func applicationWillTerminate(_ application: UIApplication) {
NativeCookieModule.persistSessionCookies()
}

override func sourceURL(for bridge: RCTBridge) -> URL? {
self.bundleURL()
}
Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ PODS:
- hermes-engine (0.78.2):
- hermes-engine/Pre-built (= 0.78.2)
- hermes-engine/Pre-built (0.78.2)
- MendixNative (0.1.3):
- MendixNative (0.3.0):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1851,7 +1851,7 @@ SPEC CHECKSUMS:
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
hermes-engine: 2771b98fb813fdc6f92edd7c9c0035ecabf9fee7
MendixNative: 36190d86a65cb57b351c6396bc1349a7823206b0
MendixNative: a55e00448d33a66d59bd4b5c5d3057123d34337c
op-sqlite: 12554de3e1a0cb86cbad3cf1f0c50450f57d3855
OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2
RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82
Expand Down
31 changes: 21 additions & 10 deletions ios/Modules/AppPreferences/AppPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,29 @@ public class AppPreferences: NSObject {
private static var _packagerPort: Int

public static var remoteDebuggingPackagerPort: Int {
get {
return AppUrl.ensurePort(_packagerPort)
}
set {
_packagerPort = newValue
}
get { AppUrl.ensurePort(_packagerPort) }
set { _packagerPort = newValue }
}

public static var appUrl = _appUrl
public static var devModeEnabled = _devModeEnabled
public static var remoteDebuggingEnabled = _remoteDebuggingEnabled
public static var elementInspectorEnabled = _elementInspectorEnabled
public static var appUrl: String? {
get { _appUrl }
set { _appUrl = newValue }
}

public static var devModeEnabled: Bool {
get { _devModeEnabled }
set { _devModeEnabled = newValue }
}

public static var remoteDebuggingEnabled: Bool {
get { _remoteDebuggingEnabled }
set { _remoteDebuggingEnabled = newValue }
}

public static var elementInspectorEnabled: Bool {
get { _elementInspectorEnabled }
set { _elementInspectorEnabled = newValue }
}

public static var safeAppUrl: String {
return appUrl ?? ""
Expand Down
65 changes: 65 additions & 0 deletions ios/Modules/NativeCookieModule/NativeCookieModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,70 @@ public class NativeCookieModule: NSObject {
for cookie in (storage.cookies ?? []) {
storage.deleteCookie(cookie)
}
SessionCookieStore.clear()
}

public static func persistSessionCookies() {
SessionCookieStore.persist()
}

public static func restoreSessionCookies() {
SessionCookieStore.restore()
}

final class SessionCookieStore {

private static let bundleIdentifier = Bundle.main.bundleIdentifier ?? "com.mendix.app"
private static let storageKey = bundleIdentifier + "sessionCookies"
private static let queue = DispatchQueue(label: bundleIdentifier + ".session-cookie-store", qos: .utility)

// MARK: - Public API
public static func restore() {

guard
let data = UserDefaults.standard.data(forKey: storageKey),
let cookies = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, HTTPCookie.self], from: data) as? [HTTPCookie]
else {
NSLog("SessionCookieStore: No cookies to restore")
return
}

let storage = HTTPCookieStorage.shared
let existing = Set(storage.cookies ?? [])

cookies.filter { !existing.contains($0) }.forEach { storage.setCookie($0) }

// Clear stored cookies after restoration to avoid any side effects
clear()
}

public static func persist() {
queue.async {

let cookies = HTTPCookieStorage.shared.cookies ?? []
let sessionCookies = cookies.filter { isSessionCookie($0) }

guard !sessionCookies.isEmpty else {
clear()
NSLog("SessionCookieStore: Clear existing session cookies from storage")
return
}

do {
let data = try NSKeyedArchiver.archivedData(withRootObject: sessionCookies, requiringSecureCoding: false)
UserDefaults.standard.set(data, forKey: storageKey)
} catch {
NSLog("SessionCookieStore: Failed to persist session cookies: \(error.localizedDescription)")
}
}
}

public static func clear() {
UserDefaults.standard.removeObject(forKey: storageKey)
}

public static func isSessionCookie(_ cookie: HTTPCookie) -> Bool {
return cookie.expiresDate == nil
}
}
}