Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ jobs:
required = [
"buildTransitive/Kapusch.Facebook.iOS.targets",
"kfb.xcframework/Info.plist",
"kfbshare.xcframework/Info.plist",
"fb/FBAEMKit.xcframework/Info.plist",
"fb/FBSDKCoreKit.xcframework/Info.plist",
"fb/FBSDKCoreKit_Basics.xcframework/Info.plist",
"fb/FBSDKGamingServicesKit.xcframework/Info.plist",
"fb/FBSDKLoginKit.xcframework/Info.plist",
"fb/FBSDKShareKit.xcframework/Info.plist",
]
Expand All @@ -87,6 +87,10 @@ jobs:
print("ERROR: wrapper appears flattened (found 'Info.plist' at package root).")
sys.exit(1)

if any(name.startswith("fb/FBSDKGamingServicesKit.xcframework/") for name in names):
print("ERROR: GamingServicesKit must not be packaged.")
sys.exit(1)

print("OK: nupkg layout looks correct.")
PY

Expand Down
36 changes: 13 additions & 23 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ jobs:
shell: bash
run: |
REF="${{ github.ref }}"
EVENT="${{ github.event_name }}"
MANUAL_VERSION="${{ github.event.inputs.manual_version }}"
CSPROJ="src/Kapusch.FacebookApisForiOSComponents/Kapusch.FacebookApisForiOSComponents.csproj"
BASE_VERSION=$(grep -oE '<Version>[^<]+' "$CSPROJ" | head -n 1 | sed 's/<Version>//' || echo "0.1.0")
Expand All @@ -52,27 +51,14 @@ jobs:
fi

