From f8064f271f2e35139c69e8db40724c740e0cf865 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Fri, 1 Aug 2025 15:50:09 +0200 Subject: [PATCH 01/13] fix(types): comprehensive bip39 validation --- packages/dragon_logs/example/pubspec.lock | 22 ++-- packages/komodo_defi_sdk/example/pubspec.lock | 16 +-- .../komodo_defi_types/analysis_options.yaml | 1 + .../lib/src/utils/mnemonic_validator.dart | 122 +++++++++++++++++- packages/komodo_defi_types/pubspec.yaml | 1 + packages/komodo_wallet_cli/pubspec.lock | 2 +- playground/pubspec.lock | 10 +- products/dex_dungeon/pubspec.lock | 16 +-- 8 files changed, 152 insertions(+), 38 deletions(-) diff --git a/packages/dragon_logs/example/pubspec.lock b/packages/dragon_logs/example/pubspec.lock index d81b2a59..901c3cc2 100644 --- a/packages/dragon_logs/example/pubspec.lock +++ b/packages/dragon_logs/example/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" boolean_selector: dependency: transitive description: @@ -63,15 +63,15 @@ packages: path: ".." relative: true source: path - version: "1.0.4" + version: "1.1.0" fake_async: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -136,10 +136,10 @@ packages: dependency: transitive description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" js: dependency: transitive description: @@ -152,10 +152,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -421,10 +421,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" web: dependency: transitive description: diff --git a/packages/komodo_defi_sdk/example/pubspec.lock b/packages/komodo_defi_sdk/example/pubspec.lock index 20c959f6..82867744 100644 --- a/packages/komodo_defi_sdk/example/pubspec.lock +++ b/packages/komodo_defi_sdk/example/pubspec.lock @@ -315,56 +315,56 @@ packages: path: "../../komodo_coins" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_defi_framework: dependency: "direct overridden" description: path: "../../komodo_defi_framework" relative: true source: path - version: "0.2.0" + version: "0.3.0+0" komodo_defi_local_auth: dependency: "direct overridden" description: path: "../../komodo_defi_local_auth" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_defi_rpc_methods: dependency: "direct overridden" description: path: "../../komodo_defi_rpc_methods" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_defi_sdk: dependency: "direct main" description: path: ".." relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_defi_types: dependency: "direct main" description: path: "../../komodo_defi_types" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_ui: dependency: "direct main" description: path: "../../komodo_ui" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_wallet_build_transformer: dependency: "direct overridden" description: path: "../../komodo_wallet_build_transformer" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" leak_tracker: dependency: transitive description: diff --git a/packages/komodo_defi_types/analysis_options.yaml b/packages/komodo_defi_types/analysis_options.yaml index a60f3c10..a36ca9e8 100644 --- a/packages/komodo_defi_types/analysis_options.yaml +++ b/packages/komodo_defi_types/analysis_options.yaml @@ -3,6 +3,7 @@ analyzer: public_member_api_docs: ignore invalid_annotation_target: ignore use_if_null_to_convert_nulls_to_bools: ignore + omit_local_variable_types: ignore include: package:very_good_analysis/analysis_options.6.0.0.yaml \ No newline at end of file diff --git a/packages/komodo_defi_types/lib/src/utils/mnemonic_validator.dart b/packages/komodo_defi_types/lib/src/utils/mnemonic_validator.dart index 9d2cb0ce..db4e8084 100644 --- a/packages/komodo_defi_types/lib/src/utils/mnemonic_validator.dart +++ b/packages/komodo_defi_types/lib/src/utils/mnemonic_validator.dart @@ -1,8 +1,12 @@ // TODO: This may be better suited to be moved to the UI package. +import 'dart:typed_data'; + +import 'package:crypto/crypto.dart'; import 'package:flutter/services.dart' show rootBundle; final Set _validMnemonicWords = {}; +final Map _wordToIndex = {}; const _validLengths = [12, 15, 18, 21, 24]; @@ -11,6 +15,8 @@ enum MnemonicFailedReason { customNotSupportedForHd, customNotAllowed, invalidLength, + invalidWord, + invalidChecksum, } class MnemonicValidator { @@ -19,7 +25,13 @@ class MnemonicValidator { final wordlist = await rootBundle.loadString( 'packages/komodo_defi_types/assets/bip-0039/english-wordlist.txt', ); - _validMnemonicWords.addAll(wordlist.split('\n').map((w) => w.trim())); + final words = wordlist.split('\n').map((w) => w.trim()).toList(); + _validMnemonicWords.addAll(words); + + // Build word-to-index mapping for BIP39 validation + for (int i = 0; i < words.length; i++) { + _wordToIndex[words[i]] = i; + } } } @@ -50,19 +62,31 @@ class MnemonicValidator { return MnemonicFailedReason.invalidLength; } - final isValidBip39 = validateBip39(input); + // Get detailed validation error if any + final detailedError = _getDetailedValidationError(input); - if (isValidBip39) { + // If no error, it's a valid BIP39 mnemonic + if (detailedError == null) { return null; } + // For specific errors, return them directly + if (detailedError == MnemonicFailedReason.empty || + detailedError == MnemonicFailedReason.invalidLength) { + return detailedError; + } + + // For HD wallets, any BIP39 error means it's not supported if (isHd) { return MnemonicFailedReason.customNotSupportedForHd; } + // For non-HD wallets, check if custom seeds are allowed if (!allowCustomSeed) { return MnemonicFailedReason.customNotAllowed; } + + // Custom seed is allowed, so return null (valid) return null; } @@ -73,7 +97,7 @@ class MnemonicValidator { 'Call MnemonicValidator.init() first.', ); - final inputWordsList = input.split(' '); + final inputWordsList = input.trim().split(' '); if (!_validLengths.contains(inputWordsList.length)) { return false; @@ -84,6 +108,94 @@ class MnemonicValidator { )) { return false; } - return true; + + // Validate checksum + return _validateChecksum(inputWordsList); + } + + /// Validates the BIP39 checksum for a given mnemonic + bool _validateChecksum(List words) { + try { + // Convert words to indices + final indices = []; + for (final word in words) { + final index = _wordToIndex[word]; + if (index == null) return false; + indices.add(index); + } + + // Convert indices to binary string (11 bits per word) + final binaryString = + indices.map((i) => i.toRadixString(2).padLeft(11, '0')).join(); + + // Calculate entropy and checksum lengths + final totalBits = binaryString.length; + final checksumBits = totalBits ~/ 33; // Checksum is 1 bit per 3 words + final entropyBits = totalBits - checksumBits; + + // Extract entropy and checksum + final entropyBinary = binaryString.substring(0, entropyBits); + final checksumBinary = binaryString.substring(entropyBits); + + // Convert entropy to bytes + final entropyBytes = _binaryToBytes(entropyBinary); + + // Calculate SHA256 hash of entropy + final hash = sha256.convert(entropyBytes); + final hashBits = _bytesToBinary(hash.bytes); + + // Extract first checksumBits from hash + final calculatedChecksum = hashBits.substring(0, checksumBits); + + // Compare checksums + return checksumBinary == calculatedChecksum; + } catch (e) { + return false; + } + } + + /// Converts a binary string to bytes + Uint8List _binaryToBytes(String binary) { + final bytes = []; + for (int i = 0; i < binary.length; i += 8) { + final byte = binary.substring(i, i + 8); + bytes.add(int.parse(byte, radix: 2)); + } + return Uint8List.fromList(bytes); + } + + /// Converts bytes to binary string + String _bytesToBinary(List bytes) { + return bytes.map((b) => b.toRadixString(2).padLeft(8, '0')).join(); } + + /// Gets detailed validation error for a mnemonic + MnemonicFailedReason? _getDetailedValidationError(String input) { + final words = input.trim().split(' '); + + if (words.isEmpty || words.every((w) => w.isEmpty)) { + return MnemonicFailedReason.empty; + } + + if (!_validLengths.contains(words.length)) { + return MnemonicFailedReason.invalidLength; + } + + // Check for invalid words + for (final word in words) { + if (!_validMnemonicWords.contains(word)) { + return MnemonicFailedReason.invalidWord; + } + } + + // Check checksum + if (!_validateChecksum(words)) { + return MnemonicFailedReason.invalidChecksum; + } + + return null; + } + + /// Checks if the wordlist has been initialized + bool get isInitialized => _validMnemonicWords.isNotEmpty; } diff --git a/packages/komodo_defi_types/pubspec.yaml b/packages/komodo_defi_types/pubspec.yaml index 4aa6b12f..8892f76f 100644 --- a/packages/komodo_defi_types/pubspec.yaml +++ b/packages/komodo_defi_types/pubspec.yaml @@ -10,6 +10,7 @@ environment: flutter: ">=3.29.0 <3.30.0" dependencies: + crypto: ^3.0.6 decimal: ^3.2.1 equatable: ^2.0.7 flutter: diff --git a/packages/komodo_wallet_cli/pubspec.lock b/packages/komodo_wallet_cli/pubspec.lock index 7f826679..01ec342e 100644 --- a/packages/komodo_wallet_cli/pubspec.lock +++ b/packages/komodo_wallet_cli/pubspec.lock @@ -191,7 +191,7 @@ packages: path: "../komodo_wallet_build_transformer" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" lints: dependency: transitive description: diff --git a/playground/pubspec.lock b/playground/pubspec.lock index 05aeabd6..fe1e8ba0 100644 --- a/playground/pubspec.lock +++ b/playground/pubspec.lock @@ -381,35 +381,35 @@ packages: path: "../packages/komodo_coins" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_defi_framework: dependency: "direct main" description: path: "../packages/komodo_defi_framework" relative: true source: path - version: "0.2.0" + version: "0.3.0+0" komodo_defi_rpc_methods: dependency: "direct overridden" description: path: "../packages/komodo_defi_rpc_methods" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_defi_types: dependency: "direct main" description: path: "../packages/komodo_defi_types" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_wallet_build_transformer: dependency: "direct overridden" description: path: "../packages/komodo_wallet_build_transformer" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" leak_tracker: dependency: transitive description: diff --git a/products/dex_dungeon/pubspec.lock b/products/dex_dungeon/pubspec.lock index 33569640..1ba798c3 100644 --- a/products/dex_dungeon/pubspec.lock +++ b/products/dex_dungeon/pubspec.lock @@ -497,56 +497,56 @@ packages: path: "../../packages/komodo_coins" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_defi_framework: dependency: transitive description: path: "../../packages/komodo_defi_framework" relative: true source: path - version: "0.2.0" + version: "0.3.0+0" komodo_defi_local_auth: dependency: transitive description: path: "../../packages/komodo_defi_local_auth" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_defi_rpc_methods: dependency: transitive description: path: "../../packages/komodo_defi_rpc_methods" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_defi_sdk: dependency: "direct main" description: path: "../../packages/komodo_defi_sdk" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_defi_types: dependency: "direct main" description: path: "../../packages/komodo_defi_types" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_ui: dependency: transitive description: path: "../../packages/komodo_ui" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" komodo_wallet_build_transformer: dependency: transitive description: path: "../../packages/komodo_wallet_build_transformer" relative: true source: path - version: "0.2.0+0" + version: "0.3.0+0" leak_tracker: dependency: transitive description: From 1fae456e04a24013c2ef576349dc69ff9996aaf0 Mon Sep 17 00:00:00 2001 From: Francois Date: Fri, 1 Aug 2025 17:46:34 +0200 Subject: [PATCH 02/13] fix(market-data-price): try fetch current price from komodo price repository first before cex repository (#167) * fix(market-data-manager): default to KomodoPriceRepository Binance CexRepository supports a limited number of symbols and no longer lists KMD price history after the delisting * refactor(market-data-manager): deduplicate and clarify intent * refactor(review): remove zero check and add log statements also simplify the priceIfKnown function --- .../src/market_data/market_data_manager.dart | 122 ++++++++++++++---- 1 file changed, 98 insertions(+), 24 deletions(-) diff --git a/packages/komodo_defi_sdk/lib/src/market_data/market_data_manager.dart b/packages/komodo_defi_sdk/lib/src/market_data/market_data_manager.dart index 2bb6fdb6..9c43d5bf 100644 --- a/packages/komodo_defi_sdk/lib/src/market_data/market_data_manager.dart +++ b/packages/komodo_defi_sdk/lib/src/market_data/market_data_manager.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:collection'; +import 'dart:developer'; import 'package:decimal/decimal.dart'; import 'package:komodo_cex_market_data/komodo_cex_market_data.dart'; @@ -127,6 +128,73 @@ class CexMarketDataManager implements MarketDataManager { return assetId.symbol.configSymbol; } + /// Determines if the request can be handled by Komodo price repository + /// NOTE: currently only supports USDT and USD fiat currencies + /// and does not support specific price dates (always uses current price) + bool _canUseKomodoRepository({ + DateTime? priceDate, + String fiatCurrency = 'usdt', + }) { + return priceDate == null && + (fiatCurrency.toLowerCase() == 'usdt' || + fiatCurrency.toLowerCase() == 'usd'); + } + + /// Attempts to get price from Komodo repository + Future _tryKomodoPrice(String symbol) async { + try { + final komodoPrices = await _komodoPriceRepository.getKomodoPrices(); + final priceData = komodoPrices[symbol]; + + if (priceData != null) { + return Decimal.parse(priceData.price.toString()); + } + } catch (e) { + log( + 'Failed to get price from Komodo repository for symbol: $symbol', + error: e, + ); + // Ignore errors and fall back + } + return null; + } + + /// Gets price with automatic fallback logic + Future _getPriceWithFallback( + AssetId assetId, { + DateTime? priceDate, + String fiatCurrency = 'usdt', + }) async { + final symbol = _getTradingSymbol(assetId); + + // Try Komodo repository first if applicable + if (_canUseKomodoRepository( + priceDate: priceDate, + fiatCurrency: fiatCurrency, + )) { + final komodoPrice = await _tryKomodoPrice(symbol); + if (komodoPrice != null) { + return komodoPrice; + } + } + + // Fallback to CEX repository + try { + final priceDouble = await _priceRepository.getCoinFiatPrice( + symbol, + priceDate: priceDate, + fiatCoinId: fiatCurrency, + ); + return Decimal.parse(priceDouble.toString()); + } catch (e) { + log( + 'Failed to get price from Cex Repository for symbol $symbol', + error: e, + ); + return null; + } + } + @override Decimal? priceIfKnown( AssetId assetId, { @@ -143,6 +211,7 @@ class CexMarketDataManager implements MarketDataManager { fiatCurrency: fiatCurrency, ); + // Check cache first return _priceCache[cacheKey]; } @@ -169,23 +238,20 @@ class CexMarketDataManager implements MarketDataManager { return cachedPrice; } - try { - final priceDouble = await _priceRepository.getCoinFiatPrice( - _getTradingSymbol(assetId), - priceDate: priceDate, - fiatCoinId: fiatCurrency, - ); + final price = await _getPriceWithFallback( + assetId, + priceDate: priceDate, + fiatCurrency: fiatCurrency, + ); - // Convert double to Decimal via string - final price = Decimal.parse(priceDouble.toString()); + if (price == null) { + throw StateError('Failed to get price for ${assetId.name}'); + } - // Cache the result - _priceCache[cacheKey] = price; + // Cache the result + _priceCache[cacheKey] = price; - return price; - } catch (e) { - throw StateError('Failed to get price for ${assetId.name}: $e'); - } + return price; } @override @@ -208,23 +274,31 @@ class CexMarketDataManager implements MarketDataManager { return cachedPrice; } + // Check if ticker is known in CEX repository for fallback scenarios final tradingSymbol = _getTradingSymbol(assetId); final isKnownTicker = _knownTickers?.contains(tradingSymbol) ?? false; - if (!isKnownTicker) { + // If not using Komodo repository and ticker is not known in CEX, return null + if (!_canUseKomodoRepository( + priceDate: priceDate, + fiatCurrency: fiatCurrency, + ) && + !isKnownTicker) { return null; } - try { - final price = await fiatPrice( - assetId, - priceDate: priceDate, - fiatCurrency: fiatCurrency, - ); - return price; - } catch (_) { - return null; + final price = await _getPriceWithFallback( + assetId, + priceDate: priceDate, + fiatCurrency: fiatCurrency, + ); + + if (price != null) { + // Cache the result + _priceCache[cacheKey] = price; } + + return price; } @override From 0303d39c0aea143be66ebe58a4c8bbb78ad0df25 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Fri, 1 Aug 2025 15:52:14 +0200 Subject: [PATCH 03/13] chore: switch KDF to `dev` Switch KDF to `dev` to allow usage of unreleased changes (particularly related to fees/priorities) --- .../app_build/build_config.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/komodo_defi_framework/app_build/build_config.json b/packages/komodo_defi_framework/app_build/build_config.json index 6bf6b554..d8b156c4 100644 --- a/packages/komodo_defi_framework/app_build/build_config.json +++ b/packages/komodo_defi_framework/app_build/build_config.json @@ -1,7 +1,7 @@ { "api": { - "api_commit_hash": "a9fbf6096257b802e0aac4d936dcc5d2f0c28461", - "branch": "main", + "api_commit_hash": "68bc4ebfa95e5bb24cc698346e023af72a66b4ae", + "branch": "dev", "fetch_at_build_enabled": true, "concurrent_downloads_enabled": true, "source_urls": [ @@ -12,49 +12,49 @@ "web": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-wasm|mm2_[a-f0-9]{7,40}-wasm|mm2-[a-f0-9]{7,40}-wasm)\\.zip$", "valid_zip_sha256_checksums": [ - "d4476637d566a87974ed54bd708e83f9d05a45a7dfae7ade57c0d00b0f0df631" + "c132f3867217de9eeeba86f6138202e0ee7fad70571aba62c09d3febd0ce948f" ], "path": "web/kdf/bin" }, "ios": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-ios-aarch64|mm2_[a-f0-9]{7,40}-ios-aarch64|mm2-[a-f0-9]{7,40}-ios-aarch64-CI)\\.zip$", "valid_zip_sha256_checksums": [ - "efd8e8e738541a4838a2b044edc60030db9a4ba14392e30fb1a152472d4f4313" + "b5b033ba377d2ff3a36f09d170a095435d159bf40581ba96700761f3df3c1f5d" ], "path": "ios" }, "macos": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-mac-arm64|mm2-[a-f0-9]{7,40}-Darwin-Release)\\.zip$", "valid_zip_sha256_checksums": [ - "bc411c8d95dbe565b0e56871babaea7412ccbd1ad7f525f3cf56a384a4a77ee7" + "d8743c23a23516665370f6fc75c93bb9d21c65b5e2f1bf025b5ce0d42bc57e07" ], "path": "macos/bin" }, "windows": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-win-x86-64|mm2_[a-f0-9]{7,40}-win-x86-64|mm2-[a-f0-9]{7,40}-Win64)\\.zip$", "valid_zip_sha256_checksums": [ - "d9849d01962b4e05899cde7ec17f6b9e8ba9411f484369724c7a73a4b6a3fb80" + "aa81b295a116a77da88102350fbb8a9695e8a54bb92c34432aa890113599e693" ], "path": "windows/bin" }, "android-armv7": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-android-armv7|mm2_[a-f0-9]{7,40}-android-armv7|mm2-[a-f0-9]{7,40}-android-armv7-CI)\\.zip$", "valid_zip_sha256_checksums": [ - "10ae609f3c7e4ed47e5a1134dd74da84375f9a1c6538c985afb1c148d58f8756" + "581c35888a8c8073ebac1067ad93ffa850624c544914fbd87fe538e49b8f7829" ], "path": "android/app/src/main/cpp/libs/armeabi-v7a" }, "android-aarch64": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-android-aarch64|mm2_[a-f0-9]{7,40}-android-aarch64|mm2-[a-f0-9]{7,40}-android-aarch64-CI)\\.zip$", "valid_zip_sha256_checksums": [ - "ab4b5311e0d1b6b2f57ed1783d5b7a51c4b7558cbf0bad593d9235f6a32db906" + "2be8c10798e692f0e459aeffb54bddb49ddeaa22bacd78d34b3ca3b575cb6d08" ], "path": "android/app/src/main/cpp/libs/arm64-v8a" }, "linux": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-linux-x86-64|mm2_[a-f0-9]{7,40}-linux-x86-64|mm2-[a-f0-9]{7,40}-Linux-Release)\\.zip$", "valid_zip_sha256_checksums": [ - "913a165e434ed9696c0e8c9a1875bfd6448e291f85d2e7e8dae78618ef3534e3" + "9a1644aa5d3252aaf165c963218dda29727bc543181ecfc643aa4df39a24fc53" ], "path": "linux/bin" } From 77ff4b2d5d863331f8bd1cc2322001f91888b9b4 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Tue, 5 Aug 2025 21:27:36 +0200 Subject: [PATCH 04/13] feat: add Flutter Web WASM support with OPFS interop extensions This commit implements comprehensive Flutter Web WASM support by: - Adding opfs_interop.dart with FileSystemDirectoryHandle extensions for values(), keys(), and entries() methods - Implementing JSAsyncIterator type definitions for proper JavaScript interop - Creating WebLogStorageWasm implementation compatible with both JS and WASM targets - Consolidating platform implementations to use unified WASM-compatible storage - Removing redundant WebLogStorage in favor of cross-compatible implementation - Updating platform configuration files for seamless WASM integration The implementation provides backwards compatibility with JavaScript compilation while enabling full WASM support through browser File System APIs that work consistently across both compilation targets. BREAKING CHANGE: WebLogStorage class has been removed in favor of WebLogStorageWasm --- packages/dragon_logs/CHANGELOG.md | 16 +- packages/dragon_logs/example/pubspec.lock | 18 +- .../lib/src/storage/log_storage.dart | 3 +- .../lib/src/storage/opfs_interop.dart | 72 +++++ .../log_storage_wasm_platform.dart | 4 + .../log_storage_web_platform.dart | 4 +- .../lib/src/storage/web_log_storage.dart | 249 ------------------ .../lib/src/storage/web_log_storage_wasm.dart | 223 ++++++++++++++++ packages/dragon_logs/pubspec.yaml | 8 +- 9 files changed, 318 insertions(+), 279 deletions(-) create mode 100644 packages/dragon_logs/lib/src/storage/opfs_interop.dart create mode 100644 packages/dragon_logs/lib/src/storage/platform_instance/log_storage_wasm_platform.dart delete mode 100644 packages/dragon_logs/lib/src/storage/web_log_storage.dart create mode 100644 packages/dragon_logs/lib/src/storage/web_log_storage_wasm.dart diff --git a/packages/dragon_logs/CHANGELOG.md b/packages/dragon_logs/CHANGELOG.md index dc7c7e6c..b969474d 100644 --- a/packages/dragon_logs/CHANGELOG.md +++ b/packages/dragon_logs/CHANGELOG.md @@ -1,3 +1,14 @@ +## 1.2.0 + +- **BREAKING**: Add WASM web support with OPFS-only storage +- **BREAKING**: Remove `file_system_access_api` and `js` dependencies +- **BREAKING**: Require Dart SDK `>=3.3.0` for extension types support +- Add `package:web` for modern web APIs compatibility +- Migrate from `dart:html` and `dart:js` to `dart:js_interop` and `package:web` +- Add WASM-specific platform detection using `dart.tool.dart2wasm` +- Implement Origin Private File System (OPFS) using modern JS interop +- Maintain full API compatibility while supporting both regular web and WASM compilation + ## 1.1.0 - Bump packages to latest versions. @@ -24,20 +35,15 @@ Refactor to share more code between web and native platforms (focused mainly on - Stable release - Tweak: Localisation initialisation no longer needs to be inialised before logs. - ## 0.1.1-preview.1 - Memory improvement for log flushing. - Bug fixes. - ## 0.1.0-preview.1 - Bug fixes. - ## 0.0.1-preview.1 - Initial preview version. - - diff --git a/packages/dragon_logs/example/pubspec.lock b/packages/dragon_logs/example/pubspec.lock index 901c3cc2..ecf8c3fc 100644 --- a/packages/dragon_logs/example/pubspec.lock +++ b/packages/dragon_logs/example/pubspec.lock @@ -63,7 +63,7 @@ packages: path: ".." relative: true source: path - version: "1.1.0" + version: "1.2.0" fake_async: dependency: transitive description: @@ -88,14 +88,6 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" - file_system_access_api: - dependency: transitive - description: - name: file_system_access_api - sha256: c961c5020ab4e5f05200dbdd9809c5256c3dc4a1fe5746ca7d8cf8e8cc11c47d - url: "https://pub.dev" - source: hosted - version: "2.0.0" fixnum: dependency: transitive description: @@ -140,14 +132,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" - js: - dependency: transitive - description: - name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" - source: hosted - version: "0.7.2" leak_tracker: dependency: transitive description: diff --git a/packages/dragon_logs/lib/src/storage/log_storage.dart b/packages/dragon_logs/lib/src/storage/log_storage.dart index 64552313..8db04163 100644 --- a/packages/dragon_logs/lib/src/storage/log_storage.dart +++ b/packages/dragon_logs/lib/src/storage/log_storage.dart @@ -1,5 +1,6 @@ import 'package:dragon_logs/src/storage/platform_instance/log_storage_web_platform.dart' - if (dart.library.io) 'package:dragon_logs/src/storage/platform_instance/log_storage_native_platform.dart'; + if (dart.library.io) 'package:dragon_logs/src/storage/platform_instance/log_storage_native_platform.dart' + if (dart.tool.dart2wasm) 'package:dragon_logs/src/storage/platform_instance/log_storage_wasm_platform.dart'; abstract class LogStorage { Future init(); diff --git a/packages/dragon_logs/lib/src/storage/opfs_interop.dart b/packages/dragon_logs/lib/src/storage/opfs_interop.dart new file mode 100644 index 00000000..de7cea6b --- /dev/null +++ b/packages/dragon_logs/lib/src/storage/opfs_interop.dart @@ -0,0 +1,72 @@ +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; +import 'package:web/web.dart'; + +/// JavaScript async iterator result type +@JS() +@anonymous +extension type JSIteratorResult._(JSObject _) implements JSObject { + external bool get done; + external JSAny? get value; +} + +/// JavaScript async iterator type +@JS() +@anonymous +extension type JSAsyncIterator._(JSObject _) implements JSObject { + external JSPromise next(); +} + +/// Extensions for FileSystemDirectoryHandle to provide missing async iterator methods +/// that are available in the JavaScript File System API but not exposed in Flutter's web package. +@JS() +extension FileSystemDirectoryHandleExtension on FileSystemDirectoryHandle { + /// Returns an async iterator for the values (handles) in this directory. + /// Equivalent to calling `directoryHandle.values()` in JavaScript. + external JSAsyncIterator values(); + + /// Returns an async iterator for the keys (names) in this directory. + /// Equivalent to calling `directoryHandle.keys()` in JavaScript. + external JSAsyncIterator keys(); + + /// Returns an async iterator for the entries (name-handle pairs) in this directory. + /// Equivalent to calling `directoryHandle.entries()` in JavaScript. + external JSAsyncIterator entries(); +} + +/// Helper extensions to convert JavaScript async iterators to Dart async iterables +extension JSAsyncIteratorExtension on JSAsyncIterator { + /// Converts a JavaScript async iterator to a Dart Stream + Stream asStream() async* { + while (true) { + final result = await next().toDart; + if (result.done) break; + yield result.value; + } + } +} + +/// Extension to provide async iteration capabilities for FileSystemDirectoryHandle values +extension FileSystemDirectoryHandleValuesIterable on FileSystemDirectoryHandle { + /// Returns a Stream of FileSystemHandle objects for async iteration over directory contents + Stream valuesStream() { + return values().asStream().map((jsValue) => jsValue as FileSystemHandle); + } + + /// Returns a Stream of file/directory names for async iteration over directory contents + Stream keysStream() { + return keys().asStream().map((jsValue) => (jsValue as JSString).toDart); + } + + /// Returns a Stream of [name, handle] pairs for async iteration over directory contents + Stream<(String, FileSystemHandle)> entriesStream() { + return entries().asStream().map((jsValue) { + // The entries() iterator returns [name, handle] arrays + // We need to use js_interop_unsafe to access array elements + final jsObject = jsValue as JSObject; + final name = (jsObject['0']! as JSString).toDart; + final handle = jsObject['1']! as FileSystemHandle; + return (name, handle); + }); + } +} diff --git a/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_wasm_platform.dart b/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_wasm_platform.dart new file mode 100644 index 00000000..f4af52fa --- /dev/null +++ b/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_wasm_platform.dart @@ -0,0 +1,4 @@ +import 'package:dragon_logs/src/storage/log_storage.dart'; +import 'package:dragon_logs/src/storage/web_log_storage_wasm.dart'; + +LogStorage getLogStorageInstance() => WebLogStorageWasm(); \ No newline at end of file diff --git a/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_web_platform.dart b/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_web_platform.dart index f42f53b6..4a6a79bd 100644 --- a/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_web_platform.dart +++ b/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_web_platform.dart @@ -1,4 +1,4 @@ import 'package:dragon_logs/src/storage/log_storage.dart'; -import 'package:dragon_logs/src/storage/web_log_storage.dart'; +import 'package:dragon_logs/src/storage/web_log_storage_wasm.dart'; -LogStorage getLogStorageInstance() => WebLogStorage(); +LogStorage getLogStorageInstance() => WebLogStorageWasm(); diff --git a/packages/dragon_logs/lib/src/storage/web_log_storage.dart b/packages/dragon_logs/lib/src/storage/web_log_storage.dart deleted file mode 100644 index 7446cc40..00000000 --- a/packages/dragon_logs/lib/src/storage/web_log_storage.dart +++ /dev/null @@ -1,249 +0,0 @@ -// TODO: Remove after completing wasm implementation -// ignore_for_file: deprecated_member_use - -import 'dart:async'; -import 'dart:html' as html; -import 'dart:html'; -import 'dart:typed_data'; - -import 'package:dragon_logs/src/storage/input_output_mixin.dart'; -import 'package:dragon_logs/src/storage/log_storage.dart'; -import 'package:dragon_logs/src/storage/queue_mixin.dart'; -import 'package:file_system_access_api/file_system_access_api.dart'; -import 'package:intl/intl.dart'; -import 'package:js/js.dart'; -import 'package:js/js_util.dart' as js; - -/// Declare navigator like in a Web Worker context. -@JS() -external dynamic get navigator; - -class WebLogStorage - with QueueMixin, CommonLogStorageOperations - implements LogStorage { - // TODO: Multi-day support - // final List _logHandles = []; - FileSystemDirectoryHandle? _logDirectory; - - FileSystemFileHandle? _currentLogFile; - FileSystemWritableFileStream? _currentLogStream; - String _currentLogFileName = ""; - - late Timer _flushTimer; - - late final StorageManager? storage = - js.getProperty(navigator, "storage") as StorageManager?; - - @override - Future init() async { - if (!FileSystemAccess.supported) { - throw Exception( - "FileSystemAccess not supported for log storage on this browser", - ); - } - - final now = DateTime.now(); - _currentLogFileName = logFileNameOfDate(now); - - FileSystemDirectoryHandle? root = await storage?.getDirectory(); - - if (root != null) { - _logDirectory = await root.getDirectoryHandle( - "dragon_logs", - create: true, - ); - - // await initWriteDate(now); - } else { - throw Exception("Could not get root directory"); - } - - initQueueFlusher(); - } - - @override - Future writeToTextFile(String logs) async { - if (_currentLogStream == null) { - await initWriteDate(DateTime.now()); - } - - try { - await _currentLogStream!.writeAsText('$logs\n'); - - await closeLogFile(); - await initWriteDate(DateTime.now()); - } catch (e) { - rethrow; - } - } - - @override - // TODO: implement so that we don't have to delete the whole file - Future deleteOldLogs(int size) async { - await startFlush(); - - try { - while (await getLogFolderSize() > size) { - final files = await _getLogFiles(); - - final sortedFiles = files - .where( - (handle) => CommonLogStorageOperations.isLogFileNameValid( - handle.name, - ), - ) - .toList() - ..sort((a, b) { - final aDate = CommonLogStorageOperations.tryParseLogFileDate( - a.name, - ); - final bDate = CommonLogStorageOperations.tryParseLogFileDate( - b.name, - ); - - if (aDate == null || bDate == null) { - return 0; - } - - return aDate.compareTo(bDate); - }); - await sortedFiles.first.remove(); - } - } catch (e) { - rethrow; - } finally { - endFlush(); - } - } - - Future initWriteDate(DateTime date) async { - await closeLogFile(); - - _currentLogFileName = logFileNameOfDate(date); - - _currentLogFile ??= await _logDirectory?.getFileHandle( - _currentLogFileName, - create: true, - ); - - final sizeBytes = (await _currentLogFile?.getFile())?.size ?? 0; - - _currentLogStream = await _currentLogFile?.createWritable( - keepExistingData: true, - ); - - await _currentLogStream?.seek(sizeBytes); - } - - @override - Future getLogFolderSize() async { - final files = await _getLogFiles(); - - final htmlFileObjects = await Future.wait( - files.map((e) => e.getFile()), - ); - - final int totalSize = htmlFileObjects.fold( - 0, - (int? previousValue, File file) => (previousValue ?? 0) + file.size, - ); - - return totalSize; - } - - //TODO! Move to web worker for web so we can access sync flush method instead - // of this workaround - @override - Future closeLogFile() async { - if (_currentLogStream != null) { - await _currentLogStream!.close(); - - _currentLogStream = null; - } - } - - @override - Stream exportLogsStream() async* { - for (final file in await _getLogFiles()) { - String content = await _readFileContent(await file.getFile()); - yield content; - } - } - - /// Returns a list of OPFS file handles for all log files EXCLUDING any - /// temporary write file (if it exists) identified by the `.crswap` extension. - Future> _getLogFiles() async { - final files = await _logDirectory?.values - .where((handle) => handle.kind == FileSystemKind.file) - .cast() - .where((handle) => !handle.name.endsWith('.crswap')) - .toList() ?? - []; - - print('_getLogFiles: ${files.map((e) => e.name).join(',\n')}'); - - return files..sort((a, b) => a.name.compareTo(b.name)); - } - - Future _readFileContent(html.File file) async { - final completer = Completer(); - final reader = html.FileReader(); - - StreamSubscription? loadEndSubscription; - StreamSubscription? errorSubscription; - - loadEndSubscription = reader.onLoadEnd.listen((event) { - completer.complete(reader.result as String); - }); - - errorSubscription = reader.onError.listen((error) { - completer.completeError("Error reading file: $error"); - }); - - reader.readAsText(file); - - return completer.future.whenComplete(() { - loadEndSubscription?.cancel(); - errorSubscription?.cancel(); - }); - } - - @override - Future deleteExportedFiles() async { - // Since it's a web implementation, we just need to ensure necessary permissions. - // Note: Real-world applications should handle permissions gracefully, prompting users as needed. - } - - @override - // TODO: Multi-threading support in web worker - Future exportLogsToDownload() async { - final bytesStream = exportLogsStream().asyncExpand((event) { - return Stream.fromIterable(event.codeUnits); - }); - - final formatter = DateFormat('yyyyMMdd_HHmmss'); - final filename = 'log_${formatter.format(DateTime.now())}.txt'; - - List bytes = await bytesStream.toList(); - final blob = html.Blob([Uint8List.fromList(bytes)]); - final url = html.Url.createObjectUrlFromBlob(blob); - // ignore: unused_local_variable - final anchor = html.AnchorElement(href: url) - ..target = 'blank' - ..download = filename - ..click(); - html.Url.revokeObjectUrl(url); - } - - void dispose() async { - _flushTimer.cancel(); - await closeLogFile(); // Close the log file once during the dispose method - } -} - -//TODO! -Future flushInWebWorker() async { - // final logStorage = WebLogStorage(); - // await logStorage.init(); - // await logStorage.flushLogQueue(); -} diff --git a/packages/dragon_logs/lib/src/storage/web_log_storage_wasm.dart b/packages/dragon_logs/lib/src/storage/web_log_storage_wasm.dart new file mode 100644 index 00000000..56e09110 --- /dev/null +++ b/packages/dragon_logs/lib/src/storage/web_log_storage_wasm.dart @@ -0,0 +1,223 @@ +import 'dart:async'; +import 'dart:js_interop'; +import 'dart:typed_data'; + +import 'package:dragon_logs/src/storage/input_output_mixin.dart'; +import 'package:dragon_logs/src/storage/log_storage.dart'; +import 'package:dragon_logs/src/storage/opfs_interop.dart'; +import 'package:dragon_logs/src/storage/queue_mixin.dart'; +import 'package:intl/intl.dart'; +import 'package:web/web.dart'; + +/// WASM-compatible web log storage implementation using OPFS +class WebLogStorageWasm + with QueueMixin, CommonLogStorageOperations + implements LogStorage { + FileSystemDirectoryHandle? _logDirectory; + FileSystemFileHandle? _currentLogFile; + FileSystemWritableFileStream? _currentLogStream; + String _currentLogFileName = ""; + + @override + Future init() async { + final now = DateTime.now(); + _currentLogFileName = logFileNameOfDate(now); + + // Get the OPFS root directory + final storageManager = window.navigator.storage; + final root = await storageManager.getDirectory().toDart; + + // Create or get the dragon_logs directory + _logDirectory = await root + .getDirectoryHandle( + "dragon_logs", + FileSystemGetDirectoryOptions(create: true), + ) + .toDart; + + initQueueFlusher(); + } + + @override + Future writeToTextFile(String logs, {bool batchWrite = true}) async { + if (_currentLogStream == null) { + await initWriteDate(DateTime.now()); + } + + try { + await _currentLogStream!.write('$logs\n'.toJS).toDart; + await closeLogFile(); + await initWriteDate(DateTime.now()); + } catch (e) { + rethrow; + } + } + + Future initWriteDate(DateTime date) async { + await closeLogFile(); + + _currentLogFileName = logFileNameOfDate(date); + + _currentLogFile = await _logDirectory! + .getFileHandle( + _currentLogFileName, + FileSystemGetFileOptions(create: true), + ) + .toDart; + + final file = await _currentLogFile!.getFile().toDart; + final sizeBytes = file.size.toInt(); + + _currentLogStream = await _currentLogFile! + .createWritable( + FileSystemCreateWritableOptions(keepExistingData: true), + ) + .toDart; + + await _currentLogStream!.seek(sizeBytes).toDart; + } + + @override + Future deleteOldLogs(int size) async { + await startFlush(); + + try { + while (await getLogFolderSize() > size) { + final files = await _getLogFiles(); + + final sortedFiles = files + .where( + (handle) => CommonLogStorageOperations.isLogFileNameValid( + handle.name, + ), + ) + .toList() + ..sort((a, b) { + final aDate = CommonLogStorageOperations.tryParseLogFileDate( + a.name, + ); + final bDate = CommonLogStorageOperations.tryParseLogFileDate( + b.name, + ); + + if (aDate == null || bDate == null) { + return 0; + } + + return aDate.compareTo(bDate); + }); + + await _logDirectory! + .removeEntry( + sortedFiles.first.name, + FileSystemRemoveOptions(recursive: false), + ) + .toDart; + } + } catch (e) { + rethrow; + } finally { + endFlush(); + } + } + + @override + Future getLogFolderSize() async { + final files = await _getLogFiles(); + + int totalSize = 0; + for (final handle in files) { + final file = await handle.getFile().toDart; + totalSize += file.size.toInt(); + } + + return totalSize; + } + + @override + Future closeLogFile() async { + if (_currentLogStream != null) { + await _currentLogStream!.close().toDart; + _currentLogStream = null; + } + } + + @override + Stream exportLogsStream() async* { + final files = await _getLogFiles(); + + for (final fileHandle in files) { + final file = await fileHandle.getFile().toDart; + final content = await _readFileContent(file); + yield content; + } + } + + /// Returns a list of OPFS file handles for all log files EXCLUDING any + /// temporary write file (if it exists) identified by the `.crswap` extension. + Future> _getLogFiles() async { + final files = []; + + // Use the async iterator provided by FileSystemDirectoryHandle.values() + // via our custom interop extension + await for (final handle in _logDirectory!.valuesStream()) { + if (handle.kind == 'file' && !handle.name.endsWith('.crswap')) { + files.add(handle as FileSystemFileHandle); + } + } + + files.sort((a, b) => a.name.compareTo(b.name)); + return files; + } + + Future _readFileContent(File file) async { + final completer = Completer(); + final reader = FileReader(); + + reader.onLoadEnd.listen((event) { + final result = reader.result; + if (result != null) { + completer.complete(result.toString()); + } else { + completer.complete(''); + } + }); + + reader.readAsText(file); + return completer.future; + } + + @override + Future deleteExportedFiles() async { + // Since it's a web implementation, we just need to ensure necessary permissions. + // Note: Real-world applications should handle permissions gracefully, prompting users as needed. + } + + @override + Future exportLogsToDownload() async { + final bytesStream = exportLogsStream().asyncExpand((event) { + return Stream.fromIterable(event.codeUnits); + }); + + final formatter = DateFormat('yyyyMMdd_HHmmss'); + final filename = 'log_${formatter.format(DateTime.now())}.txt'; + + final bytes = await bytesStream.toList(); + final blob = Blob([Uint8List.fromList(bytes).toJS].toJS); + final url = URL.createObjectURL(blob); + + final anchor = HTMLAnchorElement() + ..href = url + ..download = filename + ..style.display = 'none'; + + document.body!.appendChild(anchor); + anchor.click(); + document.body!.removeChild(anchor); + URL.revokeObjectURL(url); + } + + void dispose() async { + await closeLogFile(); + } +} diff --git a/packages/dragon_logs/pubspec.yaml b/packages/dragon_logs/pubspec.yaml index 00ee726d..6e739b78 100644 --- a/packages/dragon_logs/pubspec.yaml +++ b/packages/dragon_logs/pubspec.yaml @@ -1,12 +1,12 @@ name: dragon_logs description: An efficient cross-platform Flutter log storage framework with minimal dependencies. -version: 1.1.0 +version: 1.2.0 repository: https://github.com/KomodoPlatform/dragon_logs_flutter homepage: https://komodoplatform.com environment: - sdk: ">=3.0.0 <4.0.0" + sdk: ">=3.3.0 <4.0.0" dev_dependencies: lints: ^5.1.1 @@ -33,8 +33,6 @@ dependencies: # Last approved via KW PR #1106 share_plus: ^10.1.4 - file_system_access_api: ^2.0.0 - # ====== Flutter.dev/Dart.dev approved ====== # Secure review for Flutter.dev/Dart.dev packages not strictly required since # they are Google/Dart products, but still recommended. @@ -42,4 +40,4 @@ dependencies: intl: ">=0.19.0 <0.21.0" # The latest `stable` Flutter version is pinned to 0.19 path_provider: ^2.1.5 path: ^1.8.3 - js: ^0.7.2 + web: ^1.1.0 From d4e7bac63cd0b419dff089af635498b59cae8335 Mon Sep 17 00:00:00 2001 From: "Charl (Nitride)" <77973576+CharlVS@users.noreply.github.com> Date: Tue, 5 Aug 2025 21:34:43 +0200 Subject: [PATCH 05/13] Update packages/dragon_logs/lib/src/storage/opfs_interop.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/dragon_logs/lib/src/storage/opfs_interop.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/dragon_logs/lib/src/storage/opfs_interop.dart b/packages/dragon_logs/lib/src/storage/opfs_interop.dart index de7cea6b..bbfcd479 100644 --- a/packages/dragon_logs/lib/src/storage/opfs_interop.dart +++ b/packages/dragon_logs/lib/src/storage/opfs_interop.dart @@ -59,13 +59,15 @@ extension FileSystemDirectoryHandleValuesIterable on FileSystemDirectoryHandle { } /// Returns a Stream of [name, handle] pairs for async iteration over directory contents + static const int nameIndex = 0; + static const int handleIndex = 1; Stream<(String, FileSystemHandle)> entriesStream() { return entries().asStream().map((jsValue) { // The entries() iterator returns [name, handle] arrays // We need to use js_interop_unsafe to access array elements final jsObject = jsValue as JSObject; - final name = (jsObject['0']! as JSString).toDart; - final handle = jsObject['1']! as FileSystemHandle; + final name = (jsObject['$nameIndex']! as JSString).toDart; + final handle = jsObject['$handleIndex']! as FileSystemHandle; return (name, handle); }); } From 58ba180ce2c610a2c4de12f9436a299273a2860d Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Tue, 5 Aug 2025 22:11:19 +0200 Subject: [PATCH 06/13] docs: update repo url --- packages/dragon_logs/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dragon_logs/pubspec.yaml b/packages/dragon_logs/pubspec.yaml index 6e739b78..fe2656c2 100644 --- a/packages/dragon_logs/pubspec.yaml +++ b/packages/dragon_logs/pubspec.yaml @@ -2,7 +2,7 @@ name: dragon_logs description: An efficient cross-platform Flutter log storage framework with minimal dependencies. version: 1.2.0 -repository: https://github.com/KomodoPlatform/dragon_logs_flutter +repository: https://github.com/KomodoPlatform/komodo-defi-sdk-flutter/tree/main/packages/dragon_logs homepage: https://komodoplatform.com environment: @@ -37,7 +37,7 @@ dependencies: # Secure review for Flutter.dev/Dart.dev packages not strictly required since # they are Google/Dart products, but still recommended. - intl: ">=0.19.0 <0.21.0" # The latest `stable` Flutter version is pinned to 0.19 + intl: ^0.20.2 path_provider: ^2.1.5 path: ^1.8.3 web: ^1.1.0 From 29fcd873756623c5b44f62e78901fd16ca752866 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Tue, 5 Aug 2025 22:20:56 +0200 Subject: [PATCH 07/13] docs: remove repo archive notice --- packages/dragon_logs/README.md | 16 ++-------------- packages/dragon_logs/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/packages/dragon_logs/README.md b/packages/dragon_logs/README.md index 3de2e124..7d51ee22 100644 --- a/packages/dragon_logs/README.md +++ b/packages/dragon_logs/README.md @@ -1,16 +1,4 @@ -# 🚚 Repository Moved - -> **⚠️ This repository has been migrated to the Komodo DeFi SDK Flutter monorepo.** -> -> 📍 **New location:** [packages/dragon_logs_flutter](https://github.com/KomodoPlatform/komodo-defi-sdk-flutter/tree/main/packages/dragon_logs_flutter) -> -> 🔄 **Active development** continues in the monorepo. Please update your forks, bookmarks, and links. -> -> 💡 **For issues, PRs, and contributions**, please use the [main monorepo](https://github.com/KomodoPlatform/komodo-defi-sdk-flutter). - ---- - -# Dragon Logs (Archived) +# Dragon Logs

