From ed6f865f6b052d017378d444287c753afe0ab02b Mon Sep 17 00:00:00 2001 From: kapusch Date: Sun, 26 Jul 2026 16:16:49 +0200 Subject: [PATCH 1/6] fix(ios): retain share interop and route callbacks --- Docs/Integration.md | 4 ++ .../Facebook/NativeFacebookShare.iOS.cs | 65 +++++++++++++------ .../KapuschFacebookShareInterop/Interop.swift | 28 ++++++++ tests/validate-feature-selection.sh | 14 ++++ 4 files changed, 90 insertions(+), 21 deletions(-) diff --git a/Docs/Integration.md b/Docs/Integration.md index b9fbe32..4255838 100644 --- a/Docs/Integration.md +++ b/Docs/Integration.md @@ -33,6 +33,10 @@ disable Meta automatic initialization and call `NativeFacebookShare.ConfigureAndInitialize(app.Handle, trackingAllowed)` only after the ATT decision, immediately before the first share. Then call `NativeFacebookShare.SharePhotoAsync(...)`; no caption is accepted. +Forward Facebook callback URLs to +`NativeFacebookShare.HandleOpenUrl(app.Handle, url.Handle, options.Handle)`. +Share-only host applications should filter by their configured Facebook URL +scheme before invoking this handler. ## 3) Feature selection diff --git a/src/Kapusch.FacebookApisForiOSComponents/Facebook/NativeFacebookShare.iOS.cs b/src/Kapusch.FacebookApisForiOSComponents/Facebook/NativeFacebookShare.iOS.cs index 0774c69..ba796ad 100644 --- a/src/Kapusch.FacebookApisForiOSComponents/Facebook/NativeFacebookShare.iOS.cs +++ b/src/Kapusch.FacebookApisForiOSComponents/Facebook/NativeFacebookShare.iOS.cs @@ -3,7 +3,7 @@ namespace Kapusch.Facebook.iOS; -public static unsafe class NativeFacebookShare +public static unsafe partial class NativeFacebookShare { public static void ConfigureAndInitialize( IntPtr uiApplicationHandle, @@ -13,7 +13,27 @@ bool trackingAllowed if (uiApplicationHandle == IntPtr.Zero) throw new ArgumentException("UIApplication is required."); - ResolveConfigureAndInitialize()(uiApplicationHandle, trackingAllowed ? (byte)1 : (byte)0); + ConfigureAndInitializeNative( + uiApplicationHandle, + trackingAllowed ? (byte)1 : (byte)0 + ); + } + + public static bool HandleOpenUrl( + IntPtr uiApplicationHandle, + IntPtr nsUrlHandle, + IntPtr optionsHandle + ) + { + if (uiApplicationHandle == IntPtr.Zero || nsUrlHandle == IntPtr.Zero) + return false; + + return HandleOpenUrlNative( + uiApplicationHandle, + nsUrlHandle, + optionsHandle + ) + != 0; } public static Task SharePhotoAsync( @@ -36,7 +56,7 @@ public static Task SharePhotoAsync( var imagePathPointer = Marshal.StringToCoTaskMemUTF8(imagePath); try { - ResolveSharePhoto()( + SharePhotoNative( presentingViewControllerHandle, imagePathPointer, &ShareCallback, @@ -75,24 +95,27 @@ private static void ShareCallback(int status, IntPtr errorCode, IntPtr context) } } - private static IntPtr Resolve(string symbol) => - NativeLibrary.GetExport(NativeLibrary.GetMainProgramHandle(), symbol); + [LibraryImport( + "__Internal", + EntryPoint = "kfb_facebook_share_configure_and_initialize" + )] + private static partial void ConfigureAndInitializeNative( + IntPtr uiApplicationHandle, + byte trackingAllowed + ); - private static delegate* unmanaged[Cdecl] ResolveConfigureAndInitialize() => - (delegate* unmanaged[Cdecl])Resolve( - "kfb_facebook_share_configure_and_initialize" - ); + [LibraryImport("__Internal", EntryPoint = "kfb_facebook_share_handle_open_url")] + private static partial byte HandleOpenUrlNative( + IntPtr uiApplicationHandle, + IntPtr nsUrlHandle, + IntPtr optionsHandle + ); - private static delegate* unmanaged[Cdecl]< - IntPtr, - IntPtr, - delegate* unmanaged[Cdecl], - IntPtr, - void> ResolveSharePhoto() => - (delegate* unmanaged[Cdecl]< - IntPtr, - IntPtr, - delegate* unmanaged[Cdecl], - IntPtr, - void>)Resolve("kfb_facebook_share_photo"); + [LibraryImport("__Internal", EntryPoint = "kfb_facebook_share_photo")] + private static partial void SharePhotoNative( + IntPtr presentingViewControllerHandle, + IntPtr imagePath, + delegate* unmanaged[Cdecl] callback, + IntPtr context + ); } diff --git a/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift b/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift index b9250c8..b70e381 100644 --- a/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift +++ b/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift @@ -145,3 +145,31 @@ public func kfb_facebook_share_photo( ) } } + +@_cdecl("kfb_facebook_share_handle_open_url") +public func kfb_facebook_share_handle_open_url( + _ applicationPtr: UnsafeMutableRawPointer, + _ urlPtr: UnsafeMutableRawPointer, + _ optionsPtr: UnsafeMutableRawPointer? +) -> Bool { + let application = Unmanaged + .fromOpaque(applicationPtr) + .takeUnretainedValue() + let url = Unmanaged.fromOpaque(urlPtr).takeUnretainedValue() as URL + guard let appID = Bundle.main.infoDictionary?["FacebookAppID"] as? String, + url.scheme?.caseInsensitiveCompare("fb\(appID)") == .orderedSame + else { + return false + } + let options: NSDictionary? = { + guard let optionsPtr else { return nil } + return Unmanaged.fromOpaque(optionsPtr).takeUnretainedValue() + }() + let openUrlOptions = options as? [UIApplication.OpenURLOptionsKey: Any] ?? [:] + + return ApplicationDelegate.shared.application( + application, + open: url, + options: openUrlOptions + ) +} diff --git a/tests/validate-feature-selection.sh b/tests/validate-feature-selection.sh index 6f3bc49..38603f9 100644 --- a/tests/validate-feature-selection.sh +++ b/tests/validate-feature-selection.sh @@ -3,6 +3,20 @@ set -euo pipefail project="tests/FeatureSelection.proj" +share_source="src/Kapusch.FacebookApisForiOSComponents/Facebook/NativeFacebookShare.iOS.cs" + +if rg -F "NativeLibrary.GetExport" "$share_source" >/dev/null; then + echo "Facebook Share must use static __Internal imports so iOS retains its native symbols." >&2 + exit 1 +fi + +for symbol in \ + kfb_facebook_share_configure_and_initialize \ + kfb_facebook_share_handle_open_url \ + kfb_facebook_share_photo; do + rg -F "EntryPoint = \"$symbol\"" "$share_source" >/dev/null \ + || { echo "Missing static Facebook Share import: $symbol" >&2; exit 1; } +done dotnet msbuild "$project" -t:ValidateFeatureSelection -p:ExpectedLoginEnabled=True -p:ExpectedShareEnabled=False From 44db10280cad7684682931e494960048e064d691 Mon Sep 17 00:00:00 2001 From: kapusch Date: Sun, 26 Jul 2026 17:10:39 +0200 Subject: [PATCH 2/6] fix(ios): prevent duplicate share failure callbacks --- .../KapuschFacebookShareInterop/Interop.swift | 30 ++++++++++++++++++- tests/validate-feature-selection.sh | 6 ++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift b/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift index b70e381..2b02c89 100644 --- a/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift +++ b/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift @@ -1,9 +1,15 @@ import Foundation +import OSLog import UIKit import FacebookCore import FacebookShare +private let shareLogger = Logger( + subsystem: Bundle.main.bundleIdentifier ?? "Kapusch.Facebook.iOS", + category: "FacebookShare" +) + public typealias KapuschFacebookShareCallback = @convention(c) ( Int32, UnsafePointer?, @@ -32,6 +38,7 @@ private func invokeShareCallback( private final class ShareDelegate: NSObject, SharingDelegate { let callback: KapuschFacebookShareCallback let context: UnsafeMutableRawPointer + private(set) var didFinish = false init(callback: @escaping KapuschFacebookShareCallback, context: UnsafeMutableRawPointer) { self.callback = callback @@ -39,12 +46,20 @@ private final class ShareDelegate: NSObject, SharingDelegate { } func sharer(_ sharer: Sharing, didCompleteWithResults results: [String: Any]) { + guard !didFinish else { return } + didFinish = true + shareLogger.info("Facebook share completed.") invokeShareCallback(callback, status: .success, context: context) ShareState.clear() } func sharer(_ sharer: Sharing, didFailWithError error: Error) { + guard !didFinish else { return } + didFinish = true let nsError = error as NSError + shareLogger.error( + "Facebook share failed. domain=\(nsError.domain, privacy: .public) code=\(nsError.code)" + ) invokeShareCallback( callback, status: .failed, @@ -55,6 +70,9 @@ private final class ShareDelegate: NSObject, SharingDelegate { } func sharerDidCancel(_ sharer: Sharing) { + guard !didFinish else { return } + didFinish = true + shareLogger.info("Facebook share cancelled.") invokeShareCallback(callback, status: .cancelled, context: context) ShareState.clear() } @@ -76,6 +94,9 @@ public func kfb_facebook_share_configure_and_initialize( _ applicationPtr: UnsafeMutableRawPointer, _ trackingAllowed: Bool ) { + shareLogger.debug( + "Configuring Facebook Share SDK. trackingAllowed=\(trackingAllowed, privacy: .public)" + ) Settings.shared.isAutoLogAppEventsEnabled = false Settings.shared.isAdvertiserIDCollectionEnabled = trackingAllowed Settings.shared.isAdvertiserTrackingEnabled = trackingAllowed @@ -100,7 +121,9 @@ public func kfb_facebook_share_photo( _ callback: @escaping KapuschFacebookShareCallback, _ context: UnsafeMutableRawPointer ) { + shareLogger.debug("Facebook photo share requested.") guard ShareState.dialog == nil else { + shareLogger.error("Facebook share rejected because another request is in progress.") invokeShareCallback( callback, status: .failed, @@ -112,6 +135,7 @@ public func kfb_facebook_share_photo( let imagePath = String(cString: imagePathPtr) guard let image = UIImage(contentsOfFile: imagePath) else { + shareLogger.error("Facebook share could not decode the image.") invokeShareCallback( callback, status: .failed, @@ -135,7 +159,11 @@ public func kfb_facebook_share_photo( ) ShareState.delegate = delegate ShareState.dialog = dialog - if !dialog.show() { + shareLogger.debug("Facebook ShareDialog canShow=\(dialog.canShow, privacy: .public)") + let shown = dialog.show() + shareLogger.debug("Facebook ShareDialog show returned \(shown, privacy: .public)") + if !shown && !delegate.didFinish { + shareLogger.error("Facebook ShareDialog was unavailable without a delegate error.") ShareState.clear() invokeShareCallback( callback, diff --git a/tests/validate-feature-selection.sh b/tests/validate-feature-selection.sh index 38603f9..b7873c4 100644 --- a/tests/validate-feature-selection.sh +++ b/tests/validate-feature-selection.sh @@ -4,6 +4,7 @@ set -euo pipefail project="tests/FeatureSelection.proj" share_source="src/Kapusch.FacebookApisForiOSComponents/Facebook/NativeFacebookShare.iOS.cs" +share_interop_source="src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift" if rg -F "NativeLibrary.GetExport" "$share_source" >/dev/null; then echo "Facebook Share must use static __Internal imports so iOS retains its native symbols." >&2 @@ -18,6 +19,11 @@ for symbol in \ || { echo "Missing static Facebook Share import: $symbol" >&2; exit 1; } done +rg -F "guard !didFinish else { return }" "$share_interop_source" >/dev/null \ + || { echo "Facebook Share callbacks must be idempotent." >&2; exit 1; } +rg -F "if !shown && !delegate.didFinish" "$share_interop_source" >/dev/null \ + || { echo "Facebook Share must not invoke a second callback after ShareKit reports a synchronous failure." >&2; exit 1; } + dotnet msbuild "$project" -t:ValidateFeatureSelection -p:ExpectedLoginEnabled=True -p:ExpectedShareEnabled=False dotnet msbuild "$project" -t:ValidateFeatureSelection -p:RequestedFacebookFeatures=Share -p:ExpectedLoginEnabled=False -p:ExpectedShareEnabled=True From a2e43e247b6c901823ca32d8aeb14a59ae352f7b Mon Sep 17 00:00:00 2001 From: kapusch Date: Sun, 26 Jul 2026 18:02:25 +0200 Subject: [PATCH 3/6] fix(ios): use native facebook share dialog --- .../Sources/KapuschFacebookShareInterop/Interop.swift | 1 + tests/validate-feature-selection.sh | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift b/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift index 2b02c89..d850232 100644 --- a/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift +++ b/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift @@ -157,6 +157,7 @@ public func kfb_facebook_share_photo( content: content, delegate: delegate ) + dialog.mode = .native ShareState.delegate = delegate ShareState.dialog = dialog shareLogger.debug("Facebook ShareDialog canShow=\(dialog.canShow, privacy: .public)") diff --git a/tests/validate-feature-selection.sh b/tests/validate-feature-selection.sh index b7873c4..08aaa8f 100644 --- a/tests/validate-feature-selection.sh +++ b/tests/validate-feature-selection.sh @@ -23,6 +23,8 @@ rg -F "guard !didFinish else { return }" "$share_interop_source" >/dev/null \ || { echo "Facebook Share callbacks must be idempotent." >&2; exit 1; } rg -F "if !shown && !delegate.didFinish" "$share_interop_source" >/dev/null \ || { echo "Facebook Share must not invoke a second callback after ShareKit reports a synchronous failure." >&2; exit 1; } +rg -F "dialog.mode = .native" "$share_interop_source" >/dev/null \ + || { echo "Facebook photo share must use the native Facebook dialog instead of the deprecated iOS share sheet." >&2; exit 1; } dotnet msbuild "$project" -t:ValidateFeatureSelection -p:ExpectedLoginEnabled=True -p:ExpectedShareEnabled=False From 5eb03e1438b53f3c1b140f9bbecd5e6a195c5245 Mon Sep 17 00:00:00 2001 From: kapusch Date: Sun, 26 Jul 2026 18:22:22 +0200 Subject: [PATCH 4/6] fix(ios): avoid facebook paste permission prompt --- .../KapuschFacebookShareInterop/Interop.swift | 13 +++++++++++++ tests/validate-feature-selection.sh | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift b/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift index d850232..b938b2c 100644 --- a/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift +++ b/src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift @@ -10,6 +10,15 @@ private let shareLogger = Logger( category: "FacebookShare" ) +private enum FacebookBridgePasteboard { + static let dataType = "com.facebook.Facebook.FBAppBridgeType" + + static func clearPendingShareDataWithoutReading() { + UIPasteboard.general.setData(Data(), forPasteboardType: dataType) + shareLogger.debug("Cleared pending Facebook bridge data without reading the pasteboard.") + } +} + public typealias KapuschFacebookShareCallback = @convention(c) ( Int32, UnsafePointer?, @@ -196,6 +205,10 @@ public func kfb_facebook_share_handle_open_url( }() let openUrlOptions = options as? [UIApplication.OpenURLOptionsKey: Any] ?? [:] + if ShareState.dialog != nil { + FacebookBridgePasteboard.clearPendingShareDataWithoutReading() + } + return ApplicationDelegate.shared.application( application, open: url, diff --git a/tests/validate-feature-selection.sh b/tests/validate-feature-selection.sh index 08aaa8f..c4865f3 100644 --- a/tests/validate-feature-selection.sh +++ b/tests/validate-feature-selection.sh @@ -25,6 +25,12 @@ rg -F "if !shown && !delegate.didFinish" "$share_interop_source" >/dev/null \ || { echo "Facebook Share must not invoke a second callback after ShareKit reports a synchronous failure." >&2; exit 1; } rg -F "dialog.mode = .native" "$share_interop_source" >/dev/null \ || { echo "Facebook photo share must use the native Facebook dialog instead of the deprecated iOS share sheet." >&2; exit 1; } +rg -F "FacebookBridgePasteboard.clearPendingShareDataWithoutReading()" "$share_interop_source" >/dev/null \ + || { echo "Facebook native share callbacks must clear bridge data without triggering an iOS paste read." >&2; exit 1; } +if rg -F "data(forPasteboardType:" "$share_interop_source" >/dev/null; then + echo "Facebook Share wrapper must never read the general pasteboard." >&2 + exit 1 +fi dotnet msbuild "$project" -t:ValidateFeatureSelection -p:ExpectedLoginEnabled=True -p:ExpectedShareEnabled=False From 9f9ec0d3b796d93064032fbad54bf7c70199b8b5 Mon Sep 17 00:00:00 2001 From: kapusch Date: Sun, 26 Jul 2026 19:39:26 +0200 Subject: [PATCH 5/6] docs: document native share return behavior --- Docs/Integration.md | 9 +++++++++ src/Kapusch.FacebookApisForiOSComponents/nuget-readme.md | 3 +++ 2 files changed, 12 insertions(+) diff --git a/Docs/Integration.md b/Docs/Integration.md index 4255838..89d9009 100644 --- a/Docs/Integration.md +++ b/Docs/Integration.md @@ -38,6 +38,15 @@ Forward Facebook callback URLs to Share-only host applications should filter by their configured Facebook URL scheme before invoking this handler. +The photo wrapper uses ShareKit's native-app dialog mode. A share operation +completes its managed callback at most once, including when ShareKit reports a +synchronous presentation failure followed by a late delegate callback. + +When Facebook returns to the host app, the wrapper clears only Meta's private +bridge marker from the general pasteboard, and does so without reading the +pasteboard. This avoids triggering an iOS paste-authorization prompt while +leaving unrelated pasteboard content untouched. + ## 3) Feature selection - `Login` (default): Login wrapper and LoginKit. diff --git a/src/Kapusch.FacebookApisForiOSComponents/nuget-readme.md b/src/Kapusch.FacebookApisForiOSComponents/nuget-readme.md index c6b26c3..f528b92 100644 --- a/src/Kapusch.FacebookApisForiOSComponents/nuget-readme.md +++ b/src/Kapusch.FacebookApisForiOSComponents/nuget-readme.md @@ -6,5 +6,8 @@ This package provides: Set `KapuschFacebookFeatures` to `Login` (default), `Share`, or `Login;Share`. SDK 18.0.2 requires FBAEMKit whenever ShareKit/CoreKit is selected. +Photo sharing uses ShareKit's native-app dialog, guarantees one managed +completion per request, and avoids reading the general pasteboard when cleaning +Meta's app-bridge marker on return. See the repo `README.md` and `Docs/Integration.md` for integration steps. From c5d5ec96c77a79e00020ea691a9e2dab9b9db67c Mon Sep 17 00:00:00 2001 From: kapusch Date: Sun, 26 Jul 2026 22:01:51 +0200 Subject: [PATCH 6/6] fix(ci): support runners without ripgrep --- tests/validate-feature-selection.sh | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/validate-feature-selection.sh b/tests/validate-feature-selection.sh index c4865f3..c570c58 100644 --- a/tests/validate-feature-selection.sh +++ b/tests/validate-feature-selection.sh @@ -6,7 +6,18 @@ project="tests/FeatureSelection.proj" share_source="src/Kapusch.FacebookApisForiOSComponents/Facebook/NativeFacebookShare.iOS.cs" share_interop_source="src/Kapusch.FacebookApisForiOSComponents/Native/iOS/KapuschFacebookAuthInterop/Sources/KapuschFacebookShareInterop/Interop.swift" -if rg -F "NativeLibrary.GetExport" "$share_source" >/dev/null; then +contains_fixed_text() { + local pattern="$1" + local file="$2" + + if command -v rg >/dev/null 2>&1; then + rg -F "$pattern" "$file" >/dev/null + else + grep -Fq -- "$pattern" "$file" + fi +} + +if contains_fixed_text "NativeLibrary.GetExport" "$share_source"; then echo "Facebook Share must use static __Internal imports so iOS retains its native symbols." >&2 exit 1 fi @@ -15,19 +26,19 @@ for symbol in \ kfb_facebook_share_configure_and_initialize \ kfb_facebook_share_handle_open_url \ kfb_facebook_share_photo; do - rg -F "EntryPoint = \"$symbol\"" "$share_source" >/dev/null \ + contains_fixed_text "EntryPoint = \"$symbol\"" "$share_source" \ || { echo "Missing static Facebook Share import: $symbol" >&2; exit 1; } done -rg -F "guard !didFinish else { return }" "$share_interop_source" >/dev/null \ +contains_fixed_text "guard !didFinish else { return }" "$share_interop_source" \ || { echo "Facebook Share callbacks must be idempotent." >&2; exit 1; } -rg -F "if !shown && !delegate.didFinish" "$share_interop_source" >/dev/null \ +contains_fixed_text "if !shown && !delegate.didFinish" "$share_interop_source" \ || { echo "Facebook Share must not invoke a second callback after ShareKit reports a synchronous failure." >&2; exit 1; } -rg -F "dialog.mode = .native" "$share_interop_source" >/dev/null \ +contains_fixed_text "dialog.mode = .native" "$share_interop_source" \ || { echo "Facebook photo share must use the native Facebook dialog instead of the deprecated iOS share sheet." >&2; exit 1; } -rg -F "FacebookBridgePasteboard.clearPendingShareDataWithoutReading()" "$share_interop_source" >/dev/null \ +contains_fixed_text "FacebookBridgePasteboard.clearPendingShareDataWithoutReading()" "$share_interop_source" \ || { echo "Facebook native share callbacks must clear bridge data without triggering an iOS paste read." >&2; exit 1; } -if rg -F "data(forPasteboardType:" "$share_interop_source" >/dev/null; then +if contains_fixed_text "data(forPasteboardType:" "$share_interop_source"; then echo "Facebook Share wrapper must never read the general pasteboard." >&2 exit 1 fi