diff --git a/DuckDuckGo-macOS.xcodeproj/project.pbxproj b/DuckDuckGo-macOS.xcodeproj/project.pbxproj index d5532d5f70..1487800c62 100644 --- a/DuckDuckGo-macOS.xcodeproj/project.pbxproj +++ b/DuckDuckGo-macOS.xcodeproj/project.pbxproj @@ -15717,8 +15717,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 237.1.0; + branch = "mgurgel/duckplayer-custom-error"; + kind = branch; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo-macOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo-macOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b99ee2ce38..45e7442c56 100644 --- a/DuckDuckGo-macOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo-macOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "92f57bfcf15258a360f6df8a48da756491683fe0", - "version" : "237.1.0" + "branch" : "mgurgel/duckplayer-custom-error", + "revision" : "130692f3d84c9defa811b205603f9a7c5c3654d9" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "874b27ad51d1784c934760c85493f78e609c4409", - "version" : "7.18.0" + "branch" : "pr-releases/pr-1421", + "revision" : "371b1a444ac9ebcf553f708f9c759f1f5777fcdc" } }, { diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index 77898e6344..8a5b2489ae 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -136,6 +136,14 @@ enum GeneralPixel: PixelKitEventV2 { case duckPlayerNewTabSettingsOff case duckPlayerContingencySettingsDisplayed case duckPlayerContingencyLearnMoreClicked + case duckPlayerYouTubeSignInErrorImpression + case duckPlayerYouTubeAgeRestrictedErrorImpression + case duckPlayerYouTubeNoEmbedErrorImpression + case duckPlayerYouTubeUnknownErrorImpression + case duckPlayerYouTubeSignInErrorDaily + case duckPlayerYouTubeAgeRestrictedErrorDaily + case duckPlayerYouTubeNoEmbedErrorDaily + case duckPlayerYouTubeUnknownErrorDaily // Temporary Overlay Pixels case duckPlayerYouTubeOverlayNavigationBack @@ -685,6 +693,22 @@ enum GeneralPixel: PixelKitEventV2 { return "duckplayer_mac_contingency_settings-displayed" case .duckPlayerContingencyLearnMoreClicked: return "duckplayer_mac_contingency_learn-more-clicked" + case .duckPlayerYouTubeSignInErrorImpression: + return "duckplayer_mac_youtube-signin-error_impression" + case .duckPlayerYouTubeAgeRestrictedErrorImpression: + return "duckplayer_mac_youtube-age-restricted-error_impression" + case .duckPlayerYouTubeNoEmbedErrorImpression: + return "duckplayer_mac_youtube-no-embed-error_impression" + case .duckPlayerYouTubeUnknownErrorImpression: + return "duckplayer_mac_youtube-unknown-error_impression" + case .duckPlayerYouTubeSignInErrorDaily: + return "duckplayer_mac_youtube-signin-error_daily-unique" + case .duckPlayerYouTubeAgeRestrictedErrorDaily: + return "duckplayer_mac_youtube-age-restricted-error_daily-unique" + case .duckPlayerYouTubeNoEmbedErrorDaily: + return "duckplayer_mac_youtube-no-embed-error_daily-unique" + case .duckPlayerYouTubeUnknownErrorDaily: + return "duckplayer_mac_youtube-unknown-error_daily-unique" // Duck Player Temporary Overlay Pixels case .duckPlayerYouTubeOverlayNavigationBack: @@ -1341,7 +1365,15 @@ enum GeneralPixel: PixelKitEventV2 { .duckPlayerNewTabSettingsOff, .duckPlayerContingencySettingsDisplayed, .duckPlayerWeeklyUniqueView, - .duckPlayerContingencyLearnMoreClicked: + .duckPlayerContingencyLearnMoreClicked, + .duckPlayerYouTubeSignInErrorImpression, + .duckPlayerYouTubeAgeRestrictedErrorImpression, + .duckPlayerYouTubeNoEmbedErrorImpression, + .duckPlayerYouTubeUnknownErrorImpression, + .duckPlayerYouTubeSignInErrorDaily, + .duckPlayerYouTubeAgeRestrictedErrorDaily, + .duckPlayerYouTubeNoEmbedErrorDaily, + .duckPlayerYouTubeUnknownErrorDaily: return nil case .bookmarksSortButtonClicked(let origin), diff --git a/DuckDuckGo/YoutubePlayer/DuckPlayer.swift b/DuckDuckGo/YoutubePlayer/DuckPlayer.swift index e9c9896f1b..7c90102ee9 100644 --- a/DuckDuckGo/YoutubePlayer/DuckPlayer.swift +++ b/DuckDuckGo/YoutubePlayer/DuckPlayer.swift @@ -59,6 +59,7 @@ struct InitialPlayerSettings: Codable { let pip: PIP let autoplay: Autoplay let focusMode: FocusMode + let customError: CustomError } struct PIP: Codable { @@ -88,6 +89,11 @@ struct InitialPlayerSettings: Codable { let state: State } + struct CustomError: Codable { + let state: State + let signInRequiredSelector: String + } + enum State: String, Codable { case enabled case disabled @@ -137,6 +143,11 @@ public struct UIUserValues: Codable { } } +// Custom Error privacy config settings +struct CustomErrorSettings: Codable { + let signInRequiredSelector: String +} + final class DuckPlayer { static let usesSimulatedRequests: Bool = { if #available(macOS 12.0, *) { @@ -178,6 +189,16 @@ final class DuckPlayer { isFeatureEnabled = privacyConfigurationManager.privacyConfig.isEnabled(featureKey: .duckPlayer) isPiPFeatureEnabled = privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(DuckPlayerSubfeature.pip) isAutoplayFeatureEnabled = privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(DuckPlayerSubfeature.autoplay) + isCustomErrorFeatureEnabled = + privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(DuckPlayerSubfeature.customError) + + // TODO: Check if this logic can be moved into privacyConfig + if let customErrorSettingsJSON = privacyConfigurationManager.privacyConfig.settings(for: DuckPlayerSubfeature.customError), + let jsonData = customErrorSettingsJSON.data(using: .utf8), + let customErrorSettings: CustomErrorSettings = DecodableHelper.decode(jsonData: jsonData) { + customErrorSignInRequiredSelector = customErrorSettings.signInRequiredSelector + } + self.onboardingDecider = onboardingDecider mode = preferences.duckPlayerMode @@ -247,6 +268,13 @@ final class DuckPlayer { } } + public func handleYoutubeError(params: Any, message: UserScriptMessage) -> Encodable? { + let (volumePixel, dailyPixel) = getPixelsForYouTubeErrorParams(params) + PixelKit.fire(NonStandardEvent(dailyPixel), frequency: .daily) + PixelKit.fire(NonStandardEvent(volumePixel)) + return nil + } + public func handleGetUserValues(params: Any, message: UserScriptMessage) -> Encodable? { encodeUserValues() } @@ -294,7 +322,8 @@ final class DuckPlayer { let environment = InitialPlayerSettings.Environment.development let locale = InitialPlayerSettings.Locale.en let focusMode = InitialPlayerSettings.FocusMode(state: onboardingDecider.shouldOpenFirstVideoOnDuckPlayer ? .disabled : .enabled) - let playerSettings = InitialPlayerSettings.PlayerSettings(pip: pip, autoplay: autoplay, focusMode: focusMode) + let customError = InitialPlayerSettings.CustomError(state: isCustomErrorFeatureEnabled ? .enabled : .disabled, signInRequiredSelector: customErrorSignInRequiredSelector ?? "") + let playerSettings = InitialPlayerSettings.PlayerSettings(pip: pip, autoplay: autoplay, focusMode: focusMode, customError: customError) let userValues = encodeUserValues() /// Since the FE is requesting player-encoded values, we can assume that the first player video setup is complete from the onboarding point of view. @@ -339,6 +368,8 @@ final class DuckPlayer { private var isFeatureEnabledCancellable: AnyCancellable? private var isPiPFeatureEnabled: Bool private var isAutoplayFeatureEnabled: Bool + private var isCustomErrorFeatureEnabled: Bool + private var customErrorSignInRequiredSelector: String? private let onboardingDecider: DuckPlayerOnboardingDecider private var shouldOpenNextVideoOnYoutube: Bool = false @@ -353,6 +384,23 @@ final class DuckPlayer { modeCancellable = nil } } + + private func getPixelsForYouTubeErrorParams(_ params: Any) -> (GeneralPixel, GeneralPixel) { + if let paramsDict = params as? [String: Any], + let errorParam = paramsDict["error"] as? String{ + switch errorParam { + case "sign-in-required": + return (GeneralPixel.duckPlayerYouTubeSignInErrorImpression, GeneralPixel.duckPlayerYouTubeSignInErrorDaily) + case "age-restricted": + return (GeneralPixel.duckPlayerYouTubeAgeRestrictedErrorImpression, GeneralPixel.duckPlayerYouTubeAgeRestrictedErrorDaily) + case "no-embed": + return (GeneralPixel.duckPlayerYouTubeNoEmbedErrorImpression, GeneralPixel.duckPlayerYouTubeNoEmbedErrorDaily) + default: + return (GeneralPixel.duckPlayerYouTubeUnknownErrorImpression, GeneralPixel.duckPlayerYouTubeUnknownErrorDaily) + } + } + return (GeneralPixel.duckPlayerYouTubeUnknownErrorImpression, GeneralPixel.duckPlayerYouTubeUnknownErrorDaily) + } } // MARK: - Privacy Feed diff --git a/DuckDuckGo/YoutubePlayer/YoutubePlayerUserScript.swift b/DuckDuckGo/YoutubePlayer/YoutubePlayerUserScript.swift index 64ebf4c222..05248c2f8c 100644 --- a/DuckDuckGo/YoutubePlayer/YoutubePlayerUserScript.swift +++ b/DuckDuckGo/YoutubePlayer/YoutubePlayerUserScript.swift @@ -43,6 +43,7 @@ final class YoutubePlayerUserScript: NSObject, Subfeature { case setUserValues case getUserValues case initialSetup + case reportYouTubeError } func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { @@ -56,6 +57,8 @@ final class YoutubePlayerUserScript: NSObject, Subfeature { return DuckPlayer.shared.handleSetUserValuesMessage(from: .duckPlayer) case .initialSetup: return DuckPlayer.shared.initialPlayerSetup(with: webView) + case .reportYouTubeError: + return DuckPlayer.shared.handleYoutubeError default: assertionFailure("YoutubePlayerUserScript: Failed to parse User Script message: \(methodName)") return nil