Pub @@ -28,7 +16,7 @@ Dragon Logs aims to simplify the logging and log storage process in your Flutter - ✅ Cross-platform log storage - ✅ Cross-platform logs download -- ⬜ Flutter web wasm support +- ✅ Flutter web wasm support - ⬜ Web multi-threading support - ⬜ Log levels (e.g. debug, info, warning, error) - ⬜ Performance metrics (in progress) diff --git a/packages/dragon_logs/pubspec.yaml b/packages/dragon_logs/pubspec.yaml index fe2656c2..25848550 100644 --- a/packages/dragon_logs/pubspec.yaml +++ b/packages/dragon_logs/pubspec.yaml @@ -1,6 +1,6 @@ name: dragon_logs description: An efficient cross-platform Flutter log storage framework with minimal dependencies. -version: 1.2.0 +version: 1.2.0+1 repository: https://github.com/KomodoPlatform/komodo-defi-sdk-flutter/tree/main/packages/dragon_logs homepage: https://komodoplatform.com From 8f180f49d986a2319bfbced0ed01c53761322a7f Mon Sep 17 00:00:00 2001 From: "Charl (Nitride)" <77973576+CharlVS@users.noreply.github.com> Date: Wed, 6 Aug 2025 00:08:23 +0200 Subject: [PATCH 08/13] feat(example): integrate dragon_logs (#177) --- .../komodo_defi_sdk/example/lib/main.dart | 26 ++++++- .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + packages/komodo_defi_sdk/example/pubspec.lock | 76 +++++++++++++++++++ packages/komodo_defi_sdk/example/pubspec.yaml | 2 + .../example/pubspec_overrides.yaml | 4 +- .../flutter/generated_plugin_registrant.cc | 6 ++ .../windows/flutter/generated_plugins.cmake | 2 + 9 files changed, 121 insertions(+), 2 deletions(-) diff --git a/packages/komodo_defi_sdk/example/lib/main.dart b/packages/komodo_defi_sdk/example/lib/main.dart index 78ccd65a..bf3e53f0 100644 --- a/packages/komodo_defi_sdk/example/lib/main.dart +++ b/packages/komodo_defi_sdk/example/lib/main.dart @@ -1,6 +1,7 @@ // lib/main.dart import 'dart:async'; +import 'package:dragon_logs/dragon_logs.dart' as dragon; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kdf_sdk_example/blocs/auth/auth_bloc.dart'; @@ -17,6 +18,7 @@ final GlobalKey _navigatorKey = GlobalKey(); void main() async { WidgetsFlutterBinding.ensureInitialized(); + await dragon.DragonLogs.init(); // Create instance manager final instanceManager = KdfInstanceManager(); @@ -24,9 +26,11 @@ void main() async { // Create default SDK instance with config final defaultSdk = KomodoDefiSdk(config: _config); await defaultSdk.initialize(); + dragon.log('Default SDK instance initialized'); // Register default instance await instanceManager.registerInstance('Local Instance', _config, defaultSdk); + dragon.log('Registered default instance'); runApp( MultiRepositoryProvider( @@ -115,6 +119,7 @@ class _KomodoAppState extends State { // Load known users await _fetchKnownUsers(instance); + dragon.log('Initialized instance ${instance.name}'); } void _updateInstanceUser(String instanceName, KdfUser? user) { @@ -125,6 +130,15 @@ class _KomodoAppState extends State { ? 'Current wallet: ${user.walletId.name}' : 'Not signed in'; }); + dragon.DragonLogs.setSessionMetadata({ + 'instance': instanceName, + if (user != null) 'user': user.walletId.compoundId, + }); + dragon.log( + user != null + ? 'User ${user.walletId.compoundId} authenticated in $instanceName' + : 'User signed out of $instanceName', + ); } Future _fetchKnownUsers(KdfInstanceState instance) async { @@ -135,7 +149,7 @@ class _KomodoAppState extends State { state.knownUsers = users; setState(() {}); } catch (e, s) { - print('Error fetching known users: $e'); + dragon.log('Error fetching known users: $e', 'ERROR'); debugPrintStack(stackTrace: s); } } @@ -182,6 +196,16 @@ class _KomodoAppState extends State { : Colors.red, child: const Icon(Icons.cloud), ), + IconButton( + icon: const Icon(Icons.download), + tooltip: 'Export Logs', + onPressed: () async { + await dragon.DragonLogs.exportLogsToDownload(); + _scaffoldKey.currentState?.showSnackBar( + const SnackBar(content: Text('Logs exported')), + ); + }, + ), const SizedBox(width: 16), ], ], diff --git a/packages/komodo_defi_sdk/example/linux/flutter/generated_plugin_registrant.cc b/packages/komodo_defi_sdk/example/linux/flutter/generated_plugin_registrant.cc index d0e7f797..38dd0bc6 100644 --- a/packages/komodo_defi_sdk/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/komodo_defi_sdk/example/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/packages/komodo_defi_sdk/example/linux/flutter/generated_plugins.cmake b/packages/komodo_defi_sdk/example/linux/flutter/generated_plugins.cmake index a9f2fe5a..a1cc4f39 100644 --- a/packages/komodo_defi_sdk/example/linux/flutter/generated_plugins.cmake +++ b/packages/komodo_defi_sdk/example/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/packages/komodo_defi_sdk/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/komodo_defi_sdk/example/macos/Flutter/GeneratedPluginRegistrant.swift index 46b0a8b7..b83e6002 100644 --- a/packages/komodo_defi_sdk/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/komodo_defi_sdk/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,6 +9,7 @@ import flutter_secure_storage_darwin import local_auth_darwin import mobile_scanner import path_provider_foundation +import share_plus import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { @@ -16,5 +17,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/packages/komodo_defi_sdk/example/pubspec.lock b/packages/komodo_defi_sdk/example/pubspec.lock index 82867744..657c1edb 100644 --- a/packages/komodo_defi_sdk/example/pubspec.lock +++ b/packages/komodo_defi_sdk/example/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" crypto: dependency: transitive description: @@ -81,6 +89,13 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" + dragon_logs: + dependency: "direct main" + description: + path: "../../dragon_logs" + relative: true + source: path + version: "1.2.0" equatable: dependency: "direct main" description: @@ -147,6 +162,11 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -469,6 +489,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" mobile_scanner: dependency: transitive description: @@ -589,6 +617,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.3" + share_plus: + dependency: transitive + description: + name: share_plus + sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da + url: "https://pub.dev" + source: hosted + version: "10.1.4" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b + url: "https://pub.dev" + source: hosted + version: "5.0.2" shared_preferences: dependency: transitive description: @@ -722,6 +766,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" uuid: dependency: transitive description: diff --git a/packages/komodo_defi_sdk/example/pubspec.yaml b/packages/komodo_defi_sdk/example/pubspec.yaml index 77b2871a..9ae11b5a 100644 --- a/packages/komodo_defi_sdk/example/pubspec.yaml +++ b/packages/komodo_defi_sdk/example/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: flutter_bloc: ^9.1.1 flutter_secure_storage: ^10.0.0-beta.4 + dragon_logs: + path: ../../dragon_logs komodo_defi_sdk: path: ../ diff --git a/packages/komodo_defi_sdk/example/pubspec_overrides.yaml b/packages/komodo_defi_sdk/example/pubspec_overrides.yaml index b5025daf..996f2849 100644 --- a/packages/komodo_defi_sdk/example/pubspec_overrides.yaml +++ b/packages/komodo_defi_sdk/example/pubspec_overrides.yaml @@ -1,5 +1,7 @@ -# melos_managed_dependency_overrides: komodo_cex_market_data,komodo_coin_updates,komodo_coins,komodo_defi_framework,komodo_defi_local_auth,komodo_defi_rpc_methods,komodo_defi_sdk,komodo_defi_types,komodo_ui,komodo_wallet_build_transformer +# melos_managed_dependency_overrides: dragon_logs,komodo_cex_market_data,komodo_coin_updates,komodo_coins,komodo_defi_framework,komodo_defi_local_auth,komodo_defi_rpc_methods,komodo_defi_sdk,komodo_defi_types,komodo_ui,komodo_wallet_build_transformer dependency_overrides: + dragon_logs: + path: ../../dragon_logs komodo_cex_market_data: path: ../../komodo_cex_market_data komodo_coin_updates: diff --git a/packages/komodo_defi_sdk/example/windows/flutter/generated_plugin_registrant.cc b/packages/komodo_defi_sdk/example/windows/flutter/generated_plugin_registrant.cc index 011734da..f4b698cb 100644 --- a/packages/komodo_defi_sdk/example/windows/flutter/generated_plugin_registrant.cc +++ b/packages/komodo_defi_sdk/example/windows/flutter/generated_plugin_registrant.cc @@ -8,10 +8,16 @@ #include #include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); LocalAuthPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("LocalAuthPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/packages/komodo_defi_sdk/example/windows/flutter/generated_plugins.cmake b/packages/komodo_defi_sdk/example/windows/flutter/generated_plugins.cmake index aa117f18..7b3a5a56 100644 --- a/packages/komodo_defi_sdk/example/windows/flutter/generated_plugins.cmake +++ b/packages/komodo_defi_sdk/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,8 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows local_auth_windows + share_plus + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From ba734d80d69a80c15fe98fd7c26decdaf92649fb Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Wed, 6 Aug 2025 00:19:40 +0200 Subject: [PATCH 09/13] fix: dart wasm type errors --- packages/dragon_logs/example/pubspec.lock | 24 ++++++++-------- .../lib/src/storage/opfs_interop.dart | 6 ++-- packages/dragon_logs/pubspec.yaml | 2 +- packages/komodo_coin_updates/pubspec.yaml | 4 +-- .../lib/src/komodo_coins_base.dart | 6 ++++ .../app_build/build_config.json | 2 +- .../src/operations/kdf_operations_wasm.dart | 28 +++++++++++-------- packages/komodo_defi_framework/pubspec.yaml | 6 ++-- packages/komodo_defi_sdk/example/pubspec.lock | 2 +- packages/komodo_defi_sdk/pubspec.yaml | 2 +- packages/komodo_defi_types/pubspec.yaml | 2 +- packages/komodo_ui/pubspec.yaml | 2 +- playground/pubspec.yaml | 2 +- 13 files changed, 50 insertions(+), 38 deletions(-) diff --git a/packages/dragon_logs/example/pubspec.lock b/packages/dragon_logs/example/pubspec.lock index ecf8c3fc..886ae595 100644 --- a/packages/dragon_logs/example/pubspec.lock +++ b/packages/dragon_logs/example/pubspec.lock @@ -63,7 +63,7 @@ packages: path: ".." relative: true source: path - version: "1.2.0" + version: "1.2.0+1" fake_async: dependency: transitive description: @@ -76,10 +76,10 @@ packages: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: @@ -216,10 +216,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.15" + version: "2.2.17" path_provider_foundation: dependency: transitive description: @@ -373,10 +373,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" url_launcher_windows: dependency: transitive description: @@ -413,18 +413,18 @@ packages: 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: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" url: "https://pub.dev" source: hosted - version: "5.10.1" + version: "5.14.0" xdg_directories: dependency: transitive description: @@ -434,5 +434,5 @@ packages: source: hosted version: "1.1.0" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.8.0 <4.0.0" flutter: ">=3.27.0" diff --git a/packages/dragon_logs/lib/src/storage/opfs_interop.dart b/packages/dragon_logs/lib/src/storage/opfs_interop.dart index bbfcd479..78715d52 100644 --- a/packages/dragon_logs/lib/src/storage/opfs_interop.dart +++ b/packages/dragon_logs/lib/src/storage/opfs_interop.dart @@ -64,10 +64,10 @@ extension FileSystemDirectoryHandleValuesIterable on FileSystemDirectoryHandle { Stream<(String, FileSystemHandle)> entriesStream() { return entries().asStream().map((jsValue) { // The entries() iterator returns [name, handle] arrays - // We need to use js_interop_unsafe to access array elements + // Use js_interop_unsafe to access array elements by numeric index final jsObject = jsValue as JSObject; - final name = (jsObject['$nameIndex']! as JSString).toDart; - final handle = jsObject['$handleIndex']! as FileSystemHandle; + final name = jsObject.getProperty(nameIndex.toJS).toDart; + final handle = jsObject.getProperty(handleIndex.toJS); return (name, handle); }); } diff --git a/packages/dragon_logs/pubspec.yaml b/packages/dragon_logs/pubspec.yaml index 25848550..1a32c712 100644 --- a/packages/dragon_logs/pubspec.yaml +++ b/packages/dragon_logs/pubspec.yaml @@ -6,7 +6,7 @@ repository: https://github.com/KomodoPlatform/komodo-defi-sdk-flutter/tree/main/ homepage: https://komodoplatform.com environment: - sdk: ">=3.3.0 <4.0.0" + sdk: ^3.7.0 dev_dependencies: lints: ^5.1.1 diff --git a/packages/komodo_coin_updates/pubspec.yaml b/packages/komodo_coin_updates/pubspec.yaml index f5a837f7..838db99a 100644 --- a/packages/komodo_coin_updates/pubspec.yaml +++ b/packages/komodo_coin_updates/pubspec.yaml @@ -5,13 +5,13 @@ publish_to: none # publishable packages can't have git dependencies environment: sdk: ^3.7.0 - flutter: ">=3.29.0 <3.30.0" + flutter: ">=3.29.0 <3.36.0" # Add regular dependencies here. dependencies: equatable: ^2.0.7 flutter_bloc: ^9.1.1 - hive: 2.2.3 + hive: 2.2.3 hive_flutter: 1.1.0 http: ^1.4.0 komodo_defi_types: diff --git a/packages/komodo_coins/lib/src/komodo_coins_base.dart b/packages/komodo_coins/lib/src/komodo_coins_base.dart index af44046a..af5bd011 100644 --- a/packages/komodo_coins/lib/src/komodo_coins_base.dart +++ b/packages/komodo_coins/lib/src/komodo_coins_base.dart @@ -94,6 +94,12 @@ class KomodoCoins { assets[assetId] = asset; // } } + } + // Log exceptions related to missing config fields + on MissingProtocolFieldException catch (e) { + debugPrint( + 'Skipping asset ${entry.key} due to missing protocol field: $e', + ); } catch (e) { debugPrint( 'Error parsing asset ${entry.key}: $e , ' diff --git a/packages/komodo_defi_framework/app_build/build_config.json b/packages/komodo_defi_framework/app_build/build_config.json index d8b156c4..4c9b0818 100644 --- a/packages/komodo_defi_framework/app_build/build_config.json +++ b/packages/komodo_defi_framework/app_build/build_config.json @@ -63,7 +63,7 @@ "coins": { "fetch_at_build_enabled": true, "update_commit_on_build": true, - "bundled_coins_repo_commit": "1fe493212b34937d82c668e6118a1356d7eb2f06", + "bundled_coins_repo_commit": "322575ff3230d91e739be33861062173e1925cd3", "coins_repo_api_url": "https://api.github.com/repos/KomodoPlatform/coins", "coins_repo_content_url": "https://komodoplatform.github.io/coins", "coins_repo_branch": "master", diff --git a/packages/komodo_defi_framework/lib/src/operations/kdf_operations_wasm.dart b/packages/komodo_defi_framework/lib/src/operations/kdf_operations_wasm.dart index 2e3d9b18..78dd7a6f 100644 --- a/packages/komodo_defi_framework/lib/src/operations/kdf_operations_wasm.dart +++ b/packages/komodo_defi_framework/lib/src/operations/kdf_operations_wasm.dart @@ -135,10 +135,11 @@ class KdfOperationsWasm implements IKdfOperations { // Check for code property if (jsObj.hasProperty('code'.toJS).toDart) { - final code = jsObj.getProperty('code'.toJS); - // Print all properties of the JSObject - if (isInstance(code, 'JSNumber')) { - final errorCode = (code! as js_interop.JSNumber).toDartInt; + final code = jsObj.getProperty('code'.toJS); + // Check if the property is a JSNumber + if (code != null && + isInstance(code, 'JSNumber')) { + final errorCode = code.toDartInt; _log( 'KdfOperationsWasm: Resolved as JSObject->JSNumber error code: $errorCode', ); @@ -147,9 +148,10 @@ class KdfOperationsWasm implements IKdfOperations { } // Try toNumber method - final asNumber = jsObj.callMethod('toNumber'.toJS); + final asNumber = + jsObj.callMethod('toNumber'.toJS); if (asNumber?.isDefinedAndNotNull ?? false) { - final errorCode = (asNumber! as js_interop.JSNumber).toDartInt; + final errorCode = asNumber!.toDartInt; _log( 'KdfOperationsWasm: Resolved as JSNumber error code: $errorCode', ); @@ -188,8 +190,10 @@ class KdfOperationsWasm implements IKdfOperations { @override Future kdfMainStatus() async { await _ensureLoaded(); - final status = _kdfModule!.callMethod('mm2_main_status'.toJS); - return MainStatus.fromDefaultInt(status! as int); + final status = _kdfModule! + .callMethod('mm2_main_status'.toJS) + ?.toDartInt; + return MainStatus.fromDefaultInt(status!); } @override @@ -431,9 +435,11 @@ class KdfOperationsWasm implements IKdfOperations { 'init_wasm', '__wbg_init', ], - value: (key) => - 'Has property: ${_kdfModule!.has(key as String)} with type: ' - '${_kdfModule!.getProperty(key.toJS).runtimeType}', + value: (key) { + final jsKey = (key as String).toJS; + return 'Has property: ${_kdfModule!.hasProperty(jsKey).toDart} with type: ' + '${_kdfModule!.getProperty(jsKey).runtimeType}'; + }, ); _log('KDF Has properties: $debugProperties'); diff --git a/packages/komodo_defi_framework/pubspec.yaml b/packages/komodo_defi_framework/pubspec.yaml index 55f126ba..ddb4afc9 100644 --- a/packages/komodo_defi_framework/pubspec.yaml +++ b/packages/komodo_defi_framework/pubspec.yaml @@ -76,10 +76,10 @@ flutter: # steps. They are executed in the order listed in `_build_steps` # in `packages/komodo_wallet_build_transformer/bin/komodo_wallet_build_transformer.dart` # Configure fetch_defi_api in `config/build_config.yaml` - --fetch_defi_api, + #! --fetch_defi_api, # Configure `fetch_coin_assets` in `config/build_config.yaml` - --fetch_coin_assets, - --copy_platform_assets, + #! --fetch_coin_assets, + #! --copy_platform_assets, # Uncomment the following option to enable concurrent build step # execution. This is useful for reducing build time in development, # but is not recommended for production builds. diff --git a/packages/komodo_defi_sdk/example/pubspec.lock b/packages/komodo_defi_sdk/example/pubspec.lock index 657c1edb..1360f7b0 100644 --- a/packages/komodo_defi_sdk/example/pubspec.lock +++ b/packages/komodo_defi_sdk/example/pubspec.lock @@ -95,7 +95,7 @@ packages: path: "../../dragon_logs" relative: true source: path - version: "1.2.0" + version: "1.2.0+1" equatable: dependency: "direct main" description: diff --git a/packages/komodo_defi_sdk/pubspec.yaml b/packages/komodo_defi_sdk/pubspec.yaml index 3e09ae61..0313a03d 100644 --- a/packages/komodo_defi_sdk/pubspec.yaml +++ b/packages/komodo_defi_sdk/pubspec.yaml @@ -13,7 +13,7 @@ publish_to: "none" environment: sdk: ^3.7.0 - flutter: ">=3.29.0 <3.30.0" + flutter: ">=3.29.0 <3.36.0" dependencies: collection: ^1.18.0 diff --git a/packages/komodo_defi_types/pubspec.yaml b/packages/komodo_defi_types/pubspec.yaml index 8892f76f..e29c9051 100644 --- a/packages/komodo_defi_types/pubspec.yaml +++ b/packages/komodo_defi_types/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none environment: sdk: ">=3.7.0 <4.0.0" # TODO: Refactor to pure Dart package - flutter: ">=3.29.0 <3.30.0" + flutter: ">=3.29.0 <3.36.0" dependencies: crypto: ^3.0.6 diff --git a/packages/komodo_ui/pubspec.yaml b/packages/komodo_ui/pubspec.yaml index 2216893b..6584b47d 100644 --- a/packages/komodo_ui/pubspec.yaml +++ b/packages/komodo_ui/pubspec.yaml @@ -6,7 +6,7 @@ publish_to: none environment: sdk: ^3.7.0 - flutter: ">=3.29.0 <3.30.0" + flutter: ">=3.29.0 <3.36.0" dependencies: decimal: ^3.2.1 diff --git a/playground/pubspec.yaml b/playground/pubspec.yaml index 7c25bd5f..c2bb0fd9 100644 --- a/playground/pubspec.yaml +++ b/playground/pubspec.yaml @@ -21,7 +21,7 @@ version: 1.0.0+1 environment: sdk: ^3.7.0 - flutter: ">=3.29.0 <3.30.0" + flutter: ">=3.29.0 <3.36.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions From 358f4e942f9d2a6194da2ea583dd84406b51ba8e Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Wed, 6 Aug 2025 00:23:36 +0200 Subject: [PATCH 10/13] chore: revert skipped build steps --- packages/komodo_defi_framework/pubspec.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/komodo_defi_framework/pubspec.yaml b/packages/komodo_defi_framework/pubspec.yaml index ddb4afc9..55f126ba 100644 --- a/packages/komodo_defi_framework/pubspec.yaml +++ b/packages/komodo_defi_framework/pubspec.yaml @@ -76,10 +76,10 @@ flutter: # steps. They are executed in the order listed in `_build_steps` # in `packages/komodo_wallet_build_transformer/bin/komodo_wallet_build_transformer.dart` # Configure fetch_defi_api in `config/build_config.yaml` - #! --fetch_defi_api, + --fetch_defi_api, # Configure `fetch_coin_assets` in `config/build_config.yaml` - #! --fetch_coin_assets, - #! --copy_platform_assets, + --fetch_coin_assets, + --copy_platform_assets, # Uncomment the following option to enable concurrent build step # execution. This is useful for reducing build time in development, # but is not recommended for production builds. From 5c27d5ada4ca5b3af3b122e569cb44373a5dee91 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Wed, 6 Aug 2025 00:44:44 +0200 Subject: [PATCH 11/13] fix: add GitHub token authentication to prevent API rate limiting Add GitHub authentication token support across all components that make GitHub API requests to resolve rate limiting issues during builds. Changes: - feat(coin-updates): add githubToken parameter to CoinConfigProvider - feat(coin-updates): support GitHub auth in CoinConfigRepository.withDefaults - fix(build-transformer): add debug logging for GitHub API authentication - fix(wallet-cli): auto-use GITHUB_API_PUBLIC_READONLY_TOKEN env variable - style(build-transformer): fix lint issues with cascade operations The CoinConfigProvider was making unauthenticated GitHub API requests, contributing to rate limit errors. All GitHub API clients now properly use the GITHUB_API_PUBLIC_READONLY_TOKEN environment variable when available, increasing rate limits from 60 to 5,000 requests per hour. Fixes: Rate limit exceeded errors during build process --- .../lib/src/data/coin_config_provider.dart | 34 +++++++++++- .../lib/src/data/coin_config_repository.dart | 17 +++--- .../src/steps/github/github_api_provider.dart | 53 ++++++++++++------- .../bin/update_api_config.dart | 4 +- 4 files changed, 79 insertions(+), 29 deletions(-) diff --git a/packages/komodo_coin_updates/lib/src/data/coin_config_provider.dart b/packages/komodo_coin_updates/lib/src/data/coin_config_provider.dart index de6c7851..791952e2 100644 --- a/packages/komodo_coin_updates/lib/src/data/coin_config_provider.dart +++ b/packages/komodo_coin_updates/lib/src/data/coin_config_provider.dart @@ -16,11 +16,18 @@ class CoinConfigProvider { 'https://api.github.com/repos/KomodoPlatform/coins', this.coinsPath = 'coins', this.coinsConfigPath = 'utils/coins_config_unfiltered.json', + this.githubToken, }); - factory CoinConfigProvider.fromConfig(RuntimeUpdateConfig config) { + factory CoinConfigProvider.fromConfig( + RuntimeUpdateConfig config, { + String? githubToken, + }) { // TODO(Francois): derive all the values from the config - return CoinConfigProvider(branch: config.coinsRepoBranch); + return CoinConfigProvider( + branch: config.coinsRepoBranch, + githubToken: githubToken, + ); } final String branch; @@ -28,6 +35,7 @@ class CoinConfigProvider { final String coinsGithubApiUrl; final String coinsPath; final String coinsConfigPath; + final String? githubToken; /// Fetches the coins from the repository. /// [commit] is the commit hash to fetch the coins from. @@ -81,8 +89,30 @@ class CoinConfigProvider { final client = http.Client(); final url = Uri.parse('$coinsGithubApiUrl/branches/$branch'); final header = {'Accept': 'application/vnd.github+json'}; + + // Add authentication header if token is available + if (githubToken != null) { + header['Authorization'] = 'Bearer $githubToken'; + print('CoinConfigProvider: Using authentication for GitHub API request'); + } else { + print( + 'CoinConfigProvider: No GitHub token available - making unauthenticated request', + ); + } + final response = await client.get(url, headers: header); + if (response.statusCode != 200) { + print( + 'CoinConfigProvider: GitHub API request failed: ${response.statusCode} ${response.reasonPhrase}', + ); + print('CoinConfigProvider: Response body: ${response.body}'); + throw Exception( + 'Failed to retrieve latest commit hash: $branch' + '[${response.statusCode}]: ${response.reasonPhrase}', + ); + } + final json = jsonDecode(response.body) as Map; final commit = json['commit'] as Map; final latestCommitHash = commit['sha'] as String; diff --git a/packages/komodo_coin_updates/lib/src/data/coin_config_repository.dart b/packages/komodo_coin_updates/lib/src/data/coin_config_repository.dart index ab776e44..e4fd54ca 100644 --- a/packages/komodo_coin_updates/lib/src/data/coin_config_repository.dart +++ b/packages/komodo_coin_updates/lib/src/data/coin_config_repository.dart @@ -21,12 +21,17 @@ class CoinConfigRepository implements CoinConfigStorage { /// Creates a coin config storage provider with default databases. /// The default databases are HiveLazyBoxProvider. /// The default databases are named 'coins' and 'coins_settings'. - CoinConfigRepository.withDefaults(RuntimeUpdateConfig config) - : coinConfigProvider = CoinConfigProvider.fromConfig(config), - coinsDatabase = HiveLazyBoxProvider(name: 'coins'), - coinSettingsDatabase = HiveBoxProvider( - name: 'coins_settings', - ); + CoinConfigRepository.withDefaults( + RuntimeUpdateConfig config, { + String? githubToken, + }) : coinConfigProvider = CoinConfigProvider.fromConfig( + config, + githubToken: githubToken, + ), + coinsDatabase = HiveLazyBoxProvider(name: 'coins'), + coinSettingsDatabase = HiveBoxProvider( + name: 'coins_settings', + ); /// The provider that fetches the coins and coin configs. final CoinConfigProvider coinConfigProvider; diff --git a/packages/komodo_wallet_build_transformer/lib/src/steps/github/github_api_provider.dart b/packages/komodo_wallet_build_transformer/lib/src/steps/github/github_api_provider.dart index 2c6c16db..d0240fe0 100644 --- a/packages/komodo_wallet_build_transformer/lib/src/steps/github/github_api_provider.dart +++ b/packages/komodo_wallet_build_transformer/lib/src/steps/github/github_api_provider.dart @@ -17,8 +17,8 @@ class GithubApiProvider { required String repo, required String branch, String? token, - }) : _branch = branch, - _baseUrl = 'https://api.github.com/repos/$owner/$repo' { + }) : _branch = branch, + _baseUrl = 'https://api.github.com/repos/$owner/$repo' { if (token != null) { _log.finer('Using authentication token for GitHub API requests.'); _headers['Authorization'] = 'Bearer $token'; @@ -32,10 +32,11 @@ class GithubApiProvider { required String baseUrl, required String branch, String? token, - }) : _branch = branch, - _baseUrl = baseUrl { - final repoMatch = RegExp(r'^https://api\.github\.com/repos/([^/]+)/([^/]+)') - .firstMatch(baseUrl); + }) : _branch = branch, + _baseUrl = baseUrl { + final repoMatch = RegExp( + r'^https://api\.github\.com/repos/([^/]+)/([^/]+)', + ).firstMatch(baseUrl); assert(repoMatch != null, 'Invalid GitHub repository URL: $baseUrl'); if (token != null) { @@ -59,8 +60,10 @@ class GithubApiProvider { final fileMetadataUrl = '$_baseUrl/contents/$filePath?ref=$_branch'; _log.finest('Fetching file metadata from $fileMetadataUrl'); - final fileContentResponse = - await http.get(Uri.parse(fileMetadataUrl), headers: _headers); + final fileContentResponse = await http.get( + Uri.parse(fileMetadataUrl), + headers: _headers, + ); if (fileContentResponse.statusCode != 200) { throw Exception( 'Failed to fetch remote file metadata at $fileMetadataUrl: ' @@ -84,14 +87,21 @@ class GithubApiProvider { /// /// Returns a [Future] that completes with a [String] representing the latest /// commit hash. - Future getLatestCommitHash({ - String branch = 'master', - }) async { + Future getLatestCommitHash({String branch = 'master'}) async { final apiUrl = '$_baseUrl/commits/$branch'; - _log.finest('Fetching latest commit hash from $apiUrl'); + _log + ..finest('Fetching latest commit hash from $apiUrl') + ..finest('Using authentication: ${hasToken ? 'yes' : 'no'}'); final response = await http.get(Uri.parse(apiUrl), headers: _headers); if (response.statusCode != 200) { + _log + ..severe( + 'GitHub API request failed: ' + '${response.statusCode} ${response.reasonPhrase}', + ) + ..severe('Response body: ${response.body}') + ..severe('Request headers: $_headers'); throw Exception( 'Failed to retrieve latest commit hash: $branch' '[${response.statusCode}]: ${response.reasonPhrase}', @@ -126,14 +136,17 @@ class GithubApiProvider { final respString = response.body; final data = jsonDecode(respString) as List; - final files = data - .where( - (dynamic item) => (item as Map)['type'] == 'file', - ) - .map( - (dynamic file) => GitHubFile.fromJson(file as Map), - ) - .toList(); + final files = + data + .where( + (dynamic item) => + (item as Map)['type'] == 'file', + ) + .map( + (dynamic file) => + GitHubFile.fromJson(file as Map), + ) + .toList(); _log ..fine('Directory $repoPath contains ${data.length} items') diff --git a/packages/komodo_wallet_cli/bin/update_api_config.dart b/packages/komodo_wallet_cli/bin/update_api_config.dart index d9519d42..b4f643b3 100644 --- a/packages/komodo_wallet_cli/bin/update_api_config.dart +++ b/packages/komodo_wallet_cli/bin/update_api_config.dart @@ -110,7 +110,9 @@ void main(List arguments) async { final repo = args['repo'] as String; final configPath = args['config'] as String; final outputDir = args['output-dir'] as String; - final token = args['token'] as String?; + final token = + args['token'] as String? ?? + Platform.environment['GITHUB_API_PUBLIC_READONLY_TOKEN']; final platform = args['platform'] as String; final source = args['source'] as String; final mirrorUrl = args['mirror-url'] as String; From d283b62dfebd1e50cc003fa2f18f8ef512c40e91 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Wed, 6 Aug 2025 00:52:11 +0200 Subject: [PATCH 12/13] chore(kdf): roll KDF to latest `dev` Roll KDF to the latest `dev` to ensure we are using the latest revision of all unreleased features. --- .../app_build/build_config.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/komodo_defi_framework/app_build/build_config.json b/packages/komodo_defi_framework/app_build/build_config.json index 4c9b0818..e171eeab 100644 --- a/packages/komodo_defi_framework/app_build/build_config.json +++ b/packages/komodo_defi_framework/app_build/build_config.json @@ -1,6 +1,6 @@ { "api": { - "api_commit_hash": "68bc4ebfa95e5bb24cc698346e023af72a66b4ae", + "api_commit_hash": "6172ba8d1df0541dd319d4193cad1cb26df50eee", "branch": "dev", "fetch_at_build_enabled": true, "concurrent_downloads_enabled": true, @@ -12,49 +12,49 @@ "web": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-wasm|mm2_[a-f0-9]{7,40}-wasm|mm2-[a-f0-9]{7,40}-wasm)\\.zip$", "valid_zip_sha256_checksums": [ - "c132f3867217de9eeeba86f6138202e0ee7fad70571aba62c09d3febd0ce948f" + "abde4d74279850004445df3f1a6ecd095dbd5b12f4821c4bb2a14c1aa94ab770" ], "path": "web/kdf/bin" }, "ios": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-ios-aarch64|mm2_[a-f0-9]{7,40}-ios-aarch64|mm2-[a-f0-9]{7,40}-ios-aarch64-CI)\\.zip$", "valid_zip_sha256_checksums": [ - "b5b033ba377d2ff3a36f09d170a095435d159bf40581ba96700761f3df3c1f5d" + "8661a477437563e8978f47baf1486f9b6ed900e3f0fb030dedee60843ecfc882" ], "path": "ios" }, "macos": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-mac-arm64|mm2-[a-f0-9]{7,40}-Darwin-Release)\\.zip$", "valid_zip_sha256_checksums": [ - "d8743c23a23516665370f6fc75c93bb9d21c65b5e2f1bf025b5ce0d42bc57e07" + "142782fd8689c3106614f73065c6848192873f83f853eb357156f5d7c053fc92" ], "path": "macos/bin" }, "windows": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-win-x86-64|mm2_[a-f0-9]{7,40}-win-x86-64|mm2-[a-f0-9]{7,40}-Win64)\\.zip$", "valid_zip_sha256_checksums": [ - "aa81b295a116a77da88102350fbb8a9695e8a54bb92c34432aa890113599e693" + "8697178c85cd047a7f0a9331fd8f91dc256a6732dc311ad81b1fe7eba55baab9" ], "path": "windows/bin" }, "android-armv7": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-android-armv7|mm2_[a-f0-9]{7,40}-android-armv7|mm2-[a-f0-9]{7,40}-android-armv7-CI)\\.zip$", "valid_zip_sha256_checksums": [ - "581c35888a8c8073ebac1067ad93ffa850624c544914fbd87fe538e49b8f7829" + "b3226bced064770a09eb556d411538d5d41e4c4513deb3feb05b5b3c04896c27" ], "path": "android/app/src/main/cpp/libs/armeabi-v7a" }, "android-aarch64": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-android-aarch64|mm2_[a-f0-9]{7,40}-android-aarch64|mm2-[a-f0-9]{7,40}-android-aarch64-CI)\\.zip$", "valid_zip_sha256_checksums": [ - "2be8c10798e692f0e459aeffb54bddb49ddeaa22bacd78d34b3ca3b575cb6d08" + "e4111bbce3fa991430d632f8c0bf5d3ff55e505c34bde87f454db474f27eaa29" ], "path": "android/app/src/main/cpp/libs/arm64-v8a" }, "linux": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-linux-x86-64|mm2_[a-f0-9]{7,40}-linux-x86-64|mm2-[a-f0-9]{7,40}-Linux-Release)\\.zip$", "valid_zip_sha256_checksums": [ - "9a1644aa5d3252aaf165c963218dda29727bc543181ecfc643aa4df39a24fc53" + "cfe775cd2b4215bb2e5cb3516adb040ec8df64f8de9dc50e154f0ee863051e72" ], "path": "linux/bin" } From 2b092912ca5a66d05d6e0718687599df40354d06 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Wed, 6 Aug 2025 00:55:01 +0200 Subject: [PATCH 13/13] fix: apply cursor fixes --- .../lib/src/storage/file_log_storage.dart | 22 ++--- .../lib/src/storage/web_log_storage_wasm.dart | 95 ++++++++++--------- 2 files changed, 63 insertions(+), 54 deletions(-) diff --git a/packages/dragon_logs/lib/src/storage/file_log_storage.dart b/packages/dragon_logs/lib/src/storage/file_log_storage.dart index 55693f20..452dc864 100644 --- a/packages/dragon_logs/lib/src/storage/file_log_storage.dart +++ b/packages/dragon_logs/lib/src/storage/file_log_storage.dart @@ -78,8 +78,8 @@ class FileLogStorage Future deleteOldLogs(int size) async { while (await getLogFolderSize() > size) { final files = await getLogFiles(); - final sortedFiles = files.entries.toList() - ..sort((a, b) => a.key.compareTo(b.key)); + final sortedFiles = + files.entries.toList()..sort((a, b) => a.key.compareTo(b.key)); await sortedFiles.first.value.delete(); } } @@ -101,8 +101,8 @@ class FileLogStorage Stream exportLogsStream() async* { final files = await getLogFiles(); - final sortedFiles = files.values.toList() - ..sort((a, b) => a.path.compareTo(b.path)); + final sortedFiles = + files.values.toList()..sort((a, b) => a.path.compareTo(b.path)); for (final file in sortedFiles) { final stats = file.statSync(); final sizeKb = stats.size / 1024; @@ -127,9 +127,10 @@ class FileLogStorage @override Future deleteExportedFiles() async { - final archives = _exportFilesDirectory - .listSync(followLinks: false, recursive: true) - .whereType(); + final archives = + _exportFilesDirectory + .listSync(followLinks: false, recursive: true) + .whereType(); final deleteArchivesFutures = archives.map((archive) => archive.delete()); @@ -216,10 +217,9 @@ class FileLogStorage await raf.close(); // Use share_plus to share the log file - await Share.shareXFiles( - [XFile(file.path, mimeType: 'text/plain')], - text: 'App log file export', - ); + await Share.shareXFiles([ + XFile(file.path, mimeType: 'text/plain'), + ], text: 'App log file export'); } static Future getLogFolderPath() async { diff --git a/packages/dragon_logs/lib/src/storage/web_log_storage_wasm.dart b/packages/dragon_logs/lib/src/storage/web_log_storage_wasm.dart index 56e09110..833ee934 100644 --- a/packages/dragon_logs/lib/src/storage/web_log_storage_wasm.dart +++ b/packages/dragon_logs/lib/src/storage/web_log_storage_wasm.dart @@ -28,18 +28,19 @@ class WebLogStorageWasm final root = await storageManager.getDirectory().toDart; // Create or get the dragon_logs directory - _logDirectory = await root - .getDirectoryHandle( - "dragon_logs", - FileSystemGetDirectoryOptions(create: true), - ) - .toDart; + _logDirectory = + await root + .getDirectoryHandle( + "dragon_logs", + FileSystemGetDirectoryOptions(create: true), + ) + .toDart; initQueueFlusher(); } @override - Future writeToTextFile(String logs, {bool batchWrite = true}) async { + Future writeToTextFile(String logs) async { if (_currentLogStream == null) { await initWriteDate(DateTime.now()); } @@ -58,21 +59,23 @@ class WebLogStorageWasm _currentLogFileName = logFileNameOfDate(date); - _currentLogFile = await _logDirectory! - .getFileHandle( - _currentLogFileName, - FileSystemGetFileOptions(create: true), - ) - .toDart; + _currentLogFile = + await _logDirectory! + .getFileHandle( + _currentLogFileName, + FileSystemGetFileOptions(create: true), + ) + .toDart; final file = await _currentLogFile!.getFile().toDart; final sizeBytes = file.size.toInt(); - _currentLogStream = await _currentLogFile! - .createWritable( - FileSystemCreateWritableOptions(keepExistingData: true), - ) - .toDart; + _currentLogStream = + await _currentLogFile! + .createWritable( + FileSystemCreateWritableOptions(keepExistingData: true), + ) + .toDart; await _currentLogStream!.seek(sizeBytes).toDart; } @@ -85,27 +88,32 @@ class WebLogStorageWasm while (await getLogFolderSize() > size) { final files = await _getLogFiles(); - final sortedFiles = files - .where( - (handle) => CommonLogStorageOperations.isLogFileNameValid( - handle.name, - ), - ) - .toList() - ..sort((a, b) { - final aDate = CommonLogStorageOperations.tryParseLogFileDate( - a.name, - ); - final bDate = CommonLogStorageOperations.tryParseLogFileDate( - b.name, - ); - - if (aDate == null || bDate == null) { - return 0; - } - - return aDate.compareTo(bDate); - }); + final sortedFiles = + files + .where( + (handle) => CommonLogStorageOperations.isLogFileNameValid( + handle.name, + ), + ) + .toList() + ..sort((a, b) { + final aDate = CommonLogStorageOperations.tryParseLogFileDate( + a.name, + ); + final bDate = CommonLogStorageOperations.tryParseLogFileDate( + b.name, + ); + + if (aDate == null || bDate == null) { + return 0; + } + + return aDate.compareTo(bDate); + }); + + if (sortedFiles.isEmpty) { + break; + } await _logDirectory! .removeEntry( @@ -206,10 +214,11 @@ class WebLogStorageWasm final blob = Blob([Uint8List.fromList(bytes).toJS].toJS); final url = URL.createObjectURL(blob); - final anchor = HTMLAnchorElement() - ..href = url - ..download = filename - ..style.display = 'none'; + final anchor = + HTMLAnchorElement() + ..href = url + ..download = filename + ..style.display = 'none'; document.body!.appendChild(anchor); anchor.click();