From 28de7be6d622ae4f295494da9ebe17c97f851cb5 Mon Sep 17 00:00:00 2001 From: Brion Date: Sun, 12 Jul 2026 00:18:24 +0530 Subject: [PATCH 1/5] Introduce vendor namespace support --- .coderabbit.yaml | 36 +++++++++++++++++++ AGENTS.md | 9 +++++ .../flutter/ThunderIDMethodHandler.kt | 5 +-- ios/Classes/ThunderIDMethodHandler.swift | 3 +- lib/src/models/thunderid_config.dart | 10 ++++++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..b1e7bc4 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,36 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +language: en-US + +reviews: + request_changes_workflow: false + high_level_summary: true + poem: false + + path_instructions: + - path: "lib/**/*.dart" + instructions: | + Flag any hardcoded occurrence of the literal `thunderid`/`ThunderID`/`THUNDERID` (any casing) used to + build a runtime key or name โ€” platform-channel storage keys, log tags, and similar. + + Do not flag entry point file/package names (e.g. `thunderid_flutter.dart`, `thunderid_client.dart`, + `thunderid_provider.dart`) or types whose purpose IS to represent the SDK itself (e.g. + `ThunderIDClient`, `ThunderIDProvider`). That's a fixed identity, not a per-tenant value. + + For everything else, ask: **"Should this read from `ThunderIDConfig.vendor` instead of hardcoding the + vendor name?"** The best fix is avoiding the vendor name entirely when the brand prefix isn't + load-bearing. When a brand-scoped namespace is genuinely required, it should resolve through + `config.vendor` (which already defaults to `'thunderid'`) rather than a literal string. If the same + default-resolution logic starts appearing in more than one place, ask for it to be extracted into a + shared helper instead of repeated inline. + + auto_review: + enabled: true + ignore_title_keywords: + - "WIP" + - "DO NOT MERGE" + base_branches: + - main + + path_filters: + - "!**/build/**" + - "!**/.dart_tool/**" diff --git a/AGENTS.md b/AGENTS.md index 340c36e..d2547d9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,15 @@ Flutter plugin providing the ThunderID authentication SDK (`thunderid_flutter`). Bridges to native iOS (`ThunderID` Swift SDK) and Android (`dev.thunderid:android`) via a `MethodChannel`. The `samples/quickstart` directory contains a standalone demo app. +## Vendor naming rules + +The SDK is white-labelable: a consuming app can override the brand/vendor namespace via `ThunderIDConfig.vendor`, so storage keys, log tags, and similar runtime names shouldn't be pinned to one brand. + +- Do not hardcode the literal `thunderid`/`ThunderID`/`THUNDERID` (any casing) when building a runtime key/name that a consumer's `vendor` override should control โ€” platform-channel storage keys, log tags, and the like. +- It's fine for **entry point file/package names** (e.g. `thunderid_flutter.dart`, `thunderid_client.dart`, `thunderid_provider.dart`) and types whose purpose IS to represent the SDK itself (e.g. `ThunderIDClient`, `ThunderIDProvider`) to carry the name. That's a fixed identity, not a per-tenant value. Don't flag those. +- Avoiding the vendor name entirely is the best outcome, when the brand prefix isn't actually load-bearing. +- When a brand-scoped namespace is genuinely required, resolve it from `config.vendor` (which already defaults to `'thunderid'`) instead of hardcoding a literal. If the same default-resolution logic starts appearing in more than one place, extract a small shared helper rather than repeating the literal default. Remember this may need bridging through the platform channel since this package delegates to native iOS/Android SDKs. + ## Build & test ```bash diff --git a/android/src/main/kotlin/dev/thunderid/flutter/ThunderIDMethodHandler.kt b/android/src/main/kotlin/dev/thunderid/flutter/ThunderIDMethodHandler.kt index c1eb2a9..2d6a77d 100644 --- a/android/src/main/kotlin/dev/thunderid/flutter/ThunderIDMethodHandler.kt +++ b/android/src/main/kotlin/dev/thunderid/flutter/ThunderIDMethodHandler.kt @@ -17,7 +17,7 @@ class ThunderIDMethodHandler(private val context: Context) { when (method) { "initialize" -> { val config = buildConfig(args) - val storage = EncryptedStorageAdapter(context) + val storage = EncryptedStorageAdapter(context, prefsName = "dev.${config.vendor}.sdk.prefs") result.success(client.initialize(config, storage)) } "reInitialize" -> { @@ -120,7 +120,8 @@ class ThunderIDMethodHandler(private val context: Context) { afterSignInUrl = args["afterSignInUrl"] as? String, afterSignOutUrl = args["afterSignOutUrl"] as? String, applicationId = args["applicationId"] as? String, - tokenValidation = validation + tokenValidation = validation, + vendor = args["vendor"] as? String ?: ThunderIDConfig.DEFAULT_VENDOR ) } diff --git a/ios/Classes/ThunderIDMethodHandler.swift b/ios/Classes/ThunderIDMethodHandler.swift index 8758900..ade0b84 100644 --- a/ios/Classes/ThunderIDMethodHandler.swift +++ b/ios/Classes/ThunderIDMethodHandler.swift @@ -127,7 +127,8 @@ final class ThunderIDMethodHandler { afterSignInUrl: args["afterSignInUrl"] as? String, afterSignOutUrl: args["afterSignOutUrl"] as? String, applicationId: args["applicationId"] as? String, - tokenValidation: validation + tokenValidation: validation, + vendor: args["vendor"] as? String ?? VendorConstants.vendorPrefix ) } diff --git a/lib/src/models/thunderid_config.dart b/lib/src/models/thunderid_config.dart index 029220f..f77c25c 100644 --- a/lib/src/models/thunderid_config.dart +++ b/lib/src/models/thunderid_config.dart @@ -20,6 +20,9 @@ import 'preferences.dart'; /// Configuration for the ThunderID Flutter SDK (spec ยง5.2). class ThunderIDConfig { + /// Default vendor/brand namespace used when [vendor] is not overridden. + static const String defaultVendor = 'thunderid'; + // Core final String baseUrl; final String? clientId; @@ -46,6 +49,11 @@ class ThunderIDConfig { // UI Preferences (theme + i18n) โ€” ignored by the protocol layer final ThunderIDPreferences? preferences; + /// Vendor/brand namespace used by the native SDK layer to derive default storage identifiers. + /// Override this when white-labeling the SDK under a different brand. Defaults to + /// [defaultVendor]. + final String vendor; + const ThunderIDConfig({ required this.baseUrl, this.clientId, @@ -61,6 +69,7 @@ class ThunderIDConfig { this.organizationHandle, this.tokenValidation = const TokenValidationConfig(), this.preferences, + this.vendor = defaultVendor, }); Map toMap() => { @@ -78,6 +87,7 @@ class ThunderIDConfig { if (organizationHandle != null) 'organizationHandle': organizationHandle, 'tokenValidation': tokenValidation.toMap(), if (preferences != null) 'preferences': preferences!.toMap(), + 'vendor': vendor, }; } From fde48243e0a4867531a95f7bbd76b7d648a2c5aa Mon Sep 17 00:00:00 2001 From: Brion Date: Sun, 12 Jul 2026 00:33:30 +0530 Subject: [PATCH 2/5] fix(android): bump AGP to 8.11.1 to meet Flutter's minimum supported version CI was failing quickstart apk builds because AGP 8.2.2 is below Flutter's current minimum supported AGP version of 8.6.0. --- android/build.gradle | 2 +- samples/quickstart/android/settings.gradle.kts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index c8260db..36e99bd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.2.2' + classpath 'com.android.tools.build:gradle:8.11.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/samples/quickstart/android/settings.gradle.kts b/samples/quickstart/android/settings.gradle.kts index 9d55230..014247f 100644 --- a/samples/quickstart/android/settings.gradle.kts +++ b/samples/quickstart/android/settings.gradle.kts @@ -19,8 +19,8 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.2.2" apply false - id("org.jetbrains.kotlin.android") version "1.9.22" apply false + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false } includeBuild("../../../android") { From 8edbc1f5f3022d9c2fe2e255685df00ccac00ea0 Mon Sep 17 00:00:00 2001 From: Brion Date: Sun, 12 Jul 2026 00:44:29 +0530 Subject: [PATCH 3/5] fix(android): declare repositories for the android module's own dependency resolution Composite builds don't inherit repositories from the including build; without a project-level repositories block, Gradle can't resolve kotlin-stdlib, flutter_embedding_debug, or kotlinx-coroutines-android. --- android/build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/build.gradle b/android/build.gradle index 36e99bd..e01d336 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -13,6 +13,11 @@ buildscript { } } +repositories { + google() + mavenCentral() +} + apply plugin: 'com.android.library' apply plugin: 'kotlin-android' From 521b6a77dd9e42c3fe0da4999e9ebbcc705a44bf Mon Sep 17 00:00:00 2001 From: Brion Date: Sun, 12 Jul 2026 00:49:06 +0530 Subject: [PATCH 4/5] fix(android): add Flutter engine artifacts repository io.flutter:flutter_embedding_debug is published only to Flutter's own storage.googleapis.com/download.flutter.io maven repo, which is normally auto-added for registered Flutter plugin projects but not for the android module here since it's wired in via a manual includeBuild substitution. --- android/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android/build.gradle b/android/build.gradle index e01d336..350737b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -16,6 +16,9 @@ buildscript { repositories { google() mavenCentral() + maven { + url 'https://storage.googleapis.com/download.flutter.io' + } } apply plugin: 'com.android.library' From 382ecff24ad7581a29b7e4b67ce459b9662d11b8 Mon Sep 17 00:00:00 2001 From: Brion Date: Sun, 12 Jul 2026 01:13:52 +0530 Subject: [PATCH 5/5] fix(android): resolve native SDK via JitPack, drop self-referential composite build The quickstart's includeBuild("../../../android") substituted dev.thunderid:android with project(":") of its own composite build, which resolves to this same android/ module and created a circular dependency (:android depending on itself). dev.thunderid:android was also never actually published to any repository. Point the dependency at the real released artifact via JitPack (com.github.thunder-id:android-sdks:v0.1.0) instead, and drop the broken includeBuild/substitution. Also add the missing android.useAndroidX=true gradle.properties for the android module, since composite/included builds don't inherit properties from the including project. --- android/build.gradle | 24 ++++++++++++++++--- android/gradle.properties | 1 + .../quickstart/android/settings.gradle.kts | 7 ------ 3 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 android/gradle.properties diff --git a/android/build.gradle b/android/build.gradle index 350737b..a697c70 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,7 +2,7 @@ group 'dev.thunderid.flutter' version '0.0.0' buildscript { - ext.kotlin_version = '1.9.22' + ext.kotlin_version = '2.2.20' repositories { google() mavenCentral() @@ -19,6 +19,9 @@ repositories { maven { url 'https://storage.googleapis.com/download.flutter.io' } + maven { + url 'https://jitpack.io' + } } apply plugin: 'com.android.library' @@ -42,8 +45,23 @@ android { } } +def flutterSdkPath() { + def envRoot = System.getenv('FLUTTER_ROOT') + if (envRoot != null) { + return envRoot + } + def localPropertiesFile = new File(rootDir, '../samples/quickstart/android/local.properties') + def properties = new Properties() + localPropertiesFile.withInputStream { properties.load(it) } + def sdkPath = properties.getProperty('flutter.sdk') + assert sdkPath != null, "flutter.sdk not set in local.properties" + return sdkPath +} + +def flutterEngineVersion = new File(flutterSdkPath(), 'bin/cache/engine.stamp').text.trim() + dependencies { - compileOnly 'io.flutter:flutter_embedding_debug:1.0.0-...' - implementation 'dev.thunderid:android:0.0.0' + compileOnly "io.flutter:flutter_embedding_debug:1.0.0-$flutterEngineVersion" + implementation 'com.github.thunder-id:android-sdks:v0.1.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' } diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..5bac8ac --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/samples/quickstart/android/settings.gradle.kts b/samples/quickstart/android/settings.gradle.kts index 014247f..ca7fe06 100644 --- a/samples/quickstart/android/settings.gradle.kts +++ b/samples/quickstart/android/settings.gradle.kts @@ -23,11 +23,4 @@ plugins { id("org.jetbrains.kotlin.android") version "2.2.20" apply false } -includeBuild("../../../android") { - name = "android" - dependencySubstitution { - substitute(module("dev.thunderid:android")).using(project(":")) - } -} - include(":app")