Skip to content

commit c8a6ff0

abduznik edited this page May 23, 2026 · 1 revision

fix(windows): replace ATL-dependent plugin with pure Dart FFI win32 backend

Commit: c8a6ff0ba90e3d6c0f2181d12484aeedf8ed8709

Author: Claude

Date: 2026-03-23

Message

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.

Files Changed

.../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.dart
  • packages/flutter_secure_storage_windows/pubspec.yaml
  • pubspec.yaml

Diff

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:

Clone this wiki locally