Skip to content
Open

4.0.0 #180

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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 4.0.0 (2026-08-11)

**Breaking changes**

- Update Castle iOS SDK to 4.2.0.
- Minimum iOS version raised to 13.0.
- Update Castle Android SDK to 4.0.2.
- Android raised `minSdkVersion` to 26 (Android 8.0) and `compileSdkVersion` to 36.
- Removed `userAgent()` and `queueSize()`.
- Removed the `sensorTrackingEnabled` configuration option.
- `flushIfNeeded()` and `baseUrl()` are iOS only. On Android `flushIfNeeded()` is a no-op and `baseUrl()` resolves `null`.
- `createRequestToken()` resolves `null` instead of an empty string when called before the SDK has been configured.
- `configure()` and `configureWithPublishableKey()` now reject the promise if the SDK fails to configure.

**Enhancements**

- `setAdvertisingIdentifier()` is now implemented on Android.
- `baseURLAllowList` entries are converted to URLs on iOS, so allow list matching works as documented. Entries must include a scheme, for example `https://api.example.com`.

## 2.3.0 (2026-05-28)

- Update Castle iOS SDK to 3.2.0.
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
- Xcode 16.3+

### Android
- Android 7.0
- Android 8.0 (API 26)
- compileSdkVersion 36

## Installation

Expand All @@ -41,6 +42,9 @@ Run `pod install` in the `ios` directory in order to link to the native iOS proj
npx pod-install
```

The Castle iOS SDK ships as a binary XCFramework vendored inside this package, so
CocoaPods links, embeds and signs it for you. No extra Podfile setup is required.

Once completed, re-build the app binary and start using the library

```bash
Expand Down
29 changes: 29 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# Releasing

## Updating the Castle iOS SDK

Castle iOS 4.x is not published to CocoaPods, so `ios/CastleSDK.xcframework` is
vendored in this repository and has to be refreshed by hand whenever the native
SDK is bumped:

```bash
VERSION=4.2.0
gh release download "$VERSION" --repo castle/castle-ios --pattern "Castle.xcframework.zip" --clobber

# Verify the download matches the checksum in castle-ios' Package.swift
shasum -a 256 Castle.xcframework.zip

rm -rf ios/CastleSDK.xcframework
unzip -q Castle.xcframework.zip
mv Castle.xcframework ios/CastleSDK.xcframework
rm Castle.xcframework.zip
```

The release ships the bundle as `Castle.xcframework`, but it **must** be renamed
to `CastleSDK.xcframework`. CocoaPods derives the linker flag from the file name,
so leaving it as `Castle.xcframework` produces `-framework Castle` and the build
fails with `ld: framework 'Castle' not found`. The framework inside is named
`CastleSDK.framework`.

After updating, run `pod install` in `example/ios` and check that the example app
still launches — a missing or misnamed framework links fine but crashes at
startup with `dyld: Library not loaded: @rpath/CastleSDK.framework/CastleSDK`.

## Pre-release

