diff --git a/CHANGELOG.md b/CHANGELOG.md index 26ea0749..91e5d574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Added `orderedPreplayParameters` to `UplynkSSAIConfiguration` and fixed URL encoding for valid Uplynk signatures. + ## [10.8.0.1] - 2026-01-20 ### Fixed diff --git a/Code/Uplynk/Source/Internal/UplynkSSAIConfiguration+Extensions.swift b/Code/Uplynk/Source/Internal/UplynkSSAIConfiguration+Extensions.swift index fab531df..a962b2a8 100644 --- a/Code/Uplynk/Source/Internal/UplynkSSAIConfiguration+Extensions.swift +++ b/Code/Uplynk/Source/Internal/UplynkSSAIConfiguration+Extensions.swift @@ -14,6 +14,19 @@ extension UplynkSSAIConfiguration { } var urlParameters: String { + if let orderedParams = orderedPreplayParameters, !orderedParams.isEmpty { + // Define strict allowed characters for query values + // We MUST encode '%' (to preserve pre-encoded values), '&', '=', '+', and others that alter URL structure. + var allowed = CharacterSet.urlQueryAllowed + allowed.remove(charactersIn: "&+=?%,") + + let joinedParameters = orderedParams.map { (key, value) in + let encodedValue = value.addingPercentEncoding(withAllowedCharacters: allowed) ?? value + return "\(key)=\(encodedValue)" + }.joined(separator: "&") + return "&\(joinedParameters)" + } + guard !preplayParameters.isEmpty else { return "" } @@ -31,7 +44,7 @@ extension UplynkSSAIConfiguration { var pingParameters: String { let pingFeature = pingFeature if pingFeature == .noPing { - return "&ad.pingc=0" + return "" } else { return "&ad.pingc=1&ad.pingf=\(pingFeature.rawValue)" } diff --git a/Code/Uplynk/Source/UplynkSSAIConfiguration.swift b/Code/Uplynk/Source/UplynkSSAIConfiguration.swift index 62cd2dd6..7a21c797 100644 --- a/Code/Uplynk/Source/UplynkSSAIConfiguration.swift +++ b/Code/Uplynk/Source/UplynkSSAIConfiguration.swift @@ -27,6 +27,7 @@ public class UplynkSSAIConfiguration: CustomServerSideAdInsertionConfiguration { public let id: ID public let prefix: String? public let preplayParameters: [String: String] + public let orderedPreplayParameters: [(String, String)]? public let assetType: AssetType public let contentProtected: Bool public let assetInfo: Bool @@ -38,6 +39,7 @@ public class UplynkSSAIConfiguration: CustomServerSideAdInsertionConfiguration { assetType: AssetType, prefix: String? = nil, preplayParameters: [String: String] = [:], + orderedPreplayParameters: [(String, String)]? = nil, contentProtected: Bool = false, assetInfo: Bool = false, uplynkPingConfiguration: UplynkPingConfiguration = .init(), @@ -47,6 +49,7 @@ public class UplynkSSAIConfiguration: CustomServerSideAdInsertionConfiguration { self.assetType = assetType self.prefix = prefix self.preplayParameters = preplayParameters + self.orderedPreplayParameters = orderedPreplayParameters self.contentProtected = contentProtected self.assetInfo = assetInfo self.pingConfiguration = uplynkPingConfiguration