if [[ "$PUBLISH_TARGET" == "nuget" ]]; then
git fetch origin +refs/heads/*:refs/remotes/origin/*
TAGGED_SHA="${{ github.sha }}"
CONTAINING_BRANCHES=$(git for-each-ref --format='%(refname:short)' refs/remotes/origin --contains "$TAGGED_SHA")

ON_MASTER="false"
ON_RELEASE="false"
while IFS= read -r branch; do
[[ "$branch" == "origin/master" ]] && ON_MASTER="true"
[[ "$branch" == origin/release/* ]] && ON_RELEASE="true"
done <<< "$CONTAINING_BRANCHES"

if [[ "$VERSION" == *-* ]]; then
if [[ "$ON_RELEASE" != "true" ]]; then
echo "ERROR: Pre-release version $VERSION must be on origin/release/*"
exit 1
fi
else
if [[ "$ON_MASTER" != "true" ]]; then
echo "ERROR: Stable version $VERSION must be on origin/master"
exit 1
fi
git fetch origin master
if ! git merge-base --is-ancestor "${{ github.sha }}" origin/master; then
echo "ERROR: NuGet.org releases must be reachable from origin/master."
exit 1
fi
if [[ -n "$MANUAL_VERSION" && "$REF" != "refs/heads/master" ]]; then
echo "ERROR: Manual NuGet.org releases must be dispatched from master."
exit 1
fi
fi

Expand Down Expand Up @@ -136,10 +122,10 @@ jobs:
required = [
"buildTransitive/Kapusch.Facebook.iOS.targets",
"kfb.xcframework/Info.plist",
"kfbshare.xcframework/Info.plist",
"fb/FBAEMKit.xcframework/Info.plist",
"fb/FBSDKCoreKit.xcframework/Info.plist",
"fb/FBSDKCoreKit_Basics.xcframework/Info.plist",
"fb/FBSDKGamingServicesKit.xcframework/Info.plist",
"fb/FBSDKLoginKit.xcframework/Info.plist",
"fb/FBSDKShareKit.xcframework/Info.plist",
]
Expand All @@ -155,6 +141,10 @@ jobs:
print("ERROR: wrapper appears flattened (found 'Info.plist' at package root).")
sys.exit(1)

if any(name.startswith("fb/FBSDKGamingServicesKit.xcframework/") for name in names):
print("ERROR: GamingServicesKit must not be packaged.")
sys.exit(1)

print("OK: nupkg layout looks correct.")
PY

Expand Down
8 changes: 7 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Kapusch.FacebookApisForiOSComponents — AI Working Agreement

## Goals
- Produce a reproducible iOS NuGet package for Facebook Login interop.
- Produce a reproducible iOS NuGet package for selectable Facebook Login and Share interop.
- Do not commit secrets.

## Packaging constraints
Expand All @@ -19,3 +19,9 @@
## Safety
- Do not add new dependency ingestion paths without documenting them in `README.md`.
- Do not commit real app ids/secrets.

## Branches and releases
- `master` is the only long-lived branch and the source of every NuGet.org release.
- Implement changes on short-lived branches and target `master` through a PR. Never implement directly on `release/*`.
- Both stable (`vX.Y.Z`) and prerelease (`vX.Y.Z-rc.N`) tags must reference commits reachable from `origin/master`.
- Manual runs without a version publish previews to GitHub Packages. See `Docs/Release.md` for the complete contract.
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ Pack the NuGet:
## Pull requests

- Keep PRs focused and well-scoped.
- Branch from `master` and target `master`; this repository does not use long-lived `release/*` branches.
- Do not commit secrets.
- If you update the Facebook SDK version, update `Package.swift` and `Package.resolved` together.

See `Docs/Release.md` before changing versions, tags, or publishing workflows.

## License

By contributing, you agree that your contributions will be licensed under the repository license (MIT).
19 changes: 17 additions & 2 deletions Docs/Integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,22 @@ Call the interop hooks in your `AppDelegate`:
- On launch: `NativeFacebookLogin.Initialize(app, options)`
- On URL open: `NativeFacebookLogin.HandleOpenUrl(app, url, options)`

## 3) Limited Login
These hooks apply to `KapuschFacebookFeatures=Login`. For Share-only builds,
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.

## 3) Feature selection

- `Login` (default): Login wrapper and LoginKit.
- `Share`: photo-share wrapper and ShareKit.
- `Login;Share`: both managed/native entry points.

At SDK 18.0.2, ShareKit's CoreKit dependency requires FBAEMKit. Treat this as a
privacy-review blocker if the consumer's artifact policy excludes FBAEMKit.

## 4) Limited Login

For Limited Login, pass:
- `FacebookTrackingMode.Limited`
Expand All @@ -38,7 +53,7 @@ The result can contain:
- `AuthenticationToken`
- `Nonce`

## 4) Secrets policy
## 5) Secrets policy

Do not commit real values in this repo.
Use templates and `.gitignore`d local files for any sample app configuration.
23 changes: 23 additions & 0 deletions Docs/Release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Release workflow

`master` is the only long-lived branch. Feature work uses short-lived branches and pull requests targeting `master`; this repository does not create `release/*` branches.

## Channels

- Manual workflow run without `manual_version`: build a preview package and publish it to GitHub Packages.
- `vX.Y.Z-rc.N` tag reachable from `origin/master`: publish that prerelease to NuGet.org.
- `vX.Y.Z` tag reachable from `origin/master`: publish that stable version to NuGet.org.
- Manual workflow run with `manual_version`: publish to NuGet.org only when the workflow runs from `master`.

NuGet versions and Git tags are immutable. Query the official NuGet index before publishing and never move or recreate an existing tag.

## Release sequence

1. Fetch `origin`, start a short-lived branch from `origin/master`, and verify the worktree is clean.
2. Build the Swift wrappers, collect the locked Facebook xcframeworks, pack the NuGet and validate its module selection.
3. Open a PR to `master` and require CI to pass.
4. Merge and fetch the resulting `origin/master` commit.
5. Create the RC or stable tag on that exact commit and push only the new tag.
6. Verify the publish workflow and the resulting NuGet package before updating consumers.

Do not delete historical tags. Delete a temporary branch only after every retained commit is reachable from `origin/master`.
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# FacebookApisForiOSComponents

Public OSS repository that packages **Facebook Login for iOS** into a consumable .NET NuGet.
Public OSS repository that packages selected Facebook iOS features into a consumable .NET NuGet.

## Package

Expand All @@ -9,10 +9,18 @@ Public OSS repository that packages **Facebook Login for iOS** into a consumable
## What this repo ships

A NuGet package that:
- provides a small managed API for **Facebook Login (iOS)**, and
- provides managed APIs for **Facebook Login** and **Facebook photo sharing**, and
- redistributes the required **Facebook iOS SDK xcframeworks** inside the `.nupkg` (classic/native binding packaging),
- injects the xcframeworks into consuming apps via `buildTransitive` `NativeReference` items.

Select features with `KapuschFacebookFeatures=Login`, `Share`, or `Login;Share`.
The default remains `Login` for compatibility.

Facebook iOS SDK 18.0.2 links `FBSDKCoreKit` to `FBAEMKit` non-weakly. Therefore
`Share` currently includes `ShareKit`, `CoreKit`, `CoreKit_Basics` and `FBAEMKit`.
A consumer that forbids `FBAEMKit` must block Store delivery; this repository
does not strip or conceal the dependency.

## Third-party licenses

See `THIRD_PARTY_NOTICES.md`.
Expand All @@ -21,6 +29,7 @@ See `THIRD_PARTY_NOTICES.md`.

- Formatting: `Docs/Formatting.md`
- Source mode: `Docs/SourceMode.md`
- Release workflow: `Docs/Release.md`
- Samples: `samples/README.md`

## Build (local)
Expand Down Expand Up @@ -49,22 +58,13 @@ Pack the NuGet:
- PR CI is build-only.
- Publishing is handled by `.github/workflows/publish.yml` with channel routing:
- tag `vX.Y.Z` on `master` -> NuGet.org (stable)
- tag `vX.Y.Z-rc.N` on `release/*` -> NuGet.org (pre-release)
- tag `vX.Y.Z-rc.N` on `master` -> NuGet.org (pre-release)
- non-tag runs (`workflow_dispatch`) -> GitHub Packages (`X.Y.Z-preview.<run>.<sha>`)
- `workflow_dispatch` with `manual_version` -> NuGet.org (forced version)
- `workflow_dispatch` on `master` with `manual_version` -> NuGet.org (forced version)
- NuGet.org publishing uses NuGet Trusted Publishing (OIDC via `NuGet/login@v1`), no long-lived NuGet API key.

### Required GitHub secret

- `NUGET_USER`: your nuget.org profile username (not email), used by `NuGet/login@v1`.

## Release examples

- Pre-release candidate from a release branch:
- `git checkout release/1.0.0`
- `git tag v1.0.0-rc.1`
- `git push origin v1.0.0-rc.1`
- Stable release from master:
- `git checkout master`
- `git tag v1.0.0`
- `git push origin v1.0.0`
See `Docs/Release.md` for the validated release sequence. Historical tags remain immutable even when obsolete branches are deleted.
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.100",
"rollForward": "patch"
"rollForward": "latestFeature"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ namespace Kapusch.Facebook.iOS;

public static unsafe class NativeFacebookLogin
{
private const string LibraryName = "__Internal";

public static void Initialize(IntPtr uiApplicationHandle, IntPtr launchOptionsHandle)
{
if (uiApplicationHandle == IntPtr.Zero)
return;

try
{
KfiFacebookInitialize(
ResolveInitialize()(
uiApplicationHandle,
launchOptionsHandle
);
Expand All @@ -36,11 +34,11 @@ IntPtr optionsHandle

try
{
return KfiFacebookHandleOpenUrl(
return ResolveHandleOpenUrl()(
uiApplicationHandle,
nsUrlHandle,
optionsHandle
);
) != 0;
}
catch
{
Expand Down Expand Up @@ -68,13 +66,29 @@ public static Task<NativeFacebookSignInResult> SignInAsync(
var gch = GCHandle.Alloc(tcs);
var context = GCHandle.ToIntPtr(gch);

KfiFacebookSignInStart(
presentingViewControllerHandle,
(int)trackingMode,
rawNonce,
&KfiFacebookCallback,
context
);
var noncePointer = string.IsNullOrEmpty(rawNonce)
? IntPtr.Zero
: Marshal.StringToCoTaskMemUTF8(rawNonce);
try
{
ResolveSignInStart()(
presentingViewControllerHandle,
(int)trackingMode,
noncePointer,
&KfiFacebookCallback,
context
);
}
catch
{
gch.Free();
throw;
}
finally
{
if (noncePointer != IntPtr.Zero)
Marshal.FreeCoTaskMem(noncePointer);
}

_ = cancellationToken.Register(() =>
tcs.TrySetResult(new NativeFacebookSignInResult(NativeSignInStatus.Cancelled))
Expand All @@ -87,7 +101,7 @@ public static void SignOut()
{
try
{
KfiFacebookSignOut();
ResolveSignOut()();
}
catch
{
Expand Down Expand Up @@ -130,35 +144,32 @@ IntPtr context
}
}

[DllImport(LibraryName, EntryPoint = "kfb_facebook_initialize")]
private static extern void KfiFacebookInitialize(IntPtr uiApplication, IntPtr launchOptions);

[DllImport(LibraryName, EntryPoint = "kfb_facebook_handle_open_url")]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool KfiFacebookHandleOpenUrl(
IntPtr uiApplication,
IntPtr nsUrl,
IntPtr options
);

[DllImport(LibraryName, EntryPoint = "kfb_facebook_signin_start")]
private static extern void KfiFacebookSignInStart(
IntPtr presentingViewController,
int trackingMode,
[MarshalAs(UnmanagedType.LPUTF8Str)] string? rawNonce,
delegate* unmanaged[Cdecl]<
int,
IntPtr,
IntPtr,
IntPtr,
IntPtr,
private static IntPtr Resolve(string symbol) =>
NativeLibrary.GetExport(NativeLibrary.GetMainProgramHandle(), symbol);

private static delegate* unmanaged[Cdecl]<IntPtr, IntPtr, void> ResolveInitialize() =>
(delegate* unmanaged[Cdecl]<IntPtr, IntPtr, void>)Resolve("kfb_facebook_initialize");

private static delegate* unmanaged[Cdecl]<IntPtr, IntPtr, IntPtr, byte> ResolveHandleOpenUrl() =>
(delegate* unmanaged[Cdecl]<IntPtr, IntPtr, IntPtr, byte>)Resolve(
"kfb_facebook_handle_open_url"
);

private static delegate* unmanaged[Cdecl]<
IntPtr,
int,
IntPtr,
delegate* unmanaged[Cdecl]<int, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, void>,
IntPtr,
void> ResolveSignInStart() =>
(delegate* unmanaged[Cdecl]<
IntPtr,
int,
IntPtr,
delegate* unmanaged[Cdecl]<int, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, void>,
IntPtr,
void> callback,
IntPtr context
);
void>)Resolve("kfb_facebook_signin_start");

[DllImport(LibraryName, EntryPoint = "kfb_facebook_signout")]
private static extern void KfiFacebookSignOut();
private static delegate* unmanaged[Cdecl]<void> ResolveSignOut() =>
(delegate* unmanaged[Cdecl]<void>)Resolve("kfb_facebook_signout");
}
Loading
Loading