diff --git a/FBSDKShareKit/FBSDKShareKit/Content/_ShareUtility.swift b/FBSDKShareKit/FBSDKShareKit/Content/_ShareUtility.swift
index c887765456..38367475c3 100644
--- a/FBSDKShareKit/FBSDKShareKit/Content/_ShareUtility.swift
+++ b/FBSDKShareKit/FBSDKShareKit/Content/_ShareUtility.swift
@@ -163,7 +163,7 @@ extension _ShareUtility: ShareUtilityProtocol {
guard let linkContent = content as? ShareLinkContent else { return nil }
let parameters: [String: Any?] = [
- "link": linkContent.contentURL,
+ "href": linkContent.contentURL?.absoluteString,
"quote": linkContent.quote,
"hashtag": hashtagString(from: linkContent.hashtag),
"place": content.placeID,
diff --git a/FBSDKShareKit/FBSDKShareKit/PrivacyInfo.xcprivacy b/FBSDKShareKit/FBSDKShareKit/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000000..e69128a849
--- /dev/null
+++ b/FBSDKShareKit/FBSDKShareKit/PrivacyInfo.xcprivacy
@@ -0,0 +1,52 @@
+
+
+
+
+ NSPrivacyCollectedDataTypes
+
+
+ NSPrivacyCollectedDataType
+ NSPrivacyCollectedDataTypeOtherDataTypes
+ NSPrivacyCollectedDataTypeLinked
+
+ NSPrivacyCollectedDataTypeTracking
+
+ NSPrivacyCollectedDataTypePurposes
+
+ NSPrivacyCollectedDataTypePurposeAnalytics
+
+
+
+ NSPrivacyCollectedDataType
+ NSPrivacyCollectedDataTypeCrashData
+ NSPrivacyCollectedDataTypeLinked
+
+ NSPrivacyCollectedDataTypeTracking
+
+ NSPrivacyCollectedDataTypePurposes
+
+ NSPrivacyCollectedDataTypePurposeAppFunctionality
+
+
+
+ NSPrivacyCollectedDataType
+ NSPrivacyCollectedDataTypeDeviceID
+ NSPrivacyCollectedDataTypeLinked
+
+ NSPrivacyCollectedDataTypeTracking
+
+ NSPrivacyCollectedDataTypePurposes
+
+ NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising
+ NSPrivacyCollectedDataTypePurposeAppFunctionality
+
+
+
+ NSPrivacyTracking
+
+ NSPrivacyTrackingDomains
+
+ ep1.facebook.com
+
+
+
diff --git a/FBSDKShareKit/FBSDKShareKit/UserInterface/ShareDialog.swift b/FBSDKShareKit/FBSDKShareKit/UserInterface/ShareDialog.swift
index 81af991c6d..2c080cbd9b 100644
--- a/FBSDKShareKit/FBSDKShareKit/UserInterface/ShareDialog.swift
+++ b/FBSDKShareKit/FBSDKShareKit/UserInterface/ShareDialog.swift
@@ -20,7 +20,7 @@ public class ShareDialog: NSObject, SharingDialog { // swiftlint:disable:this pr
private struct UnknownValidationError: Error {}
private struct BridgeRequestCreationError: Error {}
- private static let feedMethodName = "feed"
+ private static let feedMethodName = "share"
private static var hasValidatedURLSchemeRegistration = false
private static var temporaryDirectory = URL(
@@ -172,7 +172,12 @@ extension ShareDialog {
public var canShow: Bool {
guard shareContent != nil else {
- return canShowWithoutContent
+ do {
+ try validateAdvertiserTrackingPermission(mode: mode)
+ return canShowWithoutContent
+ } catch {
+ return false
+ }
}
do {
@@ -430,7 +435,7 @@ extension ShareDialog {
throw MissingContentError()
}
- try validateShareContentForBrowser()
+ try validateShareContentForShareWebDialog(mode: .browser)
if let photoContent = content as? SharePhotoContent,
photoContentHasAtLeastOneImage(photoContent) {
@@ -490,7 +495,7 @@ extension ShareDialog {
}
private func showFeedBrowser() throws {
- try validateShareContentForFeed()
+ try validateShareContentForFeedWebDialog(mode: .feedBrowser)
let dependencies = try Self.getDependencies()
guard let content = shareContent else {
@@ -525,7 +530,7 @@ extension ShareDialog {
}
private func showFeedWeb() throws {
- try validateShareContentForFeed()
+ try validateShareContentForFeedWebDialog(mode: .feedWeb)
let dependencies = try Self.getDependencies()
guard let content = shareContent else {
@@ -680,7 +685,7 @@ extension ShareDialog {
}
private func showWeb() throws {
- try validateShareContentForBrowser(options: .photoImageURL)
+ try validateShareContentForShareWebDialog(mode: .web, options: .photoImageURL)
let dependencies = try Self.getDependencies()
guard let content = shareContent else {
@@ -748,12 +753,13 @@ extension ShareDialog {
case .shareSheet:
try validateShareContentForShareSheet()
case .browser:
- try validateShareContentForBrowser()
+ try validateShareContentForShareWebDialog(mode: mode)
case .web:
- try validateShareContentForBrowser(options: .photoImageURL)
- case .feedBrowser,
- .feedWeb:
- try validateShareContentForFeed()
+ try validateShareContentForShareWebDialog(mode: mode, options: .photoImageURL)
+ case .feedBrowser:
+ try validateShareContentForFeedWebDialog(mode: mode)
+ case .feedWeb:
+ try validateShareContentForFeedWebDialog(mode: mode)
}
}
@@ -773,23 +779,29 @@ extension ShareDialog {
}
do {
- try validateShareContentForFeed()
+ try validateShareContentForFeedWebDialog(mode: .automatic)
return
} catch {}
- try validateShareContentForBrowser()
+ try validateShareContentForShareWebDialog(mode: .automatic)
}
- private func validateShareContentForBrowser(options bridgeOptions: ShareBridgeOptions = []) throws {
+ private func validateShareContentForShareWebDialog(
+ mode: Mode,
+ options bridgeOptions: ShareBridgeOptions = []
+ ) throws {
let dependencies = try Self.getDependencies()
+ try validateAdvertiserTrackingPermission(mode: mode)
+
guard let content = shareContent else {
throw MissingContentError()
}
+ let errorFactory = dependencies.errorFactory
if let linkContent = content as? ShareLinkContent,
linkContent.contentURL == nil {
- throw dependencies.errorFactory.invalidArgumentError(
+ throw errorFactory.invalidArgumentError(
domain: ShareErrorDomain,
name: "shareContent",
value: linkContent,
@@ -799,7 +811,7 @@ extension ShareDialog {
}
guard !(shareContent is ShareCameraEffectContent) else {
- throw dependencies.errorFactory.invalidArgumentError(
+ throw errorFactory.invalidArgumentError(
domain: ShareErrorDomain,
name: "shareContent",
value: shareContent,
@@ -812,7 +824,7 @@ extension ShareDialog {
if flags.containsPhotos {
guard AccessToken.current != nil else {
- throw dependencies.errorFactory.invalidArgumentError(
+ throw errorFactory.invalidArgumentError(
domain: ShareErrorDomain,
name: "shareContent",
value: content,
@@ -824,7 +836,7 @@ extension ShareDialog {
if let photo = content as? SharePhotoContent {
try photo.validate(options: bridgeOptions)
} else {
- throw dependencies.errorFactory.invalidArgumentError(
+ throw errorFactory.invalidArgumentError(
domain: ShareErrorDomain,
name: "shareContent",
value: content,
@@ -835,18 +847,18 @@ extension ShareDialog {
}
if flags.containsVideos {
- throw dependencies.errorFactory.invalidArgumentError(
+ throw errorFactory.invalidArgumentError(
domain: ShareErrorDomain,
name: "shareContent",
value: content,
- message: "video sharing through the browser is not supported.",
+ message: "Video sharing through the browser is not supported.",
underlyingError: nil
)
}
if flags.containsMedia,
bridgeOptions == .photoImageURL { // a web-based URL is required
- throw dependencies.errorFactory.invalidArgumentError(
+ throw errorFactory.invalidArgumentError(
domain: ShareErrorDomain,
name: "shareContent",
value: content,
@@ -856,8 +868,11 @@ extension ShareDialog {
}
}
- private func validateShareContentForFeed() throws {
- let errorFactory = try Self.getDependencies().errorFactory
+ private func validateShareContentForFeedWebDialog(mode: Mode) throws {
+ let dependencies = try Self.getDependencies()
+ let errorFactory = dependencies.errorFactory
+
+ try validateAdvertiserTrackingPermission(mode: mode)
if let linkContent = shareContent as? ShareLinkContent {
if linkContent.contentURL == nil {
@@ -874,7 +889,7 @@ extension ShareDialog {
domain: ShareErrorDomain,
name: "shareContent",
value: shareContent,
- message: "Feed share dialogs support ShareLinkContent.",
+ message: "Feed share dialogs support ShareLinkContent only.",
underlyingError: nil
)
}
@@ -965,6 +980,34 @@ extension ShareDialog {
}
}
+ private func validateAdvertiserTrackingPermission(mode: Mode) throws {
+ let dependencies = try Self.getDependencies()
+ let errorFactory = dependencies.errorFactory
+
+ let isTrackingEnabled = dependencies.settings.isAdvertiserTrackingEnabled == true
+ let isWebViewsMode = mode == .feedWeb || mode == .web
+ guard !isWebViewsMode || isTrackingEnabled else {
+ throw errorFactory.invalidArgumentError(
+ domain: ShareErrorDomain,
+ name: "unavailableDestination",
+ value: shareContent,
+ message: "Tracking permission is required to share to web destination.",
+ underlyingError: nil
+ )
+ }
+
+ let isBrowsersMode = mode == .browser || mode == .feedBrowser
+ guard !isBrowsersMode || !(shareContent is SharePhotoContent) || isTrackingEnabled else {
+ throw errorFactory.invalidArgumentError(
+ domain: ShareErrorDomain,
+ name: "unavailableDestination",
+ value: shareContent,
+ message: "Valid access token is required to share photos with browser destination.",
+ underlyingError: nil
+ )
+ }
+ }
+
private func invokeDelegateDidCancel() {
AppEvents.shared.logInternalEvent(
.shareDialogResult,
diff --git a/FBSDKShareKit/FBSDKShareKit/UserInterface/ShareDialogMode.swift b/FBSDKShareKit/FBSDKShareKit/UserInterface/ShareDialogMode.swift
index 63a2ee774f..9fb34f3b4d 100644
--- a/FBSDKShareKit/FBSDKShareKit/UserInterface/ShareDialogMode.swift
+++ b/FBSDKShareKit/FBSDKShareKit/UserInterface/ShareDialogMode.swift
@@ -30,12 +30,27 @@ extension ShareDialog {
case browser
/// Displays the dialog in a WKWebView within the app.
+ @available(
+ *,
+ deprecated,
+ message: "The web sharing mode is deprecated. Consider using automatic sharing mode instead."
+ )
case web
/// Displays the feed dialog in Safari.
+ @available(
+ *,
+ deprecated,
+ message: "The feed browser sharing mode is deprecated. Consider using automatic or browser sharing modes instead."
+ )
case feedBrowser
/// Displays the feed dialog in a WKWebView within the app.
+ @available(
+ *,
+ deprecated,
+ message: "The feed web sharing mode is deprecated. Consider using automatic sharing mode instead."
+ )
case feedWeb
/// The string description
diff --git a/FBSDKShareKit/FBSDKShareKitTests/UserInterface/ShareDialogTests.swift b/FBSDKShareKit/FBSDKShareKitTests/UserInterface/ShareDialogTests.swift
index 8bb8f1a726..7eb506a474 100644
--- a/FBSDKShareKit/FBSDKShareKitTests/UserInterface/ShareDialogTests.swift
+++ b/FBSDKShareKit/FBSDKShareKitTests/UserInterface/ShareDialogTests.swift
@@ -211,6 +211,8 @@ final class ShareDialogTests: XCTestCase {
let controller = UIViewController()
let content = ShareModelTestUtility.linkContent
let delegate = TestSharingDelegate()
+ let components = WebShareBridgeComponents(methodName: "test", parameters: ["key": "value"])
+ TestShareUtility.stubbedWebShareBridgeComponents = components
dialog = ShareDialog.show(viewController: controller, content: content, delegate: delegate)
XCTAssertIdentical(dialog.fromViewController, controller, .Construction.createViaClassShowMethod)
@@ -305,6 +307,7 @@ final class ShareDialogTests: XCTestCase {
AccessToken.current = SampleAccessTokens.validToken
dialog.shareContent = ShareModelTestUtility.photoContentWithFileURLs
+ settings.isAdvertiserTrackingEnabled = true
XCTAssertTrue(
dialog.canShow,
"A dialog with photo content with file urls should be showable in a browser when there is a current access token"
@@ -323,6 +326,7 @@ final class ShareDialogTests: XCTestCase {
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertNoThrow(try dialog.validate())
+ settings.isAdvertiserTrackingEnabled = true
dialog.shareContent = ShareModelTestUtility.photoContentWithImages
AccessToken.current = SampleAccessTokens.validToken
XCTAssertNoThrow(try dialog.validate())
@@ -397,6 +401,7 @@ final class ShareDialogTests: XCTestCase {
}
func testSharingViaBrowserWithValidPhotoContent() {
+ settings.isAdvertiserTrackingEnabled = true
let request = TestBridgeAPIRequest()
bridgeAPIRequestFactory.stubbedBridgeAPIRequest = request
let components = WebShareBridgeComponents(methodName: "test", parameters: ["key": "value"])
@@ -442,7 +447,8 @@ final class ShareDialogTests: XCTestCase {
// MARK: - Web mode
- func testCanShowWeb() {
+ func testCanShowWebWithTrackingPermission() {
+ settings.isAdvertiserTrackingEnabled = true
dialog = createEmptyDialog(mode: .web)
XCTAssertTrue(
dialog.canShow,
@@ -473,7 +479,22 @@ final class ShareDialogTests: XCTestCase {
)
}
- func testValidateWeb() throws {
+ func testCanShowWebWithoutTrackingPermissions() {
+ dialog = createEmptyDialog(mode: .web)
+ XCTAssertFalse(
+ dialog.canShow,
+ "A dialog without share content should be showable on web"
+ )
+
+ dialog.shareContent = ShareModelTestUtility.linkContent
+ XCTAssertFalse(
+ dialog.canShow,
+ "A dialog with link content should not be showable on web"
+ )
+ }
+
+ func testValidateWebWithTrackingPermission() throws {
+ settings.isAdvertiserTrackingEnabled = true
dialog = createEmptyDialog(mode: .web)
dialog.shareContent = ShareModelTestUtility.linkContent
@@ -660,7 +681,8 @@ final class ShareDialogTests: XCTestCase {
// MARK: - Feed web mode
- func testCanShowFeedWeb() {
+ func testCanShowFeedWebWithTrackingPermission() {
+ settings.isAdvertiserTrackingEnabled = true
dialog = createEmptyDialog(mode: .feedWeb)
XCTAssertTrue(
@@ -687,7 +709,18 @@ final class ShareDialogTests: XCTestCase {
)
}
- func testValidateFeedWeb() throws {
+ func testCanShowFeedWebWithoutTrackingPermission() {
+ settings.isAdvertiserTrackingEnabled = false
+ dialog = createEmptyDialog(mode: .feedWeb)
+
+ XCTAssertFalse(
+ dialog.canShow,
+ "A dialog without tracking permissions should not be showable in a web feed"
+ )
+ }
+
+ func testValidateFeedWebWithTrackingPermission() throws {
+ settings.isAdvertiserTrackingEnabled = true
dialog = createEmptyDialog(mode: .feedWeb)
dialog.shareContent = ShareModelTestUtility.linkContent
XCTAssertNoThrow(try dialog.validate())
@@ -699,6 +732,16 @@ final class ShareDialogTests: XCTestCase {
XCTAssertThrowsError(try dialog.validate())
}
+ func testValidateFeedWebWithoutTrackingPermission() throws {
+ settings.isAdvertiserTrackingEnabled = false
+ dialog = createEmptyDialog(mode: .feedWeb)
+ dialog.shareContent = ShareModelTestUtility.linkContent
+ XCTAssertThrowsError(
+ try dialog.validate(),
+ "A web view dialog without tracking permission should not pass validation"
+ )
+ }
+
// MARK: - Automatic mode
func testCameraShareModesWhenNativeUnavailable() {
diff --git a/Package.swift b/Package.swift
index 86a210aa7d..80606107f7 100644
--- a/Package.swift
+++ b/Package.swift
@@ -175,7 +175,13 @@ extension Target {
]
)
- static let share = target(name: .share, dependencies: [.core, .Prefixed.share])
+ static let share = target(
+ name: .share,
+ dependencies: [.core, .Prefixed.share],
+ resources: [
+ .copy("Resources/PrivacyInfo.xcprivacy"),
+ ]
+ )
static let gaming = target(name: .gaming, dependencies: [.Prefixed.gaming])
diff --git a/Sources/FacebookShare/Resources/PrivacyInfo.xcprivacy b/Sources/FacebookShare/Resources/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000000..e69128a849
--- /dev/null
+++ b/Sources/FacebookShare/Resources/PrivacyInfo.xcprivacy
@@ -0,0 +1,52 @@
+
+
+
+
+ NSPrivacyCollectedDataTypes
+
+
+ NSPrivacyCollectedDataType
+ NSPrivacyCollectedDataTypeOtherDataTypes
+ NSPrivacyCollectedDataTypeLinked
+
+ NSPrivacyCollectedDataTypeTracking
+
+ NSPrivacyCollectedDataTypePurposes
+
+ NSPrivacyCollectedDataTypePurposeAnalytics
+
+
+
+ NSPrivacyCollectedDataType
+ NSPrivacyCollectedDataTypeCrashData
+ NSPrivacyCollectedDataTypeLinked
+
+ NSPrivacyCollectedDataTypeTracking
+
+ NSPrivacyCollectedDataTypePurposes
+
+ NSPrivacyCollectedDataTypePurposeAppFunctionality
+
+
+
+ NSPrivacyCollectedDataType
+ NSPrivacyCollectedDataTypeDeviceID
+ NSPrivacyCollectedDataTypeLinked
+
+ NSPrivacyCollectedDataTypeTracking
+
+ NSPrivacyCollectedDataTypePurposes
+
+ NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising
+ NSPrivacyCollectedDataTypePurposeAppFunctionality
+
+
+
+ NSPrivacyTracking
+
+ NSPrivacyTrackingDomains
+
+ ep1.facebook.com
+
+
+