Skip to content

Commit

Permalink
Add FXIOS-10715 [sponsored tiles] Get unified ads data with a POST re…
Browse files Browse the repository at this point in the history
…quest (#23455)

* Get unified ads data with a POST request

* Fix context id + clean up

* Revert disconnect file change

* Fix words
  • Loading branch information
lmarceau authored Nov 28, 2024
1 parent dbda1ef commit 57009ea
Show file tree
Hide file tree
Showing 6 changed files with 395 additions and 42 deletions.
12 changes: 8 additions & 4 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,6 @@
8A3345682BA499B7008C52AB /* disconnect-block-social.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A33455E2BA499B7008C52AB /* disconnect-block-social.json */; };
8A3345692BA499B7008C52AB /* disconnect-block-cookies-social.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A33455F2BA499B7008C52AB /* disconnect-block-cookies-social.json */; };
8A33456A2BA499B7008C52AB /* disconnect-block-cryptomining.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A3345602BA499B7008C52AB /* disconnect-block-cryptomining.json */; };
8A34DD892CF6B31F00DC91FB /* UnifiedAdsNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34DD882CF6B30F00DC91FB /* UnifiedAdsNetwork.swift */; };
8A359EF32A1FD449004A5BB7 /* AdjustWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A359EF22A1FD449004A5BB7 /* AdjustWrapper.swift */; };
8A359EF62A1FE840004A5BB7 /* MockAdjustWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A359EF52A1FE840004A5BB7 /* MockAdjustWrapper.swift */; };
8A36AC2C2886F27F00CDC0AD /* MockTabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A36AC2B2886F27F00CDC0AD /* MockTabManager.swift */; };
Expand Down Expand Up @@ -811,6 +810,8 @@
8A471185287F6E4800F5A6EA /* SeparatorTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A471184287F6E4800F5A6EA /* SeparatorTableViewCell.swift */; };
8A4AC0EB28C929D700439F83 /* URLSessionDataTaskProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4AC0E928C929D700439F83 /* URLSessionDataTaskProtocol.swift */; };
8A4AC0EC28C929D700439F83 /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4AC0EA28C929D700439F83 /* URLSessionProtocol.swift */; };
8A4B14852CF8D67800FCE2D0 /* UnifiedTile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4B14842CF8D67300FCE2D0 /* UnifiedTile.swift */; };
8A4B14872CF8D81800FCE2D0 /* UnifiedAdsProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4B14862CF8D80F00FCE2D0 /* UnifiedAdsProviderTests.swift */; };
8A4EA0D12C010BE700E4E4F1 /* MicrosurveySurfaceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4EA0D02C010BE700E4E4F1 /* MicrosurveySurfaceManager.swift */; };
8A4EA0D42C01100200E4E4F1 /* MicrosurveySurfaceManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4EA0D22C010BF800E4E4F1 /* MicrosurveySurfaceManagerTests.swift */; };
8A4EA0D92C01127C00E4E4F1 /* MicrosurveyMockModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4EA0D72C01125100E4E4F1 /* MicrosurveyMockModel.swift */; };
Expand Down Expand Up @@ -7358,7 +7359,6 @@
8A33455E2BA499B7008C52AB /* disconnect-block-social.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-social.json"; path = "../../../ContentBlockingLists/disconnect-block-social.json"; sourceTree = "<group>"; };
8A33455F2BA499B7008C52AB /* disconnect-block-cookies-social.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-cookies-social.json"; path = "../../../ContentBlockingLists/disconnect-block-cookies-social.json"; sourceTree = "<group>"; };
8A3345602BA499B7008C52AB /* disconnect-block-cryptomining.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-cryptomining.json"; path = "../../../ContentBlockingLists/disconnect-block-cryptomining.json"; sourceTree = "<group>"; };
8A34DD882CF6B30F00DC91FB /* UnifiedAdsNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedAdsNetwork.swift; sourceTree = "<group>"; };
8A359EF22A1FD449004A5BB7 /* AdjustWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdjustWrapper.swift; sourceTree = "<group>"; };
8A359EF52A1FE840004A5BB7 /* MockAdjustWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAdjustWrapper.swift; sourceTree = "<group>"; };
8A36AC2B2886F27F00CDC0AD /* MockTabManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTabManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7408,6 +7408,8 @@
8A471184287F6E4800F5A6EA /* SeparatorTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorTableViewCell.swift; sourceTree = "<group>"; };
8A4AC0E928C929D700439F83 /* URLSessionDataTaskProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionDataTaskProtocol.swift; sourceTree = "<group>"; };
8A4AC0EA28C929D700439F83 /* URLSessionProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionProtocol.swift; sourceTree = "<group>"; };
8A4B14842CF8D67300FCE2D0 /* UnifiedTile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedTile.swift; sourceTree = "<group>"; };
8A4B14862CF8D80F00FCE2D0 /* UnifiedAdsProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedAdsProviderTests.swift; sourceTree = "<group>"; };
8A4EA0D02C010BE700E4E4F1 /* MicrosurveySurfaceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MicrosurveySurfaceManager.swift; sourceTree = "<group>"; };
8A4EA0D22C010BF800E4E4F1 /* MicrosurveySurfaceManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MicrosurveySurfaceManagerTests.swift; sourceTree = "<group>"; };
8A4EA0D72C01125100E4E4F1 /* MicrosurveyMockModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosurveyMockModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -12006,6 +12008,7 @@
8AE80BAB2891955400BC12EA /* TopSites */ = {
isa = PBXGroup;
children = (
8A4B14862CF8D80F00FCE2D0 /* UnifiedAdsProviderTests.swift */,
8A7A93ED2810ADF2005E7E1B /* ContileProviderTests.swift */,
961577932A39008100391E8D /* SponsoredTileDataUtilityTests.swift */,
8A33221E27DFE318008F809E /* TopSitesDataAdaptorTests.swift */,
Expand Down Expand Up @@ -12047,7 +12050,7 @@
8AE9FD272CF665C7001053EE /* UnifiedAds */ = {
isa = PBXGroup;
children = (
8A34DD882CF6B30F00DC91FB /* UnifiedAdsNetwork.swift */,
8A4B14842CF8D67300FCE2D0 /* UnifiedTile.swift */,
8AE9FD252CF662FF001053EE /* UnifiedAdsProvider.swift */,
);
path = UnifiedAds;
Expand Down Expand Up @@ -16681,6 +16684,7 @@
EBC4869D2195F58300CDA48D /* ErrorPageHelper.swift in Sources */,
C84655E8288739CB00861B4A /* WallpaperCollectionAvailability.swift in Sources */,
8A454D462CB9C83F009436D9 /* TopSitesMiddleware.swift in Sources */,
8A4B14852CF8D67800FCE2D0 /* UnifiedTile.swift in Sources */,
8ABC5AEE284532C900FEA552 /* PocketDiscoverCell.swift in Sources */,
ABE856AD2C75029F00C56F47 /* TrackingProtectionStatusView.swift in Sources */,
C834330026BAD32800ABAAA6 /* EnhancedTrackingProtectionDetailsVM.swift in Sources */,
Expand Down Expand Up @@ -16998,7 +17002,6 @@
0BDDB3462CA6E43A00D501DF /* BookmarksSaver.swift in Sources */,
EBB89506219398E500EB91A0 /* TabContentBlocker.swift in Sources */,
E1B9A2C22CAD91EF00F6A0E9 /* ToolbarTelemetry.swift in Sources */,
8A34DD892CF6B31F00DC91FB /* UnifiedAdsNetwork.swift in Sources */,
21F2A2D22B0BC85200626AEC /* InactiveTabsModel.swift in Sources */,
966206CD2698DE1E005C0A55 /* BookmarksViewModel.swift in Sources */,
D3E8EF101B97BE69001900FB /* ClearPrivateDataTableViewController.swift in Sources */,
Expand Down Expand Up @@ -17293,6 +17296,7 @@
8A87B4322CC1A3C0003A9239 /* PocketManagerTests.swift in Sources */,
2F13E79B1AC0C02700D75081 /* StringExtensionsTests.swift in Sources */,
CA24B52224ABD7D40093848C /* PasswordManagerViewModelTests.swift in Sources */,
8A4B14872CF8D81800FCE2D0 /* UnifiedAdsProviderTests.swift in Sources */,
E169C6E82979CA0E0017B8D7 /* URLMailTests.swift in Sources */,
8A7A26E129D4785900EA76F1 /* MockRouter.swift in Sources */,
965C3C96293431FC006499ED /* MockLaunchSessionProvider.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ContileProvider: ContileProviderInterface, URLCaching, FeatureFlaggable {
urlCache: URLCache = URLCache.shared,
logger: Logger = DefaultLogger.shared
) {
self.logger = logger
self.logger = logger
self.networking = networking
self.urlCache = urlCache
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,38 @@ import Common
import Foundation
import Shared

typealias UnifiedTileResult = Swift.Result<[UnifiedTile], Error>

/// Used only for sponsored tiles content and telemetry. This is aiming to be a temporary API
/// as we'll migrate to using A-S for this at some point in 2025
protocol UnifiedAdsProviderInterface {
func fetchTiles(completion: @escaping (ContileResult) -> Void)
/// Fetch tiles either from cache or backend
/// - Parameters:
/// - timestamp: The timestamp to retrieve from cache, useful for tests. Default is Date.now()
/// - completion: Returns an array of Tiles, can be empty
func fetchTiles(timestamp: Timestamp, completion: @escaping (UnifiedTileResult) -> Void)
}

extension UnifiedAdsProviderInterface {
func fetchTiles(timestamp: Timestamp = Date.now(), completion: @escaping (UnifiedTileResult) -> Void) {
fetchTiles(timestamp: timestamp, completion: completion)
}
}

class UnifiedAdsProvider: UnifiedAdsProviderInterface {
private static let resourceEndpoint = "https://ads.mozilla.org/v1/ads"
class UnifiedAdsProvider: URLCaching, UnifiedAdsProviderInterface, FeatureFlaggable {
private static let prodResourceEndpoint = "https://ads.mozilla.org/v1/ads"
static let stagingResourceEndpoint = "https://ads.allizom.org/v1/ads"

var urlCache: URLCache
private var logger: Logger
private var networking: UnifiedAdsNetwork
private var networking: ContileNetworking

enum Error: Swift.Error {
case noDataAvailable
}

init(
networking: UnifiedAdsNetwork = DefaultUnifiedAdsNetwork(
networking: ContileNetworking = DefaultContileNetwork(
with: makeURLSession(userAgent: UserAgent.mobileUserAgent(),
configuration: URLSessionConfiguration.defaultMPTCP)),
urlCache: URLCache = URLCache.shared,
Expand All @@ -31,7 +48,107 @@ class UnifiedAdsProvider: UnifiedAdsProviderInterface {
self.urlCache = urlCache
}

func fetchTiles(completion: @escaping (ContileResult) -> Void) {
// TODO: FXIOS-10715
private struct AdPlacement: Codable {
let placement: String
let count: Int
}

private struct RequestBody: Codable {
let context_id: String
let placements: [AdPlacement]
}

func fetchTiles(timestamp: Timestamp = Date.now(), completion: @escaping (UnifiedTileResult) -> Void) {
guard let request = buildRequest() else {
completion(.failure(Error.noDataAvailable))
return
}

if let cachedData = findCachedData(for: request, timestamp: timestamp) {
decode(data: cachedData, completion: completion)
} else {
fetchTiles(request: request, completion: completion)
}
}

private func buildRequest() -> URLRequest? {
guard let resourceEndpoint = resourceEndpoint else {
logger.log("The resource URL is invalid: \(String(describing: resourceEndpoint))",
level: .warning,
category: .legacyHomepage)
return nil
}

guard let contextId = TelemetryContextualIdentifier.contextId else {
logger.log("No context id: \(String(describing: TelemetryContextualIdentifier.contextId))",
level: .warning,
category: .legacyHomepage)
return nil
}

let requestBody = RequestBody(
context_id: contextId,
placements: [
AdPlacement(placement: "newtab_mobile_tile_1", count: 1),
AdPlacement(placement: "newtab_mobile_tile_2", count: 1)
]
)

var request = URLRequest(url: resourceEndpoint)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.timeoutInterval = 5
request.cachePolicy = .reloadIgnoringLocalCacheData

do {
let jsonData = try JSONEncoder().encode(requestBody)
request.httpBody = jsonData
} catch {
logger.log("The request body is invalid: \(String(describing: requestBody))",
level: .warning,
category: .legacyHomepage)
return nil
}
return request
}

private func fetchTiles(request: URLRequest, completion: @escaping (UnifiedTileResult) -> Void) {
networking.data(from: request) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let result):
self.cache(response: result.response, for: request, with: result.data)
self.decode(data: result.data, completion: completion)
case .failure:
completion(.failure(Error.noDataAvailable))
}
}
}

private func decode(data: Data, completion: @escaping (UnifiedTileResult) -> Void) {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let tilesDictionary = try decoder.decode([String: [UnifiedTile]].self, from: data)
let tiles = tilesDictionary.values.flatMap { $0 }

guard !tiles.isEmpty else {
completion(.failure(Error.noDataAvailable))
return
}
completion(.success(tiles))
} catch let error {
self.logger.log("Unable to parse with error: \(error)",
level: .warning,
category: .legacyHomepage)
completion(.failure(Error.noDataAvailable))
}
}

private var resourceEndpoint: URL? {
if featureFlags.isCoreFeatureEnabled(.useStagingContileAPI) {
return URL(string: UnifiedAdsProvider.stagingResourceEndpoint, invalidCharacters: false)
}
return URL(string: UnifiedAdsProvider.prodResourceEndpoint, invalidCharacters: false)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

/// Unified tiles are a type of tiles belonging in the Top sites section on the Firefox home page.
/// See UnifiedAdsProvider and the resource endpoint there for context.
struct UnifiedTile: Decodable {
let format: String
let url: String
let callbacks: UnifiedTileCallback
let imageUrl: String
let name: String
let blockKey: String
}

// Callbacks for telemetry events
struct UnifiedTileCallback: Decodable {
let click: String
let impression: String
}
Loading

0 comments on commit 57009ea

Please sign in to comment.