Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
branches: [ v11, 'feat/**' ]
paths-ignore:
- '**.md'
workflow_dispatch:

jobs:
lint:
Expand Down Expand Up @@ -131,6 +132,20 @@ jobs:

- uses: ./.github/actions/test-swiftpm

test-swiftpm-windows:
name: Windows - Swift 5.9 - SPM
runs-on: windows-latest

steps:
- uses: actions/checkout@v4
- name: Install Swift
uses: compnerd/gha-setup-swift@main

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:
An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by third-party-action-not-pinned-to-commit-sha.

You can view more details about this finding in the Semgrep AppSec Platform.

with:
branch: swift-5.9-release
tag: 5.9-RELEASE
- name: Run tests
run: swift test

contract-tests:
runs-on: macos-15

Expand Down
26 changes: 26 additions & 0 deletions LaunchDarkly.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,12 @@
C443A41023186A4F00145710 /* ConnectionModeChangeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C443A40E23186A4F00145710 /* ConnectionModeChangeObserver.swift */; };
C443A41123186A4F00145710 /* ConnectionModeChangeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C443A40E23186A4F00145710 /* ConnectionModeChangeObserver.swift */; };
C443A41223186A4F00145710 /* ConnectionModeChangeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C443A40E23186A4F00145710 /* ConnectionModeChangeObserver.swift */; };
BC0000000000000000000101 /* WindowsEnvironmentReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0000000000000000000100 /* WindowsEnvironmentReporter.swift */; };
BC0000000000000000000102 /* WindowsEnvironmentReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0000000000000000000100 /* WindowsEnvironmentReporter.swift */; };
BC0000000000000000000103 /* WindowsEnvironmentReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0000000000000000000100 /* WindowsEnvironmentReporter.swift */; };
BC0000000000000000000104 /* WindowsEnvironmentReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0000000000000000000100 /* WindowsEnvironmentReporter.swift */; };
BC0000000000000000000201 /* WindowsEnvironmentReporterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0000000000000000000200 /* WindowsEnvironmentReporterSpec.swift */; };
BC0000000000000000000302 /* Match.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0000000000000000000301 /* Match.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -582,6 +588,9 @@
C43C37E0236BA050003C1624 /* LDEvaluationDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDEvaluationDetail.swift; sourceTree = "<group>"; };
C443A4092315AA4D00145710 /* NetworkReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkReporter.swift; sourceTree = "<group>"; };
C443A40E23186A4F00145710 /* ConnectionModeChangeObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionModeChangeObserver.swift; sourceTree = "<group>"; };
BC0000000000000000000100 /* WindowsEnvironmentReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowsEnvironmentReporter.swift; sourceTree = "<group>"; };
BC0000000000000000000200 /* WindowsEnvironmentReporterSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowsEnvironmentReporterSpec.swift; sourceTree = "<group>"; };
BC0000000000000000000301 /* Match.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Match.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -782,6 +791,7 @@
B4265EB024E7390C001CFD2C /* TestUtil.swift */,
A3FFE1122B7D4BA2009EF93F /* LDValueDecoderSpec.swift */,
A3BA7D032BD2BD620000DB28 /* TestContext.swift */,
BC0000000000000000000300 /* Matcher */,
);
name = LaunchDarklyTests;
path = LaunchDarkly/LaunchDarklyTests;
Expand Down Expand Up @@ -949,6 +959,7 @@
isa = PBXGroup;
children = (
A35038842F4F96CA0032BA9F /* WatchOSEnvironmentReporterSpec.swift */,
BC0000000000000000000200 /* WindowsEnvironmentReporterSpec.swift */,
A3047D622A606B6000F568E0 /* ApplicationInfoEnvironmentReporterSpec.swift */,
A3047D5F2A606B6000F568E0 /* EnvironmentReporterChainBaseSpec.swift */,
A3047D5E2A606B6000F568E0 /* IOSEnvironmentReporterSpec.swift */,
Expand Down Expand Up @@ -990,6 +1001,7 @@
A358D6E12A4DE98300270C60 /* MacOSEnvironmentReporter.swift */,
A358D6E62A4DE99B00270C60 /* WatchOSEnvironmentReporter.swift */,
A358D6EB2A4DE9A600270C60 /* TVOSEnvironmentReporter.swift */,
BC0000000000000000000100 /* WindowsEnvironmentReporter.swift */,
A358D6F12A4DEB4C00270C60 /* EnvironmentReporterBuilder.swift */,
A358D6F62A4DF1D500270C60 /* SDKEnvironmentReporter.swift */,
A35AD45F2A619E45005A8DCB /* SystemCapabilities.swift */,
Expand All @@ -1016,6 +1028,14 @@
name = Frameworks;
sourceTree = "<group>";
};
BC0000000000000000000300 /* Matcher */ = {
isa = PBXGroup;
children = (
BC0000000000000000000301 /* Match.swift */,
);
path = Matcher;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand Down Expand Up @@ -1440,6 +1460,7 @@
A3BA7CEC2BD056920000DB28 /* Hook.swift in Sources */,
832D68A5224A38FC005F052A /* CacheConverter.swift in Sources */,
A35AD4632A619E45005A8DCB /* SystemCapabilities.swift in Sources */,
BC0000000000000000000101 /* WindowsEnvironmentReporter.swift in Sources */,
A358D6FA2A4DF1D500270C60 /* SDKEnvironmentReporter.swift in Sources */,
831188432113ADBE00D77CB5 /* LDCommon.swift in Sources */,
A358D6D42A4DD48600270C60 /* EnvironmentReporterChainBase.swift in Sources */,
Expand Down Expand Up @@ -1480,6 +1501,7 @@
3D24061B2E0D90E000F91253 /* SdkMetadata.swift in Sources */,
A310881D2837DC0400184942 /* Kind.swift in Sources */,
A35AD4622A619E45005A8DCB /* SystemCapabilities.swift in Sources */,
BC0000000000000000000102 /* WindowsEnvironmentReporter.swift in Sources */,
C443A40423145FBE00145710 /* ConnectionInformation.swift in Sources */,
832D68A4224A38FC005F052A /* CacheConverter.swift in Sources */,
831EF34C20655E730001C643 /* FlagChangeObserver.swift in Sources */,
Expand Down Expand Up @@ -1603,6 +1625,7 @@
A3BA7CE92BD056920000DB28 /* Hook.swift in Sources */,
832D68A2224A38FC005F052A /* CacheConverter.swift in Sources */,
A35AD4602A619E45005A8DCB /* SystemCapabilities.swift in Sources */,
BC0000000000000000000103 /* WindowsEnvironmentReporter.swift in Sources */,
A358D6F72A4DF1D500270C60 /* SDKEnvironmentReporter.swift in Sources */,
835E1D401F63450A00184DB4 /* ObjcLDConfig.swift in Sources */,
A358D6D12A4DD48600270C60 /* EnvironmentReporterChainBase.swift in Sources */,
Expand All @@ -1629,11 +1652,13 @@
83411A5F1FABDA8700E5CF39 /* mocks.generated.swift in Sources */,
83DDBF001FA2589900E428B6 /* FlagStoreSpec.swift in Sources */,
A35038852F4F96CA0032BA9F /* WatchOSEnvironmentReporterSpec.swift in Sources */,
BC0000000000000000000201 /* WindowsEnvironmentReporterSpec.swift in Sources */,
B4F689142497B2FC004D3CE0 /* DiagnosticEventSpec.swift in Sources */,
83396BC91F7C3711000E256E /* DarklyServiceSpec.swift in Sources */,
3D9A12582A73236800698B8D /* UtilSpec.swift in Sources */,
83EF67931F9945E800403126 /* EventSpec.swift in Sources */,
A3BA7D042BD2BD620000DB28 /* TestContext.swift in Sources */,
BC0000000000000000000302 /* Match.swift in Sources */,
83B6E3F1222EFA3800FF2A6A /* ThreadSpec.swift in Sources */,
831AAE3020A9E75D00B46DBA /* ThrottlerSpec.swift in Sources */,
832D68AC224B3321005F052A /* CacheConverterSpec.swift in Sources */,
Expand Down Expand Up @@ -1743,6 +1768,7 @@
A3BA7CEA2BD056920000DB28 /* Hook.swift in Sources */,
83D9EC952062DEAB004D7FA6 /* Date.swift in Sources */,
A35AD4612A619E45005A8DCB /* SystemCapabilities.swift in Sources */,
BC0000000000000000000104 /* WindowsEnvironmentReporter.swift in Sources */,
A358D6F82A4DF1D500270C60 /* SDKEnvironmentReporter.swift in Sources */,
832D68A3224A38FC005F052A /* CacheConverter.swift in Sources */,
A358D6D22A4DD48600270C60 /* EnvironmentReporterChainBase.swift in Sources */,
Expand Down
23 changes: 17 additions & 6 deletions LaunchDarkly/LaunchDarkly/LDClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -261,14 +261,14 @@ public class LDClient {
os_log("%s stopped", log: config.logger, type: .debug, typeName(and: #function))
}

@objc private func didEnterBackground() {
private func didEnterBackground() {
os_log("%s", log: config.logger, type: .debug, typeName(and: #function))
Thread.performOnMain {
runMode = .background
}
}

@objc private func willEnterForeground() {
private func willEnterForeground() {
os_log("%s", log: config.logger, type: .debug, typeName(and: #function))
Thread.performOnMain {
runMode = .foreground
Expand Down Expand Up @@ -764,7 +764,7 @@ public class LDClient {
}
}

@objc private func didCloseEventSource() {
private func didCloseEventSource() {
os_log("%s", log: config.logger, type: .debug, typeName(and: #function))
self.connectionInformation = ConnectionInformation.lastSuccessfulConnectionCheck(connectionInformation: self.connectionInformation)
}
Expand Down Expand Up @@ -949,6 +949,8 @@ public class LDClient {
private var initializedQueue = DispatchQueue(label: "com.launchdarkly.LDClient.initializedQueue")
private var identifyQueue = SheddingQueue()

private var notificationTokens = [NSObjectProtocol]()

private init(serviceFactory: ClientServiceCreating, configuration: LDConfig, startContext: LDContext?, completion: (() -> Void)? = nil) {
self.serviceFactory = serviceFactory
self.hooks = Array(configuration.hooks)
Expand Down Expand Up @@ -1003,13 +1005,22 @@ public class LDClient {
service: service)

if let backgroundNotification = SystemCapabilities.backgroundNotification {
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: backgroundNotification, object: nil)
let background = NotificationCenter.default.addObserver(forName: backgroundNotification, object:nil, queue: OperationQueue.current, using: { [weak self] notification in
self?.didEnterBackground()
})
notificationTokens.append(background)
}
if let foregroundNotification = SystemCapabilities.foregroundNotification {
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: foregroundNotification, object: nil)
let foreground = NotificationCenter.default.addObserver(forName: foregroundNotification, object: nil, queue: OperationQueue.current, using: { [weak self] notification in
self?.willEnterForeground()
})
notificationTokens.append(foreground)
}

NotificationCenter.default.addObserver(self, selector: #selector(didCloseEventSource), name: Notification.Name(FlagSynchronizer.Constants.didCloseEventSourceName), object: nil)
let didClose = NotificationCenter.default.addObserver(forName: Notification.Name(FlagSynchronizer.Constants.didCloseEventSourceName), object: nil, queue: OperationQueue.current, using: { [weak self] _ in
self?.didCloseEventSource()
})
notificationTokens.append(didClose)

eventReporter = self.serviceFactory.makeEventReporter(config: configuration, service: service, onSyncComplete: onEventSyncComplete)
service.resetFlagResponseCache(etag: cachedData.etag)
Expand Down
11 changes: 11 additions & 0 deletions LaunchDarkly/LaunchDarkly/LDCommon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ extension LDFlagKey {
}

/// An error thrown from APIs when an invalid argument is provided.
#if os(Linux) || os(Windows)
public class LDInvalidArgumentError: Error {
/// A description of the error.
public let localizedDescription: String

init(_ description: String) {
self.localizedDescription = description
}
}
#else
@objc public class LDInvalidArgumentError: NSObject, Error {
/// A description of the error.
public let localizedDescription: String
Expand All @@ -28,6 +38,7 @@ extension LDFlagKey {
self.localizedDescription = description
}
}
#endif

struct DynamicKey: CodingKey {
let intValue: Int? = nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Foundation
import OSLog

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

public struct ConnectionInformation: Codable, CustomStringConvertible {
public enum ConnectionMode: String, Codable {
case streaming, offline, establishingStreamingConnection, polling
Expand Down
11 changes: 10 additions & 1 deletion LaunchDarkly/LaunchDarkly/Models/LDConfig.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Foundation
import OSLog

#if os(Linux) || os(Windows)
import FoundationNetworking
#endif

/// Defines the connection modes the SDK may be configured to use to retrieve feature flag data from LaunchDarkly.
public enum LDStreamingMode {
/**
Expand Down Expand Up @@ -32,7 +36,10 @@ public enum LDStreamingMode {
you can use targeting rules to enable "dark mode" for all customers who are using version 15 or greater, and ensure
that customers on previous versions don't use the earlier, unfinished version of the feature.
*/
@objc public enum AutoEnvAttributes: Int {
#if !os(Linux) && !os(Windows)
@objc
#endif
public enum AutoEnvAttributes: Int {
/// Enables the Auto EnvironmentAttributes functionality.
case enabled
/// Disables the Auto EnvironmentAttributes functionality.
Expand Down Expand Up @@ -439,8 +446,10 @@ public struct LDConfig {
/// LaunchDarkly defined minima for selected configurable items
public let minima: Minima

#if !os(Linux) && !os(Windows)
/// An NSObject wrapper for the Swift LDConfig struct. Intended for use in mixed apps when Swift code needs to pass a config into an Objective-C method.
public var objcLdConfig: ObjcLDConfig { ObjcLDConfig(self) }
#endif

/// Initial set of hooks for the client.
///
Expand Down
18 changes: 15 additions & 3 deletions LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import Foundation
import LDSwiftEventSource
import OSLog

#if os(Linux) || os(Windows)
import class FoundationNetworking.URLResponse
import class FoundationNetworking.HTTPURLResponse
import struct FoundationNetworking.URLRequest

import class FoundationNetworking.URLSessionConfiguration
import AnyURLSession
#endif

// swiftlint:disable:next large_tuple
typealias ServiceResponse = (data: Data?, urlResponse: URLResponse?, error: Error?, etag: String?)
typealias ServiceCompletionHandler = (ServiceResponse) -> Void
Expand Down Expand Up @@ -70,11 +79,13 @@ final class DarklyService: DarklyServiceProvider {
// URLSessionConfiguration is a class, but `.default` creates a new instance. This does not effect other session configuration.
let sessionConfig = URLSessionConfiguration.default

#if !os(Linux) && !os(Windows)
if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) {
sessionConfig.tlsMinimumSupportedProtocolVersion = .TLSv12
} else {
sessionConfig.tlsMinimumSupportedProtocol = .tlsProtocol12
}
#endif

// We always revalidate the cache which we handle manually
sessionConfig.requestCachePolicy = .reloadIgnoringLocalCacheData
Expand Down Expand Up @@ -114,9 +125,10 @@ final class DarklyService: DarklyServiceProvider {
}

self.session.dataTask(with: request) { [weak self] data, response, error in
DispatchQueue.main.async {
self?.processEtag(from: (data: data, urlResponse: response, error: error, etag: self?.flagRequestEtag))
completion?((data: data, urlResponse: response, error: error, etag: self?.flagRequestEtag))
DispatchQueue.main.async { [weak self] in
let etag = self?.flagRequestEtag
self?.processEtag(from: (data: data, urlResponse: response, error: error, etag: etag))
completion?((data: data, urlResponse: response, error: error, etag: etag))
}
}.resume()
}
Expand Down
4 changes: 4 additions & 0 deletions LaunchDarkly/LaunchDarkly/Networking/HTTPURLRequest.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import Foundation

#if os(Linux) || os(Windows)
import FoundationNetworking
#endif

extension URLRequest {
struct HTTPMethods {
static let get = "GET"
Expand Down
4 changes: 4 additions & 0 deletions LaunchDarkly/LaunchDarkly/Networking/HTTPURLResponse.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import Foundation

#if os(Linux) || os(Windows)
import FoundationNetworking
#endif

extension HTTPURLResponse {

struct HeaderKeys {
Expand Down
4 changes: 4 additions & 0 deletions LaunchDarkly/LaunchDarkly/Networking/URLResponse.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import Foundation

#if os(Linux) || os(Windows)
import FoundationNetworking
#endif

extension URLResponse {
var httpStatusCode: Int? { (self as? HTTPURLResponse)?.statusCode }
var httpHeaderEtag: String? { (self as? HTTPURLResponse)?.headerEtag }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ final class ClientServiceFactory: ClientServiceCreating {
config.headerTransform = { delegate?(url, $0) ?? $0 }
config.headers = httpHeaders
config.method = connectMethod
config.logger = self.logger
if let errorHandler = errorHandler {
config.connectionErrorHandler = errorHandler
}
Expand Down
2 changes: 2 additions & 0 deletions LaunchDarkly/LaunchDarkly/ServiceObjects/CwlSysctl.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if canImport(Darwin)
//
// CwlSysctl.swift
// CwlUtils
Expand Down Expand Up @@ -78,3 +79,4 @@ struct Sysctl {
return try! Sysctl.stringForKeys([CTL_HW, HW_MODEL])
}
}
#endif // canImport(Darwin)
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Foundation
import OSLog

#if os(Linux) || os(Windows)
import FoundationNetworking
#endif

// sourcery: autoMockable
protocol DiagnosticReporting {
func setMode(_ runMode: LDClientRunMode, online: Bool)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import UIKit
#endif

enum OperatingSystem: String {
case iOS, watchOS, macOS, tvOS, unknown
case iOS, watchOS, macOS, tvOS, windows, linux, unknown

static var allOperatingSystems: [OperatingSystem] {
[.iOS, .watchOS, .macOS, .tvOS]
[.iOS, .watchOS, .macOS, .tvOS, .windows, .linux]
}

var isBackgroundEnabled: Bool {
Expand All @@ -28,7 +28,7 @@ enum OperatingSystem: String {
OperatingSystem.streamingEnabledOperatingSystems.contains(self)
}
static var streamingEnabledOperatingSystems: [OperatingSystem] {
[.iOS, .macOS, .tvOS]
[.iOS, .macOS, .tvOS, .windows]
}
}

Expand Down
Loading
Loading