diff --git a/.gitignore b/.gitignore index ca475a8..7644c9a 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,7 @@ Carthage/Build # hence it is not needed unless you have added a package configuration file to your project .swiftpm .build + +# This was causing confusion on PRs and is really just a record of the last time the example +# pods were re-built from scratch. It's not useful to keep in the repo. +Example/Podfile.lock diff --git a/Example/FlagsmithClient.xcodeproj/project.pbxproj b/Example/FlagsmithClient.xcodeproj/project.pbxproj index 64b6249..4ea3aac 100644 --- a/Example/FlagsmithClient.xcodeproj/project.pbxproj +++ b/Example/FlagsmithClient.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 7F76D20A2C9852510028470B /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F76D2092C9852510028470B /* SwiftUIView.swift */; }; C87CA15C7294632245730C64 /* Pods_FlagsmithClient_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9F7BEB767BE008D76723DD6 /* Pods_FlagsmithClient_Example.framework */; }; DAED1E94268DBF9100F91DBC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAED1E8C268DBF9100F91DBC /* ViewController.swift */; }; DAED1E95268DBF9100F91DBC /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = DAED1E8D268DBF9100F91DBC /* LaunchScreen.xib */; }; @@ -18,6 +19,7 @@ /* Begin PBXFileReference section */ 5357A0EF6DA00FB7B1E84F8C /* Pods-FlagsmithClient_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlagsmithClient_Example.release.xcconfig"; path = "Target Support Files/Pods-FlagsmithClient_Example/Pods-FlagsmithClient_Example.release.xcconfig"; sourceTree = ""; }; 607FACD01AFB9204008FA782 /* FlagsmithClient_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FlagsmithClient_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7F76D2092C9852510028470B /* SwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIView.swift; sourceTree = ""; }; 9DFD4B33E10902546A38D90F /* Pods-FlagsmithClient_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlagsmithClient_Example.debug.xcconfig"; path = "Target Support Files/Pods-FlagsmithClient_Example/Pods-FlagsmithClient_Example.debug.xcconfig"; sourceTree = ""; }; C4197E729B096AA93F72A537 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; C9F7BEB767BE008D76723DD6 /* Pods_FlagsmithClient_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FlagsmithClient_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -96,6 +98,7 @@ DAED1E8F268DBF9100F91DBC /* Main.storyboard */, DAED1E91268DBF9100F91DBC /* Images.xcassets */, DAED1E92268DBF9100F91DBC /* AppDelegate.swift */, + 7F76D2092C9852510028470B /* SwiftUIView.swift */, ); path = FlagsmithClient; sourceTree = ""; @@ -220,6 +223,7 @@ files = ( DAED1E98268DBF9100F91DBC /* AppDelegate.swift in Sources */, DAED1E94268DBF9100F91DBC /* ViewController.swift in Sources */, + 7F76D20A2C9852510028470B /* SwiftUIView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/FlagsmithClient.xcodeproj/xcshareddata/xcschemes/FlagsmithClient_Example.xcscheme b/Example/FlagsmithClient.xcodeproj/xcshareddata/xcschemes/FlagsmithClient_Example.xcscheme index 08cd5c4..ebc62bd 100644 --- a/Example/FlagsmithClient.xcodeproj/xcshareddata/xcschemes/FlagsmithClient_Example.xcscheme +++ b/Example/FlagsmithClient.xcodeproj/xcshareddata/xcschemes/FlagsmithClient_Example.xcscheme @@ -1,6 +1,6 @@ (_ result: Result) -> Bool { if case .success = result { return true } else { return false } @@ -43,6 +46,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // set analytics on or off Flagsmith.shared.enableAnalytics = true + + // Enable real time updates + Flagsmith.shared.enableRealtimeUpdates = true // set the analytics flush period in seconds Flagsmith.shared.analyticsFlushPeriod = 10 @@ -65,6 +71,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Flagsmith.shared.setTrait(Trait(key: "", value: ""), forIdentity: "") { (result) in print(result) } // Flagsmith.shared.getIdentity("") { (result) in print(result) } + + // Launch SwiftUIView + if #available(iOS 13.0, *) { + let swiftUIView = SwiftUIView() + window = UIWindow(frame: UIScreen.main.bounds) + window?.rootViewController = UIHostingController(rootView: swiftUIView) + window?.makeKeyAndVisible() + } + return true } diff --git a/Example/FlagsmithClient/Base.lproj/LaunchScreen.xib b/Example/FlagsmithClient/Base.lproj/LaunchScreen.xib index 5eebe65..0d4f87b 100644 --- a/Example/FlagsmithClient/Base.lproj/LaunchScreen.xib +++ b/Example/FlagsmithClient/Base.lproj/LaunchScreen.xib @@ -29,9 +29,6 @@ - - - diff --git a/Example/FlagsmithClient/SwiftUIView.swift b/Example/FlagsmithClient/SwiftUIView.swift new file mode 100644 index 0000000..0ef5c49 --- /dev/null +++ b/Example/FlagsmithClient/SwiftUIView.swift @@ -0,0 +1,68 @@ +// +// SwiftUIView.swift +// FlagsmithClient_Example +// +// Created by Gareth Reese on 16/09/2024. +// Copyright © 2024 CocoaPods. All rights reserved. +// + +#if canImport(SwiftUI) + import SwiftUI +#endif +import FlagsmithClient + +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) +struct SwiftUIView: View { + @State private var flags: [Flag] = [] + @State private var isLoading = true + + let flagsmith = Flagsmith.shared + + var body: some View { + VStack { + if isLoading { + ProgressView() + } else { + Text("Feature Flags") + .font(.title) + .padding() + List(flags, id: \.feature.name) { flag in + HStack { + Text("\(flag.feature.name): \(flag.value)") + Spacer() + Text("\(flag.enabled ? "✅" : "❌")") + } + } + } + } + .onAppear { + initializeFlagsmith() + subscribeToFlagUpdates() + } + } + + func initializeFlagsmith() { + // Fetch initial flags + flagsmith.getFeatureFlags { result in + DispatchQueue.main.async { + switch result { + case .success(let fetchedFlags): + flags = fetchedFlags + case .failure(let error): + print("Error fetching flags: \(error)") + } + isLoading = false + } + } + } + + func subscribeToFlagUpdates() { + Task { + for await updatedFlags in flagsmith.flagStream { + DispatchQueue.main.async { + flags = updatedFlags + } + } + } + } +} diff --git a/Example/Podfile.lock b/Example/Podfile.lock deleted file mode 100644 index 75ff619..0000000 --- a/Example/Podfile.lock +++ /dev/null @@ -1,16 +0,0 @@ -PODS: - - FlagsmithClient (3.6.0) - -DEPENDENCIES: - - FlagsmithClient (from `../`) - -EXTERNAL SOURCES: - FlagsmithClient: - :path: "../" - -SPEC CHECKSUMS: - FlagsmithClient: 101151384696085c085d06b1c202946827e058d6 - -PODFILE CHECKSUM: 9fc876dee0cf031cae843156b0740a94b4994d8c - -COCOAPODS: 1.15.2 diff --git a/Example/Pods/Local Podspecs/FlagsmithClient.podspec.json b/Example/Pods/Local Podspecs/FlagsmithClient.podspec.json index 36cb165..0daa770 100644 --- a/Example/Pods/Local Podspecs/FlagsmithClient.podspec.json +++ b/Example/Pods/Local Podspecs/FlagsmithClient.podspec.json @@ -1,6 +1,6 @@ { "name": "FlagsmithClient", - "version": "3.6.0", + "version": "3.6.2", "summary": "iOS Client written in Swift for Flagsmith. Ship features with confidence using feature flags and remote config.", "homepage": "https://github.com/Flagsmith/flagsmith-ios-client", "license": { @@ -12,12 +12,12 @@ }, "source": { "git": "https://github.com/Flagsmith/flagsmith-ios-client.git", - "tag": "3.6.0" + "tag": "3.6.2" }, "social_media_url": "https://twitter.com/getflagsmith", "resource_bundles": { - "FlagSmith_Privacy": [ - "Classes/PrivacyInfo.xcprivacy" + "Flagsmith_Privacy": [ + "FlagsmithClient/Classes/PrivacyInfo.xcprivacy" ] }, "platforms": { diff --git a/Example/Pods/Manifest.lock b/Example/Pods/Manifest.lock index 75ff619..121e58b 100644 --- a/Example/Pods/Manifest.lock +++ b/Example/Pods/Manifest.lock @@ -1,5 +1,5 @@ PODS: - - FlagsmithClient (3.6.0) + - FlagsmithClient (3.6.2) DEPENDENCIES: - FlagsmithClient (from `../`) @@ -9,7 +9,7 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - FlagsmithClient: 101151384696085c085d06b1c202946827e058d6 + FlagsmithClient: 31e78456bd614b87be83b6c83efb10b917fc62fc PODFILE CHECKSUM: 9fc876dee0cf031cae843156b0740a94b4994d8c diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj index 07d636e..6bd0024 100644 --- a/Example/Pods/Pods.xcodeproj/project.pbxproj +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -7,134 +7,133 @@ objects = { /* Begin PBXBuildFile section */ - 0083552669FD822D4A203F429BEF6F8C /* Flag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193A8DBBF59C95D3ADE40A6CACE88317 /* Flag.swift */; }; - 145BDA8A923A4A97F7D1985125F60E67 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */; }; - 1848829C29C5631D664CC0742A63EC33 /* APIManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 740BE3A9082C69EDDCE885472B239E2B /* APIManager.swift */; }; + 02828F81751D68CC19DE337936329454 /* PrivacyInfo.xcprivacy in Sources */ = {isa = PBXBuildFile; fileRef = 52845A8A8A4C6292F43309F6A180B4D8 /* PrivacyInfo.xcprivacy */; }; + 037725D5CFD3B162DB874076446013F5 /* FlagsmithClient-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E2C2AC5693315F6FF359AAFF1DBCCD6 /* FlagsmithClient-dummy.m */; }; + 0C66F595D6DBEA017866CA6623A3441B /* FlagsmithClient-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D6EF7174577EA07427F20B96212DAD8 /* FlagsmithClient-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1D8CF59EBBFF8148D15D0EFCAAA414A2 /* Pods-FlagsmithClient_Example-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 02E4DC32B2FC713ED935462DA9F1CBF9 /* Pods-FlagsmithClient_Example-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 200C93079A049CC69CF5844427F65359 /* TypedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E24C65212832E53CD320E489F86D56 /* TypedValue.swift */; }; - 36A99CA483CA4242032567EFEC9024E9 /* FlagsmithClient-FlagSmith_Privacy in Resources */ = {isa = PBXBuildFile; fileRef = 7F9C7D04F801E733A55ED8BA67AA84CE /* FlagsmithClient-FlagSmith_Privacy */; }; - 391C8F320DDD9EAB5996574CB69D38A6 /* Trait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F1CB950A2B9D94D55201B7B8191BB83 /* Trait.swift */; }; + 2ADE4C4C0A6290AC0935A43B4357A211 /* UnknownTypeValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C592D17AD608D21D9D74AE825FF8675 /* UnknownTypeValue.swift */; }; + 2B1240706C35627EEE86A8675C72F953 /* Flagsmith.swift in Sources */ = {isa = PBXBuildFile; fileRef = B220A338926B2DE5B01EA55B42DEC7A2 /* Flagsmith.swift */; }; 3CB1981582BED249B73F39640A046EC6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */; }; - 41403B5BE8BE6F8EF3DD6D51EB8A05CD /* Feature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99036F2475C7DD87269B0693A1E508D1 /* Feature.swift */; }; - 4BC792BD210D6770E120952EC1A6149F /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560A2A43C2532F9A60D40C498910BDAD /* Identity.swift */; }; - 4FB62CFD8230DFD531423AC407FFBF12 /* CachedURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EDBFEFB26106F14D3ACD6A6E8DAF369 /* CachedURLResponse.swift */; }; - 7A6E9ACE7C0730D83DC68A952B450CBF /* FlagsmithError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 334A531B6E779CB5BBB111407DAB389B /* FlagsmithError.swift */; }; - 8C16371FF3BD75AA8903F1364B84C902 /* Flagsmith.swift in Sources */ = {isa = PBXBuildFile; fileRef = D30C8727FF378667687B927B401ACFC2 /* Flagsmith.swift */; }; - 976897ECFF1B33FC921C0C50A0F2A156 /* Flagsmith+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33401B963FE6A82269201D80F422B5DC /* Flagsmith+Concurrency.swift */; }; - 9A443C01EC676628F306F7E210F63CBC /* Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BD44276DCF6BDE68099A5762C3E3248 /* Traits.swift */; }; - C462C452A86C94C3B33BA403FD5102C3 /* FlagsmithAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010890F60854F25B5CF6D19BAF3B3774 /* FlagsmithAnalytics.swift */; }; + 4B40443D5FE9A0DBBF5112C23B1629A5 /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC7B9D7D6AA6A11B6BEAE4AAB00AAAF /* Identity.swift */; }; + 4FAB11F14645CC89276B729F8BE34AEC /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881B2A3AF56BA171E0F9E187698A2A1A /* Router.swift */; }; + 53DCEB6C6C95F0D2D6806B42816100B9 /* FlagEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB29AEDAE44BB85C47C35AF0C1C21CF9 /* FlagEvent.swift */; }; + 54822BAB37B8A2525E97D75928D7DD76 /* Flag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DC051B5A096E5B1B6B9AA284119664 /* Flag.swift */; }; + 5A2B75222740D6227142A25329465949 /* FlagsmithAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964E54325F994DA2BA6D096CEC973B27 /* FlagsmithAnalytics.swift */; }; + 6573330E26D76C6427BEA704DD624882 /* Flagsmith+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = E09CA3FD6CEC48CF0ACAEACEBA9962B1 /* Flagsmith+Concurrency.swift */; }; + 725BC82A464F54C5E52918EC277AF41D /* TypedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F655DCB3D9C96A4B79EFCD4A6DE5EC1 /* TypedValue.swift */; }; + 7C7B677ECC185354844CE513BBBF91FF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */; }; + 8A19E7AB53D3DDB219C62ECFA679D4BD /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 52845A8A8A4C6292F43309F6A180B4D8 /* PrivacyInfo.xcprivacy */; }; + 91C87A61094984812E6B2CDAFF1B064E /* SSEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA2DD515210C3938904B5ACEEF2C8F1 /* SSEManager.swift */; }; + 943B0DFB191B3E6ADBE5AD7BE4E17F02 /* FlagsmithError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E9B58EDF83BB73CBD7B31A6E538D3D /* FlagsmithError.swift */; }; + 9F173867D326EDBF402CD72A7A249C4B /* CachedURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03690CB91FD49C9415894F892269CA8D /* CachedURLResponse.swift */; }; + 9F4A8AEC8660D2613B1041FBA494CDF4 /* ReconnectionDelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6765882F108D65FBCB8149C35E9DCF5 /* ReconnectionDelay.swift */; }; + AFDF13EFB490B1251F149B61154F3B91 /* Trait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6018B9D5E1F39238EA27E45432ACEF59 /* Trait.swift */; }; C5344C505516395035EAB8B9FC111FE2 /* Pods-FlagsmithClient_Example-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F66D3EFFD8615AE149DCEEE155C049F /* Pods-FlagsmithClient_Example-dummy.m */; }; - D804DCDEA2D5AC6CE809EC8D972B3A80 /* FlagsmithClient-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0E8F8144A353EF4972DA8231507159 /* FlagsmithClient-dummy.m */; }; - E4162B22739D5F7D0728E35C4B806BB5 /* FlagsmithClient-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 42419C1B4BE086CC475FA111C3C7F89B /* FlagsmithClient-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - F440E618661D6B6DB91C40AA067EE9AE /* UnknownTypeValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C37C353E63EE8DAA3130D043A0D2EB2F /* UnknownTypeValue.swift */; }; - F44ECBF1ABB58E1D4064C12D88BA909D /* PrivacyInfo.xcprivacy in Sources */ = {isa = PBXBuildFile; fileRef = E685A93BDD480D9EBB3E40D9D31E964E /* PrivacyInfo.xcprivacy */; }; - F7A953943973635341397277A7B77E44 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E0332EE4AA77385BBB3C3A282AEFDFB /* Router.swift */; }; + D67E497B05259E0AD9C30CC46158D1A7 /* FlagsmithClient-Flagsmith_Privacy in Resources */ = {isa = PBXBuildFile; fileRef = 8C51EF4BBA8F7B354867592C80F3B5A3 /* FlagsmithClient-Flagsmith_Privacy */; }; + DEF6E0073F05A284953EF11E5F292733 /* APIManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27D373CC0D0864ABD84A3A70B28A7E2F /* APIManager.swift */; }; + F4ED5E5217FF68022229AB757FC270D0 /* Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE7AFB7D320C4ED00D12C81759DA2FA /* Traits.swift */; }; + F9792F93C632BCBEF5F0C2AEE9F61724 /* Feature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95D8BE0835A69EEC959187F593A1766C /* Feature.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 9D5732A5EB8BA218D9E058369413E3D9 /* PBXContainerItemProxy */ = { + 5DE21B5F391D80CE947928ED856E8498 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = 11AAFF8883A32F4C9C2E17C1B1AE0614; - remoteInfo = FlagsmithClient; + remoteGlobalIDString = DDB5D474692F015527858230D3C21513; + remoteInfo = "FlagsmithClient-Flagsmith_Privacy"; }; - AF57E5ECF5AA5CC6888F2C49B2406A20 /* PBXContainerItemProxy */ = { + F40EFDA65002C5643C63285A398DA30B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = D089F624F760A848617C705876C78058; - remoteInfo = "FlagsmithClient-FlagSmith_Privacy"; + remoteGlobalIDString = 11AAFF8883A32F4C9C2E17C1B1AE0614; + remoteInfo = FlagsmithClient; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 010890F60854F25B5CF6D19BAF3B3774 /* FlagsmithAnalytics.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = FlagsmithAnalytics.swift; sourceTree = ""; }; - 01A052BA5448975C6D01FF1003C2FB1D /* FlagsmithClient-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FlagsmithClient-Info.plist"; sourceTree = ""; }; - 01BED8F56D2D29829501F35ABA812D07 /* FlagsmithClient-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FlagsmithClient-prefix.pch"; sourceTree = ""; }; - 02B76A0CC5DD33DC3828D9A6C4BF09F0 /* FlagsmithClient.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = FlagsmithClient.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 02E4DC32B2FC713ED935462DA9F1CBF9 /* Pods-FlagsmithClient_Example-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-FlagsmithClient_Example-umbrella.h"; sourceTree = ""; }; - 0F1CB950A2B9D94D55201B7B8191BB83 /* Trait.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Trait.swift; path = FlagsmithClient/Classes/Trait.swift; sourceTree = ""; }; + 03690CB91FD49C9415894F892269CA8D /* CachedURLResponse.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CachedURLResponse.swift; sourceTree = ""; }; + 0643BC5D2BB2DC93011E1E762669070A /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; path = LICENSE; sourceTree = ""; }; + 07DC051B5A096E5B1B6B9AA284119664 /* Flag.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Flag.swift; path = FlagsmithClient/Classes/Flag.swift; sourceTree = ""; }; + 0E2C2AC5693315F6FF359AAFF1DBCCD6 /* FlagsmithClient-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FlagsmithClient-dummy.m"; sourceTree = ""; }; 0F66D3EFFD8615AE149DCEEE155C049F /* Pods-FlagsmithClient_Example-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-FlagsmithClient_Example-dummy.m"; sourceTree = ""; }; - 193A8DBBF59C95D3ADE40A6CACE88317 /* Flag.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Flag.swift; path = FlagsmithClient/Classes/Flag.swift; sourceTree = ""; }; + 1F4686BE4BA219BE2C189F245B022291 /* ResourceBundle-Flagsmith_Privacy-FlagsmithClient-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-Flagsmith_Privacy-FlagsmithClient-Info.plist"; sourceTree = ""; }; + 23F9F9F9965CB8A46D92B5008FEBDD52 /* FlagsmithClient.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FlagsmithClient.modulemap; sourceTree = ""; }; 250DE57229233B0BAD273A076F108A0E /* Pods-FlagsmithClient_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-FlagsmithClient_Example.release.xcconfig"; sourceTree = ""; }; - 33401B963FE6A82269201D80F422B5DC /* Flagsmith+Concurrency.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Flagsmith+Concurrency.swift"; path = "FlagsmithClient/Classes/Flagsmith+Concurrency.swift"; sourceTree = ""; }; - 334A531B6E779CB5BBB111407DAB389B /* FlagsmithError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FlagsmithError.swift; path = FlagsmithClient/Classes/FlagsmithError.swift; sourceTree = ""; }; + 27D373CC0D0864ABD84A3A70B28A7E2F /* APIManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = APIManager.swift; sourceTree = ""; }; + 2F655DCB3D9C96A4B79EFCD4A6DE5EC1 /* TypedValue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TypedValue.swift; path = FlagsmithClient/Classes/TypedValue.swift; sourceTree = ""; }; 368DFAE2AAAB80524EFAFD71A2C92F84 /* Pods-FlagsmithClient_Example-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-FlagsmithClient_Example-acknowledgements.markdown"; sourceTree = ""; }; 3B040CFA25391975C1615BFB481B68C9 /* Pods-FlagsmithClient_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-FlagsmithClient_Example.debug.xcconfig"; sourceTree = ""; }; - 42419C1B4BE086CC475FA111C3C7F89B /* FlagsmithClient-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FlagsmithClient-umbrella.h"; sourceTree = ""; }; - 48E24C65212832E53CD320E489F86D56 /* TypedValue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TypedValue.swift; path = FlagsmithClient/Classes/TypedValue.swift; sourceTree = ""; }; - 560A2A43C2532F9A60D40C498910BDAD /* Identity.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Identity.swift; path = FlagsmithClient/Classes/Identity.swift; sourceTree = ""; }; - 5E0332EE4AA77385BBB3C3A282AEFDFB /* Router.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; - 5EDBFEFB26106F14D3ACD6A6E8DAF369 /* CachedURLResponse.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CachedURLResponse.swift; sourceTree = ""; }; + 3BC7B9D7D6AA6A11B6BEAE4AAB00AAAF /* Identity.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Identity.swift; path = FlagsmithClient/Classes/Identity.swift; sourceTree = ""; }; + 41C19EF5BDA5D6E01CDA5508B6FD5DE1 /* FlagsmithClient.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = FlagsmithClient.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 52845A8A8A4C6292F43309F6A180B4D8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = FlagsmithClient/Classes/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 5C592D17AD608D21D9D74AE825FF8675 /* UnknownTypeValue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UnknownTypeValue.swift; path = FlagsmithClient/Classes/UnknownTypeValue.swift; sourceTree = ""; }; + 5EA2DD515210C3938904B5ACEEF2C8F1 /* SSEManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SSEManager.swift; sourceTree = ""; }; + 6018B9D5E1F39238EA27E45432ACEF59 /* Trait.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Trait.swift; path = FlagsmithClient/Classes/Trait.swift; sourceTree = ""; }; + 65FF08866BCA26ED4DA993080881DE94 /* FlagsmithClient.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FlagsmithClient.release.xcconfig; sourceTree = ""; }; 68DDF334F75630BAA85571DF47D87C89 /* FlagsmithClient */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FlagsmithClient; path = FlagsmithClient.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6A93708A2DFB73CD0A47844194EEEF0E /* FlagsmithClient.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FlagsmithClient.debug.xcconfig; sourceTree = ""; }; - 6BD44276DCF6BDE68099A5762C3E3248 /* Traits.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Traits.swift; path = FlagsmithClient/Classes/Traits.swift; sourceTree = ""; }; + 70AD07C2E36235F98AA3CCE647177F5E /* FlagsmithClient.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FlagsmithClient.debug.xcconfig; sourceTree = ""; }; 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - 740BE3A9082C69EDDCE885472B239E2B /* APIManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = APIManager.swift; sourceTree = ""; }; - 79F99623D8F2206A889E3FC1BB7E028F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; path = LICENSE; sourceTree = ""; }; - 7F9C7D04F801E733A55ED8BA67AA84CE /* FlagsmithClient-FlagSmith_Privacy */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "FlagsmithClient-FlagSmith_Privacy"; path = FlagSmith_Privacy.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 81A815D8A0C28062CD4A8224C6883D5D /* Pods-FlagsmithClient_Example.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-FlagsmithClient_Example.modulemap"; sourceTree = ""; }; - 97FECC05C71E791B8A6247E180CDC9BB /* FlagsmithClient.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FlagsmithClient.modulemap; sourceTree = ""; }; - 99036F2475C7DD87269B0693A1E508D1 /* Feature.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Feature.swift; path = FlagsmithClient/Classes/Feature.swift; sourceTree = ""; }; + 881B2A3AF56BA171E0F9E187698A2A1A /* Router.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; + 8C1C17F165DFEBF040C1762A0ACF12C1 /* FlagsmithClient-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FlagsmithClient-prefix.pch"; sourceTree = ""; }; + 8C51EF4BBA8F7B354867592C80F3B5A3 /* FlagsmithClient-Flagsmith_Privacy */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "FlagsmithClient-Flagsmith_Privacy"; path = Flagsmith_Privacy.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 8D6EF7174577EA07427F20B96212DAD8 /* FlagsmithClient-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FlagsmithClient-umbrella.h"; sourceTree = ""; }; + 95D8BE0835A69EEC959187F593A1766C /* Feature.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Feature.swift; path = FlagsmithClient/Classes/Feature.swift; sourceTree = ""; }; + 964E54325F994DA2BA6D096CEC973B27 /* FlagsmithAnalytics.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = FlagsmithAnalytics.swift; sourceTree = ""; }; + 96D95BE5EB81DB627D8831BEA9C85694 /* FlagsmithClient-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FlagsmithClient-Info.plist"; sourceTree = ""; }; 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 9E877EDD18BF267BC157C6CD6E133BA8 /* FlagsmithClient.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FlagsmithClient.release.xcconfig; sourceTree = ""; }; A7D159AFD71C50F45CAAD458140D8648 /* Pods-FlagsmithClient_Example-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-FlagsmithClient_Example-acknowledgements.plist"; sourceTree = ""; }; - B3B60DD8AD34844A7BC951D1CADF34DB /* ResourceBundle-FlagSmith_Privacy-FlagsmithClient-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-FlagSmith_Privacy-FlagsmithClient-Info.plist"; sourceTree = ""; }; + B220A338926B2DE5B01EA55B42DEC7A2 /* Flagsmith.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Flagsmith.swift; path = FlagsmithClient/Classes/Flagsmith.swift; sourceTree = ""; }; B7528E5D2516E37BD5B3A5DC02010477 /* Pods-FlagsmithClient_Example-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-FlagsmithClient_Example-frameworks.sh"; sourceTree = ""; }; C1817E8624F31BD483479898AD8A9F9C /* Pods-FlagsmithClient_Example */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-FlagsmithClient_Example"; path = Pods_FlagsmithClient_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C37C353E63EE8DAA3130D043A0D2EB2F /* UnknownTypeValue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UnknownTypeValue.swift; path = FlagsmithClient/Classes/UnknownTypeValue.swift; sourceTree = ""; }; - CC0E8F8144A353EF4972DA8231507159 /* FlagsmithClient-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FlagsmithClient-dummy.m"; sourceTree = ""; }; - D30C8727FF378667687B927B401ACFC2 /* Flagsmith.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Flagsmith.swift; path = FlagsmithClient/Classes/Flagsmith.swift; sourceTree = ""; }; + CB29AEDAE44BB85C47C35AF0C1C21CF9 /* FlagEvent.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FlagEvent.swift; path = FlagsmithClient/Classes/FlagEvent.swift; sourceTree = ""; }; + D6765882F108D65FBCB8149C35E9DCF5 /* ReconnectionDelay.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReconnectionDelay.swift; sourceTree = ""; }; + DBE7AFB7D320C4ED00D12C81759DA2FA /* Traits.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Traits.swift; path = FlagsmithClient/Classes/Traits.swift; sourceTree = ""; }; + E09CA3FD6CEC48CF0ACAEACEBA9962B1 /* Flagsmith+Concurrency.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Flagsmith+Concurrency.swift"; path = "FlagsmithClient/Classes/Flagsmith+Concurrency.swift"; sourceTree = ""; }; E28010F1C58E656FC37588C8A00FEE38 /* Pods-FlagsmithClient_Example-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-FlagsmithClient_Example-Info.plist"; sourceTree = ""; }; - E685A93BDD480D9EBB3E40D9D31E964E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = FlagsmithClient/Classes/PrivacyInfo.xcprivacy; sourceTree = ""; }; - F68F491C2EB4793199539D7BB50E1A2B /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; path = README.md; sourceTree = ""; }; + F9E9B58EDF83BB73CBD7B31A6E538D3D /* FlagsmithError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FlagsmithError.swift; path = FlagsmithClient/Classes/FlagsmithError.swift; sourceTree = ""; }; + FD8F77BFD649E32CE63FA95E2C1FEE5C /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; path = README.md; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 12C0163E057E9D8C44B9E1980454B186 /* Frameworks */ = { + 28646B38D2A3A6A6694BFA2E999C1387 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 145BDA8A923A4A97F7D1985125F60E67 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - C181189091EFD63A0972B87ED143DC52 /* Frameworks */ = { + 6C92D81F7EE9CAF1FEA87581FAA08AD8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3CB1981582BED249B73F39640A046EC6 /* Foundation.framework in Frameworks */, + 7C7B677ECC185354844CE513BBBF91FF /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - E4C1A32CB959CCA3FE667491F71AA361 /* Frameworks */ = { + C181189091EFD63A0972B87ED143DC52 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3CB1981582BED249B73F39640A046EC6 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 004E68B560F9B8386F5DB36906B270A5 /* Products */ = { + 50D91CBAD445ECAC9E7E8CE70F12BE11 /* Internal */ = { isa = PBXGroup; children = ( - 68DDF334F75630BAA85571DF47D87C89 /* FlagsmithClient */, - 7F9C7D04F801E733A55ED8BA67AA84CE /* FlagsmithClient-FlagSmith_Privacy */, - C1817E8624F31BD483479898AD8A9F9C /* Pods-FlagsmithClient_Example */, - ); - name = Products; - sourceTree = ""; - }; - 5504E831FC9EDDEE6663BD8753EC1AC0 /* Internal */ = { - isa = PBXGroup; - children = ( - 740BE3A9082C69EDDCE885472B239E2B /* APIManager.swift */, - 5EDBFEFB26106F14D3ACD6A6E8DAF369 /* CachedURLResponse.swift */, - 010890F60854F25B5CF6D19BAF3B3774 /* FlagsmithAnalytics.swift */, - 5E0332EE4AA77385BBB3C3A282AEFDFB /* Router.swift */, + 27D373CC0D0864ABD84A3A70B28A7E2F /* APIManager.swift */, + 03690CB91FD49C9415894F892269CA8D /* CachedURLResponse.swift */, + 964E54325F994DA2BA6D096CEC973B27 /* FlagsmithAnalytics.swift */, + D6765882F108D65FBCB8149C35E9DCF5 /* ReconnectionDelay.swift */, + 881B2A3AF56BA171E0F9E187698A2A1A /* Router.swift */, + 5EA2DD515210C3938904B5ACEEF2C8F1 /* SSEManager.swift */, ); name = Internal; path = FlagsmithClient/Classes/Internal; @@ -165,24 +164,42 @@ path = "Target Support Files/Pods-FlagsmithClient_Example"; sourceTree = ""; }; - 83DF8963328A9E0494B7C83B809AF479 /* Development Pods */ = { + 7CF29E875D36810A8EF0D00FA3D34E95 /* Products */ = { isa = PBXGroup; children = ( - EB5B366C3102D180EE97EBB112A2C98A /* FlagsmithClient */, + 68DDF334F75630BAA85571DF47D87C89 /* FlagsmithClient */, + 8C51EF4BBA8F7B354867592C80F3B5A3 /* FlagsmithClient-Flagsmith_Privacy */, + C1817E8624F31BD483479898AD8A9F9C /* Pods-FlagsmithClient_Example */, ); - name = "Development Pods"; + name = Products; sourceTree = ""; }; - A880B46BBE672F8B5031ACED24CDDC51 /* Pod */ = { + 884F357900427B2C2DB41932AD85E7CF /* Pod */ = { isa = PBXGroup; children = ( - 02B76A0CC5DD33DC3828D9A6C4BF09F0 /* FlagsmithClient.podspec */, - 79F99623D8F2206A889E3FC1BB7E028F /* LICENSE */, - F68F491C2EB4793199539D7BB50E1A2B /* README.md */, + 41C19EF5BDA5D6E01CDA5508B6FD5DE1 /* FlagsmithClient.podspec */, + 0643BC5D2BB2DC93011E1E762669070A /* LICENSE */, + FD8F77BFD649E32CE63FA95E2C1FEE5C /* README.md */, ); name = Pod; sourceTree = ""; }; + 922842A51BB47BB673DBAEDB500DA770 /* Support Files */ = { + isa = PBXGroup; + children = ( + 23F9F9F9965CB8A46D92B5008FEBDD52 /* FlagsmithClient.modulemap */, + 0E2C2AC5693315F6FF359AAFF1DBCCD6 /* FlagsmithClient-dummy.m */, + 96D95BE5EB81DB627D8831BEA9C85694 /* FlagsmithClient-Info.plist */, + 8C1C17F165DFEBF040C1762A0ACF12C1 /* FlagsmithClient-prefix.pch */, + 8D6EF7174577EA07427F20B96212DAD8 /* FlagsmithClient-umbrella.h */, + 70AD07C2E36235F98AA3CCE647177F5E /* FlagsmithClient.debug.xcconfig */, + 65FF08866BCA26ED4DA993080881DE94 /* FlagsmithClient.release.xcconfig */, + 1F4686BE4BA219BE2C189F245B022291 /* ResourceBundle-Flagsmith_Privacy-FlagsmithClient-Info.plist */, + ); + name = "Support Files"; + path = "Example/Pods/Target Support Files/FlagsmithClient"; + sourceTree = ""; + }; BA8A3B9AA623A3EC5D887B56C04C7D73 /* Targets Support Files */ = { isa = PBXGroup; children = ( @@ -195,9 +212,9 @@ isa = PBXGroup; children = ( 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, - 83DF8963328A9E0494B7C83B809AF479 /* Development Pods */, + F5B16B955BEC2A0084665EFA15089CEC /* Development Pods */, D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */, - 004E68B560F9B8386F5DB36906B270A5 /* Products */, + 7CF29E875D36810A8EF0D00FA3D34E95 /* Products */, BA8A3B9AA623A3EC5D887B56C04C7D73 /* Targets Support Files */, ); sourceTree = ""; @@ -210,42 +227,35 @@ name = Frameworks; sourceTree = ""; }; - DD874B272A864A7F34C517E2F9F6438B /* Support Files */ = { + E3326EA0541FE9566A3D8E7CB171ED1B /* FlagsmithClient */ = { isa = PBXGroup; children = ( - 97FECC05C71E791B8A6247E180CDC9BB /* FlagsmithClient.modulemap */, - CC0E8F8144A353EF4972DA8231507159 /* FlagsmithClient-dummy.m */, - 01A052BA5448975C6D01FF1003C2FB1D /* FlagsmithClient-Info.plist */, - 01BED8F56D2D29829501F35ABA812D07 /* FlagsmithClient-prefix.pch */, - 42419C1B4BE086CC475FA111C3C7F89B /* FlagsmithClient-umbrella.h */, - 6A93708A2DFB73CD0A47844194EEEF0E /* FlagsmithClient.debug.xcconfig */, - 9E877EDD18BF267BC157C6CD6E133BA8 /* FlagsmithClient.release.xcconfig */, - B3B60DD8AD34844A7BC951D1CADF34DB /* ResourceBundle-FlagSmith_Privacy-FlagsmithClient-Info.plist */, + 95D8BE0835A69EEC959187F593A1766C /* Feature.swift */, + 07DC051B5A096E5B1B6B9AA284119664 /* Flag.swift */, + CB29AEDAE44BB85C47C35AF0C1C21CF9 /* FlagEvent.swift */, + B220A338926B2DE5B01EA55B42DEC7A2 /* Flagsmith.swift */, + E09CA3FD6CEC48CF0ACAEACEBA9962B1 /* Flagsmith+Concurrency.swift */, + F9E9B58EDF83BB73CBD7B31A6E538D3D /* FlagsmithError.swift */, + 3BC7B9D7D6AA6A11B6BEAE4AAB00AAAF /* Identity.swift */, + 52845A8A8A4C6292F43309F6A180B4D8 /* PrivacyInfo.xcprivacy */, + 6018B9D5E1F39238EA27E45432ACEF59 /* Trait.swift */, + DBE7AFB7D320C4ED00D12C81759DA2FA /* Traits.swift */, + 2F655DCB3D9C96A4B79EFCD4A6DE5EC1 /* TypedValue.swift */, + 5C592D17AD608D21D9D74AE825FF8675 /* UnknownTypeValue.swift */, + 50D91CBAD445ECAC9E7E8CE70F12BE11 /* Internal */, + 884F357900427B2C2DB41932AD85E7CF /* Pod */, + 922842A51BB47BB673DBAEDB500DA770 /* Support Files */, ); - name = "Support Files"; - path = "Example/Pods/Target Support Files/FlagsmithClient"; + name = FlagsmithClient; + path = ../..; sourceTree = ""; }; - EB5B366C3102D180EE97EBB112A2C98A /* FlagsmithClient */ = { + F5B16B955BEC2A0084665EFA15089CEC /* Development Pods */ = { isa = PBXGroup; children = ( - 99036F2475C7DD87269B0693A1E508D1 /* Feature.swift */, - 193A8DBBF59C95D3ADE40A6CACE88317 /* Flag.swift */, - D30C8727FF378667687B927B401ACFC2 /* Flagsmith.swift */, - 33401B963FE6A82269201D80F422B5DC /* Flagsmith+Concurrency.swift */, - 334A531B6E779CB5BBB111407DAB389B /* FlagsmithError.swift */, - 560A2A43C2532F9A60D40C498910BDAD /* Identity.swift */, - E685A93BDD480D9EBB3E40D9D31E964E /* PrivacyInfo.xcprivacy */, - 0F1CB950A2B9D94D55201B7B8191BB83 /* Trait.swift */, - 6BD44276DCF6BDE68099A5762C3E3248 /* Traits.swift */, - 48E24C65212832E53CD320E489F86D56 /* TypedValue.swift */, - C37C353E63EE8DAA3130D043A0D2EB2F /* UnknownTypeValue.swift */, - 5504E831FC9EDDEE6663BD8753EC1AC0 /* Internal */, - A880B46BBE672F8B5031ACED24CDDC51 /* Pod */, - DD874B272A864A7F34C517E2F9F6438B /* Support Files */, + E3326EA0541FE9566A3D8E7CB171ED1B /* FlagsmithClient */, ); - name = FlagsmithClient; - path = ../..; + name = "Development Pods"; sourceTree = ""; }; /* End PBXGroup section */ @@ -259,11 +269,11 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - B0B82A0BFE945677F027568CF40F606B /* Headers */ = { + DBAD4DF9BCB7770FB477D83DBAEAF8DD /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - E4162B22739D5F7D0728E35C4B806BB5 /* FlagsmithClient-umbrella.h in Headers */, + 0C66F595D6DBEA017866CA6623A3441B /* FlagsmithClient-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -272,17 +282,17 @@ /* Begin PBXNativeTarget section */ 11AAFF8883A32F4C9C2E17C1B1AE0614 /* FlagsmithClient */ = { isa = PBXNativeTarget; - buildConfigurationList = ABD7FC62C2B9ACFD025EB9237D4ADBFE /* Build configuration list for PBXNativeTarget "FlagsmithClient" */; + buildConfigurationList = 872A45C57BE69C0F3B405EF95F7446F3 /* Build configuration list for PBXNativeTarget "FlagsmithClient" */; buildPhases = ( - B0B82A0BFE945677F027568CF40F606B /* Headers */, - D9978955D1F7D97BA5C53A6F2B02A6AF /* Sources */, - 12C0163E057E9D8C44B9E1980454B186 /* Frameworks */, - 4B831344AB43BCEBB55AC7A0D1BD1D0A /* Resources */, + DBAD4DF9BCB7770FB477D83DBAEAF8DD /* Headers */, + 5CE14F8007EAB40BE4CED3A775C5C7A8 /* Sources */, + 6C92D81F7EE9CAF1FEA87581FAA08AD8 /* Frameworks */, + A9A058C7593CCE07F87EE04DF2CA9C0A /* Resources */, ); buildRules = ( ); dependencies = ( - AB585B4EA3B1A537EA9707B6BF2CECDC /* PBXTargetDependency */, + 1DE95B17D2E84D520E3B283CCB7C78B5 /* PBXTargetDependency */, ); name = FlagsmithClient; productName = FlagsmithClient; @@ -301,28 +311,28 @@ buildRules = ( ); dependencies = ( - 54A900312AEF2FB8D47E285B59D9AF7F /* PBXTargetDependency */, + 6A77AF7F1896A37338CE67C3A520DDDE /* PBXTargetDependency */, ); name = "Pods-FlagsmithClient_Example"; productName = Pods_FlagsmithClient_Example; productReference = C1817E8624F31BD483479898AD8A9F9C /* Pods-FlagsmithClient_Example */; productType = "com.apple.product-type.framework"; }; - D089F624F760A848617C705876C78058 /* FlagsmithClient-FlagSmith_Privacy */ = { + DDB5D474692F015527858230D3C21513 /* FlagsmithClient-Flagsmith_Privacy */ = { isa = PBXNativeTarget; - buildConfigurationList = D9D115EFB53F932E6EDDE9FCD80E521A /* Build configuration list for PBXNativeTarget "FlagsmithClient-FlagSmith_Privacy" */; + buildConfigurationList = 78E58970E65DB0CA7A0D818840D86E26 /* Build configuration list for PBXNativeTarget "FlagsmithClient-Flagsmith_Privacy" */; buildPhases = ( - A4E89541AF518F781354C01097A6F67B /* Sources */, - E4C1A32CB959CCA3FE667491F71AA361 /* Frameworks */, - EE871D842E8E68C7D392182D72D98F65 /* Resources */, + CCCCD68563FB2F48CF1885B649FAEF39 /* Sources */, + 28646B38D2A3A6A6694BFA2E999C1387 /* Frameworks */, + 373D162EFDFD4AD80FF4A6B167B2E321 /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = "FlagsmithClient-FlagSmith_Privacy"; - productName = FlagSmith_Privacy; - productReference = 7F9C7D04F801E733A55ED8BA67AA84CE /* FlagsmithClient-FlagSmith_Privacy */; + name = "FlagsmithClient-Flagsmith_Privacy"; + productName = Flagsmith_Privacy; + productReference = 8C51EF4BBA8F7B354867592C80F3B5A3 /* FlagsmithClient-Flagsmith_Privacy */; productType = "com.apple.product-type.bundle"; }; /* End PBXNativeTarget section */ @@ -343,23 +353,24 @@ en, ); mainGroup = CF1408CF629C7361332E53B88F7BD30C; - productRefGroup = 004E68B560F9B8386F5DB36906B270A5 /* Products */; + minimizedProjectReferenceProxies = 0; + productRefGroup = 7CF29E875D36810A8EF0D00FA3D34E95 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 11AAFF8883A32F4C9C2E17C1B1AE0614 /* FlagsmithClient */, - D089F624F760A848617C705876C78058 /* FlagsmithClient-FlagSmith_Privacy */, + DDB5D474692F015527858230D3C21513 /* FlagsmithClient-Flagsmith_Privacy */, 770799B5DCFAB0B1BCF1BD365E2C1BC5 /* Pods-FlagsmithClient_Example */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 4B831344AB43BCEBB55AC7A0D1BD1D0A /* Resources */ = { + 373D162EFDFD4AD80FF4A6B167B2E321 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 36A99CA483CA4242032567EFEC9024E9 /* FlagsmithClient-FlagSmith_Privacy in Resources */, + 8A19E7AB53D3DDB219C62ECFA679D4BD /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -370,107 +381,143 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - EE871D842E8E68C7D392182D72D98F65 /* Resources */ = { + A9A058C7593CCE07F87EE04DF2CA9C0A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D67E497B05259E0AD9C30CC46158D1A7 /* FlagsmithClient-Flagsmith_Privacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 660F00CEBC7EEE195DEDCC6CBB923CEA /* Sources */ = { + 5CE14F8007EAB40BE4CED3A775C5C7A8 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C5344C505516395035EAB8B9FC111FE2 /* Pods-FlagsmithClient_Example-dummy.m in Sources */, + DEF6E0073F05A284953EF11E5F292733 /* APIManager.swift in Sources */, + 9F173867D326EDBF402CD72A7A249C4B /* CachedURLResponse.swift in Sources */, + F9792F93C632BCBEF5F0C2AEE9F61724 /* Feature.swift in Sources */, + 54822BAB37B8A2525E97D75928D7DD76 /* Flag.swift in Sources */, + 53DCEB6C6C95F0D2D6806B42816100B9 /* FlagEvent.swift in Sources */, + 2B1240706C35627EEE86A8675C72F953 /* Flagsmith.swift in Sources */, + 6573330E26D76C6427BEA704DD624882 /* Flagsmith+Concurrency.swift in Sources */, + 5A2B75222740D6227142A25329465949 /* FlagsmithAnalytics.swift in Sources */, + 037725D5CFD3B162DB874076446013F5 /* FlagsmithClient-dummy.m in Sources */, + 943B0DFB191B3E6ADBE5AD7BE4E17F02 /* FlagsmithError.swift in Sources */, + 4B40443D5FE9A0DBBF5112C23B1629A5 /* Identity.swift in Sources */, + 02828F81751D68CC19DE337936329454 /* PrivacyInfo.xcprivacy in Sources */, + 9F4A8AEC8660D2613B1041FBA494CDF4 /* ReconnectionDelay.swift in Sources */, + 4FAB11F14645CC89276B729F8BE34AEC /* Router.swift in Sources */, + 91C87A61094984812E6B2CDAFF1B064E /* SSEManager.swift in Sources */, + AFDF13EFB490B1251F149B61154F3B91 /* Trait.swift in Sources */, + F4ED5E5217FF68022229AB757FC270D0 /* Traits.swift in Sources */, + 725BC82A464F54C5E52918EC277AF41D /* TypedValue.swift in Sources */, + 2ADE4C4C0A6290AC0935A43B4357A211 /* UnknownTypeValue.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - A4E89541AF518F781354C01097A6F67B /* Sources */ = { + 660F00CEBC7EEE195DEDCC6CBB923CEA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C5344C505516395035EAB8B9FC111FE2 /* Pods-FlagsmithClient_Example-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - D9978955D1F7D97BA5C53A6F2B02A6AF /* Sources */ = { + CCCCD68563FB2F48CF1885B649FAEF39 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1848829C29C5631D664CC0742A63EC33 /* APIManager.swift in Sources */, - 4FB62CFD8230DFD531423AC407FFBF12 /* CachedURLResponse.swift in Sources */, - 41403B5BE8BE6F8EF3DD6D51EB8A05CD /* Feature.swift in Sources */, - 0083552669FD822D4A203F429BEF6F8C /* Flag.swift in Sources */, - 8C16371FF3BD75AA8903F1364B84C902 /* Flagsmith.swift in Sources */, - 976897ECFF1B33FC921C0C50A0F2A156 /* Flagsmith+Concurrency.swift in Sources */, - C462C452A86C94C3B33BA403FD5102C3 /* FlagsmithAnalytics.swift in Sources */, - D804DCDEA2D5AC6CE809EC8D972B3A80 /* FlagsmithClient-dummy.m in Sources */, - 7A6E9ACE7C0730D83DC68A952B450CBF /* FlagsmithError.swift in Sources */, - 4BC792BD210D6770E120952EC1A6149F /* Identity.swift in Sources */, - F44ECBF1ABB58E1D4064C12D88BA909D /* PrivacyInfo.xcprivacy in Sources */, - F7A953943973635341397277A7B77E44 /* Router.swift in Sources */, - 391C8F320DDD9EAB5996574CB69D38A6 /* Trait.swift in Sources */, - 9A443C01EC676628F306F7E210F63CBC /* Traits.swift in Sources */, - 200C93079A049CC69CF5844427F65359 /* TypedValue.swift in Sources */, - F440E618661D6B6DB91C40AA067EE9AE /* UnknownTypeValue.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 54A900312AEF2FB8D47E285B59D9AF7F /* PBXTargetDependency */ = { + 1DE95B17D2E84D520E3B283CCB7C78B5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - name = FlagsmithClient; - target = 11AAFF8883A32F4C9C2E17C1B1AE0614 /* FlagsmithClient */; - targetProxy = 9D5732A5EB8BA218D9E058369413E3D9 /* PBXContainerItemProxy */; + name = "FlagsmithClient-Flagsmith_Privacy"; + target = DDB5D474692F015527858230D3C21513 /* FlagsmithClient-Flagsmith_Privacy */; + targetProxy = 5DE21B5F391D80CE947928ED856E8498 /* PBXContainerItemProxy */; }; - AB585B4EA3B1A537EA9707B6BF2CECDC /* PBXTargetDependency */ = { + 6A77AF7F1896A37338CE67C3A520DDDE /* PBXTargetDependency */ = { isa = PBXTargetDependency; - name = "FlagsmithClient-FlagSmith_Privacy"; - target = D089F624F760A848617C705876C78058 /* FlagsmithClient-FlagSmith_Privacy */; - targetProxy = AF57E5ECF5AA5CC6888F2C49B2406A20 /* PBXContainerItemProxy */; + name = FlagsmithClient; + target = 11AAFF8883A32F4C9C2E17C1B1AE0614 /* FlagsmithClient */; + targetProxy = F40EFDA65002C5643C63285A398DA30B /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 049EEB1E25BB61D149D6D294514FA82A /* Debug */ = { + 088BC0252F372B7E3A2E0D86612E7B23 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6A93708A2DFB73CD0A47844194EEEF0E /* FlagsmithClient.debug.xcconfig */; + baseConfigurationReference = 65FF08866BCA26ED4DA993080881DE94 /* FlagsmithClient.release.xcconfig */; buildSettings = { CODE_SIGNING_ALLOWED = NO; CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/FlagsmithClient"; IBSC_MODULE = FlagsmithClient; - INFOPLIST_FILE = "Target Support Files/FlagsmithClient/ResourceBundle-FlagSmith_Privacy-FlagsmithClient-Info.plist"; + INFOPLIST_FILE = "Target Support Files/FlagsmithClient/ResourceBundle-Flagsmith_Privacy-FlagsmithClient-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - PRODUCT_NAME = FlagSmith_Privacy; + PRODUCT_NAME = Flagsmith_Privacy; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = bundle; }; - name = Debug; + name = Release; }; - 2769E86984A0005886AC6DA3DEEF5E58 /* Release */ = { + 304DA00CF42F1DAA28D33F41934E696D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9E877EDD18BF267BC157C6CD6E133BA8 /* FlagsmithClient.release.xcconfig */; + baseConfigurationReference = 70AD07C2E36235F98AA3CCE647177F5E /* FlagsmithClient.debug.xcconfig */; buildSettings = { CODE_SIGNING_ALLOWED = NO; CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/FlagsmithClient"; IBSC_MODULE = FlagsmithClient; - INFOPLIST_FILE = "Target Support Files/FlagsmithClient/ResourceBundle-FlagSmith_Privacy-FlagsmithClient-Info.plist"; + INFOPLIST_FILE = "Target Support Files/FlagsmithClient/ResourceBundle-Flagsmith_Privacy-FlagsmithClient-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - PRODUCT_NAME = FlagSmith_Privacy; + PRODUCT_NAME = Flagsmith_Privacy; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = bundle; }; - name = Release; + name = Debug; + }; + 37689CBBDDC2DE43B59A2463997A1198 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 70AD07C2E36235F98AA3CCE647177F5E /* FlagsmithClient.debug.xcconfig */; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/FlagsmithClient/FlagsmithClient-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/FlagsmithClient/FlagsmithClient-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/FlagsmithClient/FlagsmithClient.modulemap"; + PRODUCT_MODULE_NAME = FlagsmithClient; + PRODUCT_NAME = FlagsmithClient; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.6; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; }; - 2B9E26EAE2CD392AD762421F663075A1 /* Debug */ = { + 4BC7450F9457737EE3E637BA155B56F7 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -523,7 +570,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -536,10 +583,11 @@ }; name = Debug; }; - 58E625716FA7C3CCECC76379D437F52F /* Release */ = { + 6355C6C801A7897E3AC122F4FB3DEE79 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9E877EDD18BF267BC157C6CD6E133BA8 /* FlagsmithClient.release.xcconfig */; + baseConfigurationReference = 3B040CFA25391975C1615BFB481B68C9 /* Pods-FlagsmithClient_Example.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; ARCHS = "$(ARCHS_STANDARD_64_BIT)"; CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -550,58 +598,26 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/FlagsmithClient/FlagsmithClient-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/FlagsmithClient/FlagsmithClient-Info.plist"; + INFOPLIST_FILE = "Target Support Files/Pods-FlagsmithClient_Example/Pods-FlagsmithClient_Example-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "Target Support Files/FlagsmithClient/FlagsmithClient.modulemap"; - PRODUCT_MODULE_NAME = FlagsmithClient; - PRODUCT_NAME = FlagsmithClient; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5.6; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 599DF1132B45CDE985F883321D83D83F /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 6A93708A2DFB73CD0A47844194EEEF0E /* FlagsmithClient.debug.xcconfig */; - buildSettings = { - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/FlagsmithClient/FlagsmithClient-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/FlagsmithClient/FlagsmithClient-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "Target Support Files/FlagsmithClient/FlagsmithClient.modulemap"; - PRODUCT_MODULE_NAME = FlagsmithClient; - PRODUCT_NAME = FlagsmithClient; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-FlagsmithClient_Example/Pods-FlagsmithClient_Example.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5.6; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; - 63FAF33E1C55B71A5F5A8B3CC8749F99 /* Release */ = { + 8B5A46FF8D3C1289CDEE3BAFACABCD2A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -651,7 +667,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -663,9 +679,9 @@ }; name = Release; }; - BC7D7AE6AEB2B1C6FA9194A3AB5A0B41 /* Debug */ = { + B21CF8CCF55064B777C34AB47576AB0A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3B040CFA25391975C1615BFB481B68C9 /* Pods-FlagsmithClient_Example.debug.xcconfig */; + baseConfigurationReference = 250DE57229233B0BAD273A076F108A0E /* Pods-FlagsmithClient_Example.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -680,7 +696,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/Pods-FlagsmithClient_Example/Pods-FlagsmithClient_Example-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-FlagsmithClient_Example/Pods-FlagsmithClient_Example.modulemap"; @@ -692,16 +708,16 @@ SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = Debug; + name = Release; }; - D22AD683643CD18DDDA1624DDA6590F4 /* Release */ = { + EB4BEADF33005DBFD99511FE4C2C3BDB /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 250DE57229233B0BAD273A076F108A0E /* Pods-FlagsmithClient_Example.release.xcconfig */; + baseConfigurationReference = 65FF08866BCA26ED4DA993080881DE94 /* FlagsmithClient.release.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; ARCHS = "$(ARCHS_STANDARD_64_BIT)"; CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -712,19 +728,18 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-FlagsmithClient_Example/Pods-FlagsmithClient_Example-Info.plist"; + GCC_PREFIX_HEADER = "Target Support Files/FlagsmithClient/FlagsmithClient-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/FlagsmithClient/FlagsmithClient-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-FlagsmithClient_Example/Pods-FlagsmithClient_Example.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + MODULEMAP_FILE = "Target Support Files/FlagsmithClient/FlagsmithClient.modulemap"; + PRODUCT_MODULE_NAME = FlagsmithClient; + PRODUCT_NAME = FlagsmithClient; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.6; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -738,8 +753,8 @@ 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { isa = XCConfigurationList; buildConfigurations = ( - 2B9E26EAE2CD392AD762421F663075A1 /* Debug */, - 63FAF33E1C55B71A5F5A8B3CC8749F99 /* Release */, + 4BC7450F9457737EE3E637BA155B56F7 /* Debug */, + 8B5A46FF8D3C1289CDEE3BAFACABCD2A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -747,26 +762,26 @@ 4C7C80EB087B80A197C9D65FE3ECC5B0 /* Build configuration list for PBXNativeTarget "Pods-FlagsmithClient_Example" */ = { isa = XCConfigurationList; buildConfigurations = ( - BC7D7AE6AEB2B1C6FA9194A3AB5A0B41 /* Debug */, - D22AD683643CD18DDDA1624DDA6590F4 /* Release */, + 6355C6C801A7897E3AC122F4FB3DEE79 /* Debug */, + B21CF8CCF55064B777C34AB47576AB0A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - ABD7FC62C2B9ACFD025EB9237D4ADBFE /* Build configuration list for PBXNativeTarget "FlagsmithClient" */ = { + 78E58970E65DB0CA7A0D818840D86E26 /* Build configuration list for PBXNativeTarget "FlagsmithClient-Flagsmith_Privacy" */ = { isa = XCConfigurationList; buildConfigurations = ( - 599DF1132B45CDE985F883321D83D83F /* Debug */, - 58E625716FA7C3CCECC76379D437F52F /* Release */, + 304DA00CF42F1DAA28D33F41934E696D /* Debug */, + 088BC0252F372B7E3A2E0D86612E7B23 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - D9D115EFB53F932E6EDDE9FCD80E521A /* Build configuration list for PBXNativeTarget "FlagsmithClient-FlagSmith_Privacy" */ = { + 872A45C57BE69C0F3B405EF95F7446F3 /* Build configuration list for PBXNativeTarget "FlagsmithClient" */ = { isa = XCConfigurationList; buildConfigurations = ( - 049EEB1E25BB61D149D6D294514FA82A /* Debug */, - 2769E86984A0005886AC6DA3DEEF5E58 /* Release */, + 37689CBBDDC2DE43B59A2463997A1198 /* Debug */, + EB4BEADF33005DBFD99511FE4C2C3BDB /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/Example/Pods/Target Support Files/FlagsmithClient/FlagsmithClient-Info.plist b/Example/Pods/Target Support Files/FlagsmithClient/FlagsmithClient-Info.plist index 05a2e18..d1c1be5 100644 --- a/Example/Pods/Target Support Files/FlagsmithClient/FlagsmithClient-Info.plist +++ b/Example/Pods/Target Support Files/FlagsmithClient/FlagsmithClient-Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.6.0 + 3.6.2 CFBundleSignature ???? CFBundleVersion diff --git a/Example/Pods/Target Support Files/FlagsmithClient/ResourceBundle-FlagSmith_Privacy-FlagsmithClient-Info.plist b/Example/Pods/Target Support Files/FlagsmithClient/ResourceBundle-FlagSmith_Privacy-FlagsmithClient-Info.plist index 832e218..c407461 100644 --- a/Example/Pods/Target Support Files/FlagsmithClient/ResourceBundle-FlagSmith_Privacy-FlagsmithClient-Info.plist +++ b/Example/Pods/Target Support Files/FlagsmithClient/ResourceBundle-FlagSmith_Privacy-FlagsmithClient-Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 3.6.0 + 3.6.2 CFBundleSignature ???? CFBundleVersion diff --git a/FlagsmithClient/Classes/.gitkeep b/FlagsmithClient/Classes/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/FlagsmithClient/Classes/FlagEvent.swift b/FlagsmithClient/Classes/FlagEvent.swift new file mode 100644 index 0000000..1f54ddc --- /dev/null +++ b/FlagsmithClient/Classes/FlagEvent.swift @@ -0,0 +1,16 @@ +// +// FlagEvent.swift +// FlagsmithClient +// +// Created by Gareth Reese on 13/09/2024. +// + +import Foundation + +public struct FlagEvent: Codable, Sendable { + enum CodingKeys: String, CodingKey { + case updatedAt = "updated_at" + } + + public let updatedAt: Double +} diff --git a/FlagsmithClient/Classes/Flagsmith+Concurrency.swift b/FlagsmithClient/Classes/Flagsmith+Concurrency.swift index 20171a3..53e71ee 100644 --- a/FlagsmithClient/Classes/Flagsmith+Concurrency.swift +++ b/FlagsmithClient/Classes/Flagsmith+Concurrency.swift @@ -9,6 +9,21 @@ import Foundation @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) public extension Flagsmith { + var flagStreamContinuation: AsyncStream<[Flag]>.Continuation? { + get { + return anyFlagStreamContinuation as? AsyncStream<[Flag]>.Continuation + } + set { + anyFlagStreamContinuation = newValue + } + } + + var flagStream: AsyncStream<[Flag]> { + AsyncStream { continuation in + anyFlagStreamContinuation = continuation + } + } + /// Get all feature flags (flags and remote config) optionally for a specific identity /// /// - Parameters: diff --git a/FlagsmithClient/Classes/Flagsmith.swift b/FlagsmithClient/Classes/Flagsmith.swift index 3228bd6..7e6dfaf 100644 --- a/FlagsmithClient/Classes/Flagsmith.swift +++ b/FlagsmithClient/Classes/Flagsmith.swift @@ -10,14 +10,25 @@ import Foundation import FoundationNetworking #endif +typealias CompletionHandler = @Sendable (Result) -> Void + /// Manage feature flags and remote config across multiple projects, /// environments and organisations. public final class Flagsmith: @unchecked Sendable { /// Shared singleton client object public static let shared: Flagsmith = .init() private let apiManager: APIManager + private let sseManager: SSEManager private let analytics: FlagsmithAnalytics + // The last time we got flags via the API + private var lastUpdatedAt: Double = 0.0 + + // The last identity used for fetching flags + private var lastUsedIdentity: String? + + var anyFlagStreamContinuation: Any? // AsyncStream<[Flag]>.Continuation? for iOS 13+ + /// Base URL /// /// The default implementation uses: `https://edge.api.flagsmith.com/api/v1`. @@ -26,12 +37,23 @@ public final class Flagsmith: @unchecked Sendable { set { apiManager.baseURL = newValue } } - /// API Key unique to your organization. + /// Base `URL` used for the event source. + /// + /// The default implementation uses: `https://realtime.flagsmith.com/`. + public var eventSourceBaseURL: URL { + get { sseManager.baseURL } + set { sseManager.baseURL = newValue } + } + + /// Environment Key unique to your organization. /// /// This value must be provided before any request can succeed. public var apiKey: String? { get { apiManager.apiKey } - set { apiManager.apiKey = newValue } + set { + apiManager.apiKey = newValue + sseManager.apiKey = newValue + } } /// Is flag analytics enabled? @@ -40,6 +62,21 @@ public final class Flagsmith: @unchecked Sendable { set { analytics.enableAnalytics = newValue } } + /// Are realtime updates enabled? + public var enableRealtimeUpdates: Bool { + get { sseManager.isStarted } + set { + if newValue { + sseManager.stop() + sseManager.start { [weak self] result in + self?.handleSSEResult(result) + } + } else { + sseManager.stop() + } + } + } + /// How often to send the flag analytics, in seconds public var analyticsFlushPeriod: Int { get { analytics.flushPeriod } @@ -74,6 +111,7 @@ public final class Flagsmith: @unchecked Sendable { private init() { apiManager = APIManager() + sseManager = SSEManager() analytics = FlagsmithAnalytics(apiManager: apiManager) } @@ -88,6 +126,7 @@ public final class Flagsmith: @unchecked Sendable { transient: Bool = false, completion: @Sendable @escaping (Result<[Flag], any Error>) -> Void) { + lastUsedIdentity = identity if let identity = identity { if let traits = traits { apiManager.request( @@ -104,6 +143,7 @@ public final class Flagsmith: @unchecked Sendable { getIdentity(identity, transient: transient) { result in switch result { case let .success(thisIdentity): + self.updateFlagStreamAndLastUpdatedAt(thisIdentity.flags) completion(.success(thisIdentity.flags)) case let .failure(error): self.handleFlagsError(error, completion: completion) @@ -111,26 +151,28 @@ public final class Flagsmith: @unchecked Sendable { } } } else { - if let _ = traits { + if traits != nil { completion(.failure(FlagsmithError.invalidArgument("You must provide an identity to set traits"))) } else { - apiManager.request(.getFlags) { (result: Result<[Flag], Error>) in + apiManager.request(.getFlags) { [weak self] (result: Result<[Flag], Error>) in switch result { case let .success(flags): + // Call updateFlagStream only when iOS 13+ + self?.updateFlagStreamAndLastUpdatedAt(flags) completion(.success(flags)) case let .failure(error): - self.handleFlagsError(error, completion: completion) + self?.handleFlagsError(error, completion: completion) } } } } } - + private func handleFlagsError(_ error: any Error, completion: @Sendable @escaping (Result<[Flag], any Error>) -> Void) { - if self.defaultFlags.isEmpty { + if defaultFlags.isEmpty { completion(.failure(error)) } else { - completion(.success(self.defaultFlags)) + completion(.success(defaultFlags)) } } @@ -308,6 +350,44 @@ public final class Flagsmith: @unchecked Sendable { private func getFlagUsingDefaults(withID id: String, forIdentity _: String? = nil) -> Flag? { return defaultFlags.first(where: { $0.feature.name == id }) } + + private func handleSSEResult(_ result: Result) { + switch result { + case let .success(event): + // Check whether this event is anything new + if lastUpdatedAt < event.updatedAt { + // Evict everything fron the cache + cacheConfig.cache.removeAllCachedResponses() + + // Now we can get the new values, which we can emit to the flagUpdateFlow if used + getFeatureFlags(forIdentity: lastUsedIdentity) { result in + switch result { + case let .failure(error): + print("Flagsmith - Error getting flags in SSE stream: \(error.localizedDescription)") + + case .success: + // On success the flastream is updated automatically in the API call + print("Flagsmith - Flags updated from SSE stream.") + } + } + } + + case let .failure(error): + print("handleSSEResult Error in SSE connection: \(error.localizedDescription)") + } + } + + func updateFlagStreamAndLastUpdatedAt(_ flags: [Flag]) { + // Update the flag stream + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + flagStreamContinuation?.yield(flags) + } + + // Update the last updated time if the API is giving us newer data + if let apiManagerUpdatedAt = apiManager.lastUpdatedAt, apiManagerUpdatedAt > lastUpdatedAt { + lastUpdatedAt = apiManagerUpdatedAt + } + } } public final class CacheConfig { diff --git a/FlagsmithClient/Classes/FlagsmithError.swift b/FlagsmithClient/Classes/FlagsmithError.swift index 41403e1..0ef68f8 100644 --- a/FlagsmithClient/Classes/FlagsmithError.swift +++ b/FlagsmithClient/Classes/FlagsmithError.swift @@ -23,6 +23,8 @@ public enum FlagsmithError: LocalizedError, Sendable { case unhandled(any Error) /// Invalid argument error case invalidArgument(String) + /// Mutliple starts of the SSE functionality + case sseAlreadyStarted public var errorDescription: String? { switch self { @@ -40,6 +42,8 @@ public enum FlagsmithError: LocalizedError, Sendable { return "An unknown or unhandled error was encountered: \(error.localizedDescription)" case let .invalidArgument(error): return "Invalid argument error: \(error)" + case .sseAlreadyStarted: + return "Attempt to start the SSE session when it's already started" } } diff --git a/FlagsmithClient/Classes/Identity.swift b/FlagsmithClient/Classes/Identity.swift index 5e42bcb..933a1e7 100644 --- a/FlagsmithClient/Classes/Identity.swift +++ b/FlagsmithClient/Classes/Identity.swift @@ -20,5 +20,5 @@ public struct Identity: Decodable, Sendable { public let flags: [Flag] public let traits: [Trait] - public let transient: Bool + public let transient: Bool? } diff --git a/FlagsmithClient/Classes/Internal/APIManager.swift b/FlagsmithClient/Classes/Internal/APIManager.swift index 67589ec..c6d5fbd 100644 --- a/FlagsmithClient/Classes/Internal/APIManager.swift +++ b/FlagsmithClient/Classes/Internal/APIManager.swift @@ -37,7 +37,7 @@ final class APIManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { } } - /// API Key unique to an organization. + /// Environment Key unique to an organization. private var _apiKey: String? var apiKey: String? { get { @@ -50,6 +50,18 @@ final class APIManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { } } + private var _lastUpdatedAt: Double? + var lastUpdatedAt: Double? { + get { + propertiesSerialAccessQueue.sync { _lastUpdatedAt } + } + set { + propertiesSerialAccessQueue.sync { + _lastUpdatedAt = newValue + } + } + } + // store the completion handlers and accumulated data for each task private var tasksToCompletionHandlers: [Int: @Sendable (Result) -> Void] = [:] private var tasksToData: [Int: Data] = [:] @@ -184,4 +196,12 @@ final class APIManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { } } } + + private func updateLastUpdatedFromRequest(_ request: URLRequest) { + // Extract the lastUpdatedAt from the updatedAt header + if let lastUpdatedAt = request.allHTTPHeaderFields?["x-flagsmith-document-updated-at"] { + print("Last Updated At from header: \(lastUpdatedAt)") + self.lastUpdatedAt = Double(lastUpdatedAt) + } + } } diff --git a/FlagsmithClient/Classes/Internal/ReconnectionDelay.swift b/FlagsmithClient/Classes/Internal/ReconnectionDelay.swift new file mode 100644 index 0000000..ab25e24 --- /dev/null +++ b/FlagsmithClient/Classes/Internal/ReconnectionDelay.swift @@ -0,0 +1,32 @@ +// +// ReconnectionDelay.swift +// FlagsmithClient +// +// Created by Gareth Reese on 18/09/2024. +// + +import Foundation + +class ReconnectionDelay { + private var attempt: Int + private let maxDelay: TimeInterval + private let initialDelay: TimeInterval + private let multiplier: Double + + init(initialDelay: TimeInterval = 1.0, maxDelay: TimeInterval = 60.0, multiplier: Double = 2.0) { + attempt = 0 + self.initialDelay = initialDelay + self.maxDelay = maxDelay + self.multiplier = multiplier + } + + func nextDelay() -> TimeInterval { + let delay = min(initialDelay * pow(multiplier, Double(attempt)), maxDelay) + attempt += 1 + return delay + } + + func reset() { + attempt = 0 + } +} diff --git a/FlagsmithClient/Classes/Internal/Router.swift b/FlagsmithClient/Classes/Internal/Router.swift index eeafcbc..3fd7c85 100644 --- a/FlagsmithClient/Classes/Internal/Router.swift +++ b/FlagsmithClient/Classes/Internal/Router.swift @@ -76,7 +76,7 @@ enum Router: Sendable { /// /// - parameters: /// - baseUrl: The base URL of the api on which to base the request. - /// - apiKey: The organization key to provide in the request headers. + /// - apiKey: The environment key to provide in the request headers. /// - encoder: `JSONEncoder` used to encode the request body. func request(baseUrl: URL, apiKey: String, diff --git a/FlagsmithClient/Classes/Internal/SSEManager.swift b/FlagsmithClient/Classes/Internal/SSEManager.swift new file mode 100644 index 0000000..f5778c2 --- /dev/null +++ b/FlagsmithClient/Classes/Internal/SSEManager.swift @@ -0,0 +1,168 @@ +// +// SSEManager.swift +// FlagsmithClient +// +// Created by Gareth Reese on 13/09/2024. +// + +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +/// SSEManager handles interaction with the Flagsmith SSE real-time API. +/// It manages the connection to the SSE endpoint, processes incoming events, +/// and handles reconnection logic with a backoff strategy. +final class SSEManager: NSObject, URLSessionDataDelegate, @unchecked Sendable { + private var _session: URLSession! + private var session: URLSession { + get { + propertiesSerialAccessQueue.sync { _session } + } + set { + propertiesSerialAccessQueue.sync(flags: .barrier) { + _session = newValue + } + } + } + + private var _dataTask: URLSessionDataTask? + private var dataTask: URLSessionDataTask? { + get { + propertiesSerialAccessQueue.sync { _dataTask } + } + set { + propertiesSerialAccessQueue.sync(flags: .barrier) { + _dataTask = newValue + } + } + } + + /// Base `URL` used for requests. + private var _baseURL = URL(string: "https://realtime.flagsmith.com/")! + var baseURL: URL { + get { + propertiesSerialAccessQueue.sync { _baseURL } + } + set { + propertiesSerialAccessQueue.sync { + _baseURL = newValue + } + } + } + + /// Environment Key unique to an organization. + private var _apiKey: String? + var apiKey: String? { + get { + propertiesSerialAccessQueue.sync { _apiKey } + } + set { + propertiesSerialAccessQueue.sync { + _apiKey = newValue + } + } + } + + var isStarted: Bool { + return completionHandler != nil + } + + private var completionHandler: CompletionHandler? + private let serialAccessQueue = DispatchQueue(label: "sseFlagsmithSerialAccessQueue", qos: .default) + let propertiesSerialAccessQueue = DispatchQueue(label: "ssePropertiesSerialAccessQueue", qos: .default) + private let reconnectionDelay = ReconnectionDelay() + + override init() { + super.init() + let configuration = URLSessionConfiguration.default + session = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main) + } + + // Helper function to process SSE data + internal func processSSEData(_ data: String) { + // Split the data into lines and decode the 'data:' lines from JSON into FlagEvent objects + let lines = data.components(separatedBy: "\n") + for line in lines where line.hasPrefix("data:") { + let json = line.replacingOccurrences(of: "data:", with: "") + if let jsonData = json.data(using: .utf8) { + do { + let flagEvent = try JSONDecoder().decode(FlagEvent.self, from: jsonData) + completionHandler?(.success(flagEvent)) + } catch { + if let error = error as? DecodingError { + completionHandler?(.failure(FlagsmithError.decoding(error))) + } else { + completionHandler?(.failure(FlagsmithError.unhandled(error))) + } + } + } + } + } + + // MARK: URLSessionDelegate + + func urlSession(_: URLSession, dataTask _: URLSessionDataTask, didReceive data: Data) { + serialAccessQueue.sync { + if let message = String(data: data, encoding: .utf8) { + processSSEData(message) + reconnectionDelay.reset() + } + } + } + + func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) { + serialAccessQueue.sync { + if task != dataTask { + return + } + + // If the connection times out or we have no error passed to us it's pretty common, so just reconnect + if let error = error { + if let error = error as? URLError, error.code == .timedOut { + if let completionHandler = completionHandler { + start(completion: completionHandler) + } + } + } else if error == nil { + start(completion: self.completionHandler!) + return + } + + // Otherwise reconnect with increasing delay using the reconnectionTimer so that we don't load the phone / server + serialAccessQueue.asyncAfter(deadline: .now() + reconnectionDelay.nextDelay()) { [weak self] in + if let self { + self.start(completion: self.completionHandler!) + } + } + } + } + + // MARK: Public Methods + + func start(completion: @escaping CompletionHandler) { + guard let apiKey = apiKey, !apiKey.isEmpty else { + completion(.failure(FlagsmithError.apiKey)) + return + } + + guard let completeEventSourceUrl = URL(string: "\(baseURL.absoluteString)sse/environments/\(apiKey)/stream") else { + completion(.failure(FlagsmithError.apiURL("Invalid event source URL"))) + return + } + + var request = URLRequest(url: completeEventSourceUrl) + request.setValue("text/event-stream, application/json; charset=utf-8", forHTTPHeaderField: "Accept") + request.setValue("no-cache", forHTTPHeaderField: "Cache-Control") + request.setValue("keep-alive", forHTTPHeaderField: "Connection") + + completionHandler = completion + dataTask = session.dataTask(with: request) + dataTask?.resume() + } + + func stop() { + dataTask?.cancel() + completionHandler = nil + } +} diff --git a/FlagsmithClient/Classes/Trait.swift b/FlagsmithClient/Classes/Trait.swift index 0ea78c1..d85a1de 100644 --- a/FlagsmithClient/Classes/Trait.swift +++ b/FlagsmithClient/Classes/Trait.swift @@ -26,7 +26,7 @@ public struct Trait: Codable, Sendable { /// - note: In the future, this can be renamed back to 'value' as major/feature-breaking /// updates are released. public var typedValue: TypedValue - public let transient: Bool + public let transient: Bool? /// The identity of the `Trait` when creating. internal let identifier: String? diff --git a/FlagsmithClient/Classes/Traits.swift b/FlagsmithClient/Classes/Traits.swift index 3103cc1..29667c9 100644 --- a/FlagsmithClient/Classes/Traits.swift +++ b/FlagsmithClient/Classes/Traits.swift @@ -14,7 +14,7 @@ public struct Traits: Codable, Sendable { public let traits: [Trait] public let identifier: String? public let flags: [Flag] - public let transient: Bool + public let transient: Bool? init(traits: [Trait], identifier: String?, flags: [Flag] = [], transient: Bool = false) { self.traits = traits @@ -30,3 +30,4 @@ public struct Traits: Codable, Sendable { try container.encode(transient, forKey: .transient) } } + diff --git a/FlagsmithClient/Tests/ReconnectionDelayTests.swift b/FlagsmithClient/Tests/ReconnectionDelayTests.swift new file mode 100644 index 0000000..715cf34 --- /dev/null +++ b/FlagsmithClient/Tests/ReconnectionDelayTests.swift @@ -0,0 +1,37 @@ +@testable import FlagsmithClient +import XCTest + +class ReconnectionDelayTests: XCTestCase { + func testInitialDelay() { + let reconnectionDelay = ReconnectionDelay(initialDelay: 1.0, maxDelay: 16.0, multiplier: 2.0) + XCTAssertEqual(reconnectionDelay.nextDelay(), 1.0, "Initial delay should be 1.0 seconds") + } + + func testExponentialBackoff() { + let reconnectionDelay = ReconnectionDelay(initialDelay: 1.0, maxDelay: 16.0, multiplier: 2.0) + XCTAssertEqual(reconnectionDelay.nextDelay(), 1.0, "First delay should be 1.0 seconds") + XCTAssertEqual(reconnectionDelay.nextDelay(), 2.0, "Second delay should be 2.0 seconds") + XCTAssertEqual(reconnectionDelay.nextDelay(), 4.0, "Third delay should be 4.0 seconds") + XCTAssertEqual(reconnectionDelay.nextDelay(), 8.0, "Fourth delay should be 8.0 seconds") + XCTAssertEqual(reconnectionDelay.nextDelay(), 16.0, "Fifth delay should be 16.0 seconds") + XCTAssertEqual(reconnectionDelay.nextDelay(), 16.0, "Subsequent delays should be capped at 16.0 seconds") + } + + func testMaxDelay() { + let reconnectionDelay = ReconnectionDelay(initialDelay: 1.0, maxDelay: 5.0, multiplier: 2.0) + XCTAssertEqual(reconnectionDelay.nextDelay(), 1.0, "First delay should be 1.0 seconds") + XCTAssertEqual(reconnectionDelay.nextDelay(), 2.0, "Second delay should be 2.0 seconds") + XCTAssertEqual(reconnectionDelay.nextDelay(), 4.0, "Third delay should be 4.0 seconds") + XCTAssertEqual(reconnectionDelay.nextDelay(), 5.0, "Fourth delay should be capped at 5.0 seconds") + XCTAssertEqual(reconnectionDelay.nextDelay(), 5.0, "Subsequent delays should be capped at 5.0 seconds") + } + + func testReset() { + let reconnectionDelay = ReconnectionDelay(initialDelay: 1.0, maxDelay: 16.0, multiplier: 2.0) + _ = reconnectionDelay.nextDelay() // 1.0 + _ = reconnectionDelay.nextDelay() // 2.0 + _ = reconnectionDelay.nextDelay() // 4.0 + reconnectionDelay.reset() + XCTAssertEqual(reconnectionDelay.nextDelay(), 1.0, "After reset, delay should be 1.0 seconds") + } +} diff --git a/FlagsmithClient/Tests/SSEManagerTests.swift b/FlagsmithClient/Tests/SSEManagerTests.swift new file mode 100644 index 0000000..bbd5e42 --- /dev/null +++ b/FlagsmithClient/Tests/SSEManagerTests.swift @@ -0,0 +1,118 @@ +@testable import FlagsmithClient +import XCTest + +class SSEManagerTests: FlagsmithClientTestCase { + var sseManager: SSEManager! + + override func setUp() { + super.setUp() + sseManager = SSEManager() + sseManager.apiKey = "seemingly-valid-api-key" + } + + override func tearDown() { + sseManager = nil + super.tearDown() + } + + func testBaseURL() { + let baseURL = URL(string: "https://my.url.com/")! + sseManager.baseURL = baseURL + XCTAssertEqual(sseManager.baseURL, baseURL) + } + + func testAPIKey() { + let apiKey = "testAPIKey" + sseManager.apiKey = apiKey + XCTAssertEqual(sseManager.apiKey, apiKey) + } + + /// Verify that an invalid API key produces the expected error. + func testInvalidAPIKey() throws { + sseManager.apiKey = nil + + let requestFinished = expectation(description: "Request Finished") + + sseManager.start { result in + if case let .failure(err) = result { + let error = err as? FlagsmithError + + guard let flagsmithError = try? XCTUnwrap(error), case .apiKey = flagsmithError else { + XCTFail("Wrong Error") + requestFinished.fulfill() + return + } + } + + requestFinished.fulfill() + } + + wait(for: [requestFinished], timeout: 1.0) + } + + func testValidSSEData() { + let requestFinished = expectation(description: "Request Finished") + + sseManager.start { result in + if case let .failure(err) = result { + XCTFail("Failed during testValidSSEData \(err)") + } + + if case let .success(data) = result { + XCTAssertNotNil(data) + requestFinished.fulfill() + } + } + + sseManager.processSSEData("data: {\"updated_at\": 1689172003.899101}") + + wait(for: [requestFinished], timeout: 1.0) + } + + func testInvalidSSEDataNotANum() { + let requestFinished = expectation(description: "Request Finished") + + sseManager.start { result in + if case let .failure(err) = result { + let error = err as? FlagsmithError + + guard let flagsmithError = try? XCTUnwrap(error), case .decoding = flagsmithError else { + XCTFail("Wrong Error") + return + } + + requestFinished.fulfill() + } + + if case .success = result { + XCTFail("Should not have succeeded") + } + } + + sseManager.processSSEData("data: {\"updated_at\": I-am-not-a-number-I-am-a-free-man}") + + wait(for: [requestFinished], timeout: 1.0) + } + + func testIgnoresNonDataMessages() { + let requestFinished = expectation(description: "Request Finished") + + sseManager.start { result in + if case let .failure(err) = result { + XCTFail("Failed during testValidSSEData \(err)") + } + + if case let .success(data) = result { + XCTAssertNotNil(data) + requestFinished.fulfill() + } + } + + sseManager.processSSEData("If you've got to be told by someone then it's got to be me") + sseManager.processSSEData("And that's not made from cheese and it doesn't get you free") + sseManager.processSSEData("ping: 8374934498.3453445") + sseManager.processSSEData("data: {\"updated_at\": 1689172003.899101}") + + wait(for: [requestFinished], timeout: 1.0) + } +} diff --git a/Package.resolved b/Package.resolved index 92cd757..4713076 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,97 +1,95 @@ { - "object": { - "pins": [ - { - "package": "CollectionConcurrencyKit", - "repositoryURL": "https://github.com/JohnSundell/CollectionConcurrencyKit.git", - "state": { - "branch": null, - "revision": "b4f23e24b5a1bff301efc5e70871083ca029ff95", - "version": "0.2.0" - } - }, - { - "package": "CryptoSwift", - "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", - "state": { - "branch": null, - "revision": "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", - "version": "1.8.2" - } - }, - { - "package": "SourceKitten", - "repositoryURL": "https://github.com/jpsim/SourceKitten.git", - "state": { - "branch": null, - "revision": "b6dc09ee51dfb0c66e042d2328c017483a1a5d56", - "version": "0.34.1" - } - }, - { - "package": "swift-argument-parser", - "repositoryURL": "https://github.com/apple/swift-argument-parser.git", - "state": { - "branch": null, - "revision": "8f4d2753f0e4778c76d5f05ad16c74f707390531", - "version": "1.2.3" - } - }, - { - "package": "swift-syntax", - "repositoryURL": "https://github.com/apple/swift-syntax.git", - "state": { - "branch": null, - "revision": "6ad4ea24b01559dde0773e3d091f1b9e36175036", - "version": "509.0.2" - } - }, - { - "package": "SwiftFormat", - "repositoryURL": "https://github.com/nicklockwood/SwiftFormat", - "state": { - "branch": null, - "revision": "ab238886b8b50f8b678b251f3c28c0c887305407", - "version": "0.53.8" - } - }, - { - "package": "SwiftLint", - "repositoryURL": "https://github.com/realm/SwiftLint.git", - "state": { - "branch": null, - "revision": "f17a4f9dfb6a6afb0408426354e4180daaf49cee", - "version": "0.54.0" - } - }, - { - "package": "SwiftyTextTable", - "repositoryURL": "https://github.com/scottrhoyt/SwiftyTextTable.git", - "state": { - "branch": null, - "revision": "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", - "version": "0.9.0" - } - }, - { - "package": "SWXMLHash", - "repositoryURL": "https://github.com/drmohundro/SWXMLHash.git", - "state": { - "branch": null, - "revision": "a853604c9e9a83ad9954c7e3d2a565273982471f", - "version": "7.0.2" - } - }, - { - "package": "Yams", - "repositoryURL": "https://github.com/jpsim/Yams.git", - "state": { - "branch": null, - "revision": "9234124cff5e22e178988c18d8b95a8ae8007f76", - "version": "5.1.2" - } + "pins" : [ + { + "identity" : "collectionconcurrencykit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git", + "state" : { + "revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95", + "version" : "0.2.0" } - ] - }, - "version": 1 + }, + { + "identity" : "cryptoswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", + "state" : { + "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", + "version" : "1.8.2" + } + }, + { + "identity" : "sourcekitten", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/SourceKitten.git", + "state" : { + "revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56", + "version" : "0.34.1" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", + "version" : "1.2.3" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", + "version" : "509.0.2" + } + }, + { + "identity" : "swiftformat", + "kind" : "remoteSourceControl", + "location" : "https://github.com/nicklockwood/SwiftFormat", + "state" : { + "revision" : "ab6844edb79a7b88dc6320e6cee0a0db7674dac3", + "version" : "0.54.5" + } + }, + { + "identity" : "swiftlint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/realm/SwiftLint.git", + "state" : { + "revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee", + "version" : "0.54.0" + } + }, + { + "identity" : "swiftytexttable", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scottrhoyt/SwiftyTextTable.git", + "state" : { + "revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", + "version" : "0.9.0" + } + }, + { + "identity" : "swxmlhash", + "kind" : "remoteSourceControl", + "location" : "https://github.com/drmohundro/SWXMLHash.git", + "state" : { + "revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f", + "version" : "7.0.2" + } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams.git", + "state" : { + "revision" : "9234124cff5e22e178988c18d8b95a8ae8007f76", + "version" : "5.1.2" + } + } + ], + "version" : 2 }