From 0fedd1964fef9e3145526dfb9d13a079f588fbfb Mon Sep 17 00:00:00 2001 From: Todd Anderson <127344469+tanderson-ld@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:12:15 -0500 Subject: [PATCH] prepare 9.2.1 release (#319) ## [9.2.1] - 2023-10-31 ### Changed: - Calling `identify()` with the current context is now more efficient and no longer results in re-establishing a connection. ### Fixed: - Fixed issue where flag change listeners were not being triggered when `identify()` was called. --------- Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: torchhound <5600929+torchhound@users.noreply.github.com> Co-authored-by: Gavin Whelan Co-authored-by: Louis Chan Co-authored-by: Matthew Keeler Co-authored-by: Louis Chan <91093020+louis-launchdarkly@users.noreply.github.com> Co-authored-by: Ember Stevens Co-authored-by: Ember Stevens <79482775+ember-stevens@users.noreply.github.com> Co-authored-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Co-authored-by: ld-repository-standards[bot] <113625520+ld-repository-standards[bot]@users.noreply.github.com> Co-authored-by: Kane Parkinson <93555788+kparkinson-ld@users.noreply.github.com> --- CHANGELOG.md | 14 +++++++ LaunchDarkly.podspec | 2 +- LaunchDarkly.xcodeproj/project.pbxproj | 36 ++++++++-------- LaunchDarkly/LaunchDarkly/LDClient.swift | 8 ++++ .../Models/Context/Reference.swift | 8 +++- .../ReportingConsts.swift | 2 +- .../LaunchDarklyTests/LDClientSpec.swift | 20 +++++++++ .../Models/Context/LDContextSpec.swift | 41 +++++++++++++++++++ .../Models/Context/ReferenceSpec.swift | 18 ++++++++ 9 files changed, 128 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9083b60d..84720f22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to the LaunchDarkly iOS SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [9.2.1] - 2023-10-31 +### Changed: +- Calling `identify()` with the current context is now more efficient and no longer results in re-establishing a connection. + +### Fixed: +- Fixed issue where flag change listeners were not being triggered when `identify()` was called. + ## [9.2.0] - 2023-10-24 ### Changed: - Updated swift-tools-version from 5.2 to 5.3. @@ -35,6 +42,13 @@ All notable changes to the LaunchDarkly iOS SDK will be documented in this file. ### Removed - Removed LDUser and related functionality. Use LDContext instead. To learn more, read https://docs.launchdarkly.com/home/contexts. +## [8.3.1] - 2023-10-31 +### Changed: +- Calling `identify()` with the current context is now more efficient and no longer results in re-establishing a connection. + +### Fixed: +- Fixed issue where flag change listeners were not being triggered when `identify()` was called. + ## [8.3.0] - 2023-09-08 ### Changed: - Deprecated `LDValue.init(integerLiteral: Double)` as this method signature is misleading. A new `LDValue.init(integerLiteral: Int)` signature has been added for clarity. diff --git a/LaunchDarkly.podspec b/LaunchDarkly.podspec index e82de522..4ca4b25e 100644 --- a/LaunchDarkly.podspec +++ b/LaunchDarkly.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |ld| ld.name = "LaunchDarkly" - ld.version = "9.2.0" + ld.version = "9.2.1" ld.summary = "iOS SDK for LaunchDarkly" ld.description = <<-DESC diff --git a/LaunchDarkly.xcodeproj/project.pbxproj b/LaunchDarkly.xcodeproj/project.pbxproj index 3bf3d8e1..cb640703 100644 --- a/LaunchDarkly.xcodeproj/project.pbxproj +++ b/LaunchDarkly.xcodeproj/project.pbxproj @@ -1560,12 +1560,12 @@ CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = NO; DYLIB_COMPATIBILITY_VERSION = 9.2.0; - DYLIB_CURRENT_VERSION = 9.2.0; + DYLIB_CURRENT_VERSION = 9.2.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(PROJECT_DIR)/LaunchDarkly/LaunchDarkly/Support/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - MARKETING_VERSION = 9.2.0; + MARKETING_VERSION = 9.2.1; MODULEMAP_FILE = ""; PRODUCT_BUNDLE_IDENTIFIER = "com.launchdarkly.Darkly-tvOS"; PRODUCT_NAME = LaunchDarkly_tvOS; @@ -1586,12 +1586,12 @@ CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = NO; DYLIB_COMPATIBILITY_VERSION = 9.2.0; - DYLIB_CURRENT_VERSION = 9.2.0; + DYLIB_CURRENT_VERSION = 9.2.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(PROJECT_DIR)/LaunchDarkly/LaunchDarkly/Support/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - MARKETING_VERSION = 9.2.0; + MARKETING_VERSION = 9.2.1; MODULEMAP_FILE = ""; PRODUCT_BUNDLE_IDENTIFIER = "com.launchdarkly.Darkly-tvOS"; PRODUCT_NAME = LaunchDarkly_tvOS; @@ -1612,12 +1612,12 @@ COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = NO; DYLIB_COMPATIBILITY_VERSION = 9.2.0; - DYLIB_CURRENT_VERSION = 9.2.0; + DYLIB_CURRENT_VERSION = 9.2.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(PROJECT_DIR)/LaunchDarkly/LaunchDarkly/Support/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - MARKETING_VERSION = 9.2.0; + MARKETING_VERSION = 9.2.1; PRODUCT_BUNDLE_IDENTIFIER = "com.launchdarkly.Darkly-macOS"; PRODUCT_NAME = LaunchDarkly_macOS; SDKROOT = macosx; @@ -1635,12 +1635,12 @@ COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = NO; DYLIB_COMPATIBILITY_VERSION = 9.2.0; - DYLIB_CURRENT_VERSION = 9.2.0; + DYLIB_CURRENT_VERSION = 9.2.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(PROJECT_DIR)/LaunchDarkly/LaunchDarkly/Support/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - MARKETING_VERSION = 9.2.0; + MARKETING_VERSION = 9.2.1; PRODUCT_BUNDLE_IDENTIFIER = "com.launchdarkly.Darkly-macOS"; PRODUCT_NAME = LaunchDarkly_macOS; SDKROOT = macosx; @@ -1684,7 +1684,7 @@ CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_COMPATIBILITY_VERSION = 9.2.0; - DYLIB_CURRENT_VERSION = 9.2.0; + DYLIB_CURRENT_VERSION = 9.2.1; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_VERSION = F; @@ -1755,7 +1755,7 @@ CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_COMPATIBILITY_VERSION = 9.2.0; - DYLIB_CURRENT_VERSION = 9.2.0; + DYLIB_CURRENT_VERSION = 9.2.1; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_VERSION = F; @@ -1791,13 +1791,13 @@ CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = NO; DYLIB_COMPATIBILITY_VERSION = 9.2.0; - DYLIB_CURRENT_VERSION = 9.2.0; + DYLIB_CURRENT_VERSION = 9.2.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(PROJECT_DIR)/LaunchDarkly/LaunchDarkly/Support/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; - MARKETING_VERSION = 9.2.0; + MARKETING_VERSION = 9.2.1; MODULEMAP_FILE = "$(PROJECT_DIR)/Framework/module.modulemap"; PRODUCT_BUNDLE_IDENTIFIER = com.launchdarkly.Darkly; PRODUCT_NAME = LaunchDarkly; @@ -1814,13 +1814,13 @@ CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = NO; DYLIB_COMPATIBILITY_VERSION = 9.2.0; - DYLIB_CURRENT_VERSION = 9.2.0; + DYLIB_CURRENT_VERSION = 9.2.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(PROJECT_DIR)/LaunchDarkly/LaunchDarkly/Support/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; - MARKETING_VERSION = 9.2.0; + MARKETING_VERSION = 9.2.1; MODULEMAP_FILE = "$(PROJECT_DIR)/Framework/module.modulemap"; PRODUCT_BUNDLE_IDENTIFIER = com.launchdarkly.Darkly; PRODUCT_NAME = LaunchDarkly; @@ -1859,12 +1859,12 @@ CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = NO; DYLIB_COMPATIBILITY_VERSION = 9.2.0; - DYLIB_CURRENT_VERSION = 9.2.0; + DYLIB_CURRENT_VERSION = 9.2.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(PROJECT_DIR)/LaunchDarkly/LaunchDarkly/Support/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - MARKETING_VERSION = 9.2.0; + MARKETING_VERSION = 9.2.1; PRODUCT_BUNDLE_IDENTIFIER = "com.launchdarkly.Darkly-watchOS"; PRODUCT_NAME = LaunchDarkly_watchOS; SDKROOT = watchos; @@ -1883,12 +1883,12 @@ CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = NO; DYLIB_COMPATIBILITY_VERSION = 9.2.0; - DYLIB_CURRENT_VERSION = 9.2.0; + DYLIB_CURRENT_VERSION = 9.2.1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(PROJECT_DIR)/LaunchDarkly/LaunchDarkly/Support/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - MARKETING_VERSION = 9.2.0; + MARKETING_VERSION = 9.2.1; PRODUCT_BUNDLE_IDENTIFIER = "com.launchdarkly.Darkly-watchOS"; PRODUCT_NAME = LaunchDarkly_watchOS; SDKROOT = watchos; diff --git a/LaunchDarkly/LaunchDarkly/LDClient.swift b/LaunchDarkly/LaunchDarkly/LDClient.swift index 9f4c2dcf..60b0abf9 100644 --- a/LaunchDarkly/LaunchDarkly/LDClient.swift +++ b/LaunchDarkly/LaunchDarkly/LDClient.swift @@ -293,13 +293,21 @@ public class LDClient { } internalIdentifyQueue.sync { + if self.context == updatedContext { + self.eventReporter.record(IdentifyEvent(context: self.context)) + completion?() + return + } + self.context = updatedContext Log.debug(self.typeName(and: #function) + "new context set with key: " + self.context.fullyQualifiedKey() ) let wasOnline = self.isOnline self.internalSetOnline(false) let cachedContextFlags = self.flagCache.retrieveFeatureFlags(contextKey: self.context.fullyQualifiedHashedKey()) ?? [:] + let oldItems = flagStore.storedItems.featureFlags flagStore.replaceStore(newStoredItems: cachedContextFlags) + flagChangeNotifier.notifyObservers(oldFlags: oldItems, newFlags: flagStore.storedItems.featureFlags) self.service.context = self.context self.service.clearFlagResponseCache() flagSynchronizer = serviceFactory.makeFlagSynchronizer(streamingMode: ConnectionInformation.effectiveStreamingMode(config: config, ldClient: self), diff --git a/LaunchDarkly/LaunchDarkly/Models/Context/Reference.swift b/LaunchDarkly/LaunchDarkly/Models/Context/Reference.swift index 7ab095a4..9c71efe8 100644 --- a/LaunchDarkly/LaunchDarkly/Models/Context/Reference.swift +++ b/LaunchDarkly/LaunchDarkly/Models/Context/Reference.swift @@ -87,7 +87,7 @@ extension ReferenceError: CustomStringConvertible { /// - Reference("name") or Reference("/name") would refer to the value "xyz" /// - Reference("/address/street") would refer to the value "99 Main St." /// - Reference("a/b") or Reference("/a~1b") would refer to the value "ok" -public struct Reference: Codable, Equatable, Hashable { +public struct Reference: Codable, Hashable { private var error: ReferenceError? private var rawPath: String private var components: [String] = [] @@ -230,3 +230,9 @@ public struct Reference: Codable, Equatable, Hashable { return self.components[index] } } + +extension Reference: Equatable { + public static func == (lhs: Reference, rhs: Reference) -> Bool { + return lhs.error == rhs.error && lhs.components == rhs.components + } +} diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/EnvironmentReporting/ReportingConsts.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/EnvironmentReporting/ReportingConsts.swift index 0a4bff8a..a1096e42 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/EnvironmentReporting/ReportingConsts.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/EnvironmentReporting/ReportingConsts.swift @@ -1,6 +1,6 @@ import Foundation struct ReportingConsts { - static let sdkVersion = "9.2.0" + static let sdkVersion = "9.2.1" static let sdkName = "ios-client-sdk" } diff --git a/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift b/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift index 7a7497ee..94a02327 100644 --- a/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift @@ -612,6 +612,7 @@ final class LDClientSpec: QuickSpec { expect(testContext.flagStoreMock.replaceStoreCallCount) == 1 expect(testContext.flagStoreMock.replaceStoreReceivedNewFlags) == stubFlags } + it("when we have opted into auto environment attributes") { let testContext = TestContext(startOnline: true) testContext.config.autoEnvAttributes = true @@ -628,6 +629,25 @@ final class LDClientSpec: QuickSpec { expect(kinds.contains(AutoEnvContextModifier.ldDeviceKind)) == true expect(kinds.contains(AutoEnvContextModifier.ldApplicationKind)) == true } + + it("only triggered if context is different") { + let testContext = TestContext(startOnline: true) + testContext.start() + testContext.featureFlagCachingMock.reset() + + testContext.subject.internalIdentify(newContext: testContext.context) + testContext.subject.internalIdentify(newContext: testContext.context) + testContext.subject.internalIdentify(newContext: testContext.context) + testContext.subject.internalIdentify(newContext: testContext.context) + + expect(testContext.flagStoreMock.replaceStoreCallCount) == 0 + expect(testContext.makeFlagSynchronizerService?.context) == testContext.context + + expect(testContext.subject.isOnline) == true + expect(testContext.subject.eventReporter.isOnline) == true + expect(testContext.subject.flagSynchronizer.isOnline) == true + expect(testContext.eventReporterMock.recordReceivedEvent?.kind == .identify).to(beTrue()) + } } } diff --git a/LaunchDarkly/LaunchDarklyTests/Models/Context/LDContextSpec.swift b/LaunchDarkly/LaunchDarklyTests/Models/Context/LDContextSpec.swift index bd9a9b8d..7486f47b 100644 --- a/LaunchDarkly/LaunchDarklyTests/Models/Context/LDContextSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Models/Context/LDContextSpec.swift @@ -4,6 +4,47 @@ import XCTest @testable import LaunchDarkly final class LDContextSpec: XCTestCase { + func testContextsAreEquatable() throws { + var originalBuilder = LDContextBuilder(key: "context-key") + originalBuilder.kind("user") + originalBuilder.name("Example name") + originalBuilder.trySetValue("groups", LDValue.array(["test", "it", "here"])) + originalBuilder.trySetValue("address", LDValue.object(["address": "123 Easy St", "city": "Every Town"])) + originalBuilder.addPrivateAttribute(Reference(literal: "name")) + + var duplicateBuilder = LDContextBuilder(key: "context-key") + duplicateBuilder.kind("user") + duplicateBuilder.name("Example name") + duplicateBuilder.trySetValue("groups", LDValue.array(["test", "it", "here"])) + duplicateBuilder.trySetValue("address", LDValue.object(["address": "123 Easy St", "city": "Every Town"])) + duplicateBuilder.addPrivateAttribute(Reference("/name")) + + let original = try originalBuilder.build().get() + let duplicate = try duplicateBuilder.build().get() + + XCTAssertEqual(original, duplicate) + } + + func testContextsAreNotTheSame() throws { + let values: [String: LDValue] = [ + "kind": "org", + "name": "Example name", + "groups": "This is a string", + "address": LDValue.array(["wrong type again"]) + ] + + for (name, value) in values { + var originalBuilder = LDContextBuilder(key: "context-key") + var slightlyDifferent = LDContextBuilder(key: "context-key") + slightlyDifferent.trySetValue(name, value) + + let original = try originalBuilder.build().get() + let duplicate = try slightlyDifferent.build().get() + + XCTAssertNotEqual(original, duplicate) + } + } + func testBuildCanCreateSimpleContext() throws { var builder = LDContextBuilder(key: "context-key") builder.name("Name") diff --git a/LaunchDarkly/LaunchDarklyTests/Models/Context/ReferenceSpec.swift b/LaunchDarkly/LaunchDarklyTests/Models/Context/ReferenceSpec.swift index 0cded180..36fc1634 100644 --- a/LaunchDarkly/LaunchDarklyTests/Models/Context/ReferenceSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Models/Context/ReferenceSpec.swift @@ -4,6 +4,24 @@ import XCTest @testable import LaunchDarkly final class ReferenceSpec: XCTestCase { + func testVerifyEquality() { + let tests: [(Reference, Reference, Bool)] = [ + (Reference("name"), Reference("name"), true), + (Reference("name"), Reference("/name"), true), + (Reference("/first/name"), Reference("/first/name"), true), + (Reference(literal: "/name"), Reference(literal: "/name"), true), + (Reference(literal: "/name"), Reference("/~1name"), true), + (Reference(literal: "~name"), Reference("/~0name"), true), + + (Reference("different"), Reference("values"), false), + (Reference("name/"), Reference("/name"), false), + (Reference("/first/name"), Reference("/first//name"), false) + ] + + for (lhs, rhs, expected) in tests { + XCTAssertEqual(lhs == rhs, expected) + } + } func testFailsWithCorrectError() { let tests: [(String, ReferenceError)] = [ ("", .empty),