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
58 changes: 53 additions & 5 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ name: publish

on:
workflow_dispatch:
inputs:
manual_version:
description: 'Force a specific version for NuGet.org publish (e.g., 1.0.0 or 1.0.0-rc.1). If empty, defaults to auto-generated preview for GitHub Packages.'
required: false
type: string
push:
tags:
- "v*"
Expand All @@ -13,6 +18,10 @@ permissions:
jobs:
pack_and_publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -22,18 +31,41 @@ jobs:
id: version
shell: bash
run: |
if [[ "${{ github.ref }}" =~ ^refs/tags/v ]]; then
REF="${{ github.ref }}"
REF="${{ github.ref }}"
MANUAL_VERSION="${{ github.event.inputs.manual_version }}"
CSPROJ="src/Kapusch.FacebookApisForAndroidComponents/Kapusch.FacebookApisForAndroidComponents.csproj"
BASE_VERSION=$(grep -oE '<Version>[^<]+' "$CSPROJ" | head -n 1 | sed 's/<Version>//' || echo "0.1.0")

if [[ -n "$MANUAL_VERSION" ]]; then
VERSION="$MANUAL_VERSION"
PUBLISH_TARGET="nuget"
echo "Manual version provided ($VERSION). Triggering NuGet.org publish."
elif [[ "$REF" =~ ^refs/tags/v ]]; then
VERSION="${REF#refs/tags/v}"
PUBLISH_TARGET="nuget"
else
CSPROJ="src/Kapusch.FacebookApisForAndroidComponents/Kapusch.FacebookApisForAndroidComponents.csproj"
BASE_VERSION=$(grep -oE '<Version>[^<]+' "$CSPROJ" | head -n 1 | sed 's/<Version>//' || echo "0.1.0")
COMMIT_SHORT=$(git rev-parse --short HEAD)
RUN_NUMBER="${{ github.run_number }}"
VERSION="${BASE_VERSION}-prerelease.${RUN_NUMBER}.${COMMIT_SHORT}"
VERSION="${BASE_VERSION}-preview.${RUN_NUMBER}.${COMMIT_SHORT}"
PUBLISH_TARGET="github"
fi

if [[ "$PUBLISH_TARGET" == "nuget" ]]; then
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

echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "publish_target=${PUBLISH_TARGET}" >> "$GITHUB_OUTPUT"
echo "Package version: ${VERSION}"
echo "Publish target: ${PUBLISH_TARGET}"

- uses: actions/setup-dotnet@v4
with:
Expand Down Expand Up @@ -72,7 +104,23 @@ jobs:
-o artifacts/nuget \
/p:PackageVersion="${{ steps.version.outputs.version }}"

- name: Push to NuGet.org
if: steps.version.outputs.publish_target == 'nuget'
uses: NuGet/login@v1
id: nuget_login
with:
user: ${{ secrets.NUGET_USER }}

