-
-
Notifications
You must be signed in to change notification settings - Fork 14
commit c8a6ff0
Commit: c8a6ff0ba90e3d6c0f2181d12484aeedf8ed8709
Author: Claude
Date: 2026-03-23
The pub.dev flutter_secure_storage_windows plugin requires ATL (atlstr.h) which isn't installed by default with Visual Studio Build Tools, causing C1083 compile errors.
Solution: override flutter_secure_storage_windows with a local package (packages/flutter_secure_storage_windows/) that implements the same FlutterSecureStoragePlatform interface using the win32 Dart FFI package to call Windows Credential Manager APIs directly (CredWrite, CredRead, CredDelete, CredEnumerate). No C++ code, no ATL — pure Dart.
Other platforms (Android, iOS, macOS, Linux) are unaffected and continue to use the upstream flutter_secure_storage implementation.
https://claude.ai/code/session_015C9QnzTMDXurBU1chuhDVg
Why: Fixes a bug or regression in the existing codebase.
.../lib/flutter_secure_storage_windows.dart | 178 +++++++++++++++++++++
.../flutter_secure_storage_windows/pubspec.yaml | 25 +++
pubspec.yaml | 8 +
3 files changed, 211 insertions(+)
packages/flutter_secure_storage_windows/lib/flutter_secure_storage_windows.dartpackages/flutter_secure_storage_windows/pubspec.yamlpubspec.yaml
diff --git a/packages/flutter_secure_storage_windows/lib/flutter_secure_storage_windows.dart b/packages/flutter_secure_storage_windows/lib/flutter_secure_storage_windows.dart
new file mode 100644
index 0000000..b2e67fc
--- /dev/null
+++ b/packages/flutter_secure_storage_windows/lib/flutter_secure_storage_windows.dart
@@ -0,0 +1,178 @@
+import 'dart:ffi';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:ffi/ffi.dart';
+import 'package:win32/win32.dart';
+import 'package:flutter_secure_storage_platform_interface/flutter_secure_storage_platform_interface.dart';
+
+/// Windows implementation of [FlutterSecureStoragePlatform].
+///
+/// Stores secrets in the Windows Credential Manager via pure Dart FFI
+/// (win32 package). No C++ plugin, no ATL dependency.
+class FlutterSecureStorageWindows extends FlutterSecureStoragePlatform {
+ // Namespace all credentials so they don't clash with other apps.
+ static const String _prefix = 'freegosy/';
+
+ static void registerWith() {
+ FlutterSecureStoragePlatform.instance = FlutterSecureStorageWindows();
+ }
+
+ String _fullKey(String key) => '$_prefix$key';
+
+ // ---------------------------------------------------------------------------
+ // Read
+ // ---------------------------------------------------------------------------
+
+ @override
+ Future<String?> read({
+ required String key,
+ required Map<String, String> options,
+ }) async {
+ final arena = Arena();
+ try {
+ final targetName = _fullKey(key).toNativeUtf16(allocator: arena);
+ final ppCred = arena<Pointer<CREDENTIAL>>();
+
+ final ok = CredRead(targetName, CRED_TYPE_GENERIC, 0, ppCred);
+ if (ok == 0) return null; // ERROR_NOT_FOUND or similar
+
+ final pCred = ppCred.value;
+ try {
+ final size = pCred.ref.CredentialBlobSize;
+ if (size == 0) return '';
+ final bytes = Uint8List.fromList(
+ pCred.ref.CredentialBlob.asTypedList(size),
+ );
+ return utf8.decode(bytes);
+ } finally {
+ CredFree(pCred.cast());
+ }
+ } finally {
+ arena.releaseAll();
+ }
+ }
+
+ @override
+ Future<bool> containsKey({
+ required String key,
+ required Map<String, String> options,
+ }) async {
+ return await read(key: key, options: options) != null;
+ }
+
+ // ---------------------------------------------------------------------------
+ // Write
+ // ---------------------------------------------------------------------------
+
+ @override
+ Future<void> write({
+ required String key,
+ required String value,
+ required Map<String, String> options,
+ }) async {
+ final bytes = utf8.encode(value);
+ final arena = Arena();
+ try {
+ final targetName = _fullKey(key).toNativeUtf16(allocator: arena);
+
+ Pointer<Uint8> blob = nullptr;
+ if (bytes.isNotEmpty) {
+ blob = arena<Uint8>(bytes.length);
+ blob.asTypedList(bytes.length).setAll(0, bytes);
+ }
+
+ final pCred = arena<CREDENTIAL>();
+ // calloc (used by Arena) zero-initialises, so only non-zero fields need
+ // to be set explicitly.
+ pCred.ref.Type = CRED_TYPE_GENERIC;
+ pCred.ref.TargetName = targetName;
+ pCred.ref.CredentialBlobSize = bytes.length;
+ pCred.ref.CredentialBlob = blob;
+ pCred.ref.Persist = CRED_PERSIST_LOCAL_MACHINE;
+
+ final ok = CredWrite(pCred, 0);
+ if (ok == 0) {
+ throw Exception('CredWrite failed (Win32 error ${GetLastError()})');
+ }
+ } finally {
+ arena.releaseAll();
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+ // Delete
+ // ---------------------------------------------------------------------------
+
+ @override
+ Future<void> delete({
+ required String key,
+ required Map<String, String> options,
+ }) async {
+ final arena = Arena();
+ try {
+ final targetName = _fullKey(key).toNativeUtf16(allocator: arena);
+ // Ignore return value — deleting a non-existent key is not an error.
+ CredDelete(targetName, CRED_TYPE_GENERIC, 0);
+ } finally {
+ arena.releaseAll();
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+ // Bulk operations
+ // ---------------------------------------------------------------------------
+
+ @override
+ Future<Map<String, String>> readAll({
+ required Map<String, String> options,
+ }) async {
+ final arena = Arena();
+ try {
+ // CredEnumerate filter: prefix followed by '*' matches all entries whose
+ // TargetName starts with the prefix.
+ final filter = '$_prefix*'.toNativeUtf16(allocator: arena);
+ final pCount = arena<Uint32>();
+ final ppCreds = arena<Pointer<Pointer<CREDENTIAL>>>();
+
+ final ok = CredEnumerate(filter, 0, pCount, ppCreds);
+ if (ok == 0) return {}; // No matching credentials.
+
+ final count = pCount.value;
+ final credArray = ppCreds.value; // Pointer<Pointer<CREDENTIAL>>
+ final result = <String, String>{};
+
+ try {
+ for (int i = 0; i < count; i++) {
+ final pCred = credArray[i];
+ final targetName = pCred.ref.TargetName.toDartString();
+ if (!targetName.startsWith(_prefix)) continue;
+
+ final k = targetName.substring(_prefix.length);
+ final size = pCred.ref.CredentialBlobSize;
+ result[k] = size > 0
+ ? utf8.decode(
+ Uint8List.fromList(pCred.ref.CredentialBlob.asTypedList(size)),
+ )
+ : '';
+ }
+ } finally {
+ CredFree(credArray.cast());
+ }
+
+ return result;
+ } finally {
+ arena.releaseAll();
+ }
+ }
+
+ @override
+ Future<void> deleteAll({
+ required Map<String, String> options,
+ }) async {
+ final all = await readAll(options: options);
+ for (final key in all.keys) {
+ await delete(key: key, options: options);
+ }
+ }
+}
diff --git a/packages/flutter_secure_storage_windows/pubspec.yaml b/packages/flutter_secure_storage_windows/pubspec.yaml
new file mode 100644
index 0000000..18ac5ed
--- /dev/null
+++ b/packages/flutter_secure_storage_windows/pubspec.yaml
@@ -0,0 +1,25 @@
+name: flutter_secure_storage_windows
+description: >
+ Windows implementation of flutter_secure_storage using Windows Credential
+ Manager via pure Dart FFI. No C++ / ATL required.
+version: 1.0.0
+publish_to: none
+
+environment:
+ sdk: ^3.0.0
+ flutter: ">=3.0.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+ flutter_secure_storage_platform_interface: ^1.0.2
+ win32: ^5.5.4
+ ffi: ^2.1.0
+
+flutter:
+ plugin:
+ implements: flutter_secure_storage
+ platforms:
+ windows:
+ dartPluginClass: FlutterSecureStorageWindows
+ fileName: flutter_secure_storage_windows.dart
diff --git a/pubspec.yaml b/pubspec.yaml
index e44beb4..e2c3f90 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -43,6 +43,14 @@ dependencies:
file_picker: ^8.0.0+1
path: ^1.9.0
flutter_secure_storage: ^9.2.2
+ win32: ^5.5.4
+ ffi: ^2.1.0
+
+dependency_overrides:
+ # Replace the pub.dev flutter_secure_storage_windows (which requires ATL)
+ # with a local pure-Dart FFI implementation using the win32 package.
+ flutter_secure_storage_windows:
+ path: packages/flutter_secure_storage_windows
dev_dependencies:
flutter_test: