diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..4b65aed4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# CI checks `dart format --set-exit-if-changed` on a Windows runner as well as +# on Linux. The formatter emits LF, so a checkout that converted line endings +# would fail that step — and would give contributors on Windows with +# core.autocrlf=true a working tree that never looks formatted. +*.dart text eol=lf + +# Same reasoning for the files the build reads on more than one platform. +*.gradle text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.swift text eol=lf +*.podspec text eol=lf diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index cb843094..9b3f3a61 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -1,51 +1,53 @@ name: Dart CI -on: [push] +on: + push: + pull_request: jobs: test: - - runs-on: ubuntu-22.04 - - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - uses: subosito/flutter-action@v1 - with: - channel: 'stable' # or: 'dev' or 'beta' - - name: Install dependencies - run: flutter pub get - - name: Run tests - run: flutter test - - web: - runs-on: windows-latest + # Windows is not incidental: the win32 bindings are compiled on every + # `dart.library.io` platform, so a `package:win32` break shows up as a + # broken iOS build. Running the suite on both catches it either way. + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: - channel: 'stable' # or: 'dev' or 'beta' + channel: 'stable' + - name: Install dependencies + run: flutter pub get + - name: Verify formatting + run: dart format --output=none --set-exit-if-changed . + - name: Analyze + run: flutter analyze --fatal-infos + - name: Run tests + run: flutter test + # The example is a separate package: neither analyze nor test reaches it + # from the repository root, which is how its widget test sat broken + # against a UI it had never matched. + - name: Analyze the example + run: cd example && flutter analyze --fatal-infos + - name: Test the example + run: cd example && flutter test build: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - buildcommand: [linux] - os: [ubuntu-22.04] - exclude: - - os: ubuntu-22.04 - buildcommand: linux include: -# No macos build for now, have to configure code signing.. -# - buildcommand: macos -# os: macos-latest - buildcommand: ios buildargs: --no-codesign os: macos-latest + # No macos build: the example's keychain-access-groups entitlement + # resolves $(AppIdentifierPrefix) from the signing team, so the Runner + # cannot be built without a development certificate. - buildcommand: windows os: windows-latest - buildcommand: linux @@ -55,26 +57,27 @@ jobs: - buildcommand: web os: ubuntu-latest - steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: - channel: 'stable' # or: 'dev' or 'beta' - - uses: actions/setup-java@v3 + channel: 'stable' + - uses: actions/setup-java@v4 if: matrix.buildcommand == 'appbundle' with: java-version: '17' - distribution: 'oracle' - - name: Patch for linux build + distribution: 'temurin' + - name: Install linux build dependencies if: matrix.buildcommand == 'linux' run: | - flutter doctor sudo apt-get update -y sudo apt-get install -y ninja-build libgtk-3-dev libsecret-1-dev - flutter doctor - - name: Enable build - if: matrix.buildcommand == 'macos' || matrix.buildcommand == 'linux' - run: flutter config --enable-${{ matrix.buildcommand }}-desktop - name: Build for ${{ matrix.buildcommand }} run: cd example && flutter build ${{ matrix.buildcommand }} ${{ matrix.buildargs }} + - name: Fail if CocoaPods was pulled back in + if: matrix.buildcommand == 'ios' + run: | + if [ -e "example/ios/Podfile" ]; then + echo "A Podfile was generated: the Swift Package Manager support regressed." >&2 + exit 1 + fi diff --git a/.gitignore b/.gitignore index a610b538..7b8916cb 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,11 @@ example/android/app/_debug-full-r8-config.txt .gradletasknamecache +# Swift Package Manager leaves per-user Xcode state beside Package.swift. +.swiftpm/ + +# A dependency's Package.resolved is never read — only the root package's is, +# and here that is the app's FlutterGeneratedPluginSwiftPackage. Anchored, so +# that an app-level one under example/*/Runner.xcodeproj stays committable. +/darwin/biometric_storage/Package.resolved + diff --git a/.pubignore b/.pubignore new file mode 100644 index 00000000..1460f5c0 --- /dev/null +++ b/.pubignore @@ -0,0 +1,40 @@ +# Consulted by `pub publish` INSTEAD OF the .gitignore in THIS directory. Nested +# .gitignore files are still honoured — example/ios/.gitignore and friends keep +# working — so only the root .gitignore's rules have to be restated here, and +# they are, below. Getting that wrong is not theoretical: a one-line version of +# this file dropped the root rules and shipped .dart_tool artifacts. +# +# The check that matters, and it has to be run against a DIRTY tree — after a +# build, a pod install and a dartdoc run, not a clean checkout: +# flutter pub publish --dry-run +# The archive must differ from the .gitignore-only one by CLAUDE.md alone. + +# The reason this file exists: maintainer-facing instructions, of no use to +# anyone consuming the package. +CLAUDE.md + +# Build output. Unanchored so it also catches example/build. +build/ +out/ +.dart_tool/ +.gradle/ +.gradletasknamecache +.flutter-plugins-dependencies +.packages +.pub/ +node_modules/ +example/android/app/_debug-full-r8-config.txt + +# dartdoc output. Not in .gitignore either, and it lands next to the committed +# doc/screenshot_ios.png as thousands of files. +doc/api/ + +# Editor and OS noise. +.idea/ +.vscode/ +*.iml +.DS_Store + +# Swift Package Manager per-user state. +.swiftpm/ +/darwin/biometric_storage/Package.resolved diff --git a/CHANGELOG.md b/CHANGELOG.md index ead5a7b8..2e11543d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,54 @@ +## 6.0.0-dev.1 + +**Breaking**: requires Dart 3.10 / Flutter 3.44 or newer. + +* Support `package:win32` 6.x, and drop 5.x and older. Every 5.x release of this + plugin pinned `win32 >=2.0.0 <6.0.0`, which made it unresolvable alongside + `package_info_plus >=10.1.0` and anything else on win32 6. The Windows + implementation is compiled on every `dart.library.io` platform, so win32 6 + removing `TEXT()` also broke iOS and macOS builds — the test suite now imports + the public barrel so `flutter test` compiles the win32 bindings on any host. +* windows: writing an empty value no longer throws. +* windows: fix a use-after-free in `read()`. `CredentialBlob.asTypedList()` is a + view onto memory owned by the credential, and it was decoded *after* `CredFree` + had released it. The bytes are now copied out first. Present since 1.1.0, + where a9e3944 moved the `CredFree` call in between the two. +* windows: the bindings now have runtime coverage, not just compile coverage — + `test/biometric_storage_win32_test.dart` exercises write/read/delete against + the real credential store, and runs on the Windows CI job. +* iOS/macOS: Swift Package Manager support. Adding this plugin to an app that has + migrated to SwiftPM no longer regenerates a `Podfile`. CocoaPods keeps working; + both a `Package.swift` and a podspec are shipped. +* iOS/macOS: the Swift sources moved to `darwin/` and are shared through + `sharedDarwinSource`, replacing the symlink from `ios/Classes`. The macOS + plugin class is now `BiometricStoragePlugin` (was + `BiometricStorageMacOSPlugin`) and the Objective-C shim on iOS is gone. Neither + is referenced from Dart, so this is only visible in a hand-written registrant. +* android: AGP 8.13, Kotlin 2.2, compileSdk 36, `androidx.biometric` + 1.4.0-alpha05, `core-ktx` 1.18.0, `fragment-ktx` 1.9.0, slf4j 2.0.18 and + kotlin-logging 8. The plugin no longer applies the Kotlin Gradle Plugin itself + — AGP 9 warns about that and future Flutter releases reject it. +* android: `canAuthenticate()` no longer throws on a status code the plugin does + not know about — Android 16 added `BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS` (21) + and every call blew up. Unmapped codes are reported as + `CanAuthenticateResponse.statusUnknown` and logged. + https://github.com/authpass/biometric_storage/issues/148 +* android: the plugin no longer calls `jvmToolchain`, which failed to resolve in + some consumer builds. https://github.com/authpass/biometric_storage/issues/107 +* Document the Android `FlutterFragmentActivity` and `Theme.AppCompat` + requirements as what they actually are: both only apply to storage that shows + an authentication prompt, and the theme only where `androidx.biometric` falls + back to its own dialog, which is below API 28 rather than below API 29. +* web: the package is now WebAssembly-ready. `lib/src/biometric_storage.dart` + imported `dart:io` unconditionally for `Platform`, which marked the whole + package wasm-incompatible; the host OS now comes through a conditional import, + with `dart:io` still doing the work everywhere it exists. No behaviour change + on any platform. https://github.com/authpass/biometric_storage/issues/145 +* iOS/macOS: the prompt strings (`IosPromptInfo.saveTitle` / `accessTitle`) now + travel on the `LAContext` as `localizedReason` instead of through + `kSecUseOperationPrompt`, deprecated since iOS 14 / macOS 11. Same prompts, no + deprecation warnings on build. + ## 5.2.0-dev.1 * iOS/macOS: `StorageFileInitOptions.darwinKeychainAccessGroup` to store items diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..04d70170 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,181 @@ +# biometric_storage + +A Flutter plugin published to pub.dev: an encrypted key/value store, optionally +gated behind a biometric prompt. Android (KeyStore), iOS and macOS (Keychain + +LocalAuthentication), Linux (libsecret), Windows (wincred), Web (localStorage, +unencrypted — say so whenever it comes up). + +The Dart surface is one class, `BiometricStorage`, in +[`lib/src/biometric_storage.dart`](lib/src/biometric_storage.dart). Everything +else is a platform implementation behind it. + +## The one thing that is not obvious + +**The Windows implementation is compiled on every platform that has +`dart.library.io`** — iOS, macOS, Android and Linux included. Two things put it +there, and they are not independent: + +1. `lib/biometric_storage.dart` re-exports `src/biometric_storage_win32.dart` + under `if (dart.library.io)`. +2. Flutter generates `.dart_tool/flutter_build/dart_plugin_registrant.dart` for + *all* platforms at once, not per build target. An iOS release build's copy + contains `import 'package:biometric_storage/biometric_storage.dart'` and a + `Platform.isWindows` branch calling + `Win32BiometricStoragePlugin.registerWith()`. + +So a breaking change in `package:win32` breaks an **iOS** build. That is how +5.x ended up unusable: `win32` 6 removed `TEXT()`, and the error surfaced as a +failing `flutter test` on macOS. + +Dropping the export in (1) is not an escape: the registrant needs +`Win32BiometricStoragePlugin` to be *on* the barrel, so removing it breaks the +compile everywhere instead. Naming the file with `dartFileName:` would make the +registrant import `src/biometric_storage_win32.dart` directly — on every +platform, so nothing is gained. Nor does splitting Windows out into a federated +`biometric_storage_windows` package: the registrant would import that package +instead, on every platform, exactly as before. + +The defence is therefore a compile, not a restructuring. +`test/biometric_storage_test.dart` imports the public barrel rather than `src/` +precisely so that `flutter test` on any host compiles the win32 bindings, and CI +runs the suite on Windows too. Do not "tidy" that import. + +Related: the `windows:` block in `pubspec.yaml` has no `dartFileName`, so the +registrant imports the barrel. `fileName:` is a **web-only** key — it sat under +`windows:` for years and was silently ignored. + +## Layout + +* `darwin/biometric_storage/Sources/biometric_storage/` — the iOS and macOS + Swift sources, shared via `sharedDarwinSource: true` in `pubspec.yaml`. + `BiometricStorageImpl.swift` is platform-agnostic; `BiometricStoragePlugin.swift` + has the `#if os(iOS)` / `#if os(macOS)` split (the registrar's messenger is a + method on iOS and a property on macOS). +* `darwin/biometric_storage/Package.swift` **and** `darwin/biometric_storage.podspec` + describe the same sources. Both must be kept in step — CocoaPods stays + supported until the registry goes read-only, and Flutter picks whichever the + consuming app uses. `Package.swift` must keep its `FlutterFramework` + dependency; without it Flutter warns on every build. +* `android/` — Kotlin, `design.codeux.biometric_storage`. +* `lib/src/biometric_storage_web.dart`, `linux/`, `lib/src/biometric_storage_win32.dart`. +* `example/` — the app the CI builds. Its own `flutter pub get` runs from + `example/`, not the repository root. + +## Commands + +The whole test suite. Fast, and it is what catches a `package:win32` break. + +```bash +flutter test +``` + +`--fatal-infos` is the point: without it the promotions in +`analysis_options.yaml` change nothing at the command line. It covers only the +package you stand in, so run it in `example/` too when that is what changed. + +```bash +flutter analyze --fatal-infos +``` + +Run before committing. + +```bash +dart format lib test example/lib +``` + +## Verifying + +**A green analyze here is an answer about one resolution, not about consumers'.** +`flutter analyze` does flag an undefined win32 symbol in this package's own +sources — but it flags it against whatever `pubspec.yaml` resolves to, so while +the constraint said `win32 <6.0.0` it stayed green for a break that every app +resolving win32 6 would hit. What a version bump is actually verified by is a +build that finished, in an app that has the dependency in question. + +**Gradle and Xcode are never covered by an analyze at all.** A toolchain or +androidx bump is proven by `flutter build` in `example/`, nothing less. + +**Ask what would make this test red.** Green is information only if failure was +reachable. When you change something the win32 guard is supposed to catch, +reintroduce the break once and watch it fail before trusting the pass. + +**A zero exit code is not evidence an edit landed.** Grep for the new state. + +**Prove a plugin actually linked, rather than that the build was quiet.** A +plugin that fails to register produces a perfectly successful build. `nm` the +built binary for the plugin's symbols, or read the generated +`GeneratedPluginRegistrant.swift`. + +**Resolution is proven in a consumer app, not here.** The interesting failures +are version conflicts with packages this repo does not depend on. Generate a +throwaway app in the scratchpad, point it at this checkout by path, add the +package that conflicts, and run `flutter pub get`. + +## Android + +**Never apply the Kotlin Gradle Plugin from `android/build.gradle`.** AGP 9 +brings its own Kotlin support and Flutter warns that plugins applying KGP will +stop building; on AGP 8 Flutter's own Gradle plugin applies `kotlin-android` for +us. Either way it lands *after* this file is evaluated, so `kotlin { }` is not +available at the top level — configure the JVM target inside +`pluginManager.withPlugin('org.jetbrains.kotlin.android') { }`. + +**`androidx.core` is capped by AGP, not by what is newest.** Each androidx AAR +declares a `minCompileSdk`, and AGP refuses a compileSdk above what it supports. +Read `META-INF/com/android/build/gradle/aar-metadata.properties` out of the AAR +before bumping, rather than discovering it from `checkDebugAarMetadata`. + +**Keep the plugin's own AGP classpath behind the app template's.** A plugin that +declares AGP 9 forces every consumer onto Gradle 9. The example app is where the +newest toolchain gets exercised. + +## Documentation + +**Verify a requirement against the code before repeating it.** This package's +README drifted for years: it presented `FlutterFragmentActivity` and a +`Theme.AppCompat` theme as unconditional, when both only apply to storage that +actually shows a prompt — `withAuth` returns immediately when +`authenticationRequired` is false, and `androidx.biometric` only draws its own +AppCompat dialog below API 28. + +**Write American English** — documentation, code comments, commit messages and +identifiers alike. The audience is pub.dev. + +## Shell + +**The shell never writes files.** No `cat > file`, no `sed -i`, no interpreter +heredoc — use Write and Edit. A scripted replacement fails *silently*: +`str.replace` and `sed` both return the input unchanged when the pattern misses, +then write it back and exit zero, where a structured edit raises. The one +carve-out is a mechanical change across five or more places; it still has to +assert the pattern matched, and be verified afterwards. + +**Background anything slow** — a Gradle build, an Xcode build, `pod install`, +a first-run wrapper download. The harness re-invokes you when a backgrounded +command exits, so never pair it with a polling loop; that is the thing that +hangs. + +**`timeout` is not on macOS by default.** `timeout 30 some-command` fails with +"command not found", which reads as though the command itself were missing. Use +the tool's own deadline flag. + +**Temporary files go in the session scratchpad**, never `/tmp` and never the +checkout. + +## Git + +Commit subjects are lower-case declarative sentences, optionally prefixed with +the area they touch — `android: upgrade AGP and androidx dependencies`, +`macos/ios: use options when evaluating canAuthenticate.` No +conventional-commits prefix (the one `feat:` in the history is the exception, +not the rule), no ticket reference. The body says what was wrong, why this is +right, and how it was verified. End with: + +``` +Co-Authored-By: Claude Opus 5 +``` + +`CHANGELOG.md` is part of the change, not a release chore — every user-visible +change gets its line in the same commit. + +**Do not publish to pub.dev.** Releases are the owner's call. diff --git a/README.md b/README.md index d41886c2..38172e83 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ of data. * Android: Uses androidx with KeyStore. * iOS and MacOS: LocalAuthentication with KeyChain. * Linux: Stores values in Keyring using libsecret. (No biometric authentication support). -* Windows: Uses [wincreds.h to store into read/write into credential store](https://docs.microsoft.com/en-us/windows/win32/api/wincred/). +* Windows: Uses [wincred.h to read/write into the credential store](https://docs.microsoft.com/en-us/windows/win32/api/wincred/). * Web: **Warning** Uses unauthenticated, **unencrypted** storage in localStorage. If you have a better idea for secure storage on web platform, [please open an Issue](https://github.com/authpass/biometric_storage/issues). @@ -24,40 +24,56 @@ makes heavy use of this plugin. ### Installation #### Android -* Requirements: - * Android: API Level >= 23 (android/app/build.gradle `minSdkVersion 23`) - * Make sure to use the latest kotlin version: - * `android/build.gradle`: `ext.kotlin_version = '1.4.31'` - * MainActivity must extend FlutterFragmentActivity - * Theme for the main activity must use `Theme.AppCompat` thme. - (Otherwise there will be crashes on Android < 29) - For example: - - **android/app/src/main/AndroidManifest.xml**: - ```xml - - [...] - - - ``` - - **android/app/src/main/res/values/styles.xml**: - ```xml - - - - - ``` + +Always required: + +* API Level >= 23 (`android/app/build.gradle` `minSdkVersion 23`) + +**Required only if you actually prompt for authentication**, that is, if any +storage uses the default `authenticationRequired: true`. Storage created with +`authenticationRequired: false` never shows a `BiometricPrompt`, so neither of +the following applies to it: + +* **MainActivity must extend `FlutterFragmentActivity`.** `BiometricPrompt` + needs a `FragmentActivity` to host its dialog. If the plugin is attached to a + plain `FlutterActivity` it logs an error and every authenticated read or write + fails with `AuthError:Failed` — unauthenticated storage keeps working. + +* **The activity theme must descend from `Theme.AppCompat`** — but only for + devices where `androidx.biometric` falls back to drawing its own fingerprint + dialog, which it builds with `androidx.appcompat.app.AlertDialog`. That + fallback is used **below API 28**, on API 28 devices without a fingerprint + sensor, and on a short manufacturer allow-list where a crypto object forces + it. From API 28 onwards the system `BiometricPrompt` is used and the theme + does not matter. + + If you do need it: + + **android/app/src/main/AndroidManifest.xml**: + ```xml + + [...] + + + ``` + + **android/app/src/main/res/values/styles.xml**: + ```xml + + + + + ``` ##### Resources @@ -69,18 +85,33 @@ makes heavy use of this plugin. https://developer.apple.com/documentation/localauthentication/logging_a_user_into_your_app_with_face_id_or_touch_id * include the NSFaceIDUsageDescription key in your app’s Info.plist file -* Supports all iOS versions supported by Flutter. (ie. iOS 12) +* Deployment target >= iOS 13 (below whatever Flutter itself requires, so in + practice this never binds). **Known Issue**: since iOS 15 the simulator seem to no longer support local authentication: https://developer.apple.com/forums/thread/685773 +**`IosPromptInfo.saveTitle` / `accessTitle` are invisible on Face ID devices.** +Face ID authenticates against a HUD that shows its glyph and the words "Face ID" +and nothing else, and the "not recognized" alert after a failure offers only +retry and cancel. The strings do reach the system — they are set as +`LAContext.localizedReason` — but iOS does not draw them. Touch ID devices and +macOS do show them, so they are still worth setting. + #### Mac OS * include the NSFaceIDUsageDescription key in your app’s Info.plist file * enable keychain sharing and signing. (not sure why this is required. but without it You will probably see an error like: > SecurityError, Error while writing data: -34018: A required entitlement isn't present. -* Supports all MacOS Versions supported by Flutter (ie. >= MacOS 10.14) +* Deployment target >= macOS 10.15. + +#### Swift Package Manager (iOS and Mac OS) + +The iOS and macOS implementations ship both a `Package.swift` and a podspec, so +they work with either dependency manager. Adding this plugin to an app that has +[migrated to Swift Package Manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) +does **not** bring CocoaPods back — no `Podfile` is generated. ### Usage @@ -98,7 +129,7 @@ https://developer.apple.com/documentation/localauthentication/logging_a_user_int 2. Create the access object ```dart - final store = BiometricStorage().getStorage('mystorage'); + final storageFile = await BiometricStorage().getStorage('mystorage'); ``` 3. Read data @@ -114,4 +145,15 @@ https://developer.apple.com/documentation/localauthentication/logging_a_user_int await storageFile.write(myNewData); ``` +> Storing without a biometric prompt — for a value a background task has to be +> able to refresh, for example — is `authenticationRequired: false`. The value is +> still encrypted at rest; it is simply not gated behind an authentication. +> +> ```dart +> final storageFile = await BiometricStorage().getStorage( +> 'mystorage', +> options: StorageFileInitOptions(authenticationRequired: false), +> ); +> ``` + See also the API documentation: https://pub.dev/documentation/biometric_storage/latest/biometric_storage/BiometricStorageFile-class.html#instance-methods diff --git a/analysis_options.yaml b/analysis_options.yaml index 0ba12552..eb94a590 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -11,6 +11,13 @@ analyzer: exclude: - lib/generated_plugin_registrant.dart - example/lib/generated_plugin_registrant.dart + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** language: strict-casts: true strict-raw-types: true diff --git a/android/build.gradle b/android/build.gradle index b3bd35d3..7c1f959f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,19 +2,19 @@ group 'design.codeux.biometric_storage' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '2.0.21' + ext.kotlin_version = '2.2.20' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.4' + classpath 'com.android.tools.build:gradle:8.13.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } -rootProject.allprojects { +allprojects { repositories { google() mavenCentral() @@ -22,26 +22,16 @@ rootProject.allprojects { } apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' - -kotlin { - jvmToolchain(17) -} android { namespace "design.codeux.biometric_storage" - compileSdk 35 + compileSdk 36 compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = '17' - } - sourceSets { main.java.srcDirs += 'src/main/kotlin' } @@ -50,18 +40,45 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'proguard.pro' } - lintOptions { + lint { disable 'InvalidPackage' } } +// This plugin deliberately does not apply the Kotlin Gradle Plugin itself: AGP 9 +// brings its own Kotlin support and Flutter warns that plugins applying KGP will +// stop building, while on AGP 8 Flutter's Gradle plugin applies `kotlin-android` +// on our behalf. Either way it arrives after this file has been evaluated, so +// the JVM target has to be configured once Kotlin actually shows up. +// https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors +// +// Two things to know when that landscape moves: +// - AGP's own built-in Kotlin does not register this plugin id, so the block +// below will silently stop running once `android.builtInKotlin=true` becomes +// usable. It is unreachable today, because every AGP from 9.1 to 9.3 bundles +// KGP 2.2.10 and Flutter hard-errors below 2.2.20. Recheck the jvmTarget then. +// - Kotlin only reaches this project because Flutter applies KGP per-subproject +// for us. An app with no KGP visible anywhere gets a logged warning from +// Flutter and our .kt sources are then never compiled. +pluginManager.withPlugin('org.jetbrains.kotlin.android') { + kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } + } +} + dependencies { - def biometric_version = "1.4.0-alpha02" + // Not alpha06/07: those declare minCompileMinorSdk=1, which forces every + // consumer to compileSdk 36.1. AGP 9.1 silently ignores the minor, AGP 9.3 + // enforces it. alpha05 is the newest that asks only for compileSdk 35. + def biometric_version = "1.4.0-alpha05" - api "androidx.core:core-ktx:1.10.1" - api "androidx.fragment:fragment-ktx:1.6.1" + // 1.19.0 would require compileSdk 37, which AGP 9.1 does not support yet. + api "androidx.core:core-ktx:1.18.0" + api "androidx.fragment:fragment-ktx:1.9.0" - implementation "org.slf4j:slf4j-api:2.0.7" + implementation "org.slf4j:slf4j-api:2.0.18" implementation "androidx.biometric:biometric:$biometric_version" - implementation "io.github.oshai:kotlin-logging-jvm:5.0.1" + implementation "io.github.oshai:kotlin-logging-jvm:8.0.4" } diff --git a/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt b/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt index 97771b31..aa61c89a 100644 --- a/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt +++ b/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt @@ -67,7 +67,7 @@ enum class AuthenticationError(vararg val code: Int) { companion object { fun forCode(code: Int) = - values().firstOrNull { it.code.contains(code) } ?: Unknown + entries.firstOrNull { it.code.contains(code) } ?: Unknown } } @@ -338,14 +338,19 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler { DEVICE_CREDENTIAL or BIOMETRIC_STRONG } ) - return CanAuthenticateResponse.values().firstOrNull { it.code == response } - ?: throw Exception( - "Unknown response code {$response} (available: ${ - CanAuthenticateResponse - .values() - .contentToString() - }" - ) + return CanAuthenticateResponse.entries.firstOrNull { it.code == response } + ?: run { + // androidx.biometric keeps adding status codes — Android 16 added + // BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS (21), for example. Reporting + // an unmapped one as "status unknown" is what that value is for; + // throwing here took the whole call down. + // https://github.com/authpass/biometric_storage/issues/148 + logger.warn { + "Unmapped canAuthenticate response code $response " + + "(known: ${CanAuthenticateResponse.entries})" + } + CanAuthenticateResponse.ErrorStatusUnknown + } } @UiThread diff --git a/darwin/biometric_storage.podspec b/darwin/biometric_storage.podspec new file mode 100644 index 00000000..cf0a343c --- /dev/null +++ b/darwin/biometric_storage.podspec @@ -0,0 +1,27 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint biometric_storage.podspec' to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'biometric_storage' + s.version = '0.0.1' + s.summary = 'Encrypted file store, optionally secured by biometric lock.' + s.description = <<-DESC +Encrypted file store, optionally secured by biometric lock, backed by the +keychain and LocalAuthentication on iOS and macOS. +Downloaded by pub (not CocoaPods). + DESC + s.homepage = 'https://github.com/authpass/biometric_storage' + s.license = { :type => 'MIT', :file => '../LICENSE' } + s.author = { 'Herbert Poul' => 'herbert@codeux.design' } + s.source = { :path => '.' } + s.documentation_url = 'https://pub.dev/packages/biometric_storage' + s.source_files = 'biometric_storage/Sources/biometric_storage/**/*.swift' + s.ios.dependency 'Flutter' + s.osx.dependency 'FlutterMacOS' + s.ios.deployment_target = '13.0' + s.osx.deployment_target = '10.15' + + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + s.swift_version = '5.9' +end diff --git a/darwin/biometric_storage/Package.swift b/darwin/biometric_storage/Package.swift new file mode 100644 index 00000000..7547d910 --- /dev/null +++ b/darwin/biometric_storage/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "biometric_storage", + platforms: [ + .iOS("13.0"), + .macOS("10.15"), + ], + products: [ + .library(name: "biometric-storage", targets: ["biometric_storage"]) + ], + dependencies: [ + .package(name: "FlutterFramework", path: "../FlutterFramework") + ], + targets: [ + .target( + name: "biometric_storage", + dependencies: [ + .product(name: "FlutterFramework", package: "FlutterFramework") + ] + ) + ] +) diff --git a/macos/Classes/BiometricStorageImpl.swift b/darwin/biometric_storage/Sources/biometric_storage/BiometricStorageImpl.swift similarity index 91% rename from macos/Classes/BiometricStorageImpl.swift rename to darwin/biometric_storage/Sources/biometric_storage/BiometricStorageImpl.swift index e4164072..67e2b4cb 100644 --- a/macos/Classes/BiometricStorageImpl.swift +++ b/darwin/biometric_storage/Sources/biometric_storage/BiometricStorageImpl.swift @@ -197,6 +197,24 @@ class BiometricStorageFile { return context } } + + /// The authentication context to hand to a keychain query, carrying the reason + /// the prompt should give for itself. + /// + /// This replaces `kSecUseOperationPrompt`, deprecated since iOS 14 / macOS 11: + /// the reason now travels on the context rather than in the query. It is set + /// on every call because `context` may return a context reused across calls. + private func authenticationContext(reason: String?) -> LAContext { + let context = self.context + // Assigned unconditionally, including when empty. `context` may be one + // reused across calls when darwinTouchIDAuthenticationForceReuseContextDuration + // is set, so skipping the assignment would leave the *previous* call's + // reason in place — a read showing the save prompt's wording. The query key + // this replaced was per-query and could not carry over like that. + context.localizedReason = reason ?? "" + return context + } + private let storageError: StorageError init(name: String, initOptions: InitOptions, storageError: @escaping StorageError) { @@ -266,10 +284,10 @@ class BiometricStorageFile { return; } query[kSecMatchLimit as String] = kSecMatchLimitOne - query[kSecUseOperationPrompt as String] = promptInfo.accessTitle query[kSecReturnAttributes as String] = true query[kSecReturnData as String] = true - query[kSecUseAuthenticationContext as String] = context + query[kSecUseAuthenticationContext as String] = + authenticationContext(reason: promptInfo.accessTitle) var item: CFTypeRef? @@ -318,11 +336,9 @@ class BiometricStorageFile { if (initOptions.authenticationRequired) { query.merge([ - kSecUseAuthenticationContext as String: context, + kSecUseAuthenticationContext as String: + authenticationContext(reason: promptInfo.saveTitle), ]) { (_, new) in new } - if let operationPrompt = promptInfo.saveTitle { - query[kSecUseOperationPrompt as String] = operationPrompt - } } else { hpdebug("No authentication required for \(name)") } diff --git a/darwin/biometric_storage/Sources/biometric_storage/BiometricStoragePlugin.swift b/darwin/biometric_storage/Sources/biometric_storage/BiometricStoragePlugin.swift new file mode 100644 index 00000000..603bb0d7 --- /dev/null +++ b/darwin/biometric_storage/Sources/biometric_storage/BiometricStoragePlugin.swift @@ -0,0 +1,28 @@ +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#endif + +public class BiometricStoragePlugin: NSObject, FlutterPlugin { + + private let impl = BiometricStorageImpl( + storageError: { (code, message, details) -> Any in + FlutterError(code: code, message: message, details: details) + }, storageMethodNotImplemented: FlutterMethodNotImplemented) + + public static func register(with registrar: FlutterPluginRegistrar) { + #if os(iOS) + let messenger = registrar.messenger() + #elseif os(macOS) + let messenger = registrar.messenger + #endif + let channel = FlutterMethodChannel(name: "biometric_storage", binaryMessenger: messenger) + let instance = BiometricStoragePlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + impl.handle(StorageMethodCall(method: call.method, arguments: call.arguments), result: result) + } +} diff --git a/example/.metadata b/example/.metadata index 72aedebf..fc5c68db 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1" + revision: "4cf24164269a5ebf0c16a028a00727d0e77bbb05" channel: "stable" project_type: app @@ -13,8 +13,14 @@ project_type: app migration: platforms: - platform: root - create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 - base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 + create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + - platform: ios + create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + - platform: macos + create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 + base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05 - platform: web create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 0ba12552..eb94a590 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -11,6 +11,13 @@ analyzer: exclude: - lib/generated_plugin_registrant.dart - example/lib/generated_plugin_registrant.dart + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** language: strict-casts: true strict-raw-types: true diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index c24acc51..b9b041a3 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -22,13 +22,17 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } +// jvmTarget rather than jvmToolchain(17): the toolchain form demands a JDK 17 +// be present, which is what made this plugin unbuildable on newer JDKs (#117, +// #140). This is what `flutter create` emits today. kotlin { - jvmToolchain(17) + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } } android { - compileSdk 35 - ndkVersion "21.1.6352462" + compileSdk 36 compileOptions { sourceCompatibility = 17 @@ -43,8 +47,8 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "design.codeux.biometric_storage_example" - minSdkVersion 23 - targetSdkVersion 35 + minSdkVersion flutter.minSdkVersion + targetSdkVersion 36 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -76,12 +80,12 @@ flutter { } dependencies { - implementation 'org.slf4j:slf4j-api:2.0.7' + implementation 'org.slf4j:slf4j-api:2.0.18' implementation 'com.github.tony19:logback-android:3.0.0' - implementation "io.github.oshai:kotlin-logging-jvm:5.0.1" - implementation "androidx.appcompat:appcompat:1.6.1" + implementation "io.github.oshai:kotlin-logging-jvm:8.0.4" + implementation "androidx.appcompat:appcompat:1.8.0" testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:runner:1.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test:runner:1.7.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' } diff --git a/example/android/app/gradle.properties b/example/android/app/gradle.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/example/android/build.gradle b/example/android/build.gradle index bc157bd1..7dfa2162 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -5,14 +5,16 @@ allprojects { } } -rootProject.buildDir = '../build' +def newBuildDir = rootProject.layout.buildDirectory.dir('../../build').get() +rootProject.layout.buildDirectory.value(newBuildDir) + subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" + project.layout.buildDirectory.value(newBuildDir.dir(project.name)) } subprojects { project.evaluationDependsOn(':app') } tasks.register("clean", Delete) { - delete rootProject.buildDir + delete rootProject.layout.buildDirectory } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 19c40884..15521fdc 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,6 +1,11 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true -android.defaults.buildfeatures.buildconfig=true -android.nonTransitiveRClass=false -android.nonFinalResIds=false +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template, and cannot be +# flipped to true yet. Turning it on discards the KGP declared in +# settings.gradle and falls back to the one AGP bundles — which is 2.2.10 in +# every AGP from 9.1.0 to 9.3.2, below the 2.2.20 that Flutter's +# DependencyVersionChecker treats as a hard error. +android.builtInKotlin=false diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index df97d72b..1a704683 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 941b4993..224fabeb 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" // apply true - id "com.android.application" version "8.8.0" apply false - id "org.jetbrains.kotlin.android" version "1.8.10" apply false + id "com.android.application" version "9.3.2" apply false + id "org.jetbrains.kotlin.android" version "2.4.10" apply false } include ":app" \ No newline at end of file diff --git a/example/android/settings_aar.gradle b/example/android/settings_aar.gradle deleted file mode 100644 index e7b4def4..00000000 --- a/example/android/settings_aar.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' diff --git a/example/ios/.gitignore b/example/ios/.gitignore index 687f82a2..e676a114 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ @@ -30,4 +32,9 @@ Runner/GeneratedPluginRegistrant.* !default.mode2v3 !default.pbxuser !default.perspectivev3 -Flutter/ephemeral/ + +# This example builds through Swift Package Manager. A Podfile only appears +# when someone verifies the CocoaPods path still works, and it is generated — +# it was untracked in the 6.0.0 SwiftPM migration. +Podfile +Podfile.lock diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 8c6e5614..391a902b 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 12.0 diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index e8efba11..592ceee8 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Flutter.podspec b/example/ios/Flutter/Flutter.podspec deleted file mode 100644 index 98e16339..00000000 --- a/example/ios/Flutter/Flutter.podspec +++ /dev/null @@ -1,18 +0,0 @@ -# -# This podspec is NOT to be published. It is only used as a local source! -# This is a generated file; do not edit or check into version control. -# - -Pod::Spec.new do |s| - s.name = 'Flutter' - s.version = '1.0.0' - s.summary = 'A UI toolkit for beautiful and fast apps.' - s.homepage = 'https://flutter.dev' - s.license = { :type => 'BSD' } - s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } - s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } - s.ios.deployment_target = '12.0' - # Framework linking is handled by Flutter tooling, not CocoaPods. - # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs. - s.vendored_frameworks = 'path/to/nothing' -end diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 399e9340..592ceee8 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile deleted file mode 100644 index 279576f3..00000000 --- a/example/ios/Podfile +++ /dev/null @@ -1,41 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '12.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock deleted file mode 100644 index b80ef5a4..00000000 --- a/example/ios/Podfile.lock +++ /dev/null @@ -1,22 +0,0 @@ -PODS: - - biometric_storage (0.0.1): - - Flutter - - Flutter (1.0.0) - -DEPENDENCIES: - - biometric_storage (from `.symlinks/plugins/biometric_storage/ios`) - - Flutter (from `Flutter`) - -EXTERNAL SOURCES: - biometric_storage: - :path: ".symlinks/plugins/biometric_storage/ios" - Flutter: - :path: Flutter - -SPEC CHECKSUMS: - biometric_storage: 1400f1382af3a4cc2bf05340e13c3d8de873ceb9 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - -PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 - -COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 77ec0c92..bdd41a2e 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,14 +8,26 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 2617BEF2E85EF7B0E0C3F640 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A82D22789C0BD1DFA237DD17 /* Pods_Runner.framework */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -32,11 +44,13 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 38BFCAC58D357060798CA698 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 44A1DA681123A8D2C507E66D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -45,8 +59,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A82D22789C0BD1DFA237DD17 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D9C6C535E9616B0CA2F04339 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,24 +66,25 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 2617BEF2E85EF7B0E0C3F640 /* Pods_Runner.framework in Frameworks */, + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 5E2774A267EB0D9BC80FC38B /* Frameworks */ = { + 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( - A82D22789C0BD1DFA237DD17 /* Pods_Runner.framework */, + 331C807B294A618700263BE5 /* RunnerTests.swift */, ); - name = Frameworks; + path = RunnerTests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -86,8 +99,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 9A3F10DEB14377687BB1C2A3 /* Pods */, - 5E2774A267EB0D9BC80FC38B /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -95,6 +107,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -106,53 +119,54 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 9A3F10DEB14377687BB1C2A3 /* Pods */ = { - isa = PBXGroup; - children = ( - D9C6C535E9616B0CA2F04339 /* Pods-Runner.debug.xcconfig */, - 38BFCAC58D357060798CA698 /* Pods-Runner.release.xcconfig */, - 44A1DA681123A8D2C507E66D /* Pods-Runner.profile.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 3E25D75FEDE51BC3EBBCDF14 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - A3647EA6050904F62CB30D32 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -163,18 +177,22 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1510; - ORGANIZATIONNAME = "The Chromium Authors"; + ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = 64ZPC769JY; - LastSwiftMigration = 1140; + LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -182,16 +200,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -222,28 +251,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 3E25D75FEDE51BC3EBBCDF14 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -259,38 +266,37 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - A3647EA6050904F62CB30D32 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/biometric_storage/biometric_storage.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/biometric_storage.framework", + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -315,6 +321,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -344,6 +351,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -352,9 +360,10 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -370,15 +379,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 64ZPC769JY; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = design.codeux.biometricStorageExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -388,10 +392,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = design.codeux.biometricStorageExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = design.codeux.biometricStorageExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = design.codeux.biometricStorageExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -421,6 +473,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -435,10 +488,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -447,6 +501,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -476,6 +531,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -484,11 +540,13 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -503,15 +561,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 64ZPC769JY; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = design.codeux.biometricStorageExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -531,15 +584,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 64ZPC769JY; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = design.codeux.biometricStorageExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -552,6 +600,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -573,6 +631,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e67b2808..c3fedb29 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + - - - - + + + + + + @@ -61,8 +91,6 @@ ReferencedContainer = "container:Runner.xcodeproj"> - - - - diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index b6363034..c30b367e 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,13 +1,16 @@ -import UIKit import Flutter +import UIKit @main -@objc class AppDelegate: FlutterAppDelegate { +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } } diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf03..7353c41e 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd96..797d452e 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b0..6ed2d933 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde1211..4cd7b009 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e7..fe730945 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc2306..321773cd 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd96..797d452e 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8f..502f463a 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b860..0ec30343 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b860..0ec30343 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d164..e9f5fea2 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d3..84ac32ae 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41e..8953cba0 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f585..0467bf12 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index a8703fee..ca7a52bc 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -2,12 +2,12 @@ - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Biometric Storage Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -18,12 +18,39 @@ biometric_storage_example CFBundlePackageType APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) CFBundleSignature ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS NSFaceIDUsageDescription - Exame of using face id + Secure storage of authorization tokens + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,11 +68,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h index 7335fdf9..308a2a56 100644 --- a/example/ios/Runner/Runner-Bridging-Header.h +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/Runner/SceneDelegate.swift b/example/ios/Runner/SceneDelegate.swift new file mode 100644 index 00000000..b9ce8ea2 --- /dev/null +++ b/example/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/lib/main.dart b/example/lib/main.dart index a607995f..46eedae7 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -34,14 +34,16 @@ class ShortFormatter extends LogRecordFormatter { @override StringBuffer formatToStringBuffer(LogRecord rec, StringBuffer sb) { sb.write( - '${rec.time.hour}:${rec.time.minute}:${rec.time.second} ${rec.level.name} ' - '${rec.message}'); + '${rec.time.hour}:${rec.time.minute}:${rec.time.second} ${rec.level.name} ' + '${rec.message}', + ); if (rec.error != null) { sb.write(rec.error); } // ignore: avoid_as - final stackTrace = rec.stackTrace ?? + final stackTrace = + rec.stackTrace ?? (rec.error is Error ? (rec.error as Error).stackTrace : null); if (stackTrace != null) { sb.write(stackTrace); @@ -76,16 +78,18 @@ class MyAppState extends State { androidBiometricOnly: false, androidAuthenticationValidityDuration: const Duration(seconds: 5), darwinBiometricOnly: false, - darwinTouchIDAuthenticationForceReuseContextDuration: - const Duration(seconds: 5), + darwinTouchIDAuthenticationForceReuseContextDuration: const Duration( + seconds: 5, + ), ); BiometricStorageFile? _authStorage; BiometricStorageFile? _storage; BiometricStorageFile? _customPrompt; - final TextEditingController _writeController = - TextEditingController(text: 'Lorem Ipsum'); + final TextEditingController _writeController = TextEditingController( + text: 'Lorem Ipsum', + ); @override void initState() { @@ -114,9 +118,7 @@ class MyAppState extends State { Widget build(BuildContext context) { return MaterialApp( home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), - ), + appBar: AppBar(title: const Text('Plugin example app')), body: Column( children: [ const Text('Methods:'), @@ -124,15 +126,17 @@ class MyAppState extends State { child: const Text('init'), onPressed: () async { _logger.finer('Initializing $baseName'); - final authStorageSupport = - await _checkAuthenticate(_authStorageInitOptions); + final authStorageSupport = await _checkAuthenticate( + _authStorageInitOptions, + ); if (authStorageSupport == CanAuthenticateResponse.unsupported) { _logger.severe( - 'Unable to use authenticate. Unable to get storage.'); + 'Unable to use authenticate. Unable to get storage.', + ); return; } - final supportsAuthenticated = authStorageSupport == - CanAuthenticateResponse.success || + final supportsAuthenticated = + authStorageSupport == CanAuthenticateResponse.success || authStorageSupport == CanAuthenticateResponse.statusUnknown; if (supportsAuthenticated) { _authStorage = await BiometricStorage().getStorage( @@ -140,29 +144,32 @@ class MyAppState extends State { options: _authStorageInitOptions, ); } - _storage = await BiometricStorage() - .getStorage('${baseName}_unauthenticated', - options: StorageFileInitOptions( - authenticationRequired: false, - )); - final supportsCustomPrompt = - await _checkAuthenticate(_customPromptInitOptions); + _storage = await BiometricStorage().getStorage( + '${baseName}_unauthenticated', + options: StorageFileInitOptions( + authenticationRequired: false, + ), + ); + final supportsCustomPrompt = await _checkAuthenticate( + _customPromptInitOptions, + ); if (supportsCustomPrompt == CanAuthenticateResponse.success) { - _customPrompt = await BiometricStorage() - .getStorage('${baseName}_customPrompt', - options: _customPromptInitOptions, - promptInfo: const PromptInfo( - iosPromptInfo: IosPromptInfo( - saveTitle: 'Custom save title', - accessTitle: 'Custom access title.', - ), - androidPromptInfo: AndroidPromptInfo( - title: 'Custom title', - subtitle: 'Custom subtitle', - description: 'Custom description', - negativeButton: 'Nope!', - ), - )); + _customPrompt = await BiometricStorage().getStorage( + '${baseName}_customPrompt', + options: _customPromptInitOptions, + promptInfo: const PromptInfo( + iosPromptInfo: IosPromptInfo( + saveTitle: 'Custom save title', + accessTitle: 'Custom access title.', + ), + androidPromptInfo: AndroidPromptInfo( + title: 'Custom title', + subtitle: 'Custom subtitle', + description: 'Custom description', + negativeButton: 'Nope!', + ), + ), + ); } setState(() {}); _logger.info('initiailzed $baseName'); @@ -172,31 +179,40 @@ class MyAppState extends State { ...(_authStorage == null ? [] : [ - const Text('Biometric Authentication', - style: TextStyle(fontWeight: FontWeight.bold)), + const Text( + 'Biometric Authentication', + style: TextStyle(fontWeight: FontWeight.bold), + ), StorageActions( - storageFile: _authStorage!, - writeController: _writeController), + storageFile: _authStorage!, + writeController: _writeController, + ), const Divider(), ]), ...?(_storage == null ? null : [ - const Text('Unauthenticated', - style: TextStyle(fontWeight: FontWeight.bold)), + const Text( + 'Unauthenticated', + style: TextStyle(fontWeight: FontWeight.bold), + ), StorageActions( - storageFile: _storage!, - writeController: _writeController), + storageFile: _storage!, + writeController: _writeController, + ), const Divider(), ]), ...?(_customPrompt == null ? null : [ - const Text('Custom Prompts w/ 5s auth validity', - style: TextStyle(fontWeight: FontWeight.bold)), + const Text( + 'Custom Prompts w/ 5s auth validity', + style: TextStyle(fontWeight: FontWeight.bold), + ), StorageActions( - storageFile: _customPrompt!, - writeController: _writeController), + storageFile: _customPrompt!, + writeController: _writeController, + ), const Divider(), ]), const Divider(), @@ -214,9 +230,7 @@ class MyAppState extends State { reverse: true, child: Container( padding: const EdgeInsets.all(16), - child: Text( - logMessages.log.toString(), - ), + child: Text(logMessages.log.toString()), ), ), ), @@ -234,15 +248,18 @@ class MyAppState extends State { child: const Text('Check App Armor'), onPressed: () async { if (await BiometricStorage().linuxCheckAppArmorError()) { - _logger.info('Got an error! User has to authorize us to ' - 'use secret service.'); _logger.info( - 'Run: `snap connect biometric-storage-example:password-manager-service`'); + 'Got an error! User has to authorize us to ' + 'use secret service.', + ); + _logger.info( + 'Run: `snap connect biometric-storage-example:password-manager-service`', + ); } else { _logger.info('all good.'); } }, - ) + ), ]; } @@ -282,8 +299,9 @@ class StorageActions extends StatelessWidget { onPressed: () async { _logger.fine('Going to write...'); try { - await storageFile - .write(' [${DateTime.now()}] ${writeController.text}'); + await storageFile.write( + ' [${DateTime.now()}] ${writeController.text}', + ); _logger.info('Written content.'); } on AuthException catch (e) { if (e.code == AuthExceptionCode.userCanceled) { diff --git a/example/macos/.gitignore b/example/macos/.gitignore index d2fd3772..a0198a8f 100644 --- a/example/macos/.gitignore +++ b/example/macos/.gitignore @@ -3,4 +3,11 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ + +# This example builds through Swift Package Manager. A Podfile only appears +# when someone verifies the CocoaPods path still works, and it is generated — +# it was untracked in the 6.0.0 SwiftPM migration. +Podfile +Podfile.lock diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig index 785633d3..c2efd0b6 100644 --- a/example/macos/Flutter/Flutter-Debug.xcconfig +++ b/example/macos/Flutter/Flutter-Debug.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig index 5fba960c..c2efd0b6 100644 --- a/example/macos/Flutter/Flutter-Release.xcconfig +++ b/example/macos/Flutter/Flutter-Release.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index a37d8058..fee451e2 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,5 +8,5 @@ import Foundation import biometric_storage func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - BiometricStorageMacOSPlugin.register(with: registry.registrar(forPlugin: "BiometricStorageMacOSPlugin")) + BiometricStoragePlugin.register(with: registry.registrar(forPlugin: "BiometricStoragePlugin")) } diff --git a/example/macos/Podfile b/example/macos/Podfile deleted file mode 100644 index 049abe29..00000000 --- a/example/macos/Podfile +++ /dev/null @@ -1,40 +0,0 @@ -platform :osx, '10.14' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_macos_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_macos_build_settings(target) - end -end diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock deleted file mode 100644 index d8315c73..00000000 --- a/example/macos/Podfile.lock +++ /dev/null @@ -1,22 +0,0 @@ -PODS: - - biometric_storage (0.0.1): - - FlutterMacOS - - FlutterMacOS (1.0.0) - -DEPENDENCIES: - - biometric_storage (from `Flutter/ephemeral/.symlinks/plugins/biometric_storage/macos`) - - FlutterMacOS (from `Flutter/ephemeral`) - -EXTERNAL SOURCES: - biometric_storage: - :path: Flutter/ephemeral/.symlinks/plugins/biometric_storage/macos - FlutterMacOS: - :path: Flutter/ephemeral - -SPEC CHECKSUMS: - biometric_storage: 43caa6e7ef00e8e19c074216e7e1786dacda9e76 - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - -PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 - -COCOAPODS: 1.15.0 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 1141e0b3..49bb74d1 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -21,15 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 7B5B42274F57585137479FC2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED4CEC9190AC6081F9235C3D /* Pods_Runner.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -53,9 +61,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* biometric_storage_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = biometric_storage_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* biometric_storage_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "biometric_storage_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -67,26 +77,38 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 8B8244600876A6AC277C2D36 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - 9C7F97A71F4300BD68B2E323 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - D5B45F9BD842CDA89D126C5B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - ED4CEC9190AC6081F9235C3D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7B5B42274F57585137479FC2 /* Pods_Runner.framework in Frameworks */, + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -103,9 +125,9 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, - 5133D102C7F573900A7F048A /* Pods */, ); sourceTree = ""; }; @@ -113,6 +135,7 @@ isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* biometric_storage_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -131,6 +154,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, @@ -152,20 +176,9 @@ path = Runner; sourceTree = ""; }; - 5133D102C7F573900A7F048A /* Pods */ = { - isa = PBXGroup; - children = ( - D5B45F9BD842CDA89D126C5B /* Pods-Runner.debug.xcconfig */, - 8B8244600876A6AC277C2D36 /* Pods-Runner.release.xcconfig */, - 9C7F97A71F4300BD68B2E323 /* Pods-Runner.profile.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( - ED4CEC9190AC6081F9235C3D /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -173,17 +186,33 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - C09D3710E339A4A9082D46EF /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - BE84919A4CA5E2A9CCDAA69F /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -191,6 +220,9 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* biometric_storage_example.app */; productType = "com.apple.product-type.application"; @@ -201,10 +233,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; - ORGANIZATIONNAME = "The Flutter Authors"; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -222,7 +259,7 @@ }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 8.0"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -230,17 +267,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -289,51 +337,19 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n"; - }; - BE84919A4CA5E2A9CCDAA69F /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/biometric_storage/biometric_storage.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/biometric_storage.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - C09D3710E339A4A9082D46EF /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -347,6 +363,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -367,11 +388,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = design.codeux.biometricStorageExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/biometric_storage_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/biometric_storage_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = design.codeux.biometricStorageExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/biometric_storage_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/biometric_storage_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = design.codeux.biometricStorageExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/biometric_storage_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/biometric_storage_example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -395,9 +459,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -405,7 +471,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -420,14 +486,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = 64ZPC769JY; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter/ephemeral", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -451,6 +511,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -474,9 +535,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -490,7 +553,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -504,6 +567,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -527,9 +591,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -537,7 +603,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -552,14 +618,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = 64ZPC769JY; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter/ephemeral", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -578,14 +638,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = 64ZPC769JY; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter/ephemeral", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -615,6 +669,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -646,6 +710,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8574ee20..8da04372 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - - - - - + + + + + + @@ -71,11 +89,9 @@ ReferencedContainer = "container:Runner.xcodeproj"> - - - - diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef643..b3c17614 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7..82b6f9d9 100644 Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc164..13b35eba 100644 Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be613..0a3f5fa4 100644 Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df..bdb57226 100644 Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a6528..f083318e 100644 Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a7261..326c0e72 100644 Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2d..2f1632cf 100644 Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/example/macos/Runner/Base.lproj/MainMenu.xib b/example/macos/Runner/Base.lproj/MainMenu.xib index 537341ab..80e867a4 100644 --- a/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@ + + + + diff --git a/example/macos/Runner/Configs/AppInfo.xcconfig b/example/macos/Runner/Configs/AppInfo.xcconfig index ab0717f0..ca6097a5 100644 --- a/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/example/macos/Runner/Configs/AppInfo.xcconfig @@ -11,4 +11,10 @@ PRODUCT_NAME = biometric_storage_example PRODUCT_BUNDLE_IDENTIFIER = design.codeux.biometricStorageExample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2020 design.codeux. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2026 design.codeux. All rights reserved. + +// The keychain-access-groups entitlement resolves $(AppIdentifierPrefix) from +// the signing team, so the Runner cannot be built with the project's default +// ad-hoc identity. +DEVELOPMENT_TEAM = 64ZPC769JY +CODE_SIGN_IDENTITY = Apple Development diff --git a/example/macos/Runner/Info.plist b/example/macos/Runner/Info.plist index 6c2c7b5b..aa83bca6 100644 --- a/example/macos/Runner/Info.plist +++ b/example/macos/Runner/Info.plist @@ -22,12 +22,12 @@ $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) + NSFaceIDUsageDescription + Secure storage of authorization tokens NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu - NSFaceIDUsageDescription - Secure Storage or authorization tokens NSPrincipalClass NSApplication diff --git a/example/macos/Runner/MainFlutterWindow.swift b/example/macos/Runner/MainFlutterWindow.swift index 2722837e..3cc05eb2 100644 --- a/example/macos/Runner/MainFlutterWindow.swift +++ b/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/example/macos/RunnerTests/RunnerTests.swift b/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 6795de2d..e31117bf 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,17 +5,17 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" biometric_storage: dependency: "direct main" description: path: ".." relative: true source: path - version: "5.1.2-dev.1" + version: "6.0.0-dev.1" boolean_selector: dependency: transitive description: @@ -48,22 +48,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" - dio: - dependency: transitive - description: - name: dio - sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" - url: "https://pub.dev" - source: hosted - version: "5.7.0" - dio_web_adapter: - dependency: transitive - description: - name: dio_web_adapter - sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" - url: "https://pub.dev" - source: hosted - version: "2.0.0" fake_async: dependency: transitive description: @@ -76,10 +60,18 @@ packages: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.2.0" + ffi_leak_tracker: + dependency: transitive + description: + name: ffi_leak_tracker + sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97" + url: "https://pub.dev" + source: hosted + version: "0.1.2" flutter: dependency: "direct main" description: flutter @@ -89,10 +81,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -107,10 +99,10 @@ packages: dependency: transitive description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.6.0" http_parser: dependency: transitive description: @@ -123,10 +115,10 @@ packages: dependency: transitive description: name: intl - sha256: "00f33b908655e606b86d2ade4710a231b802eec6f11e87e4ea3783fd72077a50" + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" url: "https://pub.dev" source: hosted - version: "0.20.1" + version: "0.20.3" leak_tracker: dependency: transitive description: @@ -155,10 +147,10 @@ packages: dependency: transitive description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "6.1.0" logging: dependency: "direct main" description: @@ -171,18 +163,18 @@ packages: dependency: "direct main" description: name: logging_appenders - sha256: e329e7472f99416d0edaaf6451fe6c02dec91d34535bd252e284a0b94ab23d79 + sha256: "3fa9192d4b6018b23c9effd3c87c738847fa0fb68e647dd2630c16207476817d" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "2.0.0+1" matcher: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.20" material_color_utilities: dependency: transitive description: @@ -195,10 +187,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" path: dependency: transitive description: @@ -224,10 +216,10 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" stack_trace: dependency: transitive description: @@ -264,10 +256,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" typed_data: dependency: transitive description: @@ -280,34 +272,34 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.2" vm_service: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.3.0" web: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" win32: dependency: transitive description: name: win32 - sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" + sha256: a0b93865d5644f11cf6a8c3f6db909f1ec168958b5805f6cc684adea957cd63d url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "6.4.0" sdks: - dart: ">=3.10.0-0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.11.0-0 <4.0.0" + flutter: ">=3.44.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 6f8412b4..fdca1e17 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: '>=3.2.0 <4.0.0' + sdk: ^3.10.0 dependencies: biometric_storage: @@ -12,12 +12,12 @@ dependencies: flutter: sdk: flutter logging: ^1.2.0 - logging_appenders: ^1.1.0 + logging_appenders: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 82cc80bb..49b0187d 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -1,26 +1,15 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - import 'package:biometric_storage_example/main.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - testWidgets('Verify Platform version', (WidgetTester tester) async { - // Build our app and trigger a frame. + // A smoke test: it only asserts that the example builds and renders. The + // storage sections appear once `init` has been tapped, and tapping it goes to + // the platform, so this stops at the first frame on purpose. + testWidgets('renders the example UI', (WidgetTester tester) async { await tester.pumpWidget(const MyApp()); - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => - widget is Text && widget.data!.startsWith('Running on:'), - ), - findsOneWidget, - ); + expect(find.text('Plugin example app'), findsOneWidget); + expect(find.widgetWithText(ElevatedButton, 'init'), findsOneWidget); }); } diff --git a/ios/.gitignore b/ios/.gitignore deleted file mode 100644 index aa479fd3..00000000 --- a/ios/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -.idea/ -.vagrant/ -.sconsign.dblite -.svn/ - -.DS_Store -*.swp -profile - -DerivedData/ -build/ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m - -.generated/ - -*.pbxuser -*.mode1v3 -*.mode2v3 -*.perspectivev3 - -!default.pbxuser -!default.mode1v3 -!default.mode2v3 -!default.perspectivev3 - -xcuserdata - -*.moved-aside - -*.pyc -*sync/ -Icon? -.tags* - -/Flutter/Generated.xcconfig -/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/ios/Classes/BiometricStorageImpl.swift b/ios/Classes/BiometricStorageImpl.swift deleted file mode 120000 index 6019e91b..00000000 --- a/ios/Classes/BiometricStorageImpl.swift +++ /dev/null @@ -1 +0,0 @@ -../../macos/Classes/BiometricStorageImpl.swift \ No newline at end of file diff --git a/ios/Classes/BiometricStoragePlugin.h b/ios/Classes/BiometricStoragePlugin.h deleted file mode 100644 index a23a5ef4..00000000 --- a/ios/Classes/BiometricStoragePlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface BiometricStoragePlugin : NSObject -@end diff --git a/ios/Classes/BiometricStoragePlugin.m b/ios/Classes/BiometricStoragePlugin.m deleted file mode 100644 index 21c1bc31..00000000 --- a/ios/Classes/BiometricStoragePlugin.m +++ /dev/null @@ -1,8 +0,0 @@ -#import "BiometricStoragePlugin.h" -#import - -@implementation BiometricStoragePlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - [SwiftBiometricStoragePlugin registerWithRegistrar:registrar]; -} -@end diff --git a/ios/Classes/SwiftBiometricStoragePlugin.swift b/ios/Classes/SwiftBiometricStoragePlugin.swift deleted file mode 100644 index 0d6be911..00000000 --- a/ios/Classes/SwiftBiometricStoragePlugin.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Flutter -//import UIKit - -public class SwiftBiometricStoragePlugin: NSObject, FlutterPlugin { - private let impl = BiometricStorageImpl(storageError: { (code, message, details) -> Any in - FlutterError(code: code, message: message, details: details) - }, storageMethodNotImplemented: FlutterMethodNotImplemented) - - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "biometric_storage", binaryMessenger: registrar.messenger()) - let instance = SwiftBiometricStoragePlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - impl.handle(StorageMethodCall(method: call.method, arguments: call.arguments), result: result) - } -} diff --git a/ios/biometric_storage.podspec b/ios/biometric_storage.podspec deleted file mode 100644 index 00167be6..00000000 --- a/ios/biometric_storage.podspec +++ /dev/null @@ -1,25 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint biometric_storage.podspec' to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'biometric_storage' - s.version = '0.0.1' - s.summary = 'A new flutter plugin project.' - s.description = <<-DESC -A new flutter plugin project. - DESC - s.homepage = 'http://example.com' - s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' #, '../macos/Classes/BiometricStorageImpl.swift' - s.public_header_files = 'Classes/**/*.h' - s.dependency 'Flutter' - s.platform = :ios, '9.0' - - # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } - s.swift_version = '5.0' -end - diff --git a/lib/src/biometric_storage.dart b/lib/src/biometric_storage.dart index 7d29b727..9571fe76 100644 --- a/lib/src/biometric_storage.dart +++ b/lib/src/biometric_storage.dart @@ -1,11 +1,16 @@ import 'dart:async'; -import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:logging/logging.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +// Not `dart:io` directly: importing it here unconditionally is what made the +// whole package report as WebAssembly-incompatible, since this library is +// reachable from the web implementation. The io branch still uses `dart:io`. +import 'platform_os_io.dart' + if (dart.library.js_interop) 'platform_os_web.dart'; + final _logger = Logger('biometric_storage'); /// Reason for not supporting authentication. @@ -88,22 +93,23 @@ class StorageFileInitOptions { Duration? darwinTouchIDAuthenticationAllowableReuseDuration, this.darwinTouchIDAuthenticationForceReuseContextDuration, @Deprecated( - 'use use androidAuthenticationValidityDuration, iosTouchIDAuthenticationAllowableReuseDuration or iosTouchIDAuthenticationForceReuseContextDuration instead') + 'use use androidAuthenticationValidityDuration, iosTouchIDAuthenticationAllowableReuseDuration or iosTouchIDAuthenticationForceReuseContextDuration instead', + ) int authenticationValidityDurationSeconds = -1, this.authenticationRequired = true, this.androidBiometricOnly = true, this.darwinBiometricOnly = true, this.darwinKeychainAccessGroup, - }) : androidAuthenticationValidityDuration = - androidAuthenticationValidityDuration ?? - (authenticationValidityDurationSeconds <= 0 - ? null - : Duration(seconds: authenticationValidityDurationSeconds)), - darwinTouchIDAuthenticationAllowableReuseDuration = - darwinTouchIDAuthenticationAllowableReuseDuration ?? - (authenticationValidityDurationSeconds <= 0 - ? null - : Duration(seconds: authenticationValidityDurationSeconds)); + }) : androidAuthenticationValidityDuration = + androidAuthenticationValidityDuration ?? + (authenticationValidityDurationSeconds <= 0 + ? null + : Duration(seconds: authenticationValidityDurationSeconds)), + darwinTouchIDAuthenticationAllowableReuseDuration = + darwinTouchIDAuthenticationAllowableReuseDuration ?? + (authenticationValidityDurationSeconds <= 0 + ? null + : Duration(seconds: authenticationValidityDurationSeconds)); /// see https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int,%20int) final Duration? androidAuthenticationValidityDuration; @@ -163,17 +169,17 @@ class StorageFileInitOptions { final String? darwinKeychainAccessGroup; Map toJson() => { - 'androidAuthenticationValidityDurationSeconds': - androidAuthenticationValidityDuration?.inSeconds, - 'darwinTouchIDAuthenticationAllowableReuseDurationSeconds': - darwinTouchIDAuthenticationAllowableReuseDuration?.inSeconds, - 'darwinTouchIDAuthenticationForceReuseContextDurationSeconds': - darwinTouchIDAuthenticationForceReuseContextDuration?.inSeconds, - 'authenticationRequired': authenticationRequired, - 'androidBiometricOnly': androidBiometricOnly, - 'darwinBiometricOnly': darwinBiometricOnly, - 'darwinKeychainAccessGroup': darwinKeychainAccessGroup, - }; + 'androidAuthenticationValidityDurationSeconds': + androidAuthenticationValidityDuration?.inSeconds, + 'darwinTouchIDAuthenticationAllowableReuseDurationSeconds': + darwinTouchIDAuthenticationAllowableReuseDuration?.inSeconds, + 'darwinTouchIDAuthenticationForceReuseContextDurationSeconds': + darwinTouchIDAuthenticationForceReuseContextDuration?.inSeconds, + 'authenticationRequired': authenticationRequired, + 'androidBiometricOnly': androidBiometricOnly, + 'darwinBiometricOnly': darwinBiometricOnly, + 'darwinKeychainAccessGroup': darwinKeychainAccessGroup, + }; } /// Android specific configuration of the prompt displayed for biometry. @@ -195,15 +201,26 @@ class AndroidPromptInfo { static const defaultValues = AndroidPromptInfo(); Map _toJson() => { - 'title': title, - 'subtitle': subtitle, - 'description': description, - 'negativeButton': negativeButton, - 'confirmationRequired': confirmationRequired, - }; + 'title': title, + 'subtitle': subtitle, + 'description': description, + 'negativeButton': negativeButton, + 'confirmationRequired': confirmationRequired, + }; } /// iOS **and MacOS** specific configuration of the prompt displayed for biometry. +/// +/// **These strings are not shown on a Face ID device.** They are passed to the +/// system as `LAContext.localizedReason`, and Face ID authenticates against a +/// HUD that has no room for a reason: the panel shows the Face ID glyph and the +/// words "Face ID", and the "not recognized" alert that follows a failure offers +/// only retry and cancel. Verified on an iPhone Xr running iOS 18.7.9, against +/// both `localizedReason` and the deprecated `kSecUseOperationPrompt` set at the +/// same time — neither was rendered. +/// +/// They are still worth setting: Touch ID devices draw the reason in the +/// authentication alert, and macOS shows it too. class IosPromptInfo { const IosPromptInfo({ this.saveTitle = 'Unlock to save data', @@ -216,9 +233,9 @@ class IosPromptInfo { static const defaultValues = IosPromptInfo(); Map _toJson() => { - 'saveTitle': saveTitle, - 'accessTitle': accessTitle, - }; + 'saveTitle': saveTitle, + 'accessTitle': accessTitle, + }; } /// Wrapper for platform specific prompt infos. @@ -291,23 +308,13 @@ abstract class BiometricStorage extends PlatformInterface { }); @protected - Future read( - String name, - PromptInfo promptInfo, - ); + Future read(String name, PromptInfo promptInfo); @protected - Future delete( - String name, - PromptInfo promptInfo, - ); + Future delete(String name, PromptInfo promptInfo); @protected - Future write( - String name, - String content, - PromptInfo promptInfo, - ); + Future write(String name, String content, PromptInfo promptInfo); } class MethodChannelBiometricStorage extends BiometricStorage { @@ -322,16 +329,10 @@ class MethodChannelBiometricStorage extends BiometricStorage { if (kIsWeb) { return CanAuthenticateResponse.unsupported; } - if (Platform.isAndroid || - Platform.isIOS || - Platform.isMacOS || - Platform.isLinux) { - final response = await _channel.invokeMethod( - 'canAuthenticate', - { - 'options': options?.toJson() ?? StorageFileInitOptions().toJson(), - }, - ); + if (const {'android', 'ios', 'macos', 'linux'}.contains(operatingSystem)) { + final response = await _channel.invokeMethod('canAuthenticate', { + 'options': options?.toJson() ?? StorageFileInitOptions().toJson(), + }); final ret = _canAuthenticateMapping[response]; if (ret == null) { throw StateError('Invalid response from native platform. {$response}'); @@ -356,11 +357,13 @@ class MethodChannelBiometricStorage extends BiometricStorage { /// --daemonize --login " label="unconfined") @override Future linuxCheckAppArmorError() async { - if (!Platform.isLinux) { + if (operatingSystem != 'linux') { return false; } - final tmpStorage = await getStorage('appArmorCheck', - options: StorageFileInitOptions(authenticationRequired: false)); + final tmpStorage = await getStorage( + 'appArmorCheck', + options: StorageFileInitOptions(authenticationRequired: false), + ); _logger.finer('Checking app armor'); try { await tmpStorage.read(); @@ -371,7 +374,10 @@ class MethodChannelBiometricStorage extends BiometricStorage { return true; } _logger.warning( - 'Unknown error while checking for app armor.', e, stackTrace); + 'Unknown error while checking for app armor.', + e, + stackTrace, + ); // some other weird error? rethrow; } @@ -390,91 +396,77 @@ class MethodChannelBiometricStorage extends BiometricStorage { PromptInfo promptInfo = PromptInfo.defaultValues, }) async { try { - final result = await _channel.invokeMethod( - 'init', - { - 'name': name, - 'options': options?.toJson() ?? StorageFileInitOptions().toJson(), - 'forceInit': forceInit, - }, - ); + final result = await _channel.invokeMethod('init', { + 'name': name, + 'options': options?.toJson() ?? StorageFileInitOptions().toJson(), + 'forceInit': forceInit, + }); _logger.finest('getting storage. was created: $result'); - return BiometricStorageFile( - this, - name, - promptInfo, - ); + return BiometricStorageFile(this, name, promptInfo); } catch (e, stackTrace) { _logger.warning( - 'Error while initializing biometric storage.', e, stackTrace); + 'Error while initializing biometric storage.', + e, + stackTrace, + ); rethrow; } } @override - Future read( - String name, - PromptInfo promptInfo, - ) => - _transformErrors(_channel.invokeMethod('read', { - 'name': name, - ..._promptInfoForCurrentPlatform(promptInfo), - })); + Future read(String name, PromptInfo promptInfo) => _transformErrors( + _channel.invokeMethod('read', { + 'name': name, + ..._promptInfoForCurrentPlatform(promptInfo), + }), + ); @override - Future delete( - String name, - PromptInfo promptInfo, - ) => - _transformErrors(_channel.invokeMethod('delete', { - 'name': name, - ..._promptInfoForCurrentPlatform(promptInfo), - })); + Future delete(String name, PromptInfo promptInfo) => _transformErrors( + _channel.invokeMethod('delete', { + 'name': name, + ..._promptInfoForCurrentPlatform(promptInfo), + }), + ); @override - Future write( - String name, - String content, - PromptInfo promptInfo, - ) => - _transformErrors(_channel.invokeMethod('write', { - 'name': name, - 'content': content, - ..._promptInfoForCurrentPlatform(promptInfo), - })); - - Map _promptInfoForCurrentPlatform(PromptInfo promptInfo) { - // Don't expose Android configurations to other platforms - if (Platform.isAndroid) { - return { - 'androidPromptInfo': promptInfo.androidPromptInfo._toJson() - }; - } else if (Platform.isIOS) { - return { - 'iosPromptInfo': promptInfo.iosPromptInfo._toJson() - }; - } else if (Platform.isMacOS) { - return { + Future write(String name, String content, PromptInfo promptInfo) => + _transformErrors( + _channel.invokeMethod('write', { + 'name': name, + 'content': content, + ..._promptInfoForCurrentPlatform(promptInfo), + }), + ); + + Map _promptInfoForCurrentPlatform(PromptInfo promptInfo) => + switch (operatingSystem) { + // Don't expose Android configurations to other platforms. + 'android' => { + 'androidPromptInfo': promptInfo.androidPromptInfo._toJson(), + }, + 'ios' => { + 'iosPromptInfo': promptInfo.iosPromptInfo._toJson(), + }, // This is no typo, we use the same implementation on iOS and MacOS, // so we use the same parameter. - 'iosPromptInfo': promptInfo.macOsPromptInfo._toJson() + 'macos' => { + 'iosPromptInfo': promptInfo.macOsPromptInfo._toJson(), + }, + 'linux' => {}, + // Windows has no method channel implementation + // Web has a Noop implementation. + final os => throw StateError('Unsupported Platform $os'), }; - } else if (Platform.isLinux) { - return {}; - } else { - // Windows has no method channel implementation - // Web has a Noop implementation. - throw StateError('Unsupported Platform ${Platform.operatingSystem}'); - } - } Future _transformErrors(Future future) => future.catchError((Object error, StackTrace stackTrace) { if (error is PlatformException) { _logger.finest( - 'Error during plugin operation (details: ${error.details})', - error, - stackTrace); + 'Error during plugin operation (details: ${error.details})', + error, + stackTrace, + ); if (error.code.startsWith('AuthError:')) { return Future.error( AuthException( @@ -490,9 +482,12 @@ class MethodChannelBiometricStorage extends BiometricStorage { message.contains('AppArmor')) { _logger.fine('Got app armor error.'); return Future.error( - AuthException( - AuthExceptionCode.linuxAppArmorDenied, error.message!), - stackTrace); + AuthException( + AuthExceptionCode.linuxAppArmorDenied, + error.message!, + ), + stackTrace, + ); } } } diff --git a/lib/src/biometric_storage_web.dart b/lib/src/biometric_storage_web.dart index e4409c94..29d2e5c7 100644 --- a/lib/src/biometric_storage_web.dart +++ b/lib/src/biometric_storage_web.dart @@ -17,8 +17,7 @@ class BiometricStoragePluginWeb extends BiometricStorage { @override Future canAuthenticate({ StorageFileInitOptions? options, - }) async => - CanAuthenticateResponse.errorHwUnavailable; + }) async => CanAuthenticateResponse.errorHwUnavailable; @override Future getStorage( @@ -31,10 +30,7 @@ class BiometricStoragePluginWeb extends BiometricStorage { } @override - Future delete( - String name, - PromptInfo promptInfo, - ) async { + Future delete(String name, PromptInfo promptInfo) async { final oldValue = web.window.localStorage.getItem(name); web.window.localStorage.removeItem(name); return oldValue != null; @@ -44,19 +40,12 @@ class BiometricStoragePluginWeb extends BiometricStorage { Future linuxCheckAppArmorError() async => false; @override - Future read( - String name, - PromptInfo promptInfo, - ) async { + Future read(String name, PromptInfo promptInfo) async { return web.window.localStorage.getItem(name); } @override - Future write( - String name, - String content, - PromptInfo promptInfo, - ) async { + Future write(String name, String content, PromptInfo promptInfo) async { web.window.localStorage.setItem(name, content); } } diff --git a/lib/src/biometric_storage_win32.dart b/lib/src/biometric_storage_win32.dart index 27754c18..07a095cf 100644 --- a/lib/src/biometric_storage_win32.dart +++ b/lib/src/biometric_storage_win32.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:ffi'; +import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:logging/logging.dart'; @@ -14,7 +15,9 @@ class Win32BiometricStoragePlugin extends BiometricStorage { static const namePrefix = 'design.codeux.authpass.'; - /// Registers this class as the default instance of [PathProviderPlatform] + static const _userName = 'flutter.biometric_storage'; + + /// Registers this class as the default instance of [BiometricStorage]. static void registerWith() { BiometricStorage.instance = Win32BiometricStoragePlugin(); } @@ -40,97 +43,86 @@ class Win32BiometricStoragePlugin extends BiometricStorage { Future linuxCheckAppArmorError() async => false; @override - Future delete( - String name, - PromptInfo promptInfo, - ) async { - final namePointer = TEXT(name); - try { - final result = CredDelete(namePointer, CRED_TYPE.CRED_TYPE_GENERIC, 0); - if (result != TRUE) { - final errorCode = GetLastError(); - if (errorCode == WIN32_ERROR.ERROR_NOT_FOUND) { - _logger.fine('Unable to find credential of name $name'); - } else { - _logger.warning('Error ($result): $errorCode'); - } + Future delete(String name, PromptInfo promptInfo) async { + return using((arena) { + final result = CredDelete( + name.toPcwstr(allocator: arena), + CRED_TYPE_GENERIC, + ); + if (!result.value) { + _logFailure('deleting', name, result.error); return false; } - } finally { - calloc.free(namePointer); - } - return true; + return true; + }); } @override - Future read( - String name, - PromptInfo promptInfo, - ) async { + Future read(String name, PromptInfo promptInfo) async { _logger.finer('read($name)'); - final credPointer = calloc>(); - final namePointer = TEXT(name); - try { - if (CredRead(namePointer, CRED_TYPE.CRED_TYPE_GENERIC, 0, credPointer) != - TRUE) { - final errorCode = GetLastError(); - if (errorCode == WIN32_ERROR.ERROR_NOT_FOUND) { - _logger.fine('Unable to find credential of name $name'); - } else { - _logger.warning('Error: $errorCode ', - WindowsException(HRESULT_FROM_WIN32(errorCode))); - } + return using((arena) { + final credentialPointer = arena>(); + final result = CredRead( + name.toPcwstr(allocator: arena), + CRED_TYPE_GENERIC, + credentialPointer, + ); + if (!result.value) { + _logFailure('reading', name, result.error); return null; } - final cred = credPointer.value.ref; - final blob = cred.CredentialBlob.asTypedList(cred.CredentialBlobSize); - - _logger.fine('CredFree()'); - CredFree(credPointer.value); - - return utf8.decode(blob); - } finally { - _logger.fine('free(credPointer)'); - calloc.free(credPointer); - _logger.fine('free(namePointer)'); - calloc.free(namePointer); - _logger.fine('read($name) done.'); - } + final credential = credentialPointer.value; + try { + // asTypedList is a view onto memory owned by the credential, so the + // bytes have to be copied out before CredFree invalidates them. + final blob = Uint8List.fromList( + credential.ref.CredentialBlob.asTypedList( + credential.ref.CredentialBlobSize, + ), + ); + return utf8.decode(blob); + } finally { + CredFree(credential); + } + }); } @override - Future write( - String name, - String content, - PromptInfo promptInfo, - ) async { - _logger.fine('write()'); - final examplePassword = utf8.encode(content); - final blob = examplePassword.allocatePointer(); - final namePointer = TEXT(name); - final userNamePointer = TEXT('flutter.biometric_storage'); + Future write(String name, String content, PromptInfo promptInfo) async { + _logger.finer('write($name)'); + using((arena) { + final blob = utf8.encode(content); + // toNative rejects an empty list, but an empty value is a legitimate + // thing to store: a zero-length blob needs a valid pointer all the same. + final blobPointer = blob.isEmpty + ? arena() + : blob.toNative(allocator: arena); + final credential = arena() + ..ref.Type = CRED_TYPE_GENERIC + ..ref.TargetName = name.toPwstr(allocator: arena) + ..ref.Persist = CRED_PERSIST_LOCAL_MACHINE + ..ref.UserName = _userName.toPwstr(allocator: arena) + ..ref.CredentialBlob = blobPointer + ..ref.CredentialBlobSize = blob.length; - final credential = calloc() - ..ref.Type = CRED_TYPE.CRED_TYPE_GENERIC - ..ref.TargetName = namePointer - ..ref.Persist = CRED_PERSIST.CRED_PERSIST_LOCAL_MACHINE - ..ref.UserName = userNamePointer - ..ref.CredentialBlob = blob - ..ref.CredentialBlobSize = examplePassword.length; - try { final result = CredWrite(credential, 0); - if (result != TRUE) { - final errorCode = GetLastError(); + if (!result.value) { throw BiometricStorageException( - 'Error writing credential $name ($result): $errorCode'); + 'Error writing credential $name: ${result.error} ' + '(${WindowsException(HRESULT_FROM_WIN32(result.error))})', + ); } - } finally { - _logger.fine('free'); - calloc.free(blob); - calloc.free(credential); - calloc.free(namePointer); - calloc.free(userNamePointer); - _logger.fine('free done'); + }); + } + + void _logFailure(String action, String name, WIN32_ERROR error) { + if (error == ERROR_NOT_FOUND) { + _logger.fine('Unable to find credential of name $name'); + } else { + _logger.warning( + 'Error $action credential $name: $error', + WindowsException(HRESULT_FROM_WIN32(error)), + ); } } } diff --git a/lib/src/platform_os_io.dart b/lib/src/platform_os_io.dart new file mode 100644 index 00000000..deb5c6d1 --- /dev/null +++ b/lib/src/platform_os_io.dart @@ -0,0 +1,14 @@ +import 'dart:io' as io; + +/// The host operating system, in the spelling `dart:io` uses: +/// `android`, `ios`, `macos`, `linux`, `windows`, `fuchsia`. +/// +/// Deliberately `dart:io`'s own value rather than Flutter's +/// `defaultTargetPlatform`. The question this answers is "what OS is on the +/// other side of the method channel", and `defaultTargetPlatform` answers a +/// different one — it reports the platform Flutter is *emulating*, and an app +/// that sets `debugDefaultTargetPlatformOverride` would make us send iOS +/// arguments to an Android plugin. +/// +/// See [platform_os_web.dart] for the counterpart the web build gets. +String get operatingSystem => io.Platform.operatingSystem; diff --git a/lib/src/platform_os_web.dart b/lib/src/platform_os_web.dart new file mode 100644 index 00000000..d2c3df9f --- /dev/null +++ b/lib/src/platform_os_web.dart @@ -0,0 +1,13 @@ +/// The web counterpart of [platform_os_io.dart], which exists so that +/// `lib/src/biometric_storage.dart` need not import `dart:io` unconditionally. +/// That import is what made the package report as WebAssembly-incompatible. +/// +/// Returns a value rather than throwing. Every caller is inside +/// `MethodChannelBiometricStorage`, which the web build never instantiates — +/// `BiometricStoragePluginWeb.registerWith` replaces the instance — so this +/// should be unreachable. If some path does reach it, `'web'` degrades the way +/// the callers already expect: `canAuthenticate` reports the platform as +/// unsupported, the AppArmor check reports no error, and the prompt-info switch +/// throws the same `StateError` it throws for any platform without a method +/// channel. Throwing here would instead turn an unreachable branch into a crash. +String get operatingSystem => 'web'; diff --git a/macos/Classes/BiometricStorageMacOSPlugin.swift b/macos/Classes/BiometricStorageMacOSPlugin.swift deleted file mode 100644 index 58c30a45..00000000 --- a/macos/Classes/BiometricStorageMacOSPlugin.swift +++ /dev/null @@ -1,20 +0,0 @@ -import FlutterMacOS -import Cocoa - -public class BiometricStorageMacOSPlugin: NSObject, FlutterPlugin { - - private let impl = BiometricStorageImpl(storageError: { (code, message, details) -> Any in - FlutterError(code: code, message: message, details: details) - }, storageMethodNotImplemented: FlutterMethodNotImplemented) - - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "biometric_storage", binaryMessenger: registrar.messenger) - let instance = BiometricStorageMacOSPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - impl.handle(StorageMethodCall(method: call.method, arguments: call.arguments), result: result) - } - -} diff --git a/macos/Classes/BiometricStoragePlugin.swift b/macos/Classes/BiometricStoragePlugin.swift deleted file mode 100644 index 65d25878..00000000 --- a/macos/Classes/BiometricStoragePlugin.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Cocoa -import FlutterMacOS - -public class BiometricStoragePlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "biometric_storage", binaryMessenger: registrar.messenger) - let instance = BiometricStoragePlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) - default: - result(FlutterMethodNotImplemented) - } - } -} diff --git a/macos/biometric_storage.podspec b/macos/biometric_storage.podspec deleted file mode 100644 index 7bc528d6..00000000 --- a/macos/biometric_storage.podspec +++ /dev/null @@ -1,22 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint biometric_storage.podspec' to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'biometric_storage' - s.version = '0.0.1' - s.summary = 'A new flutter plugin project.' - s.description = <<-DESC -A new flutter plugin project. - DESC - s.homepage = 'http://example.com' - s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'FlutterMacOS' - - s.platform = :osx, '10.11' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - s.swift_version = '5.0' -end diff --git a/pubspec.yaml b/pubspec.yaml index 5e6a6920..4a0b7374 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,12 +2,12 @@ name: biometric_storage description: | Secure Storage: Encrypted data store optionally secured by biometric lock with support for iOS, Android, MacOS. Partial support for Linux, Windows and web (localStorage). -version: 5.2.0-dev.1 +version: 6.0.0-dev.1 homepage: https://github.com/authpass/biometric_storage/ environment: - sdk: '>=3.2.0 <4.0.0' - flutter: ">=2.8.0" + sdk: ^3.10.0 + flutter: ">=3.44.0" dependencies: flutter: @@ -17,14 +17,14 @@ dependencies: logging: ">=1.0.0 <2.0.0" plugin_platform_interface: ">=2.0.0 <3.0.0" - ffi: '>=1.0.0 <3.0.0' - win32: '>=2.0.0 <6.0.0' + ffi: '>=2.1.0 <3.0.0' + win32: '>=6.0.1 <7.0.0' web: ">=0.5.0 <2.0.0" dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -42,13 +42,17 @@ flutter: pluginClass: BiometricStoragePlugin ios: pluginClass: BiometricStoragePlugin + sharedDarwinSource: true macos: - pluginClass: BiometricStorageMacOSPlugin + pluginClass: BiometricStoragePlugin + sharedDarwinSource: true linux: pluginClass: BiometricStoragePlugin windows: + # No dartFileName: the generated registrant imports the package's own + # barrel, which re-exports Win32BiometricStoragePlugin. (The `fileName` + # key that used to sit here is a web-only key and was never read.) dartPluginClass: Win32BiometricStoragePlugin - fileName: src/biometric_storage_win32.dart web: pluginClass: BiometricStoragePluginWeb fileName: src/biometric_storage_web.dart diff --git a/test/biometric_storage_test.dart b/test/biometric_storage_test.dart index a6874fc2..8804bff4 100644 --- a/test/biometric_storage_test.dart +++ b/test/biometric_storage_test.dart @@ -1,4 +1,9 @@ -import 'package:biometric_storage/src/biometric_storage.dart'; +// Deliberately the public barrel rather than `src/`: it re-exports the Windows +// implementation on every `dart.library.io` platform, so importing it here is +// what makes `flutter test` on macOS or Linux compile the win32 bindings. A +// breaking change in package:win32 shows up as a failing test run rather than +// as a broken iOS build in somebody else's app. +import 'package:biometric_storage/biometric_storage.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -10,11 +15,11 @@ void main() { setUp(() { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) async { - if (methodCall.method == 'canAuthenticate') { - return 'ErrorUnknown'; - } - throw PlatformException(code: 'NotImplemented'); - }); + if (methodCall.method == 'canAuthenticate') { + return 'ErrorUnknown'; + } + throw PlatformException(code: 'NotImplemented'); + }); }); tearDown(() { @@ -27,6 +32,10 @@ void main() { expect(result, CanAuthenticateResponse.unsupported); }); + test('the windows implementation is part of the compiled library', () { + expect(Win32BiometricStoragePlugin, isNotNull); + }); + group('StorageFileInitOptions', () { test('omits the keychain access group by default', () { expect( diff --git a/test/biometric_storage_win32_test.dart b/test/biometric_storage_win32_test.dart new file mode 100644 index 00000000..f5a675fa --- /dev/null +++ b/test/biometric_storage_win32_test.dart @@ -0,0 +1,75 @@ +@TestOn('windows') +library; + +// Deliberately `src/` rather than the barrel: the barrel reaches this class +// through a conditional export whose *default* branch is an empty stub, and the +// analyzer resolves to that stub, so calls through it would not type-check. +// biometric_storage_test.dart keeps the barrel import, which is what makes the +// bindings compile on every host. +import 'package:biometric_storage/src/biometric_storage.dart'; +import 'package:biometric_storage/src/biometric_storage_win32.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// The only place the win32 bindings are *executed* rather than merely +/// compiled. Everything else in the suite compiles them on whatever host it +/// runs on, which catches an API break but not a wrong pointer. +/// +/// This talks to the real Windows credential store, which is available headless +/// — including on CI runners. Each test uses a name of its own and deletes it +/// again, so it cannot collide with a developer's own credentials. +void main() { + final plugin = Win32BiometricStoragePlugin(); + + Future storageForThisTest() async { + final name = 'test_${DateTime.now().microsecondsSinceEpoch}'; + final file = await plugin.getStorage(name); + addTearDown(() => file.delete()); + return file; + } + + test('a value written can be read back, and is gone after delete', () async { + final file = await storageForThisTest(); + + expect(await file.read(), isNull, reason: 'nothing stored yet'); + + // Deliberately not ASCII: the blob round-trips through utf8. + await file.write('hello wörld'); + expect(await file.read(), 'hello wörld'); + + await file.delete(); + expect(await file.read(), isNull); + }); + + test('an empty value round-trips', () async { + final file = await storageForThisTest(); + + // Regression: this used to reach Uint8List.toNative(), which rejects an + // empty list, so writing an empty value threw. + await file.write(''); + expect(await file.read(), ''); + }); + + test('writing twice keeps the second value', () async { + final file = await storageForThisTest(); + + await file.write('first'); + await file.write('second'); + expect(await file.read(), 'second'); + }); + + test('the credential name keeps its historical prefix', () async { + // Every other test here writes and reads through the same prefix, so all of + // them would stay green if it changed — while every existing user's stored + // value was orphaned. This pins the on-disk contract. `getStorage` only + // builds the name; it does not touch the credential store. + final file = await plugin.getStorage('example'); + expect(file.name, 'design.codeux.authpass.example'); + }); + + test('reading an unknown name returns null rather than throwing', () async { + final file = await plugin.getStorage( + 'test_absent_${DateTime.now().microsecondsSinceEpoch}', + ); + expect(await file.read(), isNull); + }); +}