diff --git a/.github/cocoapods-deploy.yml b/.github/cocoapods-deploy.yml deleted file mode 100644 index 11355e9bf6..0000000000 --- a/.github/cocoapods-deploy.yml +++ /dev/null @@ -1,39 +0,0 @@ - -# This workflow is a WIP to get the plugins to deploy to cocoapods -# Its put on hold as it's currently failing during the pod trunk push command -# See the docs on how to run during manual deployment - -# name: Cocoapods Deploy - -# on: -# workflow_dispatch: - -# jobs: -# deploy-cocoapods: -# runs-on: macos-latest -# timeout-minutes: 30 -# steps: -# - uses: actions/setup-node@v1 -# with: -# node-version: 14.x -# - uses: actions/checkout@v2 -# with: -# fetch-depth: 0 -# - name: Install Cocoapods -# run: | -# gem install cocoapods -# - name: Restore Dependency Cache -# id: cache-modules -# uses: actions/cache@v2 -# with: -# path: | -# node_modules -# */node_modules -# key: dependency-cache-${{ hashFiles('package.json', '*/package.json') }} -# - run: npm install -# - name: Deploy to Cocoapods -# run: | -# set -eo pipefail -# npm run publish:cocoapod -# env: -# COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} \ No newline at end of file diff --git a/app/CHANGELOG.md b/app/CHANGELOG.md index 5fa026f0a7..88ce507297 100644 --- a/app/CHANGELOG.md +++ b/app/CHANGELOG.md @@ -13,6 +13,10 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @capacitor/app +## [8.1.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/app@8.1.0...@capacitor/app@8.1.1) (2026-07-15) + +**Note:** Version bump only for package @capacitor/app + # [8.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/app@8.0.1...@capacitor/app@8.1.0) (2026-03-25) ### Features diff --git a/browser/CHANGELOG.md b/browser/CHANGELOG.md index cd770cee01..e61064b5c1 100644 --- a/browser/CHANGELOG.md +++ b/browser/CHANGELOG.md @@ -11,6 +11,10 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @capacitor/browser +## [8.0.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/browser@8.0.3...@capacitor/browser@8.0.4) (2026-07-15) + +**Note:** Version bump only for package @capacitor/browser + ## [8.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/browser@8.0.2...@capacitor/browser@8.0.3) (2026-03-25) **Note:** Version bump only for package @capacitor/browser diff --git a/device/CHANGELOG.md b/device/CHANGELOG.md index a78dd20cbe..463d39d2c7 100644 --- a/device/CHANGELOG.md +++ b/device/CHANGELOG.md @@ -11,6 +11,10 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @capacitor/device +## [8.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/device@8.0.2...@capacitor/device@8.0.3) (2026-07-15) + +**Note:** Version bump only for package @capacitor/device + ## [8.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/device@8.0.1...@capacitor/device@8.0.2) (2026-03-25) **Note:** Version bump only for package @capacitor/device diff --git a/motion/CHANGELOG.md b/motion/CHANGELOG.md index 4eeebd7c1f..bd3f7daad8 100644 --- a/motion/CHANGELOG.md +++ b/motion/CHANGELOG.md @@ -11,6 +11,10 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @capacitor/motion +## [8.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/motion@8.0.0...@capacitor/motion@8.0.1) (2026-07-15) + +**Note:** Version bump only for package @capacitor/motion + # [8.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/motion@8.0.0-beta.0...@capacitor/motion@8.0.0) (2025-12-08) **Note:** Version bump only for package @capacitor/motion diff --git a/push-notifications/CHANGELOG.md b/push-notifications/CHANGELOG.md index a75f0ff70c..6439926351 100644 --- a/push-notifications/CHANGELOG.md +++ b/push-notifications/CHANGELOG.md @@ -11,6 +11,10 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @capacitor/push-notifications +## [8.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/push-notifications@8.1.1...@capacitor/push-notifications@8.1.2) (2026-07-15) + +**Note:** Version bump only for package @capacitor/push-notifications + ## [8.1.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/push-notifications@8.1.0...@capacitor/push-notifications@8.1.1) (2026-05-15) **Note:** Version bump only for package @capacitor/push-notifications diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java index 8622e6f07d..c00494ef56 100644 --- a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java @@ -7,20 +7,30 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Looper; import android.service.notification.StatusBarNotification; +import androidx.core.app.NotificationCompat; import com.getcapacitor.*; import com.getcapacitor.annotation.CapacitorPlugin; import com.getcapacitor.annotation.Permission; import com.getcapacitor.annotation.PermissionCallback; +import com.google.android.gms.tasks.Tasks; import com.google.firebase.messaging.CommonNotificationBuilder; import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.ImageDownload; import com.google.firebase.messaging.NotificationParams; import com.google.firebase.messaging.RemoteMessage; import java.util.Arrays; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.json.JSONException; import org.json.JSONObject; @@ -30,6 +40,7 @@ ) public class PushNotificationsPlugin extends Plugin { + private static final int IMAGE_DOWNLOAD_TIMEOUT_SECONDS = 5; static final String PUSH_NOTIFICATIONS = "receive"; public static Bridge staticBridge = null; @@ -281,7 +292,7 @@ public void fireNotification(RemoteMessage remoteMessage) { CommonNotificationBuilder.DisplayNotificationInfo notificationInfo = CommonNotificationBuilder.createNotificationInfo(getContext(), getContext(), params, channelId, bundle); - notificationManager.notify(notificationInfo.tag, notificationInfo.id, notificationInfo.notificationBuilder.build()); + showForegroundNotification(notificationInfo, notification.getImageUrl()); } } } @@ -298,6 +309,61 @@ public void fireNotification(RemoteMessage remoteMessage) { notifyListeners("pushNotificationReceived", remoteMessageData, true); } + private void showForegroundNotification(CommonNotificationBuilder.DisplayNotificationInfo notificationInfo, Uri imageUrl) { + Runnable showNotification = () -> { + try { + applyNotificationImage(notificationInfo.notificationBuilder, imageUrl); + } catch (RuntimeException e) { + Logger.error("Unexpected error while applying notification image", e); + } finally { + notificationManager.notify(notificationInfo.tag, notificationInfo.id, notificationInfo.notificationBuilder.build()); + } + }; + + if (Looper.myLooper() != Looper.getMainLooper()) { + showNotification.run(); + return; + } + + ExecutorService displayExecutor = Executors.newSingleThreadExecutor(); + displayExecutor.execute(() -> { + try { + showNotification.run(); + } finally { + displayExecutor.shutdown(); + } + }); + } + + private void applyNotificationImage(NotificationCompat.Builder notificationBuilder, Uri imageUrl) { + if (imageUrl == null) { + return; + } + + ImageDownload imageDownload = ImageDownload.create(imageUrl.toString()); + if (imageDownload == null) { + return; + } + + ExecutorService imageExecutor = Executors.newSingleThreadExecutor(); + try { + imageDownload.start(imageExecutor); + Bitmap bitmap = Tasks.await(imageDownload.getTask(), IMAGE_DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); + notificationBuilder.setLargeIcon(bitmap); + notificationBuilder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bitmap).bigLargeIcon((Bitmap) null)); + } catch (ExecutionException e) { + Logger.warn("Failed to download notification image: " + e.getCause()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Logger.warn("Notification image download was interrupted"); + } catch (TimeoutException e) { + Logger.warn("Notification image download timed out"); + } finally { + imageDownload.close(); + imageExecutor.shutdownNow(); + } + } + public static PushNotificationsPlugin getPushNotificationsInstance() { if (staticBridge != null && staticBridge.getWebView() != null) { PluginHandle handle = staticBridge.getPlugin("PushNotifications"); diff --git a/share/CHANGELOG.md b/share/CHANGELOG.md index fc6b913e39..c418bc0305 100644 --- a/share/CHANGELOG.md +++ b/share/CHANGELOG.md @@ -11,6 +11,12 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @capacitor/share +## [8.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/share@8.0.1...@capacitor/share@8.0.2) (2026-09-16) + +### Bug Fixes + +- **share:** Add nonce validation to broadcast receiver on Android ([#2592](https://github.com/ionic-team/capacitor-plugins/issues/2592)) ([fa8ddfb](https://github.com/ionic-team/capacitor-plugins/commit/fa8ddfb4e671fa1c91e6d92c80512f09cfd3a335)) + ## [8.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/share@8.0.0...@capacitor/share@8.0.1) (2026-02-12) ### Bug Fixes diff --git a/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java b/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java index dcfa338315..9b97dc0135 100644 --- a/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java +++ b/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java @@ -19,25 +19,44 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import org.json.JSONException; @CapacitorPlugin(name = "Share") public class SharePlugin extends Plugin { + private static final String NONCE_EXTRA_KEY = "_share_nonce"; + private BroadcastReceiver broadcastReceiver; private boolean stopped = false; private boolean isPresenting = false; private ComponentName chosenComponent; + private String expectedNonce; @Override public void load() { broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + // Validate nonce to prevent spoofing from other apps + // Reference: https://github.com/ionic-team/capacitor-plugins/pull/2592 + String receivedNonce = intent.getStringExtra(NONCE_EXTRA_KEY); + if (receivedNonce == null || !receivedNonce.equals(expectedNonce)) { + // Reject broadcasts that don't have the correct nonce + return; + } + + ComponentName component; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - chosenComponent = intent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT, ComponentName.class); + component = intent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT, ComponentName.class); } else { - chosenComponent = getParcelableExtraLegacy(intent, Intent.EXTRA_CHOSEN_COMPONENT); + component = getParcelableExtraLegacy(intent, Intent.EXTRA_CHOSEN_COMPONENT); + } + + // Only clear nonce if we successfully got the component data + if (component != null) { + chosenComponent = component; + expectedNonce = null; } } }; @@ -64,6 +83,7 @@ private void activityResult(PluginCall call, ActivityResult result) { call.resolve(callResult); } isPresenting = false; + expectedNonce = null; } @PluginMethod @@ -117,6 +137,12 @@ public void share(PluginCall call) { if (files != null && files.length() != 0) { shareFiles(files, intent, call); } + + // Generate a random nonce to prevent spoofing via exported receiver + expectedNonce = UUID.randomUUID().toString(); + Intent callbackIntent = new Intent(Intent.EXTRA_CHOSEN_COMPONENT); + callbackIntent.putExtra(NONCE_EXTRA_KEY, expectedNonce); + int flags = PendingIntent.FLAG_UPDATE_CURRENT; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { flags = flags | PendingIntent.FLAG_MUTABLE; @@ -126,7 +152,7 @@ public void share(PluginCall call) { } // requestCode parameter is not used. Providing 0 - PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, new Intent(Intent.EXTRA_CHOSEN_COMPONENT), flags); + PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, callbackIntent, flags); Intent chooser = Intent.createChooser(intent, dialogTitle, pi.getIntentSender()); chosenComponent = null; chooser.addCategory(Intent.CATEGORY_DEFAULT); @@ -180,6 +206,7 @@ private void shareFiles(JSArray files, Intent intent, PluginCall call) { @Override protected void handleOnDestroy() { + expectedNonce = null; if (broadcastReceiver != null) { getActivity().unregisterReceiver(broadcastReceiver); } diff --git a/share/ios/Sources/SharePlugin/SharePlugin.swift b/share/ios/Sources/SharePlugin/SharePlugin.swift index 73aa61b2b2..234c5f2865 100644 --- a/share/ios/Sources/SharePlugin/SharePlugin.swift +++ b/share/ios/Sources/SharePlugin/SharePlugin.swift @@ -64,12 +64,28 @@ public class SharePlugin: CAPPlugin, CAPBridgedPlugin { } } - if self?.bridge?.viewController?.presentedViewController != nil { - call.reject("Can't share while sharing is in progress") - return + // Present from the topmost presented view controller so the share sheet appears + // above any view controller the app has presented over the webview. With nothing + // presented this resolves to the bridge view controller, i.e. unchanged behaviour. + // A share is only in progress when a share sheet is already presented. + var presenter = self?.bridge?.viewController + while let presented = presenter?.presentedViewController { + if presented is UIActivityViewController { + call.reject("Can't share while sharing is in progress") + return + } + presenter = presented + } + // `setCenteredPopover` anchors to the bridge view controller's view, which is not in + // the presenter's hierarchy when presenting from a different view controller. Anchor + // the popover to the presenting view controller's own view instead, centered and + // without an arrow, matching `setCenteredPopover` behaviour. + if let popover = actionController.popoverPresentationController, let presenterView = presenter?.view { + popover.sourceView = presenterView + popover.sourceRect = CGRect(x: presenterView.bounds.midX, y: presenterView.bounds.midY, width: 0, height: 0) + popover.permittedArrowDirections = [] } - self?.setCenteredPopover(actionController) - self?.bridge?.viewController?.present(actionController, animated: true, completion: nil) + presenter?.present(actionController, animated: true, completion: nil) } } } diff --git a/splash-screen/CHANGELOG.md b/splash-screen/CHANGELOG.md index 094bae77ce..924e4193d4 100644 --- a/splash-screen/CHANGELOG.md +++ b/splash-screen/CHANGELOG.md @@ -13,6 +13,10 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - **splash-screen:** implement launchFadeOutDuration support on iOS ([#2524](https://github.com/ionic-team/capacitor-plugins/issues/2524)) ([d1b094d](https://github.com/ionic-team/capacitor-plugins/commit/d1b094d52153bf4c2d219c5ee3315aea22ef4e1e)) +## [8.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/splash-screen@8.0.1...@capacitor/splash-screen@8.0.2) (2026-07-15) + +**Note:** Version bump only for package @capacitor/splash-screen + ## [8.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/splash-screen@8.0.0...@capacitor/splash-screen@8.0.1) (2026-02-12) ### Bug Fixes diff --git a/status-bar/CHANGELOG.md b/status-bar/CHANGELOG.md index 8240aaee8f..4a99b2080c 100644 --- a/status-bar/CHANGELOG.md +++ b/status-bar/CHANGELOG.md @@ -11,6 +11,10 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @capacitor/status-bar +## [8.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/status-bar@8.0.2...@capacitor/status-bar@8.0.3) (2026-07-15) + +**Note:** Version bump only for package @capacitor/status-bar + ## [8.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/status-bar@8.0.1...@capacitor/status-bar@8.0.2) (2026-03-25) ### Bug Fixes