Create a new `X.Y.Z` branch from `master` and run:
Expand Down
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ android {
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
buildToolsVersion getExtOrDefault('buildToolsVersion')
defaultConfig {
minSdkVersion 24
minSdkVersion 26
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
}

Expand Down Expand Up @@ -138,5 +138,5 @@ dependencies {
// noinspection GradleDynamicVersion
api 'com.facebook.react:react-native:+'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
api 'io.castle.android:castle:3.1.8'
api 'io.castle.android:castle:4.0.2'
}
6 changes: 3 additions & 3 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Castle_kotlinVersion=2.1.20
Castle_compileSdkVersion=34
Castle_buildToolsVersion=35.0.0
Castle_targetSdkVersion=35
Castle_compileSdkVersion=36
Castle_buildToolsVersion=36.0.0
Castle_targetSdkVersion=36
android.useAndroidX=true
96 changes: 50 additions & 46 deletions android/src/main/java/com/reactnativecastle/CastleModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package com.reactnativecastle

import android.app.Application
import com.facebook.react.bridge.*
import io.castle.android.Castle
import io.castle.android.CastleConfiguration
import io.castle.Castle
import io.castle.Configuration

class CastleModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {

private var advertisingIdentifier: String? = null

override fun getName(): String {
return "Castle"
}
Expand All @@ -18,40 +20,51 @@ class CastleModule(reactContext: ReactApplicationContext) : ReactContextBaseJava

@ReactMethod
fun configure(options: ReadableMap?, promise: Promise) {
if (options != null) {
val builder = CastleConfiguration.Builder()
builder.publishableKey(options.getString("publishableKey"))
builder.screenTrackingEnabled(false)
if (options.hasKey("debugLoggingEnabled")) {
builder.debugLoggingEnabled(options.getBoolean("debugLoggingEnabled"))
}
if (options.hasKey("maxQueueLimit")) {
builder.maxQueueLimit(options.getInt("maxQueueLimit"))
}
if (options.hasKey("flushLimit")) {
builder.flushLimit(options.getInt("flushLimit"))
}
if (options.hasKey("baseURLAllowList")) {
val array = options.getArray("baseURLAllowList")
array?.let {
val baseURLAllowList = mutableListOf<String>()
for (i in 0 until array.size()) {
array.getString(i)?.let {
s -> baseURLAllowList.add(s)
}
if (options == null) {
promise.reject("castle_configuration_error", "Invalid configuration")
return
}

val publishableKey = options.getString("publishableKey")
if (publishableKey == null) {
promise.reject("castle_configuration_error", "Missing publishableKey")
return
}

val builder = Configuration.Builder()
builder.publishableKey(publishableKey)
builder.screenTrackingEnabled(false)
builder.adIdProvider { advertisingIdentifier ?: "" }
if (options.hasKey("debugLoggingEnabled")) {
builder.debugLoggingEnabled(options.getBoolean("debugLoggingEnabled"))
}
if (options.hasKey("maxQueueLimit")) {
builder.maxQueueLimit(options.getInt("maxQueueLimit"))
}
if (options.hasKey("flushLimit")) {
builder.flushLimit(options.getInt("flushLimit"))
}
if (options.hasKey("baseURLAllowList")) {
val array = options.getArray("baseURLAllowList")
array?.let {
val baseURLAllowList = mutableListOf<String>()
for (i in 0 until array.size()) {
array.getString(i)?.let {
s -> baseURLAllowList.add(s)
}
builder.baseURLAllowList(baseURLAllowList)
}
}
if (options.hasKey("lifeCycleEventsEnabled")) {
builder.applicationLifecycleTrackingEnabled(options.getBoolean("lifeCycleEventsEnabled"))
}
builder.baseURLAllowList(baseURLAllowList)
}
}
if (options.hasKey("lifeCycleEventsEnabled")) {
builder.applicationLifecycleTrackingEnabled(options.getBoolean("lifeCycleEventsEnabled"))
}

try {
Castle.configure(reactApplicationContext.applicationContext as Application, builder.build())

promise.resolve(null)
} else {
promise.reject("Invalid configuration")
} catch (e: RuntimeException) {
promise.reject("castle_configuration_error", e.message, e)
}
}

Expand All @@ -62,12 +75,12 @@ class CastleModule(reactContext: ReactApplicationContext) : ReactContextBaseJava

@ReactMethod
fun resetConfiguration() {
Castle.reset()
Castle.resetConfiguration()
}

@ReactMethod
fun userJwt(userJwt: String) {
Castle.userJwt(userJwt)
Castle.setUserJwt(userJwt)
}

@ReactMethod
Expand All @@ -92,7 +105,7 @@ class CastleModule(reactContext: ReactApplicationContext) : ReactContextBaseJava

@ReactMethod
fun flushIfNeeded(url: String) {
Castle.flushIfNeeded(url)
// Not available on Android since Castle Android 4.0.0, iOS only.
}

@ReactMethod
Expand All @@ -102,7 +115,8 @@ class CastleModule(reactContext: ReactApplicationContext) : ReactContextBaseJava

@ReactMethod
fun baseUrl(promise: Promise) {
promise.resolve(Castle.baseUrl())
// Not available on Android since Castle Android 4.0.0, iOS only.
promise.resolve(null)
}

@ReactMethod
Expand All @@ -112,16 +126,6 @@ class CastleModule(reactContext: ReactApplicationContext) : ReactContextBaseJava

@ReactMethod
fun setAdvertisingIdentifier(idfa: String) {
// Do nothing, setting IDFA is not applicable on Android
}

@ReactMethod
fun userAgent(promise: Promise) {
promise.resolve(Castle.userAgent())
}

@ReactMethod
fun queueSize(promise: Promise) {
promise.resolve(Castle.queueSize())
advertisingIdentifier = idfa
}
}
8 changes: 4 additions & 4 deletions example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
buildscript {
ext {
buildToolsVersion = "34.0.0"
minSdkVersion = 24
compileSdkVersion = 35
targetSdkVersion = 34
buildToolsVersion = "36.0.0"
minSdkVersion = 26
compileSdkVersion = 36
targetSdkVersion = 36
ndkVersion = "27.1.12297006"
kotlinVersion = "2.0.21"
}
Expand Down
46 changes: 36 additions & 10 deletions example/ios/CastleExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

/* Begin PBXBuildFile section */
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
532E0611E0C56E470189E591 /* libPods-CastleExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B5F3EE976AFAD5D53153C839 /* libPods-CastleExample.a */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
9EB2394123E1745F9D9D92F0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0D2312465685223C87B663F6 /* PrivacyInfo.xcprivacy */; };
B6A09F982E2F925C00505B0A /* Appdelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A09F972E2F925C00505B0A /* Appdelegate.swift */; };
D6BA01F102E93AE12BB56748 /* libPods-CastleExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 876CB16ACB2DA29C48367FD1 /* libPods-CastleExample.a */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -33,8 +33,8 @@
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = CastleExample/Info.plist; sourceTree = "<group>"; };
19781D9CC10C3637F42736B6 /* Pods-CastleExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CastleExample.release.xcconfig"; path = "Target Support Files/Pods-CastleExample/Pods-CastleExample.release.xcconfig"; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = CastleExample/LaunchScreen.storyboard; sourceTree = "<group>"; };
876CB16ACB2DA29C48367FD1 /* libPods-CastleExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CastleExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
94571458C340E4FCBBD7504F /* Pods-CastleExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CastleExample.debug.xcconfig"; path = "Target Support Files/Pods-CastleExample/Pods-CastleExample.debug.xcconfig"; sourceTree = "<group>"; };
B5F3EE976AFAD5D53153C839 /* libPods-CastleExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CastleExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
B6A09F972E2F925C00505B0A /* Appdelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Appdelegate.swift; path = CastleExample/Appdelegate.swift; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
Expand All @@ -51,7 +51,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
532E0611E0C56E470189E591 /* libPods-CastleExample.a in Frameworks */,
D6BA01F102E93AE12BB56748 /* libPods-CastleExample.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -75,7 +75,7 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
B5F3EE976AFAD5D53153C839 /* libPods-CastleExample.a */,
876CB16ACB2DA29C48367FD1 /* libPods-CastleExample.a */,
);
name = Frameworks;
sourceTree = "<group>";
Expand Down Expand Up @@ -262,19 +262,17 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CastleExample/Pods-CastleExample-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Castle/Highwind.framework/Highwind",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Castle/GeoZip.framework/GeoZip",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermesvm.framework/hermesvm",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/react-native-castle/CastleSDK.framework/CastleSDK",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Highwind.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GeoZip.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermesvm.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CastleSDK.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
Expand All @@ -288,13 +286,11 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CastleExample/Pods-CastleExample-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/Castle/Castle.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Castle.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle",
);
Expand Down Expand Up @@ -490,6 +486,21 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"${PODS_ROOT}/ReactCommon",
"${PODS_ROOT}/ReactCommon/react/nativemodule/core",
"${PODS_ROOT}/React-runtimeexecutor",
"${PODS_ROOT}/React-runtimeexecutor/platform/ios",
"${PODS_ROOT}/ReactCommon-Samples",
"${PODS_ROOT}/ReactCommon-Samples/platform/ios",
"${PODS_ROOT}/React-Fabric/react/renderer/components/view/platform/cxx",
"${PODS_ROOT}/React-NativeModulesApple",
"${PODS_ROOT}/React-graphics",
"${PODS_ROOT}/React-graphics/react/renderer/graphics/platform/ios",
"${PODS_ROOT}/React-featureflags",
"${PODS_ROOT}/React-renderercss",
);
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD = "";
LDPLUSPLUS = "";
Expand Down Expand Up @@ -564,6 +575,21 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"${PODS_ROOT}/ReactCommon",
"${PODS_ROOT}/ReactCommon/react/nativemodule/core",
"${PODS_ROOT}/React-runtimeexecutor",
"${PODS_ROOT}/React-runtimeexecutor/platform/ios",
"${PODS_ROOT}/ReactCommon-Samples",
"${PODS_ROOT}/ReactCommon-Samples/platform/ios",
"${PODS_ROOT}/React-Fabric/react/renderer/components/view/platform/cxx",
"${PODS_ROOT}/React-NativeModulesApple",
"${PODS_ROOT}/React-graphics",
"${PODS_ROOT}/React-graphics/react/renderer/graphics/platform/ios",
"${PODS_ROOT}/React-featureflags",
"${PODS_ROOT}/React-renderercss",
);
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD = "";
LDPLUSPLUS = "";
Expand Down
Loading