- name: Push to NuGet.org
if: steps.version.outputs.publish_target == 'nuget'
run: |
dotnet nuget push artifacts/nuget/*.nupkg \
--api-key "${{ steps.nuget_login.outputs.NUGET_API_KEY }}" \
--source "https://api.nuget.org/v3/index.json" \
--skip-duplicate

- name: Push to GitHub Packages
if: steps.version.outputs.publish_target == 'github'
env:
NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
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.FacebookApisForAndroidComponents — AI Working Agreement

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

## Packaging constraints
Expand All @@ -20,3 +20,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,11 +32,14 @@ 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 both:
- `src/Kapusch.FacebookApisForAndroidComponents/Native/Android/facebookinterop/build.gradle.kts`
- `DependencyLocks/Android/lockstate.txt` (URLs + SHA256)

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).
1 change: 1 addition & 0 deletions DependencyLocks/Android/lockstate.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ https://repo1.maven.org/maven2/com/facebook/android/facebook-login/18.1.3/facebo
https://repo1.maven.org/maven2/com/facebook/android/facebook-core/18.1.3/facebook-core-18.1.3.aar ef480916149e078f955195981130b05746ca33b41618911e7093aa32c03d601b facebook-core-18.1.3.aar
https://repo1.maven.org/maven2/com/facebook/android/facebook-common/18.1.3/facebook-common-18.1.3.aar 62adfb1cd05184065d69033a9f917709b392004f87a8aa1cc1c4f0e9fb5ca781 facebook-common-18.1.3.aar
https://repo1.maven.org/maven2/com/facebook/android/facebook-bolts/18.1.3/facebook-bolts-18.1.3.aar 05c6bae0130487e63add05854a3bbd21465c06a295a2104b3948d8759536b699 facebook-bolts-18.1.3.aar
https://repo1.maven.org/maven2/com/facebook/android/facebook-share/18.1.3/facebook-share-18.1.3.aar cc3bc76fdb987ac245ddc54b37096e2afcfb438872487034027639dd4564aef7 facebook-share-18.1.3.aar
10 changes: 10 additions & 0 deletions Docs/Integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This package injects a small Android Activity (provided by an embedded AAR) that runs the Facebook Login flow and returns its result via `Intent` extras.

Set `KapuschFacebookFeatures` to `Login` (default), `Share`, or `Login;Share`.

## Required AndroidManifest entries

You must include the required Meta/Facebook Android SDK entries in your app manifest (example only; use resources, do not hardcode secrets):
Expand All @@ -26,3 +28,11 @@ Then parse the `Intent` extras:
## Sign-out

Call `AndroidFacebookInterop.SendSignOutBroadcast(context)`.

## Photo sharing

With the `Share` feature selected, start
`AndroidFacebookInterop.ShareActivityClassName` and pass a JPEG file path using
`AndroidFacebookInterop.ExtraShareImagePath`. Read `ExtraStatus` and
`ExtraErrorCode` from the activity result. The wrapper uses `SharePhotoContent`
and never injects a message.
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 native wrapper, restore the locked AARs, pack the NuGet and inspect its selected modules.
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`.
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# FacebookApisForAndroidComponents

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

## Package

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

A NuGet package that:
- provides a small managed API for starting a Facebook Login flow on Android, and
- provides managed entry points for Facebook Login and photo sharing, and
- redistributes the required **Facebook Android SDK AARs** inside the `.nupkg` (classic/native packaging),
- injects the AARs into consuming apps via `buildTransitive` `AndroidAarLibrary` items.

Select `KapuschFacebookFeatures=Login`, `Share`, or `Login;Share`. The default is
`Login`; only selected native AARs are injected into the consuming artifact.

## Third-party licenses

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

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

## Build (local)
Expand All @@ -41,10 +45,22 @@ Pack the NuGet:

## Consumption

- Install the package from GitHub Packages (pre-release).
- Install the package from NuGet.org for public release tags.
- Install the package from GitHub Packages for internal preview builds.
- Follow `Docs/Integration.md` for required AndroidManifest entries / meta-data.

## CI

- PR CI is build-only.
- Publishing is handled by a workflow that pushes a pre-release to GitHub Packages.
- 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 `master` -> NuGet.org (pre-release)
- non-tag runs (`workflow_dispatch`) -> GitHub Packages (`X.Y.Z-preview.<run>.<sha>`)
- `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`.

See `Docs/Release.md` for the validated release sequence. Historical tags remain immutable even when obsolete branches are deleted.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ namespace Kapusch.Facebook.Android;
public static class AndroidFacebookInterop
{
public static string LoginActivityClassName => "com.kapusch.facebook.androidinterop.FacebookLoginActivity";
public static string ShareActivityClassName => "com.kapusch.facebook.androidinterop.FacebookShareActivity";

public static string ExtraStatus => "kfb_status";
public static string ExtraAccessToken => "kfb_access_token";
public static string ExtraUserId => "kfb_user_id";
public static string ExtraErrorCode => "kfb_error_code";
public static string ExtraErrorMessage => "kfb_error_message";
public static string ExtraShareImagePath => "kfb_share_image_path";

public static string LogoutAction => "com.kapusch.facebook.androidinterop.LOGOUT";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<NoWarn>$(NoWarn);NU5128</NoWarn>
<AssemblyName>Kapusch.Facebook.Android</AssemblyName>
<RootNamespace>Kapusch.Facebook.Android</RootNamespace>
<Version>0.1.0</Version>
<Version>1.1.0</Version>
<PackageReadmeFile>nuget-readme.md</PackageReadmeFile>
<InteropAndroidWrapperAar
Condition="'$(InteropAndroidWrapperAar)' == '' and Exists('Native/Android/build/aar/kfb-facebookinterop-release.aar')"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ android {

dependencies {
implementation("com.facebook.android:facebook-login:18.1.3")
implementation("com.facebook.android:facebook-share:18.1.3")
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
android:exported="false"
android:launchMode="singleTask"
/>
<activity
android:name=".FacebookShareActivity"
android:exported="false"
android:launchMode="singleTask"
/>
<receiver android:name=".FacebookLogoutReceiver" android:exported="false">
<intent-filter>
<action android:name="com.kapusch.facebook.androidinterop.LOGOUT" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public final class FacebookInteropConstants {
public static final String EXTRA_USER_ID = "kfb_user_id";
public static final String EXTRA_ERROR_CODE = "kfb_error_code";
public static final String EXTRA_ERROR_MESSAGE = "kfb_error_message";
public static final String EXTRA_SHARE_IMAGE_PATH = "kfb_share_image_path";

public static final String LOGOUT_ACTION = "com.kapusch.facebook.androidinterop.LOGOUT";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.kapusch.facebook.androidinterop;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;

import com.facebook.CallbackManager;
import com.facebook.FacebookCallback;
import com.facebook.FacebookException;
import com.facebook.share.Sharer;
import com.facebook.share.model.SharePhoto;
import com.facebook.share.model.SharePhotoContent;
import com.facebook.share.widget.ShareDialog;

public final class FacebookShareActivity extends Activity {
private CallbackManager callbackManager;
private Bitmap bitmap;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

String imagePath = getIntent().getStringExtra(FacebookInteropConstants.EXTRA_SHARE_IMAGE_PATH);
if (imagePath == null || imagePath.trim().isEmpty()) {
completeFailed("missing_image_path");
return;
}

bitmap = BitmapFactory.decodeFile(imagePath);
if (bitmap == null) {
completeFailed("image_decode_failed");
return;
}

callbackManager = CallbackManager.Factory.create();
ShareDialog dialog = new ShareDialog(this);
dialog.registerCallback(callbackManager, new FacebookCallback<Sharer.Result>() {
@Override
public void onSuccess(Sharer.Result result) {
complete("success", RESULT_OK, null);
}

@Override
public void onCancel() {
complete("cancelled", RESULT_CANCELED, null);
}

@Override
public void onError(FacebookException error) {
completeFailed(error.getClass().getSimpleName());
}
});

SharePhoto photo = new SharePhoto.Builder().setBitmap(bitmap).build();
SharePhotoContent content = new SharePhotoContent.Builder().addPhoto(photo).build();
if (!dialog.canShow(content, ShareDialog.Mode.AUTOMATIC)) {
completeFailed("share_dialog_unavailable");
return;
}
dialog.show(content, ShareDialog.Mode.AUTOMATIC);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (callbackManager != null) {
callbackManager.onActivityResult(requestCode, resultCode, data);
}
}

private void completeFailed(String errorCode) {
complete("failed", RESULT_OK, errorCode);
}

private void complete(String status, int resultCode, String errorCode) {
Intent data = new Intent();
data.putExtra(FacebookInteropConstants.EXTRA_STATUS, status);
if (errorCode != null) {
data.putExtra(FacebookInteropConstants.EXTRA_ERROR_CODE, errorCode);
}
setResult(resultCode, data);
finish();
}

@Override
protected void onDestroy() {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
bitmap = null;
super.onDestroy();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<Project>
<PropertyGroup>
<KapuschFacebookFeatures Condition="'$(KapuschFacebookFeatures)' == ''">Login</KapuschFacebookFeatures>
<_KapuschFacebookFeaturesDelimited>;$([System.String]::Copy('$(KapuschFacebookFeatures)').Replace(' ', ''));</_KapuschFacebookFeaturesDelimited>
<_KapuschFacebookLoginEnabled>$([System.String]::Copy('$(_KapuschFacebookFeaturesDelimited)').Contains(';Login;'))</_KapuschFacebookLoginEnabled>
<_KapuschFacebookShareEnabled>$([System.String]::Copy('$(_KapuschFacebookFeaturesDelimited)').Contains(';Share;'))</_KapuschFacebookShareEnabled>
<DisableKapuschFacebookAndroidNativeAssets Condition="'$(DisableKapuschFacebookAndroidNativeAssets)' == ''">false</DisableKapuschFacebookAndroidNativeAssets>
<UseKapuschFacebookAndroidInteropFromSource Condition="'$(UseKapuschFacebookAndroidInteropFromSource)' == ''">false</UseKapuschFacebookAndroidInteropFromSource>
</PropertyGroup>
Expand Down
Loading
Loading