diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9935f19..ef149e8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -308,6 +308,10 @@ jobs: with: packages: tools platform-tools platforms;android-35 build-tools;35.0.0 + - name: Run tests + working-directory: kotlin/ + run: gradle bindings:test + - name: Build Kotlin sample app working-directory: kotlin/Examples/IDKitSampleApp run: ./gradlew :app:assembleDebug diff --git a/kotlin/Examples/IDKitSampleApp/app/src/main/java/com/worldcoin/idkit/sample/MainActivity.kt b/kotlin/Examples/IDKitSampleApp/app/src/main/java/com/worldcoin/idkit/sample/MainActivity.kt index 57957a5d..85e0c56a 100644 --- a/kotlin/Examples/IDKitSampleApp/app/src/main/java/com/worldcoin/idkit/sample/MainActivity.kt +++ b/kotlin/Examples/IDKitSampleApp/app/src/main/java/com/worldcoin/idkit/sample/MainActivity.kt @@ -36,6 +36,7 @@ import com.worldcoin.idkit.IDKitRequestConfig import com.worldcoin.idkit.documentLegacy import com.worldcoin.idkit.idkitResultToJson import com.worldcoin.idkit.deviceLegacy +import com.worldcoin.idkit.identityCheck import com.worldcoin.idkit.orbLegacy import com.worldcoin.idkit.secureDocumentLegacy import com.worldcoin.idkit.selfieCheckLegacy @@ -53,7 +54,9 @@ import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONArray import org.json.JSONObject +import uniffi.idkit_core.DocumentType import uniffi.idkit_core.Environment +import uniffi.idkit_core.IdentityAttribute import uniffi.idkit_core.RpContext class MainActivity : ComponentActivity() { @@ -239,7 +242,7 @@ private fun LegacyPresetSelector( onSelect: (SampleLegacyPreset) -> Unit, ) { Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { - Text("Legacy preset", style = MaterialTheme.typography.labelLarge) + Text("Preset", style = MaterialTheme.typography.labelLarge) SampleLegacyPreset.entries .chunked(2) @@ -281,6 +284,7 @@ private enum class SampleLegacyPreset(val label: String) { DOCUMENT("document"), DEVICE("device"), SELFIE_CHECK("selfie check"), + IDENTITY_CHECK("identity check"), ; fun toPreset(signal: String) = when (this) { @@ -289,6 +293,13 @@ private enum class SampleLegacyPreset(val label: String) { DOCUMENT -> documentLegacy(signal = signal) DEVICE -> deviceLegacy(signal = signal) SELFIE_CHECK -> selfieCheckLegacy(signal = signal) + IDENTITY_CHECK -> identityCheck( + attributes = listOf( + IdentityAttribute.MinimumAge(21u), + IdentityAttribute.Nationality("JPN"), + IdentityAttribute.DocumentType(DocumentType.PASSPORT), + ), + ) } } @@ -366,7 +377,7 @@ private class SampleModel { deepLinkReceivedForPendingRequest = false android.util.Log.i("IDKitSample", "IDKit connector URL: ${request.connectorURI}") - log("Using legacy preset: ${legacyPreset.label}") + log("Using preset: ${legacyPreset.label}") log("Generated request ID: ${request.requestId}") log("Configured return_to callback: $returnToURL") startPollingForRequest( diff --git a/kotlin/README.md b/kotlin/README.md index 06b56991..ee239c45 100644 --- a/kotlin/README.md +++ b/kotlin/README.md @@ -63,9 +63,12 @@ import com.worldcoin.idkit.IDKit import com.worldcoin.idkit.IDKitPollOptions import com.worldcoin.idkit.IDKitRequestConfig import com.worldcoin.idkit.IDKitCompletionResult +import com.worldcoin.idkit.IdentityAttribute import com.worldcoin.idkit.selfieCheckLegacy +import com.worldcoin.idkit.identityCheck import com.worldcoin.idkit.orbLegacy import com.worldcoin.idkit.deviceLegacy +import uniffi.idkit_core.DocumentType import uniffi.idkit_core.Environment import uniffi.idkit_core.RpContext @@ -117,6 +120,22 @@ val request = IDKit .preset(selfieCheckLegacy(signal = "user-123")) ``` +For document-based identity attestation, use: + +```kotlin +val request = IDKit + .request(config) + .preset( + identityCheck( + attributes = listOf( + IdentityAttribute.MinimumAge(21u), + IdentityAttribute.Nationality("JPN"), + IdentityAttribute.DocumentType(DocumentType.PASSPORT), + ), + ), + ) +``` + ## Credential request options parity ```kotlin diff --git a/kotlin/bindings/build.gradle.kts b/kotlin/bindings/build.gradle.kts index bbc15b0d..aecc07b9 100644 --- a/kotlin/bindings/build.gradle.kts +++ b/kotlin/bindings/build.gradle.kts @@ -33,6 +33,13 @@ android { withSourcesJar() } } + + testOptions { + unitTests.all { test -> + val rustLibDir = project.projectDir.resolve("../../target/release").canonicalPath + test.jvmArgs("-Djna.library.path=$rustLibDir") + } + } } dependencies { @@ -42,6 +49,8 @@ dependencies { implementation(kotlin("stdlib")) testImplementation(kotlin("test")) + // The @aar variant doesn't bundle libjnidispatch โ€” use the plain JVM jar for unit tests + testImplementation("net.java.dev.jna:jna:5.14.0") } afterEvaluate { diff --git a/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt b/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt index 5b8591ab..4cd60d49 100644 --- a/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt +++ b/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt @@ -37,6 +37,8 @@ typealias IDKitRequestConfig = IdKitRequestConfig typealias IDKitResult = IdKitResult typealias RpContext = uniffi.idkit_core.RpContext typealias Environment = uniffi.idkit_core.Environment +typealias DocumentType = uniffi.idkit_core.DocumentType +typealias IdentityAttribute = uniffi.idkit_core.IdentityAttribute class IDKitClientError(message: String) : IllegalArgumentException(message) @@ -333,6 +335,13 @@ fun deviceLegacy(signal: String? = null): Preset = Preset.DeviceLegacy(signal = */ fun selfieCheckLegacy(signal: String? = null): Preset = Preset.SelfieCheckLegacy(signal = signal) +/** + * Returns the identity check preset. + * + * This preset requires World ID 4.0-compatible clients. + */ +fun identityCheck(attributes: List): Preset = Preset.IdentityCheck(attributes = attributes) + fun idkitResultToJson(result: IDKitResult): String = nativeIdkitResultToJson(result) fun idkitResultFromJson(json: String): IDKitResult = nativeIdkitResultFromJson(json) diff --git a/kotlin/bindings/src/test/kotlin/com/worldcoin/idkit/IDKitTests.kt b/kotlin/bindings/src/test/kotlin/com/worldcoin/idkit/IDKitTests.kt index da50115e..8c408f20 100644 --- a/kotlin/bindings/src/test/kotlin/com/worldcoin/idkit/IDKitTests.kt +++ b/kotlin/bindings/src/test/kotlin/com/worldcoin/idkit/IDKitTests.kt @@ -7,9 +7,12 @@ import kotlin.test.assertEquals import kotlin.test.assertNotEquals import kotlin.test.assertTrue import uniffi.idkit_core.AppError +import uniffi.idkit_core.ConnectUrlMode // TODO: Re-enable when World ID 4.0 is live // import uniffi.idkit_core.CredentialType +import uniffi.idkit_core.DocumentType import uniffi.idkit_core.Environment +import uniffi.idkit_core.IdentityAttribute import uniffi.idkit_core.Preset import uniffi.idkit_core.ResponseItem import uniffi.idkit_core.RpContext @@ -25,6 +28,7 @@ class IDKitTests { sessionId = sessionId, responses = emptyList(), environment = "production", + identityAttested = null, ) private fun sampleRpContext(): RpContext { @@ -50,6 +54,7 @@ class IDKitTests { overrideConnectBaseUrl = null, returnTo = null, environment = Environment.STAGING, + connectUrlMode = ConnectUrlMode.DEFAULT, ) // TODO: Re-enable when World ID 4.0 is live @@ -291,11 +296,25 @@ class IDKitTests { assertTrue(doc is Preset.DocumentLegacy) assertTrue(device is Preset.DeviceLegacy) assertTrue(face is Preset.SelfieCheckLegacy) - assertEquals("x", (orb as Preset.OrbLegacy).signal) - assertEquals("y", (secureDoc as Preset.SecureDocumentLegacy).signal) - assertEquals("z", (doc as Preset.DocumentLegacy).signal) - assertEquals("d", (device as Preset.DeviceLegacy).signal) - assertEquals("f", (face as Preset.SelfieCheckLegacy).signal) + assertEquals("x", (orb).signal) + assertEquals("y", (secureDoc).signal) + assertEquals("z", (doc).signal) + assertEquals("d", (device).signal) + assertEquals("f", (face).signal) + } + + @Test + fun `identityCheck helper exposes canonical preset`() { + val attributes = listOf( + IdentityAttribute.MinimumAge(21u), + IdentityAttribute.Nationality("JPN"), + IdentityAttribute.DocumentType(DocumentType.PASSPORT), + ) + + val preset = identityCheck(attributes = attributes) + + assertTrue(preset is Preset.IdentityCheck) + assertEquals(attributes, preset.attributes) } @Test diff --git a/scripts/build-kotlin.sh b/scripts/build-kotlin.sh index 6dc13321..ade12e5e 100755 --- a/scripts/build-kotlin.sh +++ b/scripts/build-kotlin.sh @@ -20,6 +20,9 @@ esac HOST_LIB="$PROJECT_ROOT/target/release/libidkit.$LIB_EXT" +echo "๐ŸŽฏ Installing Android Rust targets" +rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android >/dev/null + echo "๐Ÿ”ง Building Rust library (host) for binding generation" CARGO_PROFILE_RELEASE_STRIP=none cargo build --package idkit-core --release --locked --features uniffi-bindings @@ -32,8 +35,12 @@ CARGO_PROFILE_RELEASE_STRIP=none cargo run -p uniffi-bindgen generate \ if [ -n "${CI:-}" ]; then echo "๐Ÿงน Cleaning host build artifacts to free disk space for Android builds" + # Preserve the host library โ€” JVM unit tests need it via jna.library.path + cp "$HOST_LIB" "/tmp/libidkit.$LIB_EXT" cargo clean --package idkit-core --release || true rm -rf ~/.cargo/registry/cache || true + mkdir -p "$(dirname "$HOST_LIB")" + mv "/tmp/libidkit.$LIB_EXT" "$HOST_LIB" fi echo "๐Ÿค– Building Android ABIs"