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
38 changes: 36 additions & 2 deletions .github/workflows/pr-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,20 +140,54 @@ jobs:
java-version: '17'
distribution: 'temurin'

# The sample lives in thunder-id/samples; fetch it and build it against this checkout.
- name: 📥 Fetch Quickstart Sample
run: tests/e2e/scripts/prepare-sample.sh

- name: 🗄️ Cache Gradle Packages
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-sample-${{ hashFiles('samples/quickstart/**/*.gradle.kts', 'samples/quickstart/**/gradle-wrapper.properties') }}
key: ${{ runner.os }}-gradle-sample-${{ hashFiles('*.gradle.kts', 'tests/e2e/.samples/quickstarts/android/**/*.gradle.kts', 'tests/e2e/.samples/quickstarts/android/**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-sample-

- name: 🔨 Build Quickstart Sample
working-directory: samples/quickstart
working-directory: tests/e2e/.samples/quickstarts/android
run: ./gradlew build -x test

build-playground:
name: 🛠️ Build Playground
if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: ☕ Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4
with:
java-version: '17'
distribution: 'temurin'

- name: 🗄️ Cache Gradle Packages
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-playground-${{ hashFiles('*.gradle.kts', 'playground/*.gradle.kts', 'playground/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-playground-

# The playground builds the SDK from this checkout through includeBuild("..").
- name: 🔨 Lint and Build Playground
working-directory: playground
run: ./gradlew ktlintCheck assembleDebug

e2e:
name: 🎭 E2E Tests
if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }}
Expand Down
46 changes: 0 additions & 46 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ jobs:
needs: [validate]
if: needs.validate.result == 'success'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.bump.outputs.version }}
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
Expand Down Expand Up @@ -117,47 +115,3 @@ jobs:
gh release create "v${VERSION}" \
--title "ThunderID Android SDK v${VERSION}" \
--generate-notes

package-quickstart:
name: 📦 Package Quickstart Sample
needs: [release]
runs-on: ubuntu-latest
env:
VERSION: ${{ needs.release.outputs.version }}
steps:
- name: 📥 Checkout Tag
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: "v${{ needs.release.outputs.version }}"
persist-credentials: false

- name: 📦 Extract Quickstart Sample
run: git archive "v${VERSION}" samples/quickstart | tar -x -C /tmp

- name: 🔗 Detach Sample from Local SDK Source
working-directory: /tmp/samples/quickstart
run: |
sed -i '/includeBuild("\.\.\/\.\.")\s*{\s*name = "android"\s*}/d' settings.gradle.kts
perl -0777 -pi -e 's/(RepositoriesMode\.FAIL_ON_PROJECT_REPOS\)\n\s*repositories \{)/$1\n maven("https:\/\/jitpack.io")/' settings.gradle.kts
sed -i "s#implementation(\"dev.thunderid:android\")#implementation(\"com.github.thunder-id:android-sdks:v${VERSION}\")#" build.gradle.kts
grep -q "com.github.thunder-id:android-sdks:v${VERSION}" build.gradle.kts
grep -qF 'maven("https://jitpack.io")' settings.gradle.kts
! grep -q "includeBuild" settings.gradle.kts

- name: 🚫 Reject Symlinks
working-directory: /tmp/samples/quickstart
run: |
if find . -type l | grep -q .; then
echo "Symbolic links found in the sample tree — refusing to archive." >&2
find . -type l -exec echo " {}" \;
exit 1
fi

- name: 🗜️ Zip Sample
working-directory: /tmp/samples
run: mv quickstart android-quickstart && zip -r "android-quickstart-v${VERSION}.zip" android-quickstart

- name: 🚀 Upload to Release
env:
GH_TOKEN: ${{ secrets.THUNDERID_AUTOMATION_BOT }}
run: gh release upload "v${VERSION}" "/tmp/samples/android-quickstart-v${VERSION}.zip"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ google-services.json

# Maestro JUnit report from the E2E suite.
tests/e2e/report.xml

# Samples repository checkout fetched by tests/e2e/scripts/prepare-sample.sh
tests/e2e/.samples/
14 changes: 11 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Project overview

Kotlin Android library providing the ThunderID authentication SDK (`dev.thunderid.android`) and a Jetpack Compose component library (`dev.thunderid.compose`). The `samples/quickstart` directory contains a standalone demo app.
Kotlin Android library providing the ThunderID authentication SDK (`dev.thunderid.android`) and a Jetpack Compose component library (`dev.thunderid.compose`). The Quickstart demo app lives in the samples repository at [`quickstarts/android`](https://github.com/thunder-id/samples/tree/main/quickstarts/android). The `playground/` directory holds a development app that exercises every SDK capability against the SDK built from source.

## SDK development specification

Expand Down Expand Up @@ -45,8 +45,15 @@ The SDK is white-labelable: a consuming app can override the brand/vendor namesp

# Run lint and ktlint checks (must pass before any PR)
./gradlew lint ktlintCheck

# Lint and build the playground against the local SDK (see playground/README.md)
cd playground && ./gradlew ktlintCheck assembleDebug
```

When you add or change an SDK capability or Compose component, update the playground's checks
(`playground/src/main/kotlin/dev/thunderid/playground/Checks.kt`) or component stories
(`Stories.kt`) to cover it.

## Ktlint compliance (required)

All code **must pass `ktlintCheck` with zero violations**. The CI `lint` job runs this check on every PR and treats any violation as a build failure.
Expand Down Expand Up @@ -91,7 +98,8 @@ src/main/kotlin/dev/thunderid/
user/ User, UserDropdown, UserProfile
i18n/ Localization (ThunderI18n, DefaultStrings)
src/test/kotlin/ Unit tests
samples/quickstart/ Demo app (not part of the SDK)
playground/ Development app (checks, component explorer, session inspector); builds the SDK via includeBuild
tests/e2e/ Maestro E2E suite (fetches the Quickstart sample from thunder-id/samples)
build.gradle.kts SDK library build config
settings.gradle.kts Project settings
```
Expand All @@ -100,7 +108,7 @@ settings.gradle.kts Project settings

- **Layer 1–2** (`dev.thunderid.android`): platform SDK — HTTP, token management, auth flows, PKCE, storage.
- **Layer 3** (`dev.thunderid.compose`): Compose UI wrappers around the platform SDK.
- **Layer 4** (`samples/quickstart`): standalone demo app that depends on the SDK via `includeBuild`.
- **Layer 4** ([`quickstarts/android`](https://github.com/thunder-id/samples/tree/main/quickstarts/android) in thunder-id/samples): standalone demo app that depends on the published SDK from JitPack. The E2E suite and the PR builder fetch it with `tests/e2e/scripts/prepare-sample.sh`, which points it at the local checkout via `includeBuild`.

Every Compose component ships in two variants:
- **Styled** — opinionated Material 3 defaults (e.g. `SignInButton`).
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Android SDK for ThunderID. Provides authentication and user management for nativ

- [Quickstart](https://thunderid.dev/docs/next/getting-started/connect-your-application/android/)
- [API reference](https://thunderid.dev/docs/next/sdks/android/overview/)
- [Sample app](https://github.com/thunder-id/samples/tree/main/quickstarts/android)

## Installation

Expand Down Expand Up @@ -38,6 +39,12 @@ dependencies {
> [!NOTE]
> Replace `<latest-release-tag>` with the [latest release tag](https://github.com/thunder-id/android-sdks/releases) of the `android-sdks` repository.

## Playground

The [`playground`](playground) directory contains a Jetpack Compose app that builds this SDK from source and exercises
every capability: capability checks, a component explorer, and a session inspector. See the
[playground README](playground/README.md) to run it against a local ThunderID server.

## Contributing

Please refer to the [Contributing Guide](https://thunderid.dev/community/overview) for the different ways to contribute to this project and the relevant guidelines.
Expand Down
4 changes: 0 additions & 4 deletions samples/quickstart/.gitignore → playground/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ local.properties
# Local build config (copy config.properties.example and fill in your values)
config.properties

# Local Digital Asset Links file (copy assetlinks.json.example, fill in your values, and
# host it at https://<your-domain>/.well-known/assetlinks.json — not served from this app)
assetlinks.json

# Log/OS Files
*.log
.DS_Store
Expand Down
65 changes: 65 additions & 0 deletions playground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# ThunderID Android Playground

A Jetpack Compose app for exercising every capability of the ThunderID Android SDK from source.
It builds the SDK in this repository through `includeBuild("..")`, so changes to `src/` show up the
next time you run it. It is a development tool, not a sample: for a minimal integration, see the
[Android Quickstart](https://github.com/thunder-id/samples/tree/main/quickstarts/android).

## What it shows

- **Checks**: capability checks that call the real SDK and report pass or fail with a reason.
Sign-in through the system browser (Custom Tabs), app-native sign-in (`SignIn`), passkeys
(Credential Manager), sign-out, the redirect callback, token refresh, secure token storage,
user info, theming, and translations. Capabilities the Android SDK has no API for (biometric
unlock, organizations) are shown as unavailable. "Run all checks" runs everything that does not
need interaction; the browser sign-in, app-native sign-in, and sign-out checks run on tap. The
summary card also probes the server's `/health/readiness` endpoint.
- **Components**: an explorer for the SDK's Compose components. Pick a component, switch stories,
edit props in Controls, toggle a light or dark preview, and copy the Compose snippet generated
from the current props in Code.
- **Session**: the signed-in user, an access token countdown, refresh and sign-out, the stored
tokens (reveal and copy), the decoded access token claims, and session events.
- **Logs**: everything the playground observed from the SDK: auth state changes, storage writes
(key names only), redirects, and check results.

## Prerequisites

- JDK 17 and the Android SDK (API 34). Set `ANDROID_HOME`, or add `sdk.dir` to a
`local.properties` file in this directory.
- A running ThunderID server. See [Get started](https://thunderid.dev/docs/next/getting-started/)
to run one locally at `https://localhost:8090`.

## Configure

1. In the ThunderID Console, create a mobile application. Note its application ID and OAuth client
ID, and register `dev.thunderid.playground://callback` as a redirect URI.
2. Copy the example config and fill in your values:

```bash
cp config.properties.example config.properties
```

| Key | Description |
|---|---|
| `THUNDERID_BASE_URL` | ThunderID server URL. The emulator reaches your machine's `localhost` at `10.0.2.2`, so a local server is `https://10.0.2.2:8090`. |
| `THUNDERID_APPLICATION_ID` | Application ID for the app-native flow. |
| `THUNDERID_CLIENT_ID` | OAuth client ID for the browser sign-in and token refresh. |
| `THUNDERID_AFTER_SIGN_IN_URL` | Sign-in redirect URI. Its scheme is registered on the app, so changing it here is enough. |
| `THUNDERID_AFTER_SIGN_OUT_URL` | Sign-out redirect URI. |
| `THUNDERID_SCOPES` | Space separated scopes. Defaults to `openid profile email`. |

`config.properties` is gitignored. Every key can also come from an environment variable of the
same name.

Debug builds accept the local server's self-signed certificate. The browser sign-in opens the server
in a Custom Tab, which shows a certificate warning for a self-signed certificate; continue past it,
or install the certificate on the emulator.

## Run

```bash
./gradlew installDebug
```

Or open this directory in Android Studio and run it. Without a configured
server the app still starts: checks that need the server fail with the reason.
84 changes: 84 additions & 0 deletions playground/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import java.util.Properties

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jlleitschuh.gradle.ktlint")
}

// ThunderID settings come from config.properties (gitignored) or the environment.
// Copy config.properties.example to config.properties and fill in your values.
val configProps =
Properties().apply {
val file = rootProject.file("config.properties")
if (file.exists()) file.inputStream().use { load(it) }
}

fun config(
key: String,
default: String = "",
): String = configProps.getProperty(key) ?: System.getenv(key) ?: default

val afterSignInUrl = config("THUNDERID_AFTER_SIGN_IN_URL", "dev.thunderid.playground://callback")

android {
namespace = "dev.thunderid.playground"
compileSdk = 34

defaultConfig {
applicationId = "dev.thunderid.playground"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0.0"

buildConfigField("String", "THUNDERID_BASE_URL", "\"${config("THUNDERID_BASE_URL")}\"")
buildConfigField("String", "THUNDERID_APPLICATION_ID", "\"${config("THUNDERID_APPLICATION_ID")}\"")
buildConfigField("String", "THUNDERID_CLIENT_ID", "\"${config("THUNDERID_CLIENT_ID")}\"")
buildConfigField("String", "THUNDERID_AFTER_SIGN_IN_URL", "\"$afterSignInUrl\"")
buildConfigField(
"String",
"THUNDERID_AFTER_SIGN_OUT_URL",
"\"${config("THUNDERID_AFTER_SIGN_OUT_URL", "dev.thunderid.playground://signed-out")}\"",
)
buildConfigField("String", "THUNDERID_SCOPES", "\"${config("THUNDERID_SCOPES", "openid profile email")}\"")

// The redirect URI's scheme is registered on MainActivity, so the callback lands back here.
manifestPlaceholders["redirectScheme"] = afterSignInUrl.substringBefore("://")
}

buildFeatures {
compose = true
buildConfig = true
}

composeOptions {
kotlinCompilerExtensionVersion = "1.5.8"
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = "17"
}
}

ktlint {
version.set("1.8.0")
}

dependencies {
implementation("dev.thunderid:android")

val composeBom = platform("androidx.compose:compose-bom:2024.02.00")
implementation(composeBom)
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
}
19 changes: 19 additions & 0 deletions playground/config.properties.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copy to config.properties (gitignored) and fill in the values for your ThunderID server.
# Every key can also be supplied as an environment variable of the same name.

# ThunderID server. The Android emulator reaches the host machine's localhost at 10.0.2.2.
THUNDERID_BASE_URL=https://10.0.2.2:8090

# Application ID, used by the app-native flow (SignIn, SignUp, flow execution).
THUNDERID_APPLICATION_ID=your-application-id

# OAuth2 client ID, used by the system browser (Custom Tabs) sign-in and token refresh.
THUNDERID_CLIENT_ID=your-client-id

# Redirect URIs. Register the sign-in redirect URI on the application in ThunderID.
# The URI scheme is wired into the manifest, so a custom scheme here is all you need to change.
THUNDERID_AFTER_SIGN_IN_URL=dev.thunderid.playground://callback
THUNDERID_AFTER_SIGN_OUT_URL=dev.thunderid.playground://signed-out

# Space separated OAuth2 scopes.
THUNDERID_SCOPES=openid profile email
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
android.useAndroidX=true
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError
File renamed without changes.
Loading
Loading