diff --git a/Documentation/Extensions.md b/Documentation/Extensions.md index 4a02881..5a23e7f 100644 --- a/Documentation/Extensions.md +++ b/Documentation/Extensions.md @@ -1760,12 +1760,24 @@ digest.map { String(format: "%02x", $0) }.joined()
-LCECryptoKitManager — OTP / peppered-login bridge (needs the LCECryptoKit binary) +LCECryptoKitManager — OTP / peppered-login bridge (separate LCECryptoKit product) -A thin facade over the optional `LCECryptoKit` binary product (enabled by the -`LCE_ENABLE_CRYPTO_BINARY` build flag). **When the binary is not linked every -method is a no-op** returning `nil` / `""` / `false`, so calling code still -compiles and runs. +Lives in its own SPM product, `LCECryptoKit` (target `LCECryptoKitManager`), not in +`LCEssentials` itself — an optional sub-package that target-depends on `LCEssentials`. +Add it explicitly alongside `LCEssentials` to a consumer's `Package.swift`: + +```swift +.product(name: "LCEssentials", package: "LCEssentials"), +.product(name: "LCECryptoKit", package: "LCEssentials"), +``` + +```swift +import LCECryptoKitManager +``` + +Wraps the vendored `LCECryptoKit.xcframework` (iOS device + simulator only — no +macOS/tvOS/watchOS slices). Consumers who don't link this product simply don't get +the symbol; there is no no-op fallback anymore. ### `init()` / `init(privateKey:)` diff --git a/Frameworks/LCECryptoKit.xcframework/Info.plist b/Frameworks/LCECryptoKit.xcframework/Info.plist new file mode 100644 index 0000000..c628319 --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/Info.plist @@ -0,0 +1,44 @@ + + + + + AvailableLibraries + + + BinaryPath + LCECryptoKit.framework/LCECryptoKit + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + LCECryptoKit.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + BinaryPath + LCECryptoKit.framework/LCECryptoKit + LibraryIdentifier + ios-arm64 + LibraryPath + LCECryptoKit.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Headers/LCECryptoKit-Swift.h b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Headers/LCECryptoKit-Swift.h new file mode 100644 index 0000000..d6185ab --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Headers/LCECryptoKit-Swift.h @@ -0,0 +1,382 @@ +#if 0 +#elif defined(__arm64__) && __arm64__ +// Generated by Apple Swift version 6.3.3 effective-5.10 (swiftlang-6.3.3.1.3 clang-2100.1.1.101) +#ifndef LCECRYPTOKIT_SWIFT_H +#define LCECRYPTOKIT_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif // defined(__OBJC__) +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module" +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#pragma clang diagnostic pop +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef unsigned char char8_t; +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +# else +# define SWIFT_ENUM(_type, _name, _extensibility) _type _name; enum +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM_TAG) +# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum) +# define SWIFT_ENUM_TAG enum +# else +# define SWIFT_ENUM_TAG +# endif +#endif +#if !defined(SWIFT_ENUM_FWD_DECL) +# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum) +# define SWIFT_ENUM_FWD_DECL(_type, _name) enum _name : _type; +# else +# define SWIFT_ENUM_FWD_DECL(_type, _name) typedef _type _name; +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_AVAILABILITY_DOMAIN) +# define SWIFT_AVAILABILITY_DOMAIN(dom, ...) __attribute__((availability(domain: dom, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if !__has_feature(nullability) +# define _Nonnull +# define _Nullable +# define _Null_unspecified +#elif !defined(__OBJC__) +# pragma clang diagnostic ignored "-Wnullability-extension" +#endif +#if !__has_feature(nullability_nullable_result) +# define _Nullable_result _Nullable +#endif +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="LCECryptoKit",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(__cplusplus) +} // extern "C" +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif // defined(__OBJC__) +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="LCECryptoKit",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) + +#endif // defined(__OBJC__) +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#else +#error unsupported Swift architecture +#endif diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Info.plist b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Info.plist new file mode 100644 index 0000000..359fa30 Binary files /dev/null and b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Info.plist differ diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/LCECryptoKit b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/LCECryptoKit new file mode 100755 index 0000000..2db08c2 Binary files /dev/null and b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/LCECryptoKit differ diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.abi.json b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.abi.json new file mode 100644 index 0000000..32ec755 --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.abi.json @@ -0,0 +1,417 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "LCECryptoKit", + "printedName": "LCECryptoKit", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LCECryptoKit" + }, + { + "kind": "Import", + "name": "CommonCrypto", + "printedName": "CommonCrypto", + "declKind": "Import", + "moduleName": "LCECryptoKit" + }, + { + "kind": "TypeDecl", + "name": "LCECryptoKit", + "printedName": "LCECryptoKit", + "children": [ + { + "kind": "Function", + "name": "generateRandomAESKeyString", + "printedName": "generateRandomAESKeyString()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV26generateRandomAESKeyStringSSyFZ", + "mangledName": "$s12LCECryptoKitAAV26generateRandomAESKeyStringSSyFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "encodeSeed", + "printedName": "encodeSeed(email:password:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10encodeSeed5email8passwordSSSgSS_SStFZ", + "mangledName": "$s12LCECryptoKitAAV10encodeSeed5email8passwordSSSgSS_SStFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "encodeSeed", + "printedName": "encodeSeed(email:password:hashKey:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10encodeSeed5email8password7hashKeySSSgSS_S2StFZ", + "mangledName": "$s12LCECryptoKitAAV10encodeSeed5email8password7hashKeySSSgSS_S2StFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "decodeSeed", + "printedName": "decodeSeed(otpKey:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10decodeSeed6otpKeySSSgSS_tFZ", + "mangledName": "$s12LCECryptoKitAAV10decodeSeed6otpKeySSSgSS_tFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "decodeSeed", + "printedName": "decodeSeed(otpKey:hashKey:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10decodeSeed6otpKey04hashF0SbSS_SStFZ", + "mangledName": "$s12LCECryptoKitAAV10decodeSeed6otpKey04hashF0SbSS_SStFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "generateSalt", + "printedName": "generateSalt()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV12generateSaltSSyFZ", + "mangledName": "$s12LCECryptoKitAAV12generateSaltSSyFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "computeClientHash", + "printedName": "computeClientHash(email:password:salt:)", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV17computeClientHash5email8password4saltS2S_S2StFZ", + "mangledName": "$s12LCECryptoKitAAV17computeClientHash5email8password4saltS2S_S2StFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "computeLoginBearerToken", + "printedName": "computeLoginBearerToken(userId:clientHash:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV23computeLoginBearerToken6userId10clientHashSSSgSS_SStFZ", + "mangledName": "$s12LCECryptoKitAAV23computeLoginBearerToken6userId10clientHashSSSgSS_SStFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "otpEncode", + "printedName": "otpEncode(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV9otpEncodeySSSgSSFZ", + "mangledName": "$s12LCECryptoKitAAV9otpEncodeySSSgSSFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "otpDecode", + "printedName": "otpDecode(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV9otpDecodeySSSgSSFZ", + "mangledName": "$s12LCECryptoKitAAV9otpDecodeySSSgSSFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Struct", + "usr": "s:12LCECryptoKitAAV", + "mangledName": "$s12LCECryptoKitAAV", + "moduleName": "LCECryptoKit", + "declAttributes": [ + "AccessControl" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Copyable", + "printedName": "Copyable", + "usr": "s:s8CopyableP", + "mangledName": "$ss8CopyableP" + }, + { + "kind": "Conformance", + "name": "Escapable", + "printedName": "Escapable", + "usr": "s:s9EscapableP", + "mangledName": "$ss9EscapableP" + } + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.private.swiftinterface b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.private.swiftinterface new file mode 100644 index 0000000..d8cb5f9 --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.private.swiftinterface @@ -0,0 +1,22 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 6.3.3 effective-5.10 (swiftlang-6.3.3.1.3 clang-2100.1.1.101) +// swift-module-flags: -target arm64-apple-ios13.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -O -enable-experimental-feature DebugDescriptionMacro -enable-bare-slash-regex -module-name LCECryptoKit +// swift-module-flags-ignorable: -no-verify-emitted-module-interface -formal-cxx-interoperability-mode=off -interface-compiler-version 6.3.3 +import CommonCrypto +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims +public struct LCECryptoKit { + public static func generateRandomAESKeyString() -> Swift.String + public static func encodeSeed(email: Swift.String, password: Swift.String) -> Swift.String? + public static func encodeSeed(email: Swift.String, password: Swift.String, hashKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String, hashKey: Swift.String) -> Swift.Bool + public static func generateSalt() -> Swift.String + public static func computeClientHash(email: Swift.String, password: Swift.String, salt: Swift.String) -> Swift.String + public static func computeLoginBearerToken(userId: Swift.String, clientHash: Swift.String) -> Swift.String? + public static func otpEncode(_ plainText: Swift.String) -> Swift.String? + public static func otpDecode(_ otpEncoded: Swift.String) -> Swift.String? +} diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.swiftdoc b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.swiftdoc new file mode 100644 index 0000000..c147959 Binary files /dev/null and b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.swiftdoc differ diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.swiftinterface b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.swiftinterface new file mode 100644 index 0000000..d8cb5f9 --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios.swiftinterface @@ -0,0 +1,22 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 6.3.3 effective-5.10 (swiftlang-6.3.3.1.3 clang-2100.1.1.101) +// swift-module-flags: -target arm64-apple-ios13.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -O -enable-experimental-feature DebugDescriptionMacro -enable-bare-slash-regex -module-name LCECryptoKit +// swift-module-flags-ignorable: -no-verify-emitted-module-interface -formal-cxx-interoperability-mode=off -interface-compiler-version 6.3.3 +import CommonCrypto +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims +public struct LCECryptoKit { + public static func generateRandomAESKeyString() -> Swift.String + public static func encodeSeed(email: Swift.String, password: Swift.String) -> Swift.String? + public static func encodeSeed(email: Swift.String, password: Swift.String, hashKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String, hashKey: Swift.String) -> Swift.Bool + public static func generateSalt() -> Swift.String + public static func computeClientHash(email: Swift.String, password: Swift.String, salt: Swift.String) -> Swift.String + public static func computeLoginBearerToken(userId: Swift.String, clientHash: Swift.String) -> Swift.String? + public static func otpEncode(_ plainText: Swift.String) -> Swift.String? + public static func otpDecode(_ otpEncoded: Swift.String) -> Swift.String? +} diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/module.modulemap b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/module.modulemap new file mode 100644 index 0000000..cccd3c0 --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64/LCECryptoKit.framework/Modules/module.modulemap @@ -0,0 +1,3 @@ +framework module LCECryptoKit { + header "LCECryptoKit-Swift.h" +} diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Headers/LCECryptoKit-Swift.h b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Headers/LCECryptoKit-Swift.h new file mode 100644 index 0000000..1f9e2d6 --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Headers/LCECryptoKit-Swift.h @@ -0,0 +1,760 @@ +#if 0 +#elif defined(__arm64__) && __arm64__ +// Generated by Apple Swift version 6.3.3 effective-5.10 (swiftlang-6.3.3.1.3 clang-2100.1.1.101) +#ifndef LCECRYPTOKIT_SWIFT_H +#define LCECRYPTOKIT_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif // defined(__OBJC__) +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module" +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#pragma clang diagnostic pop +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef unsigned char char8_t; +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +# else +# define SWIFT_ENUM(_type, _name, _extensibility) _type _name; enum +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM_TAG) +# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum) +# define SWIFT_ENUM_TAG enum +# else +# define SWIFT_ENUM_TAG +# endif +#endif +#if !defined(SWIFT_ENUM_FWD_DECL) +# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum) +# define SWIFT_ENUM_FWD_DECL(_type, _name) enum _name : _type; +# else +# define SWIFT_ENUM_FWD_DECL(_type, _name) typedef _type _name; +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_AVAILABILITY_DOMAIN) +# define SWIFT_AVAILABILITY_DOMAIN(dom, ...) __attribute__((availability(domain: dom, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if !__has_feature(nullability) +# define _Nonnull +# define _Nullable +# define _Null_unspecified +#elif !defined(__OBJC__) +# pragma clang diagnostic ignored "-Wnullability-extension" +#endif +#if !__has_feature(nullability_nullable_result) +# define _Nullable_result _Nullable +#endif +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="LCECryptoKit",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(__cplusplus) +} // extern "C" +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif // defined(__OBJC__) +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="LCECryptoKit",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) + +#endif // defined(__OBJC__) +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#elif defined(__x86_64__) && __x86_64__ +// Generated by Apple Swift version 6.3.3 effective-5.10 (swiftlang-6.3.3.1.3 clang-2100.1.1.101) +#ifndef LCECRYPTOKIT_SWIFT_H +#define LCECRYPTOKIT_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif // defined(__OBJC__) +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module" +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#pragma clang diagnostic pop +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef unsigned char char8_t; +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +# else +# define SWIFT_ENUM(_type, _name, _extensibility) _type _name; enum +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM_TAG) +# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum) +# define SWIFT_ENUM_TAG enum +# else +# define SWIFT_ENUM_TAG +# endif +#endif +#if !defined(SWIFT_ENUM_FWD_DECL) +# if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || __has_feature(objc_fixed_enum) +# define SWIFT_ENUM_FWD_DECL(_type, _name) enum _name : _type; +# else +# define SWIFT_ENUM_FWD_DECL(_type, _name) typedef _type _name; +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_AVAILABILITY_DOMAIN) +# define SWIFT_AVAILABILITY_DOMAIN(dom, ...) __attribute__((availability(domain: dom, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if !__has_feature(nullability) +# define _Nonnull +# define _Nullable +# define _Null_unspecified +#elif !defined(__OBJC__) +# pragma clang diagnostic ignored "-Wnullability-extension" +#endif +#if !__has_feature(nullability_nullable_result) +# define _Nullable_result _Nullable +#endif +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="LCECryptoKit",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(__cplusplus) +} // extern "C" +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif // defined(__OBJC__) +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="LCECryptoKit",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) + +#endif // defined(__OBJC__) +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#else +#error unsupported Swift architecture +#endif diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Info.plist b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Info.plist new file mode 100644 index 0000000..4782fa8 Binary files /dev/null and b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Info.plist differ diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/LCECryptoKit b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/LCECryptoKit new file mode 100755 index 0000000..70c713e Binary files /dev/null and b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/LCECryptoKit differ diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.abi.json b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.abi.json new file mode 100644 index 0000000..32ec755 --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.abi.json @@ -0,0 +1,417 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "LCECryptoKit", + "printedName": "LCECryptoKit", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LCECryptoKit" + }, + { + "kind": "Import", + "name": "CommonCrypto", + "printedName": "CommonCrypto", + "declKind": "Import", + "moduleName": "LCECryptoKit" + }, + { + "kind": "TypeDecl", + "name": "LCECryptoKit", + "printedName": "LCECryptoKit", + "children": [ + { + "kind": "Function", + "name": "generateRandomAESKeyString", + "printedName": "generateRandomAESKeyString()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV26generateRandomAESKeyStringSSyFZ", + "mangledName": "$s12LCECryptoKitAAV26generateRandomAESKeyStringSSyFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "encodeSeed", + "printedName": "encodeSeed(email:password:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10encodeSeed5email8passwordSSSgSS_SStFZ", + "mangledName": "$s12LCECryptoKitAAV10encodeSeed5email8passwordSSSgSS_SStFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "encodeSeed", + "printedName": "encodeSeed(email:password:hashKey:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10encodeSeed5email8password7hashKeySSSgSS_S2StFZ", + "mangledName": "$s12LCECryptoKitAAV10encodeSeed5email8password7hashKeySSSgSS_S2StFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "decodeSeed", + "printedName": "decodeSeed(otpKey:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10decodeSeed6otpKeySSSgSS_tFZ", + "mangledName": "$s12LCECryptoKitAAV10decodeSeed6otpKeySSSgSS_tFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "decodeSeed", + "printedName": "decodeSeed(otpKey:hashKey:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10decodeSeed6otpKey04hashF0SbSS_SStFZ", + "mangledName": "$s12LCECryptoKitAAV10decodeSeed6otpKey04hashF0SbSS_SStFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "generateSalt", + "printedName": "generateSalt()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV12generateSaltSSyFZ", + "mangledName": "$s12LCECryptoKitAAV12generateSaltSSyFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "computeClientHash", + "printedName": "computeClientHash(email:password:salt:)", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV17computeClientHash5email8password4saltS2S_S2StFZ", + "mangledName": "$s12LCECryptoKitAAV17computeClientHash5email8password4saltS2S_S2StFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "computeLoginBearerToken", + "printedName": "computeLoginBearerToken(userId:clientHash:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV23computeLoginBearerToken6userId10clientHashSSSgSS_SStFZ", + "mangledName": "$s12LCECryptoKitAAV23computeLoginBearerToken6userId10clientHashSSSgSS_SStFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "otpEncode", + "printedName": "otpEncode(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV9otpEncodeySSSgSSFZ", + "mangledName": "$s12LCECryptoKitAAV9otpEncodeySSSgSSFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "otpDecode", + "printedName": "otpDecode(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV9otpDecodeySSSgSSFZ", + "mangledName": "$s12LCECryptoKitAAV9otpDecodeySSSgSSFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Struct", + "usr": "s:12LCECryptoKitAAV", + "mangledName": "$s12LCECryptoKitAAV", + "moduleName": "LCECryptoKit", + "declAttributes": [ + "AccessControl" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Copyable", + "printedName": "Copyable", + "usr": "s:s8CopyableP", + "mangledName": "$ss8CopyableP" + }, + { + "kind": "Conformance", + "name": "Escapable", + "printedName": "Escapable", + "usr": "s:s9EscapableP", + "mangledName": "$ss9EscapableP" + } + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface new file mode 100644 index 0000000..f75100d --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface @@ -0,0 +1,22 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 6.3.3 effective-5.10 (swiftlang-6.3.3.1.3 clang-2100.1.1.101) +// swift-module-flags: -target arm64-apple-ios13.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -O -enable-experimental-feature DebugDescriptionMacro -enable-bare-slash-regex -module-name LCECryptoKit +// swift-module-flags-ignorable: -no-verify-emitted-module-interface -formal-cxx-interoperability-mode=off -interface-compiler-version 6.3.3 +import CommonCrypto +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims +public struct LCECryptoKit { + public static func generateRandomAESKeyString() -> Swift.String + public static func encodeSeed(email: Swift.String, password: Swift.String) -> Swift.String? + public static func encodeSeed(email: Swift.String, password: Swift.String, hashKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String, hashKey: Swift.String) -> Swift.Bool + public static func generateSalt() -> Swift.String + public static func computeClientHash(email: Swift.String, password: Swift.String, salt: Swift.String) -> Swift.String + public static func computeLoginBearerToken(userId: Swift.String, clientHash: Swift.String) -> Swift.String? + public static func otpEncode(_ plainText: Swift.String) -> Swift.String? + public static func otpDecode(_ otpEncoded: Swift.String) -> Swift.String? +} diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc new file mode 100644 index 0000000..9fa6aa2 Binary files /dev/null and b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc differ diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface new file mode 100644 index 0000000..f75100d --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface @@ -0,0 +1,22 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 6.3.3 effective-5.10 (swiftlang-6.3.3.1.3 clang-2100.1.1.101) +// swift-module-flags: -target arm64-apple-ios13.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -O -enable-experimental-feature DebugDescriptionMacro -enable-bare-slash-regex -module-name LCECryptoKit +// swift-module-flags-ignorable: -no-verify-emitted-module-interface -formal-cxx-interoperability-mode=off -interface-compiler-version 6.3.3 +import CommonCrypto +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims +public struct LCECryptoKit { + public static func generateRandomAESKeyString() -> Swift.String + public static func encodeSeed(email: Swift.String, password: Swift.String) -> Swift.String? + public static func encodeSeed(email: Swift.String, password: Swift.String, hashKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String, hashKey: Swift.String) -> Swift.Bool + public static func generateSalt() -> Swift.String + public static func computeClientHash(email: Swift.String, password: Swift.String, salt: Swift.String) -> Swift.String + public static func computeLoginBearerToken(userId: Swift.String, clientHash: Swift.String) -> Swift.String? + public static func otpEncode(_ plainText: Swift.String) -> Swift.String? + public static func otpDecode(_ otpEncoded: Swift.String) -> Swift.String? +} diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.abi.json b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.abi.json new file mode 100644 index 0000000..32ec755 --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.abi.json @@ -0,0 +1,417 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "LCECryptoKit", + "printedName": "LCECryptoKit", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "LCECryptoKit" + }, + { + "kind": "Import", + "name": "CommonCrypto", + "printedName": "CommonCrypto", + "declKind": "Import", + "moduleName": "LCECryptoKit" + }, + { + "kind": "TypeDecl", + "name": "LCECryptoKit", + "printedName": "LCECryptoKit", + "children": [ + { + "kind": "Function", + "name": "generateRandomAESKeyString", + "printedName": "generateRandomAESKeyString()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV26generateRandomAESKeyStringSSyFZ", + "mangledName": "$s12LCECryptoKitAAV26generateRandomAESKeyStringSSyFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "encodeSeed", + "printedName": "encodeSeed(email:password:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10encodeSeed5email8passwordSSSgSS_SStFZ", + "mangledName": "$s12LCECryptoKitAAV10encodeSeed5email8passwordSSSgSS_SStFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "encodeSeed", + "printedName": "encodeSeed(email:password:hashKey:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10encodeSeed5email8password7hashKeySSSgSS_S2StFZ", + "mangledName": "$s12LCECryptoKitAAV10encodeSeed5email8password7hashKeySSSgSS_S2StFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "decodeSeed", + "printedName": "decodeSeed(otpKey:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10decodeSeed6otpKeySSSgSS_tFZ", + "mangledName": "$s12LCECryptoKitAAV10decodeSeed6otpKeySSSgSS_tFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "decodeSeed", + "printedName": "decodeSeed(otpKey:hashKey:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Bool", + "printedName": "Swift.Bool", + "usr": "s:Sb" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV10decodeSeed6otpKey04hashF0SbSS_SStFZ", + "mangledName": "$s12LCECryptoKitAAV10decodeSeed6otpKey04hashF0SbSS_SStFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "generateSalt", + "printedName": "generateSalt()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV12generateSaltSSyFZ", + "mangledName": "$s12LCECryptoKitAAV12generateSaltSSyFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "computeClientHash", + "printedName": "computeClientHash(email:password:salt:)", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV17computeClientHash5email8password4saltS2S_S2StFZ", + "mangledName": "$s12LCECryptoKitAAV17computeClientHash5email8password4saltS2S_S2StFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "computeLoginBearerToken", + "printedName": "computeLoginBearerToken(userId:clientHash:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV23computeLoginBearerToken6userId10clientHashSSSgSS_SStFZ", + "mangledName": "$s12LCECryptoKitAAV23computeLoginBearerToken6userId10clientHashSSSgSS_SStFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "otpEncode", + "printedName": "otpEncode(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV9otpEncodeySSSgSSFZ", + "mangledName": "$s12LCECryptoKitAAV9otpEncodeySSSgSSFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl", + "RawDocComment" + ], + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "otpDecode", + "printedName": "otpDecode(_:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Func", + "usr": "s:12LCECryptoKitAAV9otpDecodeySSSgSSFZ", + "mangledName": "$s12LCECryptoKitAAV9otpDecodeySSSgSSFZ", + "moduleName": "LCECryptoKit", + "static": true, + "declAttributes": [ + "AccessControl" + ], + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Struct", + "usr": "s:12LCECryptoKitAAV", + "mangledName": "$s12LCECryptoKitAAV", + "moduleName": "LCECryptoKit", + "declAttributes": [ + "AccessControl" + ], + "conformances": [ + { + "kind": "Conformance", + "name": "Copyable", + "printedName": "Copyable", + "usr": "s:s8CopyableP", + "mangledName": "$ss8CopyableP" + }, + { + "kind": "Conformance", + "name": "Escapable", + "printedName": "Escapable", + "usr": "s:s9EscapableP", + "mangledName": "$ss9EscapableP" + } + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface new file mode 100644 index 0000000..81da07e --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface @@ -0,0 +1,22 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 6.3.3 effective-5.10 (swiftlang-6.3.3.1.3 clang-2100.1.1.101) +// swift-module-flags: -target x86_64-apple-ios13.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -O -enable-experimental-feature DebugDescriptionMacro -enable-bare-slash-regex -module-name LCECryptoKit +// swift-module-flags-ignorable: -no-verify-emitted-module-interface -formal-cxx-interoperability-mode=off -interface-compiler-version 6.3.3 +import CommonCrypto +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims +public struct LCECryptoKit { + public static func generateRandomAESKeyString() -> Swift.String + public static func encodeSeed(email: Swift.String, password: Swift.String) -> Swift.String? + public static func encodeSeed(email: Swift.String, password: Swift.String, hashKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String, hashKey: Swift.String) -> Swift.Bool + public static func generateSalt() -> Swift.String + public static func computeClientHash(email: Swift.String, password: Swift.String, salt: Swift.String) -> Swift.String + public static func computeLoginBearerToken(userId: Swift.String, clientHash: Swift.String) -> Swift.String? + public static func otpEncode(_ plainText: Swift.String) -> Swift.String? + public static func otpDecode(_ otpEncoded: Swift.String) -> Swift.String? +} diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc new file mode 100644 index 0000000..f2ae825 Binary files /dev/null and b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc differ diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface new file mode 100644 index 0000000..81da07e --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface @@ -0,0 +1,22 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 6.3.3 effective-5.10 (swiftlang-6.3.3.1.3 clang-2100.1.1.101) +// swift-module-flags: -target x86_64-apple-ios13.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -O -enable-experimental-feature DebugDescriptionMacro -enable-bare-slash-regex -module-name LCECryptoKit +// swift-module-flags-ignorable: -no-verify-emitted-module-interface -formal-cxx-interoperability-mode=off -interface-compiler-version 6.3.3 +import CommonCrypto +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims +public struct LCECryptoKit { + public static func generateRandomAESKeyString() -> Swift.String + public static func encodeSeed(email: Swift.String, password: Swift.String) -> Swift.String? + public static func encodeSeed(email: Swift.String, password: Swift.String, hashKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String) -> Swift.String? + public static func decodeSeed(otpKey: Swift.String, hashKey: Swift.String) -> Swift.Bool + public static func generateSalt() -> Swift.String + public static func computeClientHash(email: Swift.String, password: Swift.String, salt: Swift.String) -> Swift.String + public static func computeLoginBearerToken(userId: Swift.String, clientHash: Swift.String) -> Swift.String? + public static func otpEncode(_ plainText: Swift.String) -> Swift.String? + public static func otpDecode(_ otpEncoded: Swift.String) -> Swift.String? +} diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/module.modulemap b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/module.modulemap new file mode 100644 index 0000000..cccd3c0 --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/Modules/module.modulemap @@ -0,0 +1,3 @@ +framework module LCECryptoKit { + header "LCECryptoKit-Swift.h" +} diff --git a/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/_CodeSignature/CodeResources b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/_CodeSignature/CodeResources new file mode 100644 index 0000000..0e9463b --- /dev/null +++ b/Frameworks/LCECryptoKit.xcframework/ios-arm64_x86_64-simulator/LCECryptoKit.framework/_CodeSignature/CodeResources @@ -0,0 +1,234 @@ + + + + + files + + Headers/LCECryptoKit-Swift.h + + uIM6/aOz59qnT/jGSBAiOinS2qo= + + Info.plist + + N0tkj+ldmO7dEKF+W1/6CjYNBrg= + + Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.abi.json + + mIejxCLstZ77ufLnGayqr8wDScc= + + Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface + + GJVZwAs/SRyyfGkg1t2Twb8grXE= + + Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + vDvqI7FV3rThZRI4r/zPL877Z04= + + Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface + + GJVZwAs/SRyyfGkg1t2Twb8grXE= + + Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftmodule + + 0n6YBQBivkPuELjPpXBIj0CQFIc= + + Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.abi.json + + mIejxCLstZ77ufLnGayqr8wDScc= + + Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface + + WheGrMD0QDYsEwZ2vOGY+CQwW1o= + + Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + f52HXxbH78iRln4x78whSwyigbc= + + Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface + + WheGrMD0QDYsEwZ2vOGY+CQwW1o= + + Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + FSzeAs9LdtELeWT3VL2PMjOFZcQ= + + Modules/module.modulemap + + OnB7ckFjsSU10c/y8kUFi69Cav4= + + + files2 + + Headers/LCECryptoKit-Swift.h + + hash2 + + gHRFTmnGK28MYikS6gvnWErwc/oZ/scYeFssVTBw9MI= + + + Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.abi.json + + hash2 + + /2qkU9Pje/wZPzf5sExK+0bliYzKjDZM7iqyIusKGGk= + + + Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface + + hash2 + + BsY3SInOh3u6xj+kXxauIrgLi4cTqE1mY6weF7QOk2g= + + + Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + hash2 + + Ldu7EDkaW9WinMjRHaBaSxpZoBNDXGvNn04410k9e5k= + + + Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface + + hash2 + + BsY3SInOh3u6xj+kXxauIrgLi4cTqE1mY6weF7QOk2g= + + + Modules/LCECryptoKit.swiftmodule/arm64-apple-ios-simulator.swiftmodule + + hash2 + + cuezjPqokn+YGP9z//TY4ZUxA1Dcmd6ApUafMMaQPAQ= + + + Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.abi.json + + hash2 + + /2qkU9Pje/wZPzf5sExK+0bliYzKjDZM7iqyIusKGGk= + + + Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface + + hash2 + + lJH2d+h2iS4AwqKh/vWgSYIGEelZHLrCNQVlcCFB3V4= + + + Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + hash2 + + 5S2/W4WoD0L4bl+NBuPQI7sGxqpZ7y2s7tme6Eu2oIQ= + + + Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface + + hash2 + + lJH2d+h2iS4AwqKh/vWgSYIGEelZHLrCNQVlcCFB3V4= + + + Modules/LCECryptoKit.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + hash2 + + a5uCPhYWs0qR1YWQZb51vKm+4mYKUgc5ajZr2wEU6tU= + + + Modules/module.modulemap + + hash2 + + X+gHfuxKBEgqt+p9I9kR0FYyzml9UPuJpKzZAbPNcc0= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index 563f2c1..0000000 --- a/Package.resolved +++ /dev/null @@ -1,15 +0,0 @@ -{ - "originHash" : "6e3fcf8724d6b7d2d0bbffc25b4ec3b9d8b65a6ef53d157498d5447fd26cf5db", - "pins" : [ - { - "identity" : "lcecryptokitbinary", - "kind" : "remoteSourceControl", - "location" : "https://7876d8abdb7260a455259d01de49c4022f5da13c@git.loverde.com.br/Loverde-Company-LTDA/LCECryptoKitBinary.git", - "state" : { - "revision" : "11f66b1b9785b4965e027c2fa4742f61e8453403", - "version" : "1.0.4" - } - } - ], - "version" : 3 -} diff --git a/Package.swift b/Package.swift index d5c1b1b..ac9ab4f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,25 +1,13 @@ // swift-tools-version: 6.0 import PackageDescription -import Foundation -let isLocalDevelopment = false //FileManager.default.fileExists(atPath: "../LCECryptoKit/PrivateLib/LCECryptoKitBinary") -let enableCryptoBinary = ProcessInfo.processInfo.environment["LCE_ENABLE_CRYPTO_BINARY"] != "0" - -let cryptoPackageURL = isLocalDevelopment - ? "../LCECryptoKit/PrivateLib/LCECryptoKitBinary" - : "https://7876d8abdb7260a455259d01de49c4022f5da13c@git.loverde.com.br/Loverde-Company-LTDA/LCECryptoKitBinary.git" - -let packageDependencies: [Package.Dependency] = enableCryptoBinary - ? [ - .package(url: cryptoPackageURL, exact: "1.0.4") - ] - : [] - -let targetDependencies: [Target.Dependency] = enableCryptoBinary - ? [ - .product(name: "LCECryptoKit", package: "lcecryptokitbinary") - ] - : [] +// LCECryptoKit ships as a prebuilt .xcframework (`Frameworks/LCECryptoKit.xcframework`) — +// vendored locally in this repo, no remote package dependency. iOS device + simulator slices +// only; consumers who need macOS/tvOS/watchOS simply don't link the `LCECryptoKit` product. +// +// Sub-SPM rule: `LCEssentials` installs standalone (no sub required). Every sub +// (`LCECryptoKit` here) target-depends on `LCEssentials`, so linking the sub's product always +// pulls `LCEssentials` in too — a consumer never has to declare it separately. let package = Package( name: "LCEssentials", @@ -33,14 +21,33 @@ let package = Package( .library( name: "LCEssentials", targets: ["LCEssentials"]), + .library( + name: "LCECryptoKit", + targets: ["LCECryptoKitManager"]), + .library( + name: "LCFeatureControl", + targets: ["LCFeatureControl"]), ], - dependencies: packageDependencies, targets: [ .target( - name: "LCEssentials", - dependencies: targetDependencies), + name: "LCEssentials"), + .binaryTarget( + name: "LCECryptoKit", + path: "Frameworks/LCECryptoKit.xcframework"), + .target( + name: "LCECryptoKitManager", + dependencies: [ + "LCEssentials", + "LCECryptoKit" + ]), + .target( + name: "LCFeatureControl", + dependencies: ["LCEssentials"]), .testTarget( name: "LCEssentialsTests", dependencies: ["LCEssentials"]), + .testTarget( + name: "LCFeatureControlTests", + dependencies: ["LCFeatureControl", "LCEssentials"]), ] ) diff --git a/Sources/LCEssentials/Classes/LCECrypto.swift b/Sources/LCECryptoKitManager/LCECryptoKitManager.swift similarity index 68% rename from Sources/LCEssentials/Classes/LCECrypto.swift rename to Sources/LCECryptoKitManager/LCECryptoKitManager.swift index 731d61f..e5428cf 100644 --- a/Sources/LCEssentials/Classes/LCECrypto.swift +++ b/Sources/LCECryptoKitManager/LCECryptoKitManager.swift @@ -1,4 +1,4 @@ -// +// // Copyright (c) 2025 Loverde Co. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -18,43 +18,41 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. - import Foundation - -#if canImport(LCECryptoKit) +import LCEssentials import LCECryptoKit public final class LCECryptoKitManager { - + private let hashKey: String - + public init() { self.hashKey = "" } - - public init(privateKey: String){ + + public init(privateKey: String) { self.hashKey = privateKey } - + public static func generateKey() -> String { LCECryptoKit.generateRandomAESKeyString() } - + public func encodeTP(email: String, password: String) -> String? { return LCECryptoKit.encodeSeed(email: email, password: password) } - + public func decodeOTP(_ otpHash: String) -> String? { return LCECryptoKit.decodeSeed(otpKey: otpHash) } - + // MARK: Need hashKey to decode - + public func encodeOTPWithKey(email: String, password: String) -> String? { return LCECryptoKit.encodeSeed(email: email, password: password, hashKey: self.hashKey) } - + public func decodeOTPWithKey(_ otpHash: String) -> Bool { LCECryptoKit.decodeSeed(otpKey: otpHash, hashKey: self.hashKey) } @@ -81,60 +79,3 @@ public final class LCECryptoKitManager { LCECryptoKit.otpDecode(otpEncoded) } } -#else - -public final class LCECryptoKitManager { - - private let hashKey: String - - public init() { - self.hashKey = "" - } - - public init(privateKey: String){ - self.hashKey = privateKey - } - - public static func generateKey() -> String { - "" - } - - public func encodeTP(email: String, password: String) -> String? { - nil - } - - public func decodeOTP(_ otpHash: String) -> String? { - nil - } - - public func encodeOTPWithKey(email: String, password: String) -> String? { - nil - } - - public func decodeOTPWithKey(_ otpHash: String) -> Bool { - false - } - - // MARK: - Salted/Iterated/Peppered Login (atomenta-cryptokit-pepper-refactor-sdd.md) - - public static func generateSalt() -> String { - "" - } - - public static func computeClientHash(email: String, password: String, salt: String) -> String { - "" - } - - public static func computeLoginBearerToken(userId: String, clientHash: String) -> String? { - nil - } - - public static func otpEncode(_ plainText: String) -> String? { - nil - } - - public static func otpDecode(_ otpEncoded: String) -> String? { - nil - } -} -#endif diff --git a/Sources/LCFeatureControl/FeatureControlAuthorizing.swift b/Sources/LCFeatureControl/FeatureControlAuthorizing.swift new file mode 100644 index 0000000..8fd3124 --- /dev/null +++ b/Sources/LCFeatureControl/FeatureControlAuthorizing.swift @@ -0,0 +1,39 @@ +import Foundation + +/// Pluggable auth for outgoing Feature Control requests. No `Atomenta-Token` type +/// exists here on purpose — that module token must never be embedded in a +/// customer-facing app (see FC-060 §1 in Atomenta's `docs/feature-control/`). +/// A consumer who insists on it does so explicitly via `FeatureControlHeaderAuth`. +public protocol FeatureControlAuthorizing: Sendable { + func authorize(_ headers: inout [String: String]) async +} + +/// For internal/admin apps hitting Atomenta directly with a panel-role JWT. +public struct FeatureControlBearerAuth: FeatureControlAuthorizing { + private let tokenProvider: @Sendable () async -> String? + + public init(tokenProvider: @escaping @Sendable () async -> String?) { + self.tokenProvider = tokenProvider + } + + public func authorize(_ headers: inout [String: String]) async { + guard let token = await tokenProvider() else { return } + headers["Authorization"] = "Bearer \(token)" + } +} + +/// For a customer app calling its own BFF, which enforces its own auth. Adds +/// exactly the given headers — never synthesizes an `Authorization` header. +public struct FeatureControlHeaderAuth: FeatureControlAuthorizing { + private let headers: [String: String] + + public init(headers: [String: String]) { + self.headers = headers + } + + public func authorize(_ headers: inout [String: String]) async { + for (key, value) in self.headers { + headers[key] = value + } + } +} diff --git a/Sources/LCFeatureControl/FeatureControlCache.swift b/Sources/LCFeatureControl/FeatureControlCache.swift new file mode 100644 index 0000000..a519854 --- /dev/null +++ b/Sources/LCFeatureControl/FeatureControlCache.swift @@ -0,0 +1,46 @@ +import Foundation + +/// Identifies a cached evaluation — FC-060 §5: "environment + subjectId + keys(sorted) + +/// platform + appVersion". `sortedKeys` means caller `keys` order never affects hits. +struct FeatureControlCacheKey: Hashable, Sendable { + let environment: String + let subjectId: String + let sortedKeys: [String] + let platform: String? + let appVersion: String? +} + +/// In-memory-only TTL cache (no disk persistence — see SDD §2 Non-Goals). The clock is +/// injectable so tests can force expiry deterministically instead of sleeping. +actor FeatureControlCache { + private struct Entry { + let snapshot: FeatureControlSnapshot + let storedAt: Date + let ttl: TimeInterval + } + + private var entries: [FeatureControlCacheKey: Entry] = [:] + private let now: @Sendable () -> Date + + init(now: @escaping @Sendable () -> Date = { Date() }) { + self.now = now + } + + /// `allowStale: true` returns an expired entry rather than `nil` — used by the + /// safe-degrade fallback path (SDD §4.4/§4.5). + func get(_ key: FeatureControlCacheKey, allowStale: Bool) -> FeatureControlSnapshot? { + guard let entry = entries[key] else { return nil } + let age = now().timeIntervalSince(entry.storedAt) + if age <= entry.ttl { return entry.snapshot } + return allowStale ? entry.snapshot : nil + } + + /// Always overwrites — a fresh fetch's `configVersion` simply replaces whatever was there. + func set(_ key: FeatureControlCacheKey, snapshot: FeatureControlSnapshot, ttl: TimeInterval) { + entries[key] = Entry(snapshot: snapshot, storedAt: now(), ttl: ttl) + } + + func invalidateAll() { + entries.removeAll() + } +} diff --git a/Sources/LCFeatureControl/FeatureControlConfiguration.swift b/Sources/LCFeatureControl/FeatureControlConfiguration.swift new file mode 100644 index 0000000..10ecc45 --- /dev/null +++ b/Sources/LCFeatureControl/FeatureControlConfiguration.swift @@ -0,0 +1,33 @@ +import Foundation + +/// Wiring for one Atomenta Feature Control module deployment. +public struct FeatureControlConfiguration: Sendable { + public var baseURL: String + public var evaluatePath: String + public var notificationsPath: String + public var telemetryPath: String + public var environment: String + public var auth: any FeatureControlAuthorizing + /// In-memory cache TTL. FC-060 §5 recommends 30–60s client-side. + public var cacheTTL: TimeInterval + /// FC-060 §5 recommends 1.5–3s at the BFF; same budget applies here. + public var requestTimeout: TimeInterval + + public init(baseURL: String, + environment: String, + auth: any FeatureControlAuthorizing, + evaluatePath: String = "/api/feature-control/evaluate", + notificationsPath: String = "/api/feature-control/notifications", + telemetryPath: String = "/api/feature-control/telemetry/exposure", + cacheTTL: TimeInterval = 45, + requestTimeout: TimeInterval = 3) { + self.baseURL = baseURL + self.environment = environment + self.auth = auth + self.evaluatePath = evaluatePath + self.notificationsPath = notificationsPath + self.telemetryPath = telemetryPath + self.cacheTTL = cacheTTL + self.requestTimeout = requestTimeout + } +} diff --git a/Sources/LCFeatureControl/FeatureControlContext.swift b/Sources/LCFeatureControl/FeatureControlContext.swift new file mode 100644 index 0000000..89787d0 --- /dev/null +++ b/Sources/LCFeatureControl/FeatureControlContext.swift @@ -0,0 +1,30 @@ +import Foundation + +/// Who/what a flag evaluation is for — mirrors Atomenta's `context.subjectType` enum. +public enum FeatureControlSubjectType: String, Codable, Sendable { + case user, customer, store, anonymous +} + +/// Evaluation context sent as `context` in `POST /api/feature-control/evaluate`. +public struct FeatureControlContext: Encodable, Sendable, Equatable { + public var subjectType: FeatureControlSubjectType + public var subjectId: String + public var storeId: String? + public var platform: String? + public var appVersion: String? + public var attributes: [String: String]? + + public init(subjectType: FeatureControlSubjectType, + subjectId: String, + storeId: String? = nil, + platform: String? = nil, + appVersion: String? = nil, + attributes: [String: String]? = nil) { + self.subjectType = subjectType + self.subjectId = subjectId + self.storeId = storeId + self.platform = platform + self.appVersion = appVersion + self.attributes = attributes + } +} diff --git a/Sources/LCFeatureControl/FeatureControlDateParsing.swift b/Sources/LCFeatureControl/FeatureControlDateParsing.swift new file mode 100644 index 0000000..f66095e --- /dev/null +++ b/Sources/LCFeatureControl/FeatureControlDateParsing.swift @@ -0,0 +1,18 @@ +import Foundation + +/// Parses the ISO-8601 timestamps Atomenta sends (`2026-04-16T12:00:00.000Z`, with +/// milliseconds). `LCEssentials.API`'s internal `JSONDecoder` uses the default +/// (`.deferredToDate`, numeric epoch) strategy, so `Date` fields on wire models +/// decode the raw string manually instead of relying on `Decodable`'s default +/// date handling. +enum FeatureControlDateParsing { + static func parse(_ string: String) -> Date? { + let withFractional = ISO8601DateFormatter() + withFractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + if let date = withFractional.date(from: string) { return date } + + let plain = ISO8601DateFormatter() + plain.formatOptions = [.withInternetDateTime] + return plain.date(from: string) + } +} diff --git a/Sources/LCFeatureControl/FeatureControlError.swift b/Sources/LCFeatureControl/FeatureControlError.swift new file mode 100644 index 0000000..2bf0876 --- /dev/null +++ b/Sources/LCFeatureControl/FeatureControlError.swift @@ -0,0 +1,51 @@ +import Foundation +import LCEssentials + +/// Explicit failure for callers that want to see it (`evaluateOrThrow`, the +/// notifications client). The safe `evaluate(...)` path never surfaces this — +/// it degrades to cache/defaults instead (FC-060 §4). +public enum FeatureControlError: Error, Sendable, Equatable { + /// 400 `FEATURE_CONTROL_CONTEXT_INVALID` + case invalidContext(code: String) + /// 401 `…MISSING_AUTH` / `…INVALID_MODULE_TOKEN` + case unauthorized(code: String) + /// 403 `…PANEL_ROLE_REQUIRED` / `…INSUFFICIENT_PERMISSIONS` + case forbidden(code: String) + /// 429 `FEATURE_CONTROL_RATE_LIMIT` + case rateLimited(code: String) + /// Any other non-2xx status. + case server(code: String, status: Int) + /// Decoding failure, or any error not raised by `LCEssentials.API`'s HTTP path. + case transport(message: String) +} + +enum FeatureControlErrorMapper { + /// `LCEssentials.API` throws an `NSError` (domain `LCEssentials.DEFAULT_ERROR_DOMAIN`, + /// `code` = HTTP status, `localizedFailureReason` = pretty-printed body) for non-2xx + /// responses, or a bridged `DecodingError`/`URLError` for anything else. Only the + /// former carries a real HTTP status to map. + static func map(_ error: Error) -> FeatureControlError { + let nsError = error as NSError + guard nsError.domain == LCEssentials.DEFAULT_ERROR_DOMAIN else { + return .transport(message: nsError.localizedDescription) + } + + let status = nsError.code + let bodyCode = extractCode(from: nsError.localizedFailureReason) ?? "UNKNOWN" + switch status { + case 400: return .invalidContext(code: bodyCode) + case 401: return .unauthorized(code: bodyCode) + case 403: return .forbidden(code: bodyCode) + case 429: return .rateLimited(code: bodyCode) + default: return .server(code: bodyCode, status: status) + } + } + + private static func extractCode(from prettyJSON: String?) -> String? { + guard let prettyJSON, + let data = prettyJSON.data(using: .utf8), + let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let code = object["code"] as? String else { return nil } + return code + } +} diff --git a/Sources/LCFeatureControl/FeatureControlExposureEvent.swift b/Sources/LCFeatureControl/FeatureControlExposureEvent.swift new file mode 100644 index 0000000..d8f6523 --- /dev/null +++ b/Sources/LCFeatureControl/FeatureControlExposureEvent.swift @@ -0,0 +1,28 @@ +import Foundation + +/// One exposure event for `POST /api/feature-control/telemetry/exposure`. +public struct FeatureControlExposureEvent: Encodable, Sendable, Equatable { + public let featureKey: String + public let variant: String? + public let subjectType: FeatureControlSubjectType + public let storeId: String? + + public init(featureKey: String, variant: String?, subjectType: FeatureControlSubjectType, storeId: String?) { + self.featureKey = featureKey + self.variant = variant + self.subjectType = subjectType + self.storeId = storeId + } +} + +/// Request body — server caps `events` at 100 (`maxItems: 100`); batching into that +/// limit is the caller's (`FeatureControlManager`'s) job, not this type's. +struct FeatureControlExposureBatchBody: Encodable, Sendable { + let events: [FeatureControlExposureEvent] +} + +/// Wire envelope for the telemetry response — `{error, code, result: {count}}`. +struct FeatureControlExposureBatchEnvelope: Decodable, Sendable { + let error: Bool + let code: String? +} diff --git a/Sources/LCFeatureControl/FeatureControlFlag.swift b/Sources/LCFeatureControl/FeatureControlFlag.swift new file mode 100644 index 0000000..8e19f65 --- /dev/null +++ b/Sources/LCFeatureControl/FeatureControlFlag.swift @@ -0,0 +1,61 @@ +import Foundation + +/// One evaluated flag, as returned inside `EvaluateResponse.result.flags[key]`. +public struct FeatureControlFlag: Decodable, Sendable, Equatable { + public let enabled: Bool + public let variant: String? + public let payload: FeatureControlJSON? + public let reason: String? + + public init(enabled: Bool, variant: String?, payload: FeatureControlJSON?, reason: String?) { + self.enabled = enabled + self.variant = variant + self.payload = payload + self.reason = reason + } +} + +/// `EvaluateResponse.result` — the batch evaluation result for a set of keys. +public struct FeatureControlSnapshot: Sendable, Equatable { + public let evaluatedAt: Date + public let configVersion: Int + public let flags: [String: FeatureControlFlag] + + public init(evaluatedAt: Date, configVersion: Int, flags: [String: FeatureControlFlag]) { + self.evaluatedAt = evaluatedAt + self.configVersion = configVersion + self.flags = flags + } +} + +extension FeatureControlSnapshot: Decodable { + private enum CodingKeys: String, CodingKey { + case evaluatedAt, configVersion, flags + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let rawDate = try container.decode(String.self, forKey: .evaluatedAt) + guard let date = FeatureControlDateParsing.parse(rawDate) else { + throw DecodingError.dataCorruptedError(forKey: .evaluatedAt, in: container, + debugDescription: "Unrecognized date format: \(rawDate)") + } + self.evaluatedAt = date + self.configVersion = try container.decode(Int.self, forKey: .configVersion) + self.flags = try container.decode([String: FeatureControlFlag].self, forKey: .flags) + } +} + +/// Wire envelope for `POST /api/feature-control/evaluate` — `{error, code, result}`. +struct FeatureControlEvaluateEnvelope: Decodable, Sendable { + let error: Bool + let code: String? + let result: FeatureControlSnapshot +} + +/// Request body for `POST /api/feature-control/evaluate`. +struct FeatureControlEvaluateRequestBody: Encodable, Sendable { + let environment: String + let keys: [String] + let context: FeatureControlContext +} diff --git a/Sources/LCFeatureControl/FeatureControlJSON.swift b/Sources/LCFeatureControl/FeatureControlJSON.swift new file mode 100644 index 0000000..3e33f71 --- /dev/null +++ b/Sources/LCFeatureControl/FeatureControlJSON.swift @@ -0,0 +1,33 @@ +import Foundation + +/// Minimal "any JSON" box for a flag's `payload`, which the backend declares as +/// `additionalProperties: true` (arbitrary shape). No force operations — an +/// unrecognized shape throws a `DecodingError`, it never crashes. +public indirect enum FeatureControlJSON: Decodable, Sendable, Equatable { + case null + case bool(Bool) + case number(Double) + case string(String) + case array([FeatureControlJSON]) + case object([String: FeatureControlJSON]) + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if container.decodeNil() { + self = .null + } else if let value = try? container.decode(Bool.self) { + self = .bool(value) + } else if let value = try? container.decode(Double.self) { + self = .number(value) + } else if let value = try? container.decode(String.self) { + self = .string(value) + } else if let value = try? container.decode([FeatureControlJSON].self) { + self = .array(value) + } else if let value = try? container.decode([String: FeatureControlJSON].self) { + self = .object(value) + } else { + throw DecodingError.dataCorruptedError(in: container, + debugDescription: "Unsupported JSON value for FeatureControlJSON") + } + } +} diff --git a/Sources/LCFeatureControl/FeatureControlManager.swift b/Sources/LCFeatureControl/FeatureControlManager.swift new file mode 100644 index 0000000..bdd0fa7 --- /dev/null +++ b/Sources/LCFeatureControl/FeatureControlManager.swift @@ -0,0 +1,123 @@ +import Foundation +import LCEssentials + +/// Protocol seam for DI — ViewModels/Interactors depend on this, never on the +/// concrete actor, so a mock can stand in for tests. +public protocol FeatureControlEvaluating: Sendable { + /// Never throws. Fresh fetch → stale cache → app-supplied `defaults`, in that + /// order (FC-060 §4 "nunca bloquear fluxo crítico"). Use this everywhere a + /// flag gates real product behaviour. + func evaluate(keys: [String], context: FeatureControlContext) async -> FeatureControlSnapshot + /// Same fetch, but surfaces the real failure. For admin/debug tooling only. + func evaluateOrThrow(keys: [String], context: FeatureControlContext) async throws -> FeatureControlSnapshot + /// Drops every cached entry. Call on user/store change, or app foreground if desired — + /// this type does not observe app lifecycle itself (SDD §2 Non-Goals). + func invalidateCache() async +} + +public actor FeatureControlManager: FeatureControlEvaluating { + private let configuration: FeatureControlConfiguration + private let api: API + private let defaults: [String: FeatureControlFlag] + private let cache: FeatureControlCache + private var exposureBuffer: [FeatureControlExposureEvent] = [] + + /// `configVersion` sentinel returned when no network response and no cache exist — + /// distinguishes "never evaluated" from any real server value (server versions are ≥ 0). + public static let unresolvedConfigVersion = -1 + + public init(configuration: FeatureControlConfiguration, + api: API = .shared, + defaults: [String: FeatureControlFlag] = [:], + now: @escaping @Sendable () -> Date = { Date() }) { + self.configuration = configuration + self.api = api + self.defaults = defaults + self.cache = FeatureControlCache(now: now) + } + + public func evaluate(keys: [String], context: FeatureControlContext) async -> FeatureControlSnapshot { + do { + return try await evaluateOrThrow(keys: keys, context: context) + } catch { + let key = cacheKey(keys: keys, context: context) + if let stale = await cache.get(key, allowStale: true) { return stale } + return FeatureControlSnapshot(evaluatedAt: Date(), + configVersion: Self.unresolvedConfigVersion, + flags: defaults) + } + } + + public func evaluateOrThrow(keys: [String], context: FeatureControlContext) async throws -> FeatureControlSnapshot { + let key = cacheKey(keys: keys, context: context) + if let fresh = await cache.get(key, allowStale: false) { return fresh } + + var headers: [String: String] = [:] + await configuration.auth.authorize(&headers) + + let body = jsonBody(FeatureControlEvaluateRequestBody( + environment: configuration.environment, keys: keys, context: context)) + + do { + let envelope: FeatureControlEvaluateEnvelope = try await api.request( + url: configuration.baseURL + configuration.evaluatePath, + method: .post, + body: body, + headers: headers, + timeoutInterval: configuration.requestTimeout + ) + await cache.set(key, snapshot: envelope.result, ttl: configuration.cacheTTL) + return envelope.result + } catch { + throw FeatureControlErrorMapper.map(error) + } + } + + public func invalidateCache() async { + await cache.invalidateAll() + } + + private func cacheKey(keys: [String], context: FeatureControlContext) -> FeatureControlCacheKey { + FeatureControlCacheKey(environment: configuration.environment, + subjectId: context.subjectId, + sortedKeys: keys.sorted(), + platform: context.platform, + appVersion: context.appVersion) + } +} + +// MARK: - Exposure telemetry + +extension FeatureControlManager { + /// Buffers locally. Nothing is sent until `flushExposures()` is called. + public func recordExposure(_ event: FeatureControlExposureEvent) { + exposureBuffer.append(event) + } + + /// Best-effort — batches of ≤100 (server `maxItems: 100`); a failed batch is + /// dropped, never retried indefinitely (not a critical-path operation). + public func flushExposures() async { + guard !exposureBuffer.isEmpty else { return } + let events = exposureBuffer + exposureBuffer.removeAll() + + for batch in events.lce_featureControlChunked(into: 100) { + var headers: [String: String] = [:] + await configuration.auth.authorize(&headers) + let body = jsonBody(FeatureControlExposureBatchBody(events: batch)) + _ = try? await api.request( + url: configuration.baseURL + configuration.telemetryPath, + method: .post, + body: body, + headers: headers, + timeoutInterval: configuration.requestTimeout + ) as FeatureControlExposureBatchEnvelope + } + } +} + +private extension Array { + func lce_featureControlChunked(into size: Int) -> [[Element]] { + stride(from: 0, to: count, by: size).map { Array(self[$0.. (items: [FeatureControlNotification], nextCursor: String?) + func markRead(id: String) async throws + func markAllRead() async throws +} + +/// JWT-only — the OpenAPI fragment declares `security: [bearerAuth]` for every +/// notifications route, no module-token alternative. Configure `configuration.auth` +/// with `FeatureControlBearerAuth`; anything else gets a `401` from the server, +/// surfaced as `FeatureControlError.unauthorized`. +public actor FeatureControlNotificationsClient: FeatureControlNotifying { + private let configuration: FeatureControlConfiguration + private let api: API + + public init(configuration: FeatureControlConfiguration, api: API = .shared) { + self.configuration = configuration + self.api = api + } + + public func list(status: FeatureControlNotificationStatus = .all, limit: Int = 20) async throws + -> (items: [FeatureControlNotification], nextCursor: String?) { + var headers: [String: String] = [:] + await configuration.auth.authorize(&headers) + + let query = "?status=\(status.rawValue)&limit=\(limit)" + do { + let envelope: FeatureControlNotificationListEnvelope = try await api.request( + url: configuration.baseURL + configuration.notificationsPath + query, + method: .get, + headers: headers, + timeoutInterval: configuration.requestTimeout + ) + return (envelope.result.items, envelope.result.nextCursor) + } catch { + throw FeatureControlErrorMapper.map(error) + } + } + + public func markRead(id: String) async throws { + var headers: [String: String] = [:] + await configuration.auth.authorize(&headers) + + let encodedId = id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? id + do { + let _: String = try await api.request( + url: configuration.baseURL + configuration.notificationsPath + "/\(encodedId)/read", + method: .post, + headers: headers, + timeoutInterval: configuration.requestTimeout + ) + } catch { + throw FeatureControlErrorMapper.map(error) + } + } + + public func markAllRead() async throws { + var headers: [String: String] = [:] + await configuration.auth.authorize(&headers) + + do { + let _: String = try await api.request( + url: configuration.baseURL + configuration.notificationsPath + "/read-all", + method: .post, + headers: headers, + timeoutInterval: configuration.requestTimeout + ) + } catch { + throw FeatureControlErrorMapper.map(error) + } + } +} diff --git a/Tests/LCFeatureControlTests/FeatureControlAuthTests.swift b/Tests/LCFeatureControlTests/FeatureControlAuthTests.swift new file mode 100644 index 0000000..adeaf0c --- /dev/null +++ b/Tests/LCFeatureControlTests/FeatureControlAuthTests.swift @@ -0,0 +1,38 @@ +import XCTest +@testable import LCFeatureControl + +final class FeatureControlAuthTests: XCTestCase { + + func testBearerAuthAddsAuthorizationHeader() async { + let auth = FeatureControlBearerAuth { "jwt-123" } + var headers: [String: String] = [:] + await auth.authorize(&headers) + XCTAssertEqual(headers["Authorization"], "Bearer jwt-123") + } + + func testBearerAuthAddsNoHeaderWhenTokenIsNil() async { + let auth = FeatureControlBearerAuth { nil } + var headers: [String: String] = [:] + await auth.authorize(&headers) + XCTAssertNil(headers["Authorization"]) + } + + func testHeaderAuthAddsExactHeadersOnly() async { + let auth = FeatureControlHeaderAuth(headers: ["X-BFF-Session": "abc"]) + var headers: [String: String] = [:] + await auth.authorize(&headers) + XCTAssertEqual(headers, ["X-BFF-Session": "abc"]) + XCTAssertNil(headers["Authorization"]) + } + + func testConfigurationDefaults() { + let config = FeatureControlConfiguration(baseURL: "https://api.example.com", + environment: "production", + auth: FeatureControlHeaderAuth(headers: [:])) + XCTAssertEqual(config.cacheTTL, 45) + XCTAssertEqual(config.requestTimeout, 3) + XCTAssertEqual(config.evaluatePath, "/api/feature-control/evaluate") + XCTAssertEqual(config.notificationsPath, "/api/feature-control/notifications") + XCTAssertEqual(config.telemetryPath, "/api/feature-control/telemetry/exposure") + } +} diff --git a/Tests/LCFeatureControlTests/FeatureControlCacheTests.swift b/Tests/LCFeatureControlTests/FeatureControlCacheTests.swift new file mode 100644 index 0000000..2751859 --- /dev/null +++ b/Tests/LCFeatureControlTests/FeatureControlCacheTests.swift @@ -0,0 +1,70 @@ +import XCTest +@testable import LCFeatureControl + +/// Test-only mutable clock box — a `var` captured directly by the cache's +/// escaping `@Sendable` closure would trip strict-concurrency capture checks; +/// this mirrors the `@unchecked Sendable` pattern `StubURLProtocol` already uses. +private final class MutableClock: @unchecked Sendable { + var value: Date + init(_ value: Date) { self.value = value } +} + +final class FeatureControlCacheTests: XCTestCase { + + private func snapshot(configVersion: Int) -> FeatureControlSnapshot { + FeatureControlSnapshot(evaluatedAt: Date(), configVersion: configVersion, flags: [:]) + } + + private func key(_ keys: [String] = ["a", "b"]) -> FeatureControlCacheKey { + FeatureControlCacheKey(environment: "production", subjectId: "cust_1", + sortedKeys: keys.sorted(), platform: "ios", appVersion: "1.0") + } + + func testHitWithinTTLReturnsSameSnapshot() async { + let cache = FeatureControlCache(now: { Date() }) + await cache.set(key(), snapshot: snapshot(configVersion: 1), ttl: 45) + let hit = await cache.get(key(), allowStale: false) + XCTAssertEqual(hit?.configVersion, 1) + } + + func testMissAfterTTLExpiryReturnsNilWhenStaleNotAllowed() async { + let clock = MutableClock(Date()) + let cache = FeatureControlCache(now: { clock.value }) + await cache.set(key(), snapshot: snapshot(configVersion: 1), ttl: 1) + clock.value = clock.value.addingTimeInterval(2) + let hit = await cache.get(key(), allowStale: false) + XCTAssertNil(hit) + } + + func testStaleAllowedReturnsExpiredEntry() async { + let clock = MutableClock(Date()) + let cache = FeatureControlCache(now: { clock.value }) + await cache.set(key(), snapshot: snapshot(configVersion: 1), ttl: 1) + clock.value = clock.value.addingTimeInterval(2) + let hit = await cache.get(key(), allowStale: true) + XCTAssertEqual(hit?.configVersion, 1) + } + + func testKeyDiffersBySortedKeysNotInputOrder() async { + let cache = FeatureControlCache(now: { Date() }) + await cache.set(key(["a", "b"]), snapshot: snapshot(configVersion: 1), ttl: 45) + let hit = await cache.get(key(["b", "a"]), allowStale: false) + XCTAssertEqual(hit?.configVersion, 1) + } + + func testSetOverwritesPriorConfigVersion() async { + let cache = FeatureControlCache(now: { Date() }) + await cache.set(key(), snapshot: snapshot(configVersion: 7), ttl: 45) + await cache.set(key(), snapshot: snapshot(configVersion: 8), ttl: 45) + let hit = await cache.get(key(), allowStale: false) + XCTAssertEqual(hit?.configVersion, 8) + } + + func testInvalidateAllClearsEntries() async { + let cache = FeatureControlCache(now: { Date() }) + await cache.set(key(), snapshot: snapshot(configVersion: 1), ttl: 45) + await cache.invalidateAll() + let hit = await cache.get(key(), allowStale: true) + XCTAssertNil(hit) + } +} diff --git a/Tests/LCFeatureControlTests/FeatureControlErrorTests.swift b/Tests/LCFeatureControlTests/FeatureControlErrorTests.swift new file mode 100644 index 0000000..95864ac --- /dev/null +++ b/Tests/LCFeatureControlTests/FeatureControlErrorTests.swift @@ -0,0 +1,84 @@ +import XCTest +@testable import LCFeatureControl +@testable import LCEssentials + +final class FeatureControlErrorTests: XCTestCase { + + private var api: API! + + override func setUp() { + super.setUp() + let cfg = URLSessionConfiguration.ephemeral + cfg.protocolClasses = [StubURLProtocol.self] + api = API(testConfiguration: cfg) + } + + override func tearDown() { + StubURLProtocol.reset() + api = nil + super.tearDown() + } + + private func makeManager() -> FeatureControlManager { + let config = FeatureControlConfiguration(baseURL: "https://api.example.com", + environment: "production", + auth: FeatureControlHeaderAuth(headers: [:])) + return FeatureControlManager(configuration: config, api: api) + } + + private func context() -> FeatureControlContext { + FeatureControlContext(subjectType: .customer, subjectId: "cust_1") + } + + private func expectMappedError(status: Int, bodyCode: String) async throws -> FeatureControlError { + StubURLProtocol.setStub(.init(statusCode: status, body: Data(#"{"code":"\#(bodyCode)"}"#.utf8))) + let manager = makeManager() + do { + _ = try await manager.evaluateOrThrow(keys: ["x"], context: context()) + XCTFail("expected FeatureControlError") + throw FeatureControlError.transport(message: "unreachable") + } catch let error as FeatureControlError { + return error + } + } + + func testMaps400ToInvalidContext() async throws { + let error = try await expectMappedError(status: 400, bodyCode: "FEATURE_CONTROL_CONTEXT_INVALID") + XCTAssertEqual(error, .invalidContext(code: "FEATURE_CONTROL_CONTEXT_INVALID")) + } + + func testMaps401ToUnauthorized() async throws { + let error = try await expectMappedError(status: 401, bodyCode: "FEATURE_CONTROL_MISSING_AUTH") + XCTAssertEqual(error, .unauthorized(code: "FEATURE_CONTROL_MISSING_AUTH")) + } + + func testMaps403ToForbidden() async throws { + let error = try await expectMappedError(status: 403, bodyCode: "FEATURE_CONTROL_PANEL_ROLE_REQUIRED") + XCTAssertEqual(error, .forbidden(code: "FEATURE_CONTROL_PANEL_ROLE_REQUIRED")) + } + + func testMaps429ToRateLimited() async throws { + let error = try await expectMappedError(status: 429, bodyCode: "FEATURE_CONTROL_RATE_LIMIT") + XCTAssertEqual(error, .rateLimited(code: "FEATURE_CONTROL_RATE_LIMIT")) + } + + func testMaps500ToServerWithStatusCode() async throws { + let error = try await expectMappedError(status: 500, bodyCode: "FEATURE_CONTROL_ERROR") + XCTAssertEqual(error, .server(code: "FEATURE_CONTROL_ERROR", status: 500)) + } + + func testDecodingFailureMapsToTransport() async { + StubURLProtocol.setStub(.init(statusCode: 200, body: Data("not json at all".utf8))) + let manager = makeManager() + do { + _ = try await manager.evaluateOrThrow(keys: ["x"], context: context()) + XCTFail("expected throw") + } catch let error as FeatureControlError { + guard case .transport = error else { + return XCTFail("expected .transport, got \(error)") + } + } catch { + XCTFail("expected FeatureControlError, got \(error)") + } + } +} diff --git a/Tests/LCFeatureControlTests/FeatureControlExposureTests.swift b/Tests/LCFeatureControlTests/FeatureControlExposureTests.swift new file mode 100644 index 0000000..eef011f --- /dev/null +++ b/Tests/LCFeatureControlTests/FeatureControlExposureTests.swift @@ -0,0 +1,69 @@ +import XCTest +@testable import LCFeatureControl +@testable import LCEssentials + +final class FeatureControlExposureTests: XCTestCase { + + private var api: API! + + override func setUp() { + super.setUp() + let cfg = URLSessionConfiguration.ephemeral + cfg.protocolClasses = [StubURLProtocol.self] + api = API(testConfiguration: cfg) + } + + override func tearDown() { + StubURLProtocol.reset() + api = nil + super.tearDown() + } + + private func makeManager() -> FeatureControlManager { + let config = FeatureControlConfiguration(baseURL: "https://api.example.com", + environment: "production", + auth: FeatureControlHeaderAuth(headers: [:])) + return FeatureControlManager(configuration: config, api: api) + } + + private func event(_ index: Int) -> FeatureControlExposureEvent { + FeatureControlExposureEvent(featureKey: "fc.checkout_v2", variant: "on", + subjectType: .customer, storeId: "store_\(index)") + } + + func testFlushSendsSingleBatchUnderLimit() async { + StubURLProtocol.setStub(.init(statusCode: 200, body: Data(#"{"error":false,"code":"OK"}"#.utf8))) + let manager = makeManager() + for i in 0..<10 { await manager.recordExposure(event(i)) } + + await manager.flushExposures() + + XCTAssertEqual(StubURLProtocol.requestCount, 1) + } + + func testFlushSplitsIntoMultipleBatchesOverLimit() async { + StubURLProtocol.setStub(.init(statusCode: 200, body: Data(#"{"error":false,"code":"OK"}"#.utf8))) + let manager = makeManager() + for i in 0..<130 { await manager.recordExposure(event(i)) } + + await manager.flushExposures() + + XCTAssertEqual(StubURLProtocol.requestCount, 2) + } + + func testFailedBatchIsDroppedNotRetriedIndefinitely() async { + StubURLProtocol.setStub(.init(statusCode: 500, body: Data())) + let manager = makeManager() + for i in 0..<10 { await manager.recordExposure(event(i)) } + + await manager.flushExposures() // must not throw, must not hang + + XCTAssertEqual(StubURLProtocol.requestCount, 1) + + // buffer was cleared even though the batch failed — a second flush sends nothing new + StubURLProtocol.reset() + StubURLProtocol.setStub(.init(statusCode: 200, body: Data(#"{"error":false,"code":"OK"}"#.utf8))) + await manager.flushExposures() + XCTAssertEqual(StubURLProtocol.requestCount, 0) + } +} diff --git a/Tests/LCFeatureControlTests/FeatureControlJSONTests.swift b/Tests/LCFeatureControlTests/FeatureControlJSONTests.swift new file mode 100644 index 0000000..5da1eb9 --- /dev/null +++ b/Tests/LCFeatureControlTests/FeatureControlJSONTests.swift @@ -0,0 +1,40 @@ +import XCTest +@testable import LCFeatureControl + +final class FeatureControlJSONTests: XCTestCase { + + private func decode(_ json: String) throws -> FeatureControlJSON { + try JSONDecoder().decode(FeatureControlJSON.self, from: Data(json.utf8)) + } + + func testDecodesNull() throws { + XCTAssertEqual(try decode("null"), .null) + } + + func testDecodesBool() throws { + XCTAssertEqual(try decode("true"), .bool(true)) + } + + func testDecodesNumber() throws { + XCTAssertEqual(try decode("5"), .number(5)) + } + + func testDecodesString() throws { + XCTAssertEqual(try decode("\"hello\""), .string("hello")) + } + + func testDecodesArray() throws { + XCTAssertEqual(try decode("[\"a\",\"b\"]"), .array([.string("a"), .string("b")])) + } + + func testDecodesNestedObject() throws { + let json = #"{"limit": 5, "tags": ["a","b"], "nested": {"x": true}}"# + let value = try decode(json) + guard case let .object(object) = value else { + return XCTFail("expected .object, got \(value)") + } + XCTAssertEqual(object["limit"], .number(5)) + XCTAssertEqual(object["tags"], .array([.string("a"), .string("b")])) + XCTAssertEqual(object["nested"], .object(["x": .bool(true)])) + } +} diff --git a/Tests/LCFeatureControlTests/FeatureControlManagerTests.swift b/Tests/LCFeatureControlTests/FeatureControlManagerTests.swift new file mode 100644 index 0000000..ca8d547 --- /dev/null +++ b/Tests/LCFeatureControlTests/FeatureControlManagerTests.swift @@ -0,0 +1,160 @@ +import XCTest +@testable import LCFeatureControl +@testable import LCEssentials + +private final class MutableClock: @unchecked Sendable { + var value: Date + init(_ value: Date) { self.value = value } +} + +final class FeatureControlManagerTests: XCTestCase { + + private var api: API! + + override func setUp() { + super.setUp() + let cfg = URLSessionConfiguration.ephemeral + cfg.protocolClasses = [StubURLProtocol.self] + api = API(testConfiguration: cfg) + } + + override func tearDown() { + StubURLProtocol.reset() + api = nil + super.tearDown() + } + + private func makeManager(defaults: [String: FeatureControlFlag] = [:], + now: @escaping @Sendable () -> Date = { Date() }) -> FeatureControlManager { + let config = FeatureControlConfiguration(baseURL: "https://api.example.com", + environment: "production", + auth: FeatureControlHeaderAuth(headers: [:])) + return FeatureControlManager(configuration: config, api: api, defaults: defaults, now: now) + } + + private func evaluateResponseBody(configVersion: Int) -> Data { + Data(""" + {"error": false, "code": "FEATURE_CONTROL_EVALUATED", "result": { + "evaluatedAt": "2026-04-16T12:00:00.000Z", + "configVersion": \(configVersion), + "flags": {"fc.checkout_v2": {"enabled": true, "variant": "on", "payload": null, "reason": "rollout"}} + }} + """.utf8) + } + + private func context() -> FeatureControlContext { + FeatureControlContext(subjectType: .customer, subjectId: "cust_1") + } + + // MARK: - Happy path (T4) + + func testEvaluateFetchesAndCachesOnMiss() async throws { + StubURLProtocol.setStub(.init(statusCode: 200, body: evaluateResponseBody(configVersion: 7))) + let manager = makeManager() + + let snapshot = try await manager.evaluateOrThrow(keys: ["fc.checkout_v2"], context: context()) + + XCTAssertEqual(snapshot.configVersion, 7) + XCTAssertEqual(StubURLProtocol.requestCount, 1) + let sent = StubURLProtocol.capturedRequests.first + XCTAssertEqual(sent?.httpMethod, "POST") + XCTAssertEqual(sent?.url?.absoluteString, "https://api.example.com/api/feature-control/evaluate") + } + + func testEvaluateReturnsCacheWithoutNetworkCallOnHit() async { + StubURLProtocol.setStub(.init(statusCode: 200, body: evaluateResponseBody(configVersion: 7))) + let manager = makeManager() + + _ = await manager.evaluate(keys: ["fc.checkout_v2"], context: context()) + _ = await manager.evaluate(keys: ["fc.checkout_v2"], context: context()) + + XCTAssertEqual(StubURLProtocol.requestCount, 1) + } + + func testConfigVersionBumpReplacesCacheAfterExpiry() async { + let clock = MutableClock(Date()) + StubURLProtocol.setStub(.init(statusCode: 200, body: evaluateResponseBody(configVersion: 7))) + let manager = makeManager(now: { clock.value }) + + let first = await manager.evaluate(keys: ["fc.checkout_v2"], context: context()) + XCTAssertEqual(first.configVersion, 7) + + clock.value = clock.value.addingTimeInterval(1000) // past default 45s TTL + StubURLProtocol.setStub(.init(statusCode: 200, body: evaluateResponseBody(configVersion: 8))) + + let second = await manager.evaluate(keys: ["fc.checkout_v2"], context: context()) + XCTAssertEqual(second.configVersion, 8) + XCTAssertEqual(StubURLProtocol.requestCount, 1) + } + + func testInvalidateCacheForcesFreshFetch() async { + StubURLProtocol.setStub(.init(statusCode: 200, body: evaluateResponseBody(configVersion: 7))) + let manager = makeManager() + _ = await manager.evaluate(keys: ["fc.checkout_v2"], context: context()) + + await manager.invalidateCache() + StubURLProtocol.setStub(.init(statusCode: 200, body: evaluateResponseBody(configVersion: 9))) + let second = await manager.evaluate(keys: ["fc.checkout_v2"], context: context()) + + XCTAssertEqual(second.configVersion, 9) + XCTAssertEqual(StubURLProtocol.requestCount, 1) + } + + func testEvaluateOrThrowPropagatesDecodingFailure() async { + StubURLProtocol.setStub(.init(statusCode: 200, body: Data("not json".utf8))) + let manager = makeManager() + + do { + _ = try await manager.evaluateOrThrow(keys: ["x"], context: context()) + XCTFail("expected throw") + } catch { + // any throw is correct — evaluateOrThrow must not silently degrade + } + } + + // MARK: - Safe-degrade fallback chain (T5) + + func testEvaluate429FallsBackToStaleCache() async { + let clock = MutableClock(Date()) + StubURLProtocol.setStub(.init(statusCode: 200, body: evaluateResponseBody(configVersion: 7))) + let manager = makeManager(now: { clock.value }) + _ = await manager.evaluate(keys: ["fc.checkout_v2"], context: context()) + + clock.value = clock.value.addingTimeInterval(1000) + StubURLProtocol.setStub(.init(statusCode: 429, body: Data(#"{"code":"FEATURE_CONTROL_RATE_LIMIT"}"#.utf8))) + + let result = await manager.evaluate(keys: ["fc.checkout_v2"], context: context()) + XCTAssertEqual(result.configVersion, 7) + } + + func testEvaluate500FallsBackToStaleCache() async { + let clock = MutableClock(Date()) + StubURLProtocol.setStub(.init(statusCode: 200, body: evaluateResponseBody(configVersion: 7))) + let manager = makeManager(now: { clock.value }) + _ = await manager.evaluate(keys: ["fc.checkout_v2"], context: context()) + + clock.value = clock.value.addingTimeInterval(1000) + StubURLProtocol.setStub(.init(statusCode: 500, body: Data())) + + let result = await manager.evaluate(keys: ["fc.checkout_v2"], context: context()) + XCTAssertEqual(result.configVersion, 7) + } + + func testEvaluateNoCacheAndServerDownReturnsDefaults() async { + StubURLProtocol.setStub(.init(statusCode: 500, body: Data())) + let defaultFlag = FeatureControlFlag(enabled: false, variant: nil, payload: nil, reason: "default") + let manager = makeManager(defaults: ["fc.checkout_v2": defaultFlag]) + + let result = await manager.evaluate(keys: ["fc.checkout_v2"], context: context()) + + XCTAssertEqual(result.configVersion, FeatureControlManager.unresolvedConfigVersion) + XCTAssertEqual(result.flags["fc.checkout_v2"]?.enabled, false) + } + + func testEvaluateNeverThrowsEvenWhenServerAlwaysErrors() async { + StubURLProtocol.setStub(.init(statusCode: 500, body: Data())) + let manager = makeManager() + // No `try` above this line compiles only because `evaluate` truly never throws. + _ = await manager.evaluate(keys: ["x"], context: context()) + } +} diff --git a/Tests/LCFeatureControlTests/FeatureControlModelsTests.swift b/Tests/LCFeatureControlTests/FeatureControlModelsTests.swift new file mode 100644 index 0000000..2f15fa1 --- /dev/null +++ b/Tests/LCFeatureControlTests/FeatureControlModelsTests.swift @@ -0,0 +1,124 @@ +import XCTest +@testable import LCFeatureControl + +/// Exact evaluate-response shape from Atomenta's `docs/feature-control/EXTERNAL_CLIENTS.md` §3. +private let evaluateResponseFixture = """ +{ + "error": false, + "code": "FEATURE_CONTROL_EVALUATED", + "result": { + "evaluatedAt": "2026-04-16T12:00:00.000Z", + "configVersion": 7, + "flags": { + "fc.checkout_v2": { + "enabled": true, + "variant": "on", + "payload": null, + "reason": "rollout" + }, + "fc.search_ranking_v3": { + "enabled": false, + "variant": "off", + "payload": null, + "reason": "default" + } + } + } +} +""" + +final class FeatureControlModelsTests: XCTestCase { + + func testFlagDecodesFromEvaluateResponseFixture() throws { + let data = Data(evaluateResponseFixture.utf8) + let envelope = try JSONDecoder().decode(FeatureControlEvaluateEnvelope.self, from: data) + + XCTAssertFalse(envelope.error) + XCTAssertEqual(envelope.result.configVersion, 7) + + let checkout = try XCTUnwrap(envelope.result.flags["fc.checkout_v2"]) + XCTAssertTrue(checkout.enabled) + XCTAssertEqual(checkout.variant, "on") + XCTAssertNil(checkout.payload) + XCTAssertEqual(checkout.reason, "rollout") + + let ranking = try XCTUnwrap(envelope.result.flags["fc.search_ranking_v3"]) + XCTAssertFalse(ranking.enabled) + } + + func testEvaluatedAtParsesFractionalSecondsISO8601() throws { + let data = Data(evaluateResponseFixture.utf8) + let envelope = try JSONDecoder().decode(FeatureControlEvaluateEnvelope.self, from: data) + + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = TimeZone(identifier: "UTC")! + let components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], + from: envelope.result.evaluatedAt) + XCTAssertEqual(components.year, 2026) + XCTAssertEqual(components.month, 4) + XCTAssertEqual(components.day, 16) + XCTAssertEqual(components.hour, 12) + } + + func testContextEncodesExactOpenAPIShape() throws { + let context = FeatureControlContext(subjectType: .customer, subjectId: "cust_123", + storeId: "store_001", platform: "ios", + appVersion: "2.3.1", + attributes: ["city": "Belo Horizonte"]) + let data = try JSONEncoder().encode(context) + let object = try XCTUnwrap(try JSONSerialization.jsonObject(with: data) as? [String: Any]) + + XCTAssertEqual(object["subjectType"] as? String, "customer") + XCTAssertEqual(object["subjectId"] as? String, "cust_123") + XCTAssertEqual(object["storeId"] as? String, "store_001") + XCTAssertEqual(object["platform"] as? String, "ios") + XCTAssertEqual(object["appVersion"] as? String, "2.3.1") + let attributes = try XCTUnwrap(object["attributes"] as? [String: String]) + XCTAssertEqual(attributes["city"], "Belo Horizonte") + } + + func testNotificationDecodesFromOpenAPIShape() throws { + let json = """ + { + "id": "n1", + "title": "Manutenção", + "body": "Janela de manutenção às 22h", + "severity": "warning", + "createdAt": "2026-04-16T12:00:00.000Z", + "read": false, + "ctaLabel": "Ver detalhes", + "ctaUrl": "https://example.com" + } + """ + let notification = try JSONDecoder().decode(FeatureControlNotification.self, from: Data(json.utf8)) + XCTAssertEqual(notification.id, "n1") + XCTAssertEqual(notification.severity, "warning") + XCTAssertFalse(notification.read) + XCTAssertEqual(notification.ctaLabel, "Ver detalhes") + } + + func testNotificationDecodesWithoutOptionalCTAFields() throws { + let json = """ + { + "id": "n2", + "title": "Info", + "body": "Just FYI", + "severity": "info", + "createdAt": "2026-04-16T12:00:00Z", + "read": true + } + """ + let notification = try JSONDecoder().decode(FeatureControlNotification.self, from: Data(json.utf8)) + XCTAssertNil(notification.ctaLabel) + XCTAssertNil(notification.ctaUrl) + } + + func testExposureEventEncodesExpectedShape() throws { + let event = FeatureControlExposureEvent(featureKey: "fc.checkout_v2", variant: "on", + subjectType: .customer, storeId: "store_001") + let data = try JSONEncoder().encode(event) + let object = try XCTUnwrap(try JSONSerialization.jsonObject(with: data) as? [String: Any]) + XCTAssertEqual(object["featureKey"] as? String, "fc.checkout_v2") + XCTAssertEqual(object["subjectType"] as? String, "customer") + } +} diff --git a/Tests/LCFeatureControlTests/FeatureControlNotificationsClientTests.swift b/Tests/LCFeatureControlTests/FeatureControlNotificationsClientTests.swift new file mode 100644 index 0000000..217eb02 --- /dev/null +++ b/Tests/LCFeatureControlTests/FeatureControlNotificationsClientTests.swift @@ -0,0 +1,93 @@ +import XCTest +@testable import LCFeatureControl +@testable import LCEssentials + +final class FeatureControlNotificationsClientTests: XCTestCase { + + private var api: API! + + override func setUp() { + super.setUp() + let cfg = URLSessionConfiguration.ephemeral + cfg.protocolClasses = [StubURLProtocol.self] + api = API(testConfiguration: cfg) + } + + override func tearDown() { + StubURLProtocol.reset() + api = nil + super.tearDown() + } + + private func makeClient() -> FeatureControlNotificationsClient { + let config = FeatureControlConfiguration(baseURL: "https://api.example.com", + environment: "production", + auth: FeatureControlBearerAuth { "jwt-123" }) + return FeatureControlNotificationsClient(configuration: config, api: api) + } + + func testListDecodesItemsAndNextCursor() async throws { + let body = """ + {"error": false, "result": {"items": [ + {"id": "n1", "title": "T", "body": "B", "severity": "info", + "createdAt": "2026-04-16T12:00:00.000Z", "read": false} + ], "nextCursor": "cursor-2"}} + """ + StubURLProtocol.setStub(.init(statusCode: 200, body: Data(body.utf8))) + let client = makeClient() + + let (items, nextCursor) = try await client.list(status: .unread, limit: 20) + + XCTAssertEqual(items.count, 1) + XCTAssertEqual(items.first?.id, "n1") + XCTAssertEqual(nextCursor, "cursor-2") + } + + func testListPassesStatusAndLimitAsQueryParams() async throws { + let body = #"{"error": false, "result": {"items": [], "nextCursor": null}}"# + StubURLProtocol.setStub(.init(statusCode: 200, body: Data(body.utf8))) + let client = makeClient() + + _ = try await client.list(status: .unread, limit: 5) + + let url = StubURLProtocol.capturedRequests.first?.url?.absoluteString + XCTAssertEqual(url, "https://api.example.com/api/feature-control/notifications?status=unread&limit=5") + XCTAssertEqual(StubURLProtocol.capturedRequests.first?.value(forHTTPHeaderField: "Authorization"), + "Bearer jwt-123") + } + + func testMarkReadPostsToCorrectPathAndSucceedsOn200() async throws { + StubURLProtocol.setStub(.init(statusCode: 200, headers: ["Content-Type": "text/plain"], body: Data("ok".utf8))) + let client = makeClient() + + try await client.markRead(id: "n1") + + let sent = StubURLProtocol.capturedRequests.first + XCTAssertEqual(sent?.httpMethod, "POST") + XCTAssertEqual(sent?.url?.absoluteString, "https://api.example.com/api/feature-control/notifications/n1/read") + } + + func testMarkAllReadSucceedsOn200() async throws { + StubURLProtocol.setStub(.init(statusCode: 200, headers: ["Content-Type": "text/plain"], body: Data("ok".utf8))) + let client = makeClient() + + try await client.markAllRead() + + XCTAssertEqual(StubURLProtocol.capturedRequests.first?.url?.absoluteString, + "https://api.example.com/api/feature-control/notifications/read-all") + } + + func testUnauthorizedMapsToFeatureControlErrorNotCrash() async { + StubURLProtocol.setStub(.init(statusCode: 401, body: Data(#"{"code":"FEATURE_CONTROL_MISSING_AUTH"}"#.utf8))) + let client = makeClient() + + do { + try await client.markAllRead() + XCTFail("expected throw") + } catch let error as FeatureControlError { + XCTAssertEqual(error, .unauthorized(code: "FEATURE_CONTROL_MISSING_AUTH")) + } catch { + XCTFail("expected FeatureControlError, got \(error)") + } + } +} diff --git a/Tests/LCFeatureControlTests/Support/StubURLProtocol.swift b/Tests/LCFeatureControlTests/Support/StubURLProtocol.swift new file mode 100644 index 0000000..bd3c94e --- /dev/null +++ b/Tests/LCFeatureControlTests/Support/StubURLProtocol.swift @@ -0,0 +1,116 @@ +import Foundation + +/// Test double for `URLProtocol`. Intercepts every request on a `URLSession` +/// configured with it, records the outgoing `URLRequest`, and replays a canned +/// response or error supplied by the test. +/// +/// Register via: +/// ``` +/// let cfg = URLSessionConfiguration.ephemeral +/// cfg.protocolClasses = [StubURLProtocol.self] +/// let session = URLSession(configuration: cfg) +/// ``` +final class StubURLProtocol: URLProtocol, @unchecked Sendable { + + struct Stub { + var statusCode: Int = 200 + var headers: [String: String] = ["Content-Type": "application/json"] + var body: Data = Data() + var error: Error? + /// Bytes reported through `URLSession`'s upload progress, in order. + var uploadProgressChunks: [Int] = [] + } + + // MARK: - Test-facing state (guarded) + + private static let lock = NSLock() + // Access is serialised through `lock`; the unsafe opt-out is the documented + // pattern for lock-guarded mutable statics under strict concurrency. + nonisolated(unsafe) private static var _stub = Stub() + nonisolated(unsafe) private static var _capturedRequests: [URLRequest] = [] + nonisolated(unsafe) private static var _capturedBodies: [Data] = [] + + static func setStub(_ stub: Stub) { + lock.lock(); defer { lock.unlock() } + _stub = stub + _capturedRequests = [] + _capturedBodies = [] + } + + static func reset() { setStub(Stub()) } + + static var capturedRequests: [URLRequest] { + lock.lock(); defer { lock.unlock() } + return _capturedRequests + } + + /// Body of the last intercepted request. `URLProtocol` strips `httpBody` for + /// stream bodies, so this reads `httpBodyStream` when needed. + static var lastCapturedBody: Data? { + lock.lock(); defer { lock.unlock() } + return _capturedBodies.last + } + + static var requestCount: Int { + lock.lock(); defer { lock.unlock() } + return _capturedRequests.count + } + + private static func currentStub() -> Stub { + lock.lock(); defer { lock.unlock() } + return _stub + } + + private static func record(_ request: URLRequest, body: Data) { + lock.lock(); defer { lock.unlock() } + _capturedRequests.append(request) + _capturedBodies.append(body) + } + + // MARK: - URLProtocol + + override class func canInit(with request: URLRequest) -> Bool { true } + + override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } + + override func startLoading() { + let stub = Self.currentStub() + Self.record(request, body: Self.bodyData(from: request)) + + guard let client = client else { return } + + if let error = stub.error { + client.urlProtocol(self, didFailWithError: error) + return + } + + let url = request.url ?? URL(string: "https://stub.invalid")! + let response = HTTPURLResponse(url: url, + statusCode: stub.statusCode, + httpVersion: "HTTP/1.1", + headerFields: stub.headers)! + client.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + client.urlProtocol(self, didLoad: stub.body) + client.urlProtocolDidFinishLoading(self) + } + + override func stopLoading() {} + + // MARK: - Body extraction + + private static func bodyData(from request: URLRequest) -> Data { + if let body = request.httpBody { return body } + guard let stream = request.httpBodyStream else { return Data() } + stream.open() + defer { stream.close() } + var data = Data() + let bufferSize = 64 * 1024 + var buffer = [UInt8](repeating: 0, count: bufferSize) + while stream.hasBytesAvailable { + let read = stream.read(&buffer, maxLength: bufferSize) + if read <= 0 { break } + data.append(buffer, count: read) + } + return data